Skip to content

Commit 9f94ebd

Browse files
authored
perf(compile): plan SQL tests in the background in every mode (#926)
* 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. * perf(compile): plan SQL tests in the background in every compile mode SQL-test planning only ran in the background for uncached projects of 128 to 5000 models with rules selected, so cache-on and large compiles planned on the critical path in the write phase. Prepared artifacts now fall back to planning SQL tests alone in the background, against the published target and its artifact cache, and the write phase consumes that plan at the point it would otherwise plan inline. Results and errors are identical to inline planning. * style: reword a docstring the setting-help scanner misread
1 parent ece89f1 commit 9f94ebd

17 files changed

Lines changed: 1569 additions & 117 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)