Skip to content

Oracle: generate_limit ignores limit_fetch_style and always emits LIMIT #480

Description

@geoHeil

Repro

// Cargo.toml: polyglot-sql = { version = "0.13.0", default-features = false, features = ["generate", "builder", "dialect-oracle"] }
use polyglot_sql::builder;
use polyglot_sql::{Dialect, DialectType, Generator};

fn main() {
    let ast = builder::from("t").select_cols(["a"]).limit(5).build();
    let config = Dialect::get(DialectType::Oracle).generator_config().clone();
    let sql = Generator::with_config(config).generate(&ast).expect("generate");
    println!("{sql}");
}

Actual: SELECT a FROM t LIMIT 5
Expected: SELECT a FROM t FETCH FIRST 5 ROWS ONLY — Oracle rejects LIMIT n with ORA-03049.

Version: polyglot-sql 0.13.0 (latest on crates.io as of this report). Reproduces the same way via the top-level transpile("SELECT a FROM t LIMIT 5", DialectType::Generic, DialectType::Oracle) when the AST is built by parsing LIMIT into a Limit node rather than a Fetch node — the bug is in generate_limit, not in the entry point.

Root cause

GeneratorConfig::limit_fetch_style (src/generator.rs:333) is written by several dialects' Default impl (e.g. DuckDB, BigQuery, TSQL set it explicitly) but is read by neither renderer that could act on it:

  • generate_limit (src/generator.rs:35938, for a Limit AST node) always writes the LIMIT keyword unconditionally — it never inspects self.config.limit_fetch_style or self.config.dialect at all.
  • generate_fetch (src/generator.rs:33633, for a Fetch AST node) does have dialect-aware downgrade logic, but it's keyed off a hardcoded matches!(self.config.dialect, ...) list (Spark, Hive, DuckDB, SQLite, MySQL, BigQuery, Databricks, StarRocks, Doris, Athena, ClickHouse) — again never reading limit_fetch_style.

So limit_fetch_style is dead configuration: grep -rn '\.limit_fetch_style\b' src/ outside its own declaration and the Default impls returns nothing. Oracle's own dialect config (src/dialects/oracle.rs) never sets the field either, so even wiring the reads would need Oracle's default flipped to FetchFirst.

Workaround (downstream)

We rewrite the Limit node into a Fetch node ourselves for Dialect::Oracle after building the AST and before rendering, so the renderer takes the generate_fetch path. Fine as a stopgap, but it means every caller targeting Oracle has to know to do this rather than getting a runnable statement out of the box.

Suggested fix

Either:

  1. Have generate_limit check self.config.limit_fetch_style (or dialect) and switch to FETCH FIRST n ROWS ONLY when appropriate, mirroring what generate_fetch's hardcoded list already does in the other direction; or
  2. Set Oracle's dialect default so a Limit node is converted before rendering.

Happy to send a PR if a direction is preferred.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions