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
2 changes: 1 addition & 1 deletion crates/sqlquack-lint/src/lint/_helpers/terminal_shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ pub(super) fn collect_terminal_shape_facts(
let terminal_is_plain = from_position.is_some_and(|position| {
let projection = &tail[..position];
let relation = tail.get(position + 1);
let remainder = &tail[position.saturating_add(2)..];
let remainder = tail.get(position + 2..).unwrap_or_default();
!projection.is_empty()
&& plain_terminal_projection(tokens, projection)
&& relation.is_some_and(|&index| {
Expand Down
2 changes: 2 additions & 0 deletions crates/sqlquack-lint/src/lint/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ mod long_chains;
mod sql_lint;
#[path = "tests/test_types.rs"]
mod test_types;
#[path = "tests/test_trailing_from.rs"]
mod trailing_from;
#[path = "tests/test_unused_ctes.rs"]
mod unused_ctes;
#[path = "tests/test_unused_table_alias.rs"]
Expand Down
64 changes: 64 additions & 0 deletions crates/sqlquack-lint/src/lint/tests/test_trailing_from.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use crate::lint::tests::helpers::diagnostics_for_dialect;
use crate::lint::tests::test_types::TrailingFromTestCase;

/// A query whose final FROM names no relation: the terminal-shape rule reads past that FROM.
const SQL: &str = "WITH t AS (SELECT 1) SELECT * FROM";

#[test]
fn given_a_query_ending_in_from_when_linting_then_each_dialect_refuses_or_lints_it() {
let test_cases = [
TrailingFromTestCase {
description: "Snowflake refuses it",
dialect: "snowflake",
expected_parse_error: true,
},
TrailingFromTestCase {
description: "DuckDB refuses it",
dialect: "duckdb",
expected_parse_error: true,
},
TrailingFromTestCase {
description: "SQLite refuses it",
dialect: "sqlite",
expected_parse_error: true,
},
TrailingFromTestCase {
description: "PostgreSQL refuses it",
dialect: "postgres",
expected_parse_error: true,
},
TrailingFromTestCase {
description: "Generic refuses it",
dialect: "generic",
expected_parse_error: true,
},
TrailingFromTestCase {
description: "BigQuery refuses it",
dialect: "bigquery",
expected_parse_error: true,
},
TrailingFromTestCase {
description: "Databricks parses it, so the terminal-shape rule lints it",
dialect: "databricks",
expected_parse_error: false,
},
];
for test_case in test_cases {
let result = diagnostics_for_dialect(SQL, test_case.dialect, &["SQLCLEAN035"]);
assert_eq!(
result
.as_ref()
.err()
.is_some_and(|error| error.contains("Parse error")),
test_case.expected_parse_error,
"{}: {result:?}",
test_case.description
);
assert_eq!(
result.is_ok(),
!test_case.expected_parse_error,
"{}: {result:?}",
test_case.description
);
}
}
6 changes: 6 additions & 0 deletions crates/sqlquack-lint/src/lint/tests/test_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ pub(crate) struct NestingRefusalTestCase {
pub expected_code: &'static str,
}

pub(crate) struct TrailingFromTestCase {
pub description: &'static str,
pub dialect: &'static str,
pub expected_parse_error: bool,
}

pub(crate) struct CrossJoinTestCase {
pub description: &'static str,
pub sql: &'static str,
Expand Down
Loading