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:
- 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
- Set Oracle's dialect default so a
Limit node is converted before rendering.
Happy to send a PR if a direction is preferred.
Repro
Actual:
SELECT a FROM t LIMIT 5Expected:
SELECT a FROM t FETCH FIRST 5 ROWS ONLY— Oracle rejectsLIMIT nwithORA-03049.Version:
polyglot-sql0.13.0 (latest on crates.io as of this report). Reproduces the same way via the top-leveltranspile("SELECT a FROM t LIMIT 5", DialectType::Generic, DialectType::Oracle)when the AST is built by parsingLIMITinto aLimitnode rather than aFetchnode — the bug is ingenerate_limit, not in the entry point.Root cause
GeneratorConfig::limit_fetch_style(src/generator.rs:333) is written by several dialects'Defaultimpl (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 aLimitAST node) always writes theLIMITkeyword unconditionally — it never inspectsself.config.limit_fetch_styleorself.config.dialectat all.generate_fetch(src/generator.rs:33633, for aFetchAST node) does have dialect-aware downgrade logic, but it's keyed off a hardcodedmatches!(self.config.dialect, ...)list (Spark, Hive, DuckDB, SQLite, MySQL, BigQuery, Databricks, StarRocks, Doris, Athena, ClickHouse) — again never readinglimit_fetch_style.So
limit_fetch_styleis dead configuration:grep -rn '\.limit_fetch_style\b' src/outside its own declaration and theDefaultimpls 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 toFetchFirst.Workaround (downstream)
We rewrite the
Limitnode into aFetchnode ourselves forDialect::Oracleafter building the AST and before rendering, so the renderer takes thegenerate_fetchpath. 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:
generate_limitcheckself.config.limit_fetch_style(or dialect) and switch toFETCH FIRST n ROWS ONLYwhen appropriate, mirroring whatgenerate_fetch's hardcoded list already does in the other direction; orLimitnode is converted before rendering.Happy to send a PR if a direction is preferred.