Skip to content
Closed
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
8 changes: 7 additions & 1 deletion crates/sqlquack-core/src/diagnostics/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ pub const ZONED_LOCAL_TIME_NOTE: &str = "a Snowflake TIMESTAMP_TZ stores each va
pub const ZONED_LOCAL_TIME_HELP: &str = "convert the value to a wall time first: \
`CONVERT_TIMEZONE('UTC', col)::TIMESTAMP_NTZ` for UTC, or \
`CONVERT_TIMEZONE(<zone column>, col)::TIMESTAMP_NTZ` for local day boundaries in a known zone";
/// The delimiters of a dbt Jinja expression or statement, which Snowflake never parses.
pub const JINJA_MARKERS: [&str; 2] = ["{{", "{%"];
/// The help for SQL that does not parse where it holds Jinja: it was pasted before dbt compiled it.
pub const JINJA_HELP: &str = "this looks like uncompiled dbt Jinja; run `dbt compile` and check \
the SQL it writes under target/compiled";
/// The prefix of a complexity guard's message, before the rest of its `E_GUARD_*` name.
pub const GUARD_PREFIX: &str = "E_GUARD_";
/// The longest source line shown in full; longer lines show a window around the span.
Expand Down Expand Up @@ -62,7 +67,8 @@ pub const CODES: &[CodeEntry] = &[
converted. The span points where the parser stopped and the label carries the \
parser's message. Check the SQL at that point against Snowflake's grammar; if \
Snowflake accepts the statement, SQLQuack's parser is missing the syntax and the \
statement should be reported with the label.",
statement should be reported with the label. Where the line holds dbt Jinja, the help says \
to compile the model with dbt first, since Snowflake does not parse Jinja either.",
example: r#"echo "SELECT a, FROM orders WHERE (b = 1" | sqlquack check"#,
},
CodeEntry {
Expand Down
17 changes: 15 additions & 2 deletions crates/sqlquack-core/src/diagnostics/main/diagnose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ use crate::diagnostics::_helpers::coded::coded;
use crate::diagnostics::_helpers::line_index::LineIndex;
use crate::diagnostics::constants::{
CASE_ONLY_MATCH, CASE_ONLY_MATCH_HELP, CONVERSION_BUDGET, DIVERGENCE_REFUSED, GUARD_PREFIX,
MISSING_RULE, NOT_EXPRESSIBLE, PARSE_FIDELITY, TYPE_CONDITION, UNPARSED_PREFIX,
UNRESOLVED_NAME, ZONED_LOCAL_TIME, ZONED_LOCAL_TIME_HELP, ZONED_LOCAL_TIME_NOTE,
JINJA_HELP, JINJA_MARKERS, MISSING_RULE, NOT_EXPRESSIBLE, PARSE_FIDELITY, TYPE_CONDITION,
UNPARSED_PREFIX, UNRESOLVED_NAME, ZONED_LOCAL_TIME, ZONED_LOCAL_TIME_HELP,
ZONED_LOCAL_TIME_NOTE,
};
use crate::diagnostics::models::Diagnostic;
use crate::refusals::models::{Refusal, RefusalCategory};
Expand All @@ -25,6 +26,9 @@ pub fn diagnose(refusals: &[Refusal], source: &str) -> Vec<Diagnostic> {
{
diagnostic.help = Some(CASE_ONLY_MATCH_HELP.to_owned());
}
if refusal.category == RefusalCategory::ParseFidelity && holds_jinja(refusal, source) {
diagnostic.help = Some(JINJA_HELP.to_owned());
}
if refusal.category == RefusalCategory::TypeCondition
&& refusal.reason.contains(ZONED_LOCAL_TIME)
{
Expand All @@ -44,6 +48,15 @@ pub fn diagnose(refusals: &[Refusal], source: &str) -> Vec<Diagnostic> {
diagnostics
}

/// Whether the line the parser stopped on, or the whole source without a location, holds Jinja.
fn holds_jinja(refusal: &Refusal, source: &str) -> bool {
let text = refusal
.location
.and_then(|location| source.lines().nth(location.line.saturating_sub(1)))
.unwrap_or(source);
JINJA_MARKERS.iter().any(|marker| text.contains(marker))
}

/// The code a refusal maps to, and the label left once its code's own wording is removed.
fn code_and_label(refusal: &Refusal) -> (&'static str, String) {
let code = match refusal.category {
Expand Down
28 changes: 28 additions & 0 deletions crates/sqlquack/tests/cli/test_convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1724,6 +1724,34 @@ fn given_a_refused_divergence_when_converting_then_its_help_names_it_and_only_fi
}
}

#[test]
fn given_uncompiled_dbt_jinja_when_converting_then_the_help_points_to_dbt_compile() {
let test_cases = [
DivergenceHelpTestCase {
description: "a pasted model with Jinja expressions",
sql: "select {{ dbt_utils.generate_surrogate_key(['order_id']) }} as k \
from {{ ref('stg_orders') }}",
flags: &[],
expected_help: "this looks like uncompiled dbt Jinja; run `dbt compile` and check the \
SQL it writes under target/compiled",
},
DivergenceHelpTestCase {
description: "a parse failure without Jinja keeps no help",
sql: "SELECT a, FROM orders WHERE (b = 1",
flags: &[],
expected_help: "",
},
];
for test_case in test_cases {
assert_eq!(
first_refusal_help(test_case.sql, test_case.flags),
test_case.expected_help,
"{}",
test_case.description
);
}
}

#[test]
fn given_a_malformed_schema_when_converting_then_the_error_names_what_is_wrong_in_schema_terms() {
let test_cases = [
Expand Down
2 changes: 1 addition & 1 deletion website/src/content/docs/docs/diagnostics/reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Why a statement was not converted.

**the SQL does not parse** (`parse_fidelity`)

SQLQuack's Snowflake parser could not read the SQL, so nothing in it was converted. The span points where the parser stopped and the label carries the parser's message. Check the SQL at that point against Snowflake's grammar; if Snowflake accepts the statement, SQLQuack's parser is missing the syntax and the statement should be reported with the label.
SQLQuack's Snowflake parser could not read the SQL, so nothing in it was converted. The span points where the parser stopped and the label carries the parser's message. Check the SQL at that point against Snowflake's grammar; if Snowflake accepts the statement, SQLQuack's parser is missing the syntax and the statement should be reported with the label. Where the line holds dbt Jinja, the help says to compile the model with dbt first, since Snowflake does not parse Jinja either.

**Example:**

Expand Down
Loading