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
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use crate::bigquery::profile::tests::helpers::bigquery_output_type;
use crate::bigquery::profile::tests::helpers::typed_bigquery_shop_schema;
use crate::bigquery::profile::tests::test_types::BigqueryTypeTestCase;
use crate::dialects::constants::BIGQUERY;
use crate::lineage::tests::helpers::duckdb_shop_schema;
use crate::lineage::tests::helpers::output_ranges;
use crate::lineage::tests::test_types::StrictRangeTestCase;

/// Each expected type is the one GoogleSQL's analyzer gives the same query over the same columns.
#[test]
Expand Down Expand Up @@ -172,3 +176,39 @@ fn given_bigquery_queries_when_typing_outputs_then_each_has_the_analyzer_type()
);
}
}

#[test]
fn given_a_strict_parse_when_tracing_lineage_then_each_output_has_its_items_byte_range() {
let test_cases = [StrictRangeTestCase {
description: "a literal aliased after a two-byte character, a qualified column, a labelled call, a bare column and a qualified star",
sql: "SELECT 'é' AS e, w.status, UPPER(w.status) loud, id, w.* FROM orders AS w",
expected_ranges: &[
7..16,
18..26,
28..48,
50..52,
54..57,
54..57,
54..57,
54..57,
54..57,
],
}];
for test_case in test_cases {
let statement = sqlquack_syntax_bigquery::parse_strict(test_case.sql)
.unwrap_or_else(|failure| panic!("{}: {failure:?}", test_case.description))
.statements
.remove(0);
assert_eq!(
output_ranges(&statement, &BIGQUERY, &duckdb_shop_schema(), test_case.sql),
test_case
.expected_ranges
.iter()
.cloned()
.map(Some)
.collect::<Vec<_>>(),
"{}",
test_case.description
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ use crate::lineage::_helpers::findings::statement::statement_findings;
use crate::lineage::main::statement_lineage::statement_lineage;
use crate::lineage::models::LineageContext;
use crate::lineage::models::Nullability::{NonNull, Nullable, Unknown};
use crate::lineage::tests::helpers::duckdb_shop_schema;
use crate::lineage::tests::helpers::output_ranges;
use crate::lineage::tests::helpers::owned_findings;
use crate::lineage::tests::test_types::StrictRangeTestCase;
use crate::lineage::tests::test_types::{EngineTypeReplayTestCase, QueryFindingsTestCase};

/// Result types ClickHouse 26.1 reported for built-in calls and expressions, under SQLBuild's settings.
Expand Down Expand Up @@ -398,3 +401,44 @@ fn given_a_name_joined_relations_share_when_resolving_then_clickhouse_reads_the_
);
}
}

#[test]
fn given_a_strict_parse_when_tracing_lineage_then_each_output_has_its_items_byte_range() {
let test_cases = [StrictRangeTestCase {
description: "a literal aliased after a two-byte character, a qualified column, a labelled call, a bare column and a qualified star",
sql: "SELECT 'é' AS e, w.status, UPPER(w.status) loud, id, w.* FROM orders AS w",
expected_ranges: &[
7..16,
18..26,
28..48,
50..52,
54..57,
54..57,
54..57,
54..57,
54..57,
],
}];
for test_case in test_cases {
let statement = sqlquack_syntax_clickhouse::parse_strict(test_case.sql)
.unwrap_or_else(|failure| panic!("{}: {failure:?}", test_case.description))
.statements
.remove(0);
assert_eq!(
output_ranges(
&statement,
&CLICKHOUSE,
&duckdb_shop_schema(),
test_case.sql
),
test_case
.expected_ranges
.iter()
.cloned()
.map(Some)
.collect::<Vec<_>>(),
"{}",
test_case.description
);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use crate::databricks::profile::tests::helpers::databricks_findings;
use crate::databricks::profile::tests::helpers::databricks_outputs;
use crate::databricks::profile::tests::test_types::DatabricksOutputsTestCase;
use crate::dialects::constants::DATABRICKS;
use crate::lineage::tests::helpers::duckdb_shop_schema;
use crate::lineage::tests::helpers::output_ranges;
use crate::lineage::tests::helpers::owned_findings;
use crate::lineage::tests::test_types::QueryFindingsTestCase;
use crate::lineage::tests::test_types::StrictRangeTestCase;

#[test]
fn given_a_databricks_query_when_checking_it_then_only_rules_databricks_shares_are_found() {
Expand Down Expand Up @@ -191,3 +195,44 @@ fn given_a_databricks_query_when_naming_and_typing_outputs_then_they_are_the_war
);
}
}

