Skip to content

Commit 3308f20

Browse files
committed
perf(compile): collect SQL-test relation markers without a JSON tree
Native SQL-test planning serialized each parsed model statement to a serde_json Value only to find its FROM and JOIN marker calls. A streaming serializer now reproduces that walk, including key order, repeated-key replacement and to_value failures, cutting native planning CPU by about 40 percent. A JSON-walk oracle in the tests pins the equivalence.
1 parent ece89f1 commit 3308f20

8 files changed

Lines changed: 1099 additions & 73 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//! Error types owned by SQL-test planning helpers.
2+
3+
use std::fmt::Display;
4+
5+
use serde::ser;
6+
7+
/// A parsed statement that `serde_json::to_value` could not represent.
8+
#[derive(Debug)]
9+
pub(crate) struct RelationMarkerError;
10+
11+
impl Display for RelationMarkerError {
12+
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
13+
formatter.write_str("value cannot be represented as JSON")
14+
}
15+
}
16+
17+
impl std::error::Error for RelationMarkerError {}
18+
19+
impl ser::Error for RelationMarkerError {
20+
fn custom<M: Display>(_message: M) -> Self {
21+
RelationMarkerError
22+
}
23+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
pub(crate) mod cte_rename;
22
pub(crate) mod cte_slices;
33
pub(crate) mod cte_sql;
4+
pub(crate) mod errors;
45
pub(crate) mod expected_columns;
56
pub(crate) mod extraction;
67
pub(crate) mod helper_scope;
78
pub(crate) mod markers;
89
pub(crate) mod planning;
10+
pub(crate) mod relation_markers;
911
pub(crate) mod rendering;

‎crates/sqlbuild-rules-native/src/compiler/_helpers/sql_tests/planning.rs‎

Lines changed: 7 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ use polyglot_sql::{Dialect, Expression};
88
use rayon::iter::{IntoParallelIterator, ParallelIterator};
99
use regex::Regex;
1010
use serde::{Deserialize, Serialize};
11-
use serde_json::Value;
1211

1312
use crate::compiler::_helpers::sql_tests::cte_slices::SliceDialect;
1413
use crate::compiler::_helpers::sql_tests::cte_sql::{
1514
cte_definition_sql, leading_with_prefix_end, with_leading_ctes, with_unique_ctes,
1615
};
16+
use crate::compiler::_helpers::sql_tests::errors::RelationMarkerError;
1717
use crate::compiler::_helpers::sql_tests::expected_columns::expected_columns;
1818
use crate::compiler::_helpers::sql_tests::helper_scope::{
1919
ScopeGraph, helper_scope_ctes, merged_scoped_ctes,
@@ -22,6 +22,7 @@ use crate::compiler::_helpers::sql_tests::markers::{
2222
ProtectedRanges, marker_names, marker_names_in, replace_callable_markers,
2323
replace_dbt_ref_markers, replace_named_markers,
2424
};
25+
use crate::compiler::_helpers::sql_tests::relation_markers::relation_marker_calls;
2526
use crate::compiler::_helpers::sql_tests::rendering::{
2627
AssertionStep, ChainStep, RenderRequest, render_comparison_sql, render_dialect,
2728
rendered_chain_steps,
@@ -49,7 +50,7 @@ const ASSERT_PREFIX: &str = "__assert__";
4950
const REF_FUNCTION: &str = "__ref";
5051
const SOURCE_FUNCTION: &str = "__source";
5152
const SEED_FUNCTION: &str = "__seed";
52-
const DBT_REF_FUNCTION: &str = "__dbt_ref";
53+
pub(crate) const DBT_REF_FUNCTION: &str = "__dbt_ref";
5354
const UDF_FUNCTION: &str = "__udf";
5455
const TABLE_FUNCTION: &str = "__table_fn";
5556

@@ -1304,13 +1305,13 @@ fn analysis_template(
13041305
return None;
13051306
}
13061307
let expression = statements.remove(0);
1307-
let value = match serde_json::to_value(&expression) {
1308-
Ok(value) => value,
1309-
Err(_) => return None,
1308+
let marker_calls = match relation_marker_calls(&expression) {
1309+
Ok(marker_calls) => marker_calls,
1310+
Err(RelationMarkerError) => return None,
13101311
};
13111312
Some(AnalysisTemplate {
13121313
existing_cte_names: top_level_cte_names(&expression),
1313-
marker_calls: relation_marker_calls(&value),
1314+
marker_calls,
13141315
})
13151316
})
13161317
})
@@ -1484,73 +1485,6 @@ fn resolve_function_calls(
14841485
Ok(result)
14851486
}
14861487

1487-
fn relation_marker_calls(value: &Value) -> Vec<(String, String)> {
1488-
fn marker(expression: &Value) -> Option<(String, String)> {
1489-
let object = expression.as_object()?;
1490-
if let Some(alias) = object.get("alias") {
1491-
return marker(alias.get("this")?);
1492-
}
1493-
let function = object.get("function")?.as_object()?;
1494-
let function_name = function.get("name")?.as_str()?.to_ascii_lowercase();
1495-
let args = function.get("args")?.as_array()?;
1496-
let arg_name = |arg: &Value| {
1497-
arg.get("column")?
1498-
.get("name")?
1499-
.get("name")?
1500-
.as_str()
1501-
.map(str::to_string)
1502-
};
1503-
let referenced_name = if function_name == DBT_REF_FUNCTION {
1504-
match args.as_slice() {
1505-
[first] => arg_name(first)?,
1506-
[first, second] => format!("{}__{}", arg_name(first)?, arg_name(second)?),
1507-
_ => return None,
1508-
}
1509-
} else {
1510-
let [first] = args.as_slice() else {
1511-
return None;
1512-
};
1513-
arg_name(first)?
1514-
};
1515-
Some((function_name, referenced_name))
1516-
}
1517-
1518-
fn walk(value: &Value, calls: &mut Vec<(String, String)>) {
1519-
match value {
1520-
Value::Object(object) => {
1521-
if let Some(expressions) = object
1522-
.get("from")
1523-
.and_then(|from| from.get("expressions"))
1524-
.and_then(Value::as_array)
1525-
{
1526-
calls.extend(expressions.iter().filter_map(marker));
1527-
}
1528-
if let Some(joins) = object.get("joins").and_then(Value::as_array) {
1529-
calls.extend(
1530-
joins
1531-
.iter()
1532-
.filter_map(|join| join.get("this"))
1533-
.filter_map(marker),
1534-
);
1535-
}
1536-
for child in object.values() {
1537-
walk(child, calls);
1538-
}
1539-
}
1540-
Value::Array(values) => {
1541-
for child in values {
1542-
walk(child, calls);
1543-
}
1544-
}
1545-
_ => {}
1546-
}
1547-
}
1548-
1549-
let mut calls: Vec<(String, String)> = Vec::new();
1550-
walk(value, &mut calls);
1551-
calls
1552-
}
1553-
15541488
fn top_level_cte_names(expression: &Expression) -> HashSet<String> {
15551489
let Expression::Select(select) = expression else {
15561490
return HashSet::new();

0 commit comments

Comments
 (0)