Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 42 additions & 21 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions crates/sqlclean/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions crates/sqlclean/src/sql_lint/_helpers/cte_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<usize> = (0..tokens.len())
Expand Down Expand Up @@ -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<String, String> {
let tokens = sqlquack_syntax::tokenize(formatted, dialect)
let tokens = sqlquack_parse::tokenize(formatted, dialect)
.map_err(|_| RESTORE_FAILURE.to_string())?;
let significant: Vec<usize> = (0..tokens.len())
.filter(|&index| matches!(tokens[index].kind, TokenKind::Significant(_)))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<String, String> {
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()
Expand Down Expand Up @@ -253,7 +253,7 @@ fn restored_with_tokens(
dialect: Dialect,
) -> Result<String, String> {
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(
Expand Down
105 changes: 105 additions & 0 deletions crates/sqlclean/src/sql_lint/tests/test_sql_lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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(())
}
7 changes: 7 additions & 0 deletions crates/sqlclean/src/sql_lint/tests/test_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
2 changes: 1 addition & 1 deletion crates/sqlclean/src/sql_tokens/tests/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<String>, 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(_)))
Expand Down
Loading