#[test]
fn given_a_strict_parse_when_tracing_lineage_then_each_output_has_its_items_byte_range() {
let test_cases = [StrictRangeTestCase {
description: "a literal aliased after a two-byte character, a qualified column, a labelled call, a bare column and a qualified star",
sql: "SELECT 'é' AS e, w.status, UPPER(w.status) loud, id, w.* FROM orders AS w",
expected_ranges: &[
7..16,
18..26,
28..48,
50..52,
54..57,
54..57,
54..57,
54..57,
54..57,
],
}];
for test_case in test_cases {
let statement = sqlquack_syntax_databricks::parse_strict(test_case.sql)
.unwrap_or_else(|failure| panic!("{}: {failure:?}", test_case.description))
.statements
.remove(0);
assert_eq!(
output_ranges(
&statement,
&DATABRICKS,
&duckdb_shop_schema(),
test_case.sql
),
test_case
.expected_ranges
.iter()
.cloned()
.map(Some)
.collect::<Vec<_>>(),
"{}",
test_case.description
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ use sqlquack_syntax::{Dialect, ParseOptions};
use crate::dialects::constants::DUCKDB;
use crate::lineage::main::query_lineage::query_lineage;
use crate::lineage::models::{LineageContext, TransformKind};
use crate::lineage::tests::helpers::duckdb_shop_schema;
use crate::lineage::tests::helpers::output_ranges;
use crate::lineage::tests::helpers::{shop_schema, written};
use crate::lineage::tests::test_types::LineageDetailTestCase;
use crate::lineage::tests::test_types::StrictRangeTestCase;

#[test]
fn given_a_query_when_tracing_lineage_then_values_rows_and_untraced_outputs_are_reported() {
Expand Down Expand Up @@ -108,3 +111,39 @@ fn given_a_query_when_tracing_lineage_then_values_rows_and_untraced_outputs_are_
);
}
}

#[test]
fn given_a_strict_parse_when_tracing_lineage_then_each_output_has_its_items_byte_range() {
let test_cases = [StrictRangeTestCase {
description: "a literal aliased after a two-byte character, a qualified column, a labelled call, a bare column and a qualified star",
sql: "SELECT 'é' AS e, w.status, UPPER(w.status) loud, id, w.* FROM orders AS w",
expected_ranges: &[
7..16,
18..26,
28..48,
50..52,
54..57,
54..57,
54..57,
54..57,
54..57,
],
}];
for test_case in test_cases {
let statement = sqlquack_syntax_duckdb::parse_strict(test_case.sql)
.unwrap_or_else(|failure| panic!("{}: {failure:?}", test_case.description))
.statements
.remove(0);
assert_eq!(
output_ranges(&statement, &DUCKDB, &duckdb_shop_schema(), test_case.sql),
test_case
.expected_ranges
.iter()
.cloned()
.map(Some)
.collect::<Vec<_>>(),
"{}",
test_case.description
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use sqlquack_syntax::Expression;
use sqlquack_syntax::ExpressionWalk;
use sqlquack_syntax::expressions::names::models::{Identifier, Literal, Star};
use sqlquack_syntax::expressions::queries::models::{Cte, OrderBy, Select, Union, With};
use sqlquack_syntax::tokens::models::Span;

use crate::dialects::main::dialect_rules::dialect_rules;
use crate::lineage::_helpers::tracing::expressions::{expression_lineage, is_aggregate};
Expand Down Expand Up @@ -583,7 +584,7 @@ fn select_query(
name: output_name(projection, context),
key: output_key(projection, context),
lineage: expression_lineage(projection, &scope, context),
expression: projection.source_span(),
expression: item_range(projection),
};
query.rows.rows_of(&column.lineage);
if !holds_aggregate(projection) {
Expand Down Expand Up @@ -1018,3 +1019,15 @@ fn is_star(projection: &Expression) -> bool {
_ => false,
}
}

/// A select item's source range; a column's runs from its qualifier, which some parsers' column span omits.
fn item_range(projection: &Expression) -> Option<Span> {
let qualifier = match projection {
Expression::Column(column) => column.table.as_ref().and_then(|table| table.span),
_ => None,
};
match (qualifier, projection.source_span()) {
(Some(qualifier), Some(name)) => Some(qualifier.through(name)),
(_, range) => range,
}
}
23 changes: 22 additions & 1 deletion crates/sqlquack-semantics/src/lineage/tests/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::collections::HashMap;
use std::ops::Range;

use sqlquack_parse::parse_statements_with;
use sqlquack_syntax::{CallMode, Dialect, ParseOptions};
use sqlquack_syntax::{CallMode, Dialect, Expression, ParseOptions};

use crate::dialects::types::DialectProfile;
use crate::lineage::_helpers::findings::statement::statement_findings;
use crate::lineage::main::statement_lineage::statement_lineage;
use crate::lineage::models::LineageContext;
use crate::lineage::models::NullabilityRule;
use crate::lineage::models::{Nullability, SchemaColumn, SourceColumn};
Expand All @@ -19,6 +21,25 @@ impl LineageSchema for ShopSchema {
}
}

/// Each output's byte range in `sql` when lineage traces `statement` under `profile`.
pub(crate) fn output_ranges(
statement: &Expression,
profile: &dyn DialectProfile,
schema: &ShopSchema,
sql: &str,
) -> Vec<Option<Range<usize>>> {
let context = LineageContext {
profile,
schema,
sql: Some(sql),
};
statement_lineage(statement, context)
.outputs
.into_iter()
.map(|output| output.expression_range)
.collect()
}

/// Orders, customers and products, as a Snowflake catalog spells unquoted names.
pub(crate) fn shop_schema() -> ShopSchema {
schema_of([
Expand Down
8 changes: 8 additions & 0 deletions crates/sqlquack-semantics/src/lineage/tests/test_types.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::collections::HashMap;
use std::ops::Range;

use sqlquack_syntax::Dialect;

Expand Down Expand Up @@ -28,6 +29,13 @@ pub(crate) struct LineageDetailTestCase {
pub(crate) expected_rows_resolved: bool,
}

/// A query and each output's expected byte range, read by one dialect's strict parser.
pub(crate) struct StrictRangeTestCase {
pub(crate) description: &'static str,
pub(crate) sql: &'static str,
pub(crate) expected_ranges: &'static [Range<usize>],
}

/// A query and, per output, the SQL text its expression range covers, or none.
pub(crate) struct OutputRangeTestCase {
pub(crate) description: &'static str,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ use sqlquack_syntax::Dialect;

use crate::dialects::constants::POSTGRES;
use crate::lineage::models::TransformKind;
use crate::lineage::tests::helpers::output_ranges;
use crate::lineage::tests::helpers::{duckdb_shop_schema, typed_column, typed_duckdb_shop_schema};
use crate::lineage::tests::test_types::QueryLineageTestCase;
use crate::lineage::tests::test_types::StrictRangeTestCase;
use crate::lineage::tests::test_types::TypedShopSchema;
use crate::postgres::profile::tests::helpers::lineage_outputs;

Expand Down Expand Up @@ -238,3 +240,39 @@ fn given_a_union_of_untyped_columns_when_tracing_postgres_lineage_then_it_stays_
);
}
}

#[test]
fn given_a_strict_parse_when_tracing_lineage_then_each_output_has_its_items_byte_range() {
let test_cases = [StrictRangeTestCase {
description: "a literal aliased after a two-byte character, a qualified column, a labelled call, a bare column and a qualified star",
sql: "SELECT 'é' AS e, w.status, UPPER(w.status) loud, id, w.* FROM orders AS w",
expected_ranges: &[
7..16,
18..26,
28..48,
50..52,
54..57,
54..57,
54..57,
54..57,
54..57,
],
}];
for test_case in test_cases {
let statement = sqlquack_syntax_postgres::parse_strict(test_case.sql)
.unwrap_or_else(|failure| panic!("{}: {failure:?}", test_case.description))
.statements
.remove(0);
assert_eq!(
output_ranges(&statement, &POSTGRES, &duckdb_shop_schema(), test_case.sql),
test_case
.expected_ranges
.iter()
.cloned()
.map(Some)
.collect::<Vec<_>>(),
"{}",
test_case.description
);
}
}
Loading
Loading