diff --git a/Cargo.lock b/Cargo.lock index 996b5ee..354fa85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -587,6 +587,7 @@ dependencies = [ "sqlquack-fingerprint", "sqlquack-format", "sqlquack-lint", + "sqlquack-parse", "sqlquack-syntax", "sqlquack-syntax-sqlserver", ] @@ -609,53 +610,70 @@ dependencies = [ "toml", ] +[[package]] +name = "sqlquack-derive" +version = "0.2.0" +source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=353861ea9c44307f076e06281cb1ac91110969de#353861ea9c44307f076e06281cb1ac91110969de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "sqlquack-fingerprint" version = "0.2.0" -source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=804864d2073ef869cbfe4a9334f7e48b20072326#804864d2073ef869cbfe4a9334f7e48b20072326" +source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=353861ea9c44307f076e06281cb1ac91110969de#353861ea9c44307f076e06281cb1ac91110969de" dependencies = [ "sha2", + "sqlquack-parse", "sqlquack-syntax", - "sqlquack-syntax-bigquery", - "sqlquack-syntax-postgres", - "sqlquack-syntax-sqlserver", ] [[package]] name = "sqlquack-format" version = "0.2.0" -source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=804864d2073ef869cbfe4a9334f7e48b20072326#804864d2073ef869cbfe4a9334f7e48b20072326" +source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=353861ea9c44307f076e06281cb1ac91110969de#353861ea9c44307f076e06281cb1ac91110969de" dependencies = [ "serde_json", + "sqlquack-parse", "sqlquack-syntax", - "sqlquack-syntax-bigquery", - "sqlquack-syntax-postgres", - "sqlquack-syntax-sqlserver", ] [[package]] name = "sqlquack-lint" version = "0.2.0" -source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=804864d2073ef869cbfe4a9334f7e48b20072326#804864d2073ef869cbfe4a9334f7e48b20072326" +source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=353861ea9c44307f076e06281cb1ac91110969de#353861ea9c44307f076e06281cb1ac91110969de" dependencies = [ "rayon", "rustc-hash", "serde", "serde_json", + "sqlquack-parse", + "sqlquack-syntax", +] + +[[package]] +name = "sqlquack-parse" +version = "0.2.0" +source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=353861ea9c44307f076e06281cb1ac91110969de#353861ea9c44307f076e06281cb1ac91110969de" +dependencies = [ "sqlquack-syntax", "sqlquack-syntax-bigquery", "sqlquack-syntax-databricks", "sqlquack-syntax-postgres", + "sqlquack-syntax-snowflake", + "sqlquack-syntax-sqlserver", ] [[package]] name = "sqlquack-syntax" version = "0.2.0" -source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=804864d2073ef869cbfe4a9334f7e48b20072326#804864d2073ef869cbfe4a9334f7e48b20072326" +source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=353861ea9c44307f076e06281cb1ac91110969de#353861ea9c44307f076e06281cb1ac91110969de" dependencies = [ "serde", "serde_json", - "sqlquack-syntax-derive", + "sqlquack-derive", "stacker", "thiserror", "unicode-segmentation", @@ -664,7 +682,7 @@ dependencies = [ [[package]] name = "sqlquack-syntax-bigquery" version = "0.2.0" -source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=804864d2073ef869cbfe4a9334f7e48b20072326#804864d2073ef869cbfe4a9334f7e48b20072326" +source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=353861ea9c44307f076e06281cb1ac91110969de#353861ea9c44307f076e06281cb1ac91110969de" dependencies = [ "sqlquack-syntax", "stacker", @@ -673,35 +691,38 @@ dependencies = [ [[package]] name = "sqlquack-syntax-databricks" version = "0.2.0" -source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=804864d2073ef869cbfe4a9334f7e48b20072326#804864d2073ef869cbfe4a9334f7e48b20072326" +source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=353861ea9c44307f076e06281cb1ac91110969de#353861ea9c44307f076e06281cb1ac91110969de" dependencies = [ "sqlquack-syntax", "stacker", ] [[package]] -name = "sqlquack-syntax-derive" +name = "sqlquack-syntax-postgres" version = "0.2.0" -source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=804864d2073ef869cbfe4a9334f7e48b20072326#804864d2073ef869cbfe4a9334f7e48b20072326" +source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=353861ea9c44307f076e06281cb1ac91110969de#353861ea9c44307f076e06281cb1ac91110969de" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "sqlquack-syntax", + "stacker", ] [[package]] -name = "sqlquack-syntax-postgres" +name = "sqlquack-syntax-snowflake" version = "0.2.0" -source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=804864d2073ef869cbfe4a9334f7e48b20072326#804864d2073ef869cbfe4a9334f7e48b20072326" +source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=353861ea9c44307f076e06281cb1ac91110969de#353861ea9c44307f076e06281cb1ac91110969de" dependencies = [ + "serde", + "serde_json", "sqlquack-syntax", "stacker", + "thiserror", + "unicode-segmentation", ] [[package]] name = "sqlquack-syntax-sqlserver" version = "0.2.0" -source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=804864d2073ef869cbfe4a9334f7e48b20072326#804864d2073ef869cbfe4a9334f7e48b20072326" +source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=353861ea9c44307f076e06281cb1ac91110969de#353861ea9c44307f076e06281cb1ac91110969de" dependencies = [ "sqlquack-syntax", "stacker", diff --git a/Cargo.toml b/Cargo.toml index b16b302..fceafb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,11 +30,12 @@ sha2 = "0.10" similar = "2" sqlclean = { path = "crates/sqlclean" } # SQLQuack's formatter and lint engine, and the tokens and trees they read, which replace Polyglot. -sqlquack-fingerprint = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "804864d2073ef869cbfe4a9334f7e48b20072326" } -sqlquack-format = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "804864d2073ef869cbfe4a9334f7e48b20072326" } -sqlquack-lint = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "804864d2073ef869cbfe4a9334f7e48b20072326" } -sqlquack-syntax = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "804864d2073ef869cbfe4a9334f7e48b20072326" } -sqlquack-syntax-sqlserver = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "804864d2073ef869cbfe4a9334f7e48b20072326" } +sqlquack-fingerprint = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "353861ea9c44307f076e06281cb1ac91110969de", features = ["bigquery", "duckdb", "postgres", "snowflake", "sqlite", "sqlserver"] } +sqlquack-format = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "353861ea9c44307f076e06281cb1ac91110969de", features = ["bigquery", "duckdb", "postgres", "snowflake", "sqlserver"] } +sqlquack-lint = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "353861ea9c44307f076e06281cb1ac91110969de", features = ["bigquery", "databricks", "duckdb", "postgres", "snowflake", "sqlite"] } +sqlquack-parse = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "353861ea9c44307f076e06281cb1ac91110969de" } +sqlquack-syntax = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "353861ea9c44307f076e06281cb1ac91110969de" } +sqlquack-syntax-sqlserver = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "353861ea9c44307f076e06281cb1ac91110969de" } [workspace.lints.rust] unsafe_code = "forbid" diff --git a/crates/sqlclean/Cargo.toml b/crates/sqlclean/Cargo.toml index ef651d9..b82d6dc 100644 --- a/crates/sqlclean/Cargo.toml +++ b/crates/sqlclean/Cargo.toml @@ -17,6 +17,7 @@ test-support = [] sqlquack-fingerprint.workspace = true sqlquack-format.workspace = true sqlquack-lint.workspace = true +sqlquack-parse.workspace = true sqlquack-syntax.workspace = true sqlquack-syntax-sqlserver.workspace = true rayon.workspace = true diff --git a/crates/sqlclean/src/sql_lint/_helpers/cte_macros.rs b/crates/sqlclean/src/sql_lint/_helpers/cte_macros.rs index 7fa720d..b69cc68 100644 --- a/crates/sqlclean/src/sql_lint/_helpers/cte_macros.rs +++ b/crates/sqlclean/src/sql_lint/_helpers/cte_macros.rs @@ -38,7 +38,7 @@ pub(super) fn take_out_cte_macros( if !sql.contains(INTERPOLATION_PLACEHOLDER_PREFIX) { return Ok(None); } - let Ok(tokens) = sqlquack_syntax::tokenize(sql, dialect) else { + let Ok(tokens) = sqlquack_parse::tokenize(sql, dialect) else { return Ok(None); }; let significant: Vec = (0..tokens.len()) @@ -109,7 +109,7 @@ pub(super) fn take_out_cte_macros( impl WithoutCteMacros { /// Put the placeholders back into `formatted`, the laid-out stripped SQL, each on its own line. pub(super) fn put_back(&self, formatted: &str, dialect: Dialect) -> Result { - let tokens = sqlquack_syntax::tokenize(formatted, dialect) + let tokens = sqlquack_parse::tokenize(formatted, dialect) .map_err(|_| RESTORE_FAILURE.to_string())?; let significant: Vec = (0..tokens.len()) .filter(|&index| matches!(tokens[index].kind, TokenKind::Significant(_))) @@ -177,7 +177,7 @@ pub(super) fn wrap_query_placeholders( if !sql.contains(INTERPOLATION_PLACEHOLDER_PREFIX) { return None; } - let Ok(tokens) = sqlquack_syntax::tokenize(sql, dialect) else { + let Ok(tokens) = sqlquack_parse::tokenize(sql, dialect) else { return None; }; let significant: Vec<&LosslessToken> = tokens @@ -219,7 +219,7 @@ pub(super) fn wrap_query_placeholders( impl WrappedQueryPlaceholders { /// Take the `SELECT` written before each wrapped placeholder back out of `formatted`. pub(super) fn restore(&self, formatted: &str, dialect: Dialect) -> Result { - let tokens = sqlquack_syntax::tokenize(formatted, dialect) + let tokens = sqlquack_parse::tokenize(formatted, dialect) .map_err(|_| RESTORE_FAILURE.to_string())?; let significant: Vec<&LosslessToken> = tokens .iter() @@ -253,7 +253,7 @@ fn restored_with_tokens( dialect: Dialect, ) -> Result { let restored_tokens = - sqlquack_syntax::tokenize(&restored, dialect).map_err(|_| RESTORE_FAILURE.to_string())?; + sqlquack_parse::tokenize(&restored, dialect).map_err(|_| RESTORE_FAILURE.to_string())?; let restored_texts = token_texts(&restored, &restored_tokens); let same_tokens = restored_texts.len() == original.len() && restored_texts.iter().zip(original).all( diff --git a/crates/sqlclean/src/sql_lint/tests/test_sql_lint.rs b/crates/sqlclean/src/sql_lint/tests/test_sql_lint.rs index c4c76e4..ac463cd 100644 --- a/crates/sqlclean/src/sql_lint/tests/test_sql_lint.rs +++ b/crates/sqlclean/src/sql_lint/tests/test_sql_lint.rs @@ -3,6 +3,7 @@ use serde_json::{Value, json}; use crate::sql_lint::main::engine::lint_json; use crate::sql_lint::tests::helpers::{fixture_harness, format_batch_json}; use crate::sql_lint::tests::{helpers, test_types}; +use crate::sql_tokens::main::query_fingerprint::query_fingerprint; #[test] fn given_production_function_depth_when_linting_then_bounded_parser_accepts_it() { @@ -1879,3 +1880,107 @@ fn given_unknown_rule_when_linting_then_request_is_rejected() -> Result<(), Stri } Ok(()) } + +/// SQLQuack builds each dialect only with its feature, so every dialect the configuration page names is checked, formatted and fingerprinted as it says, and never refused as not built. +#[test] +fn given_each_documented_dialect_when_reading_sql_then_sqlquack_was_built_for_it() +-> Result<(), String> { + let sql = "select a from t where a = 1"; + let test_cases = [ + test_types::DialectReadersTestCase { + description: "bigquery is checked and formatted", + dialect: "bigquery", + expected_linted: true, + expected_formatted: true, + }, + test_types::DialectReadersTestCase { + description: "databricks is checked, and reported unavailable to be formatted", + dialect: "databricks", + expected_linted: true, + expected_formatted: false, + }, + test_types::DialectReadersTestCase { + description: "duckdb is checked and formatted", + dialect: "duckdb", + expected_linted: true, + expected_formatted: true, + }, + test_types::DialectReadersTestCase { + description: "generic is checked, and reported unavailable to be formatted", + dialect: "generic", + expected_linted: true, + expected_formatted: false, + }, + test_types::DialectReadersTestCase { + description: "postgres is checked and formatted", + dialect: "postgres", + expected_linted: true, + expected_formatted: true, + }, + test_types::DialectReadersTestCase { + description: "postgresql is checked and formatted", + dialect: "postgresql", + expected_linted: true, + expected_formatted: true, + }, + test_types::DialectReadersTestCase { + description: "snowflake is checked and formatted", + dialect: "snowflake", + expected_linted: true, + expected_formatted: true, + }, + test_types::DialectReadersTestCase { + description: "sqlite is checked, and reported unavailable to be formatted", + dialect: "sqlite", + expected_linted: true, + expected_formatted: false, + }, + test_types::DialectReadersTestCase { + description: "tsql is formatted, and reported unavailable to be checked", + dialect: "tsql", + expected_linted: false, + expected_formatted: true, + }, + test_types::DialectReadersTestCase { + description: "mssql is formatted, and reported unavailable to be checked", + dialect: "mssql", + expected_linted: false, + expected_formatted: true, + }, + test_types::DialectReadersTestCase { + description: "sqlserver is formatted, and reported unavailable to be checked", + dialect: "sqlserver", + expected_linted: false, + expected_formatted: true, + }, + ]; + for test_case in &test_cases { + let linted: Value = serde_json::from_str(&lint_json( + &json!({"version": 1, "sql": sql, "dialect": test_case.dialect}).to_string(), + )?) + .map_err(|error| error.to_string())?; + let formatted = helpers::format_at_width(sql, test_case.dialect, 100)?; + let fingerprint = query_fingerprint(sql, test_case.dialect); + assert_eq!( + linted["unavailable"].is_null() && linted["diagnostics"] == json!([]), + test_case.expected_linted, + "{}: {linted}", + test_case.description + ); + assert_eq!( + ( + formatted["formatted"] == true, + formatted["unavailable"].is_null() + ), + (test_case.expected_formatted, test_case.expected_formatted), + "{}: {formatted}", + test_case.description + ); + assert!( + fingerprint.is_ok(), + "{}: {fingerprint:?}", + test_case.description + ); + } + Ok(()) +} diff --git a/crates/sqlclean/src/sql_lint/tests/test_types.rs b/crates/sqlclean/src/sql_lint/tests/test_types.rs index a6c51e5..04dec4f 100644 --- a/crates/sqlclean/src/sql_lint/tests/test_types.rs +++ b/crates/sqlclean/src/sql_lint/tests/test_types.rs @@ -164,3 +164,10 @@ pub(crate) struct UnavailableCorpusTestCase { pub dialect: &'static str, pub expected_unavailable: usize, } + +pub(crate) struct DialectReadersTestCase { + pub description: &'static str, + pub dialect: &'static str, + pub expected_linted: bool, + pub expected_formatted: bool, +} diff --git a/crates/sqlclean/src/sql_tokens/tests/helpers.rs b/crates/sqlclean/src/sql_tokens/tests/helpers.rs index b01abdf..c668aa9 100644 --- a/crates/sqlclean/src/sql_tokens/tests/helpers.rs +++ b/crates/sqlclean/src/sql_tokens/tests/helpers.rs @@ -3,7 +3,7 @@ use sqlquack_syntax::tokens::models::Dialect; /// Significant token texts of `sql` under one of SQLQuack's tokenizers. pub(crate) fn significant_texts(sql: &str, tokenizer: Dialect) -> Result, String> { - Ok(sqlquack_syntax::tokenize(sql, tokenizer) + Ok(sqlquack_parse::tokenize(sql, tokenizer) .map_err(|error| error.to_string())? .iter() .filter(|token| matches!(token.kind, TokenKind::Significant(_)))