diff --git a/Cargo.lock b/Cargo.lock index 3026f1cd..ac5e0d42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -605,7 +605,7 @@ dependencies = [ [[package]] name = "polyglot-sql" -version = "0.13.4" +version = "0.13.5" dependencies = [ "criterion", "once_cell", @@ -624,7 +624,7 @@ dependencies = [ [[package]] name = "polyglot-sql-ast-derive" -version = "0.13.4" +version = "0.13.5" dependencies = [ "proc-macro2", "quote", @@ -633,7 +633,7 @@ dependencies = [ [[package]] name = "polyglot-sql-ffi" -version = "0.13.4" +version = "0.13.5" dependencies = [ "cbindgen", "polyglot-sql", @@ -643,11 +643,11 @@ dependencies = [ [[package]] name = "polyglot-sql-function-catalogs" -version = "0.13.4" +version = "0.13.5" [[package]] name = "polyglot-sql-python" -version = "0.13.4" +version = "0.13.5" dependencies = [ "polyglot-sql", "pyo3", @@ -658,7 +658,7 @@ dependencies = [ [[package]] name = "polyglot-sql-wasm" -version = "0.13.4" +version = "0.13.5" dependencies = [ "console_error_panic_hook", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index 915249b1..bb38d7ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ exclude = [ ] [workspace.package] -version = "0.13.4" +version = "0.13.5" edition = "2021" license = "MIT" authors = ["polyglot contributors"] diff --git a/README.md b/README.md index dacdc4bc..225f2966 100644 --- a/README.md +++ b/README.md @@ -306,7 +306,7 @@ If you want to disable `stacker` for a native Rust build, turn off default featu ```toml [dependencies] -polyglot-sql = { version = "0.13.4", default-features = false, features = ["all-dialects", "transpile"] } +polyglot-sql = { version = "0.13.5", default-features = false, features = ["all-dialects", "transpile"] } ``` That can reduce overhead slightly on trusted inputs, but you lose the default stack-growth protection for deeply nested SQL. diff --git a/crates/polyglot-sql-function-catalogs/README.md b/crates/polyglot-sql-function-catalogs/README.md index 566e0c12..157fb145 100644 --- a/crates/polyglot-sql-function-catalogs/README.md +++ b/crates/polyglot-sql-function-catalogs/README.md @@ -25,7 +25,7 @@ With `polyglot-sql` compile-time wiring (recommended): ```toml [dependencies] -polyglot-sql = { version = "0.13.4", features = ["function-catalog-clickhouse"] } +polyglot-sql = { version = "0.13.5", features = ["function-catalog-clickhouse"] } ``` Then run schema validation with type checks: diff --git a/crates/polyglot-sql/Cargo.toml b/crates/polyglot-sql/Cargo.toml index 3b0032ad..c37b1d68 100644 --- a/crates/polyglot-sql/Cargo.toml +++ b/crates/polyglot-sql/Cargo.toml @@ -100,14 +100,14 @@ function-catalog-all-dialects = [ [dependencies] regex = { version = "1", optional = true } -polyglot-sql-ast-derive = { path = "../polyglot-sql-ast-derive", version = "0.13.4" } +polyglot-sql-ast-derive = { path = "../polyglot-sql-ast-derive", version = "0.13.5" } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } unicode-segmentation = { workspace = true } stacker = { version = "0.1", optional = true } ts-rs = { version = "12.0", features = ["serde-compat"], optional = true } -polyglot-sql-function-catalogs = { path = "../polyglot-sql-function-catalogs", version = "0.13.4", optional = true, default-features = false } +polyglot-sql-function-catalogs = { path = "../polyglot-sql-function-catalogs", version = "0.13.5", optional = true, default-features = false } [dev-dependencies] pretty_assertions = "1.4" diff --git a/crates/polyglot-sql/README.md b/crates/polyglot-sql/README.md index 41699924..c5fe2ebd 100644 --- a/crates/polyglot-sql/README.md +++ b/crates/polyglot-sql/README.md @@ -25,12 +25,12 @@ By default, `polyglot-sql` enables the full public API. Parser-only consumers ca disable default features and opt into only the dialect parsers they need: ```toml -polyglot-sql = { version = "0.13.4", default-features = false } +polyglot-sql = { version = "0.13.5", default-features = false } ``` ```toml polyglot-sql = { - version = "0.13.4", + version = "0.13.5", default-features = false, features = ["dialect-clickhouse"], } @@ -44,14 +44,14 @@ Examples: ```toml # Parse and generate SQL for one dialect. polyglot-sql = { - version = "0.13.4", + version = "0.13.5", default-features = false, features = ["generate", "dialect-clickhouse"], } # Cross-dialect transpilation. polyglot-sql = { - version = "0.13.4", + version = "0.13.5", default-features = false, features = ["transpile", "dialect-clickhouse", "dialect-postgresql"], } diff --git a/crates/polyglot-sql/src/validation.rs b/crates/polyglot-sql/src/validation.rs index aa9ca538..4ba0b2a2 100644 --- a/crates/polyglot-sql/src/validation.rs +++ b/crates/polyglot-sql/src/validation.rs @@ -4558,23 +4558,23 @@ pub fn validate_parsed_with_schema( )); let mut statement = apply_projection_alias_bindings(statement, &bindings); if options.check_types { - // Expand known stars using the same qualification path as lineage. - // Open/partial schemas remain conservative if qualification fails. - if statement.dfs().any(|node| { - matches!( - node, - Expression::Union(_) | Expression::Intersect(_) | Expression::Except(_) - ) - }) && statement - .dfs() - .any(|node| matches!(node, Expression::Star(_))) - { - if let Ok(qualified) = crate::optimizer::qualify_schema_aware_expression( - statement.clone(), - &resolver_schema, - Some(dialect), - ) { - statement = qualified; + // Give the type checker star-sourced column types for every query. + // Ordered CTE/source star expansion is the cheap lineage path; full + // qualification is only needed for stars it cannot resolve, such as + // stars over derived tables. Open/partial schemas remain conservative. + if has_projection_star(&statement) { + crate::lineage::expand_cte_stars( + &mut statement, + Some(&resolver_schema as &dyn crate::schema::Schema), + ); + if has_projection_star(&statement) { + if let Ok(qualified) = crate::optimizer::qualify_schema_aware_expression( + statement.clone(), + &resolver_schema, + Some(dialect), + ) { + statement = qualified; + } } } annotate_types(&mut statement, Some(&resolver_schema), Some(dialect)); @@ -4604,6 +4604,24 @@ pub fn validate_parsed_with_schema( ValidationResult::with_errors(all_errors) } +fn has_projection_star(statement: &Expression) -> bool { + statement.dfs().any(|node| { + matches!(node, Expression::Select(select) if select.expressions.iter().any(|projection| { + let mut projection = projection; + loop { + match projection { + Expression::Alias(alias) => projection = &alias.this, + Expression::Annotated(annotated) => projection = &annotated.this, + Expression::Paren(paren) => projection = &paren.this, + _ => break, + } + } + matches!(projection, Expression::Star(_)) + || matches!(projection, Expression::Column(column) if column.name.name == "*") + })) + }) +} + /// Validate an already bound project-query scope without rebuilding its tree. /// The caller retains the standalone path for type checks, semantic lint, stars, /// and statement kinds that require DML-specific binding. diff --git a/crates/polyglot-sql/src/validation/tests.rs b/crates/polyglot-sql/src/validation/tests.rs index 55a28979..13a198f5 100644 --- a/crates/polyglot-sql/src/validation/tests.rs +++ b/crates/polyglot-sql/src/validation/tests.rs @@ -1125,6 +1125,46 @@ fn review_builtin_and_unknown_cast_targets_do_not_error() { } } +#[test] +fn star_sourced_types_are_checked_without_set_operations() { + let options = SchemaValidationOptions { + semantic: true, + check_types: true, + ..Default::default() + }; + for sql in [ + "WITH a AS (SELECT * FROM orders) SELECT 1 FROM a JOIN orders AS o ON a.s = o.i", + "WITH a AS (SELECT * FROM orders), b AS (SELECT * FROM a) SELECT i FROM b WHERE s = i", + "SELECT 1 FROM (SELECT * FROM orders) AS a JOIN orders AS o ON a.s = o.i", + ] { + let result = validate_with_schema( + sql, + DialectType::Snowflake, + &semantic_type_schema(), + &options, + ); + assert!( + result.errors.iter().any(|e| e.code == "W210"), + "{sql}: {:?}", + result.errors + ); + } + let result = validate_with_schema( + "WITH a AS (SELECT * FROM orders) SELECT 1 FROM a JOIN orders AS o ON a.i = o.i", + DialectType::Snowflake, + &semantic_type_schema(), + &options, + ); + assert!( + !result + .errors + .iter() + .any(|e| e.code.get(1..).is_some_and(|n| n.starts_with("21"))), + "{:?}", + result.errors + ); +} + fn semantic_type_schema() -> ValidationSchema { serde_json::from_value(serde_json::json!({"tables":[{"name":"orders","columns":[ {"name":"i","type":"INTEGER"}, {"name":"s","type":"VARCHAR"}, diff --git a/examples/rust/Cargo.lock b/examples/rust/Cargo.lock index 438eecc1..c70b2093 100644 --- a/examples/rust/Cargo.lock +++ b/examples/rust/Cargo.lock @@ -71,7 +71,7 @@ dependencies = [ [[package]] name = "polyglot-sql" -version = "0.13.4" +version = "0.13.5" dependencies = [ "polyglot-sql-ast-derive", "polyglot-sql-function-catalogs", @@ -85,7 +85,7 @@ dependencies = [ [[package]] name = "polyglot-sql-ast-derive" -version = "0.13.4" +version = "0.13.5" dependencies = [ "proc-macro2", "quote", @@ -101,7 +101,7 @@ dependencies = [ [[package]] name = "polyglot-sql-function-catalogs" -version = "0.13.4" +version = "0.13.5" [[package]] name = "proc-macro2" diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index bf105a6a..c90ba3b2 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -5,4 +5,4 @@ edition = "2021" publish = false [dependencies] -polyglot-sql = { version = "0.13.4", path = "../../crates/polyglot-sql" } +polyglot-sql = { version = "0.13.5", path = "../../crates/polyglot-sql" } diff --git a/packages/documentation/package.json b/packages/documentation/package.json index 0589a945..e9acc43d 100644 --- a/packages/documentation/package.json +++ b/packages/documentation/package.json @@ -1,6 +1,6 @@ { "name": "@polyglot-sql/documentation", - "version": "0.13.4", + "version": "0.13.5", "private": true, "type": "module", "scripts": { diff --git a/packages/go/README.md b/packages/go/README.md index 98a33531..7d093f0c 100644 --- a/packages/go/README.md +++ b/packages/go/README.md @@ -21,7 +21,7 @@ go get github.com/tobilg/polyglot/packages/go ``` Go module releases use nested tags that match the root Polyglot release, for -example `packages/go/v0.13.4`. +example `packages/go/v0.13.5`. ## Native Library Setup diff --git a/packages/go/types.go b/packages/go/types.go index a46e2973..ee9117e8 100644 --- a/packages/go/types.go +++ b/packages/go/types.go @@ -2,7 +2,7 @@ package polyglot import "encoding/json" -const sdkVersion = "0.13.4" +const sdkVersion = "0.13.5" func Version() string { return sdkVersion diff --git a/packages/playground/package.json b/packages/playground/package.json index 092b7c39..4f53b638 100644 --- a/packages/playground/package.json +++ b/packages/playground/package.json @@ -1,6 +1,6 @@ { "name": "@polyglot-sql/playground", - "version": "0.13.4", + "version": "0.13.5", "private": true, "type": "module", "scripts": { diff --git a/packages/python-docs/package.json b/packages/python-docs/package.json index fd23fb30..0365ccae 100644 --- a/packages/python-docs/package.json +++ b/packages/python-docs/package.json @@ -1,6 +1,6 @@ { "name": "@polyglot-sql/python-docs", - "version": "0.13.4", + "version": "0.13.5", "private": true, "type": "module", "scripts": { diff --git a/packages/sdk/package.json b/packages/sdk/package.json index ff9e75be..cb1ee5f5 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@polyglot-sql/sdk", - "version": "0.13.4", + "version": "0.13.5", "description": "SQL dialect translator powered by WebAssembly", "type": "module", "main": "./dist/index.cjs",