Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
8685dcd
fix(rename): rename contract-declared columns and audit arguments wit…
kvlonge Oct 18, 2026
0d12e36
fix(cli): report an unreadable --select-file as a coded error
kvlonge Oct 19, 2026
39ec24f
fix(rules): evaluate model rules on a selection without models
kvlonge Oct 19, 2026
0d95ece
fix(janitor): refuse cleanly when confirmation cannot be read
kvlonge Oct 19, 2026
ad47c7c
fix(macros): reject list and dict keys in macro argument dicts
kvlonge Oct 19, 2026
cdfebe5
fix(cli): report a warehouse connection failure as a coded error
kvlonge Oct 19, 2026
1467806
fix(diff): report unknown key columns and missing relations as coded …
kvlonge Oct 19, 2026
cd669ba
fix(plan): draw the tree bar under detail rows of non-last entries
kvlonge Oct 19, 2026
e403337
fix(build): show target and outcome on standalone audit rows
kvlonge Oct 19, 2026
72a945d
fix(plan): exit 1 when the plan has error findings
kvlonge Oct 19, 2026
9db6b32
fix(test): run unit tests against temporary copies of project functions
kvlonge Oct 19, 2026
da36af4
ci: fail check-ci when generated skill reference pages are stale
kvlonge Oct 19, 2026
1a06ec4
fix(audits): point seed and source audits that read @model at @relation
kvlonge Oct 19, 2026
53d5282
test: expect sqb plan to exit 1 for an on_schema_change fail finding
kvlonge Oct 19, 2026
f63b8a0
feat: add single-server ClickHouse adapter
kvlonge Oct 19, 2026
1496c5d
feat: read ReplacingMergeTree sources with the final source option
kvlonge Oct 19, 2026
33c4af5
test: run ClickHouse integration and CLI tests in CI and document the…
kvlonge Oct 19, 2026
0f52bae
fix: keep microsecond microbatch state timestamps on ClickHouse
kvlonge Oct 19, 2026
2fe2eb6
feat: configure ClickHouse table engine, sorting and partitioning per…
kvlonge Oct 19, 2026
03bae4c
feat: warn when an incremental model's ClickHouse layout change await…
kvlonge Oct 19, 2026
99b813e
fix: compare ClickHouse primary keys with sorting keys element by ele…
kvlonge Oct 19, 2026
59e98a4
fix: refuse ClickHouse connection settings that override required ones
kvlonge Oct 19, 2026
c19f81d
fix: approve the ClickHouse connection's raw query boundary
kvlonge Oct 19, 2026
64d4e69
fix: store NULL as NULL when a ClickHouse model starts producing it
kvlonge Oct 19, 2026
0824800
fix: publish multi-partition ClickHouse rewrites with one atomic exch…
kvlonge Oct 19, 2026
2ada8f6
fix: resolve UDF calls in SQL tests whose model SQL parses as a call …
kvlonge Oct 19, 2026
5377a6e
test: build, test and rebuild the waffle shop on ClickHouse
kvlonge Oct 19, 2026
1ac814d
fix: fail ClickHouse builds whose columns are named after a table alias
kvlonge Oct 19, 2026
200e75e
fix: widen ClickHouse columns to Nullable only when on_schema_change …
kvlonge Oct 19, 2026
d6b1d56
fix: give ClickHouse table copies their own generation
kvlonge Oct 19, 2026
bbec1d1
fix: size the lifecycle adapter list for both new adapters
kvlonge Oct 19, 2026
926e24a
fix: define nullability widening on the SQLite adapter
kvlonge Oct 19, 2026
a298397
test: record the source final flag in the native assembly goldens
kvlonge Oct 19, 2026
9fa3cc4
docs: list ClickHouse and SQLite in the temporary test function table
kvlonge Oct 19, 2026
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
54 changes: 49 additions & 5 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
compiler_differential: ${{ steps.scope.outputs.compiler_differential }}
duckdb: ${{ steps.scope.outputs.duckdb }}
postgres: ${{ steps.scope.outputs.postgres }}
clickhouse: ${{ steps.scope.outputs.clickhouse }}
performance: ${{ steps.scope.outputs.performance }}
format_performance: ${{ steps.scope.outputs.format_performance }}
rules_performance: ${{ steps.scope.outputs.rules_performance }}
Expand Down Expand Up @@ -50,11 +51,12 @@ jobs:
echo "checks=$6"
echo "wheels=$7"
echo "compiler_differential=$8"
echo "clickhouse=$9"
} >> "${GITHUB_OUTPUT}"
}

if [[ "${EVENT_NAME}" != "pull_request" && "${EVENT_NAME}" != "merge_group" ]]; then
write_scope true true true true true true true true
write_scope true true true true true true true true true
exit 0
fi

Expand All @@ -66,7 +68,7 @@ jobs:
if [[ -z "${BASE_SHA}" || -z "${HEAD_SHA}" ]] || \
! git diff --name-only -z "${BASE_SHA}...${HEAD_SHA}" > "${changed_paths}"; then
echo "Could not diff ${BASE_SHA:-<none>}...${HEAD_SHA:-<none>}; running every suite" >&2
write_scope true true true true true true true true
write_scope true true true true true true true true true
exit 0
fi

Expand Down Expand Up @@ -112,12 +114,13 @@ jobs:
&& "${HEAD_REF}" == release-please--branches--* ]] && release_metadata_only; then
echo "Release Please pull request changes only release metadata; skipping the suites" \
| tee -a "${GITHUB_STEP_SUMMARY}"
write_scope false false false false false false false false
write_scope false false false false false false false false false
exit 0
fi

duckdb=false
postgres=false
clickhouse=false
performance=false
format_performance=false
rules_performance=false
Expand Down Expand Up @@ -182,6 +185,9 @@ jobs:
tests/e2e/src/sqlbuild/cli/commands/main/postgres/*|tests/integration/src/sqlbuild/adapters/postgres/*|tests/integration/src/sqlbuild/runtime/execution_history/*)
postgres=true
;;
src/sqlbuild/adapters/clickhouse/*|tests/e2e/src/sqlbuild/cli/commands/main/clickhouse/*|tests/integration/src/sqlbuild/adapters/clickhouse/*)
clickhouse=true
;;
tests/e2e/*)
duckdb=true
;;
Expand All @@ -201,7 +207,7 @@ jobs:
done < "${changed_paths}"

if [[ "${changed}" != "true" ]]; then
write_scope true true true true true true true true
write_scope true true true true true true true true true
exit 0
fi

Expand All @@ -213,6 +219,7 @@ jobs:
if [[ "${broad}" == "true" ]]; then
duckdb=true
postgres=true
clickhouse=true
format_performance=true
compiler_differential=true
fi
Expand All @@ -224,7 +231,8 @@ jobs:
"${rules_performance}" \
"${checks}" \
"${wheels}" \
"${compiler_differential}"
"${compiler_differential}" \
"${clickhouse}"

lockfile:
name: Lockfile consistency
Expand Down Expand Up @@ -853,6 +861,39 @@ jobs:
-m "real_warehouse and postgres"
-vv --color=yes -n auto --dist loadfile

clickhouse:
name: ClickHouse integration and E2E tests
needs: [changes, lockfile]
if: needs.changes.outputs.clickhouse == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
version: "0.12.3"
enable-cache: true
cache-dependency-glob: |
uv.lock
Cargo.lock
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Install Python
run: uv python install 3.12
- name: Install dependencies
run: uv sync --locked --all-groups --all-extras
- name: Verify Docker
run: docker info
- name: Run ClickHouse tests
env:
PYTHONUNBUFFERED: "1"
TESTCONTAINERS_RYUK_DISABLED: "true"
run: >-
uv run pytest tests
-m "real_warehouse and clickhouse"
-vv --color=yes -n auto --dist loadfile

wheels:
name: Wheels
needs: [changes]
Expand Down Expand Up @@ -891,6 +932,7 @@ jobs:
cache_compile_performance,
rules_performance,
postgres,
clickhouse,
wheels,
]
runs-on: ubuntu-24.04
Expand All @@ -913,6 +955,7 @@ jobs:
CACHE_COMPILE_PERFORMANCE_RESULT: ${{ needs.cache_compile_performance.result }}
RULES_PERFORMANCE_RESULT: ${{ needs.rules_performance.result }}
POSTGRES_RESULT: ${{ needs.postgres.result }}
CLICKHOUSE_RESULT: ${{ needs.clickhouse.result }}
WHEELS_RESULT: ${{ needs.wheels.result }}
run: |
if [[ "${CHANGES_RESULT}" != "success" || "${LOCKFILE_RESULT}" != "success" ]]; then
Expand All @@ -937,6 +980,7 @@ jobs:
"${CACHE_COMPILE_PERFORMANCE_RESULT}" \
"${RULES_PERFORMANCE_RESULT}" \
"${POSTGRES_RESULT}" \
"${CLICKHOUSE_RESULT}" \
"${WHEELS_RESULT}"; do
if [[ "${result}" != "success" && "${result}" != "skipped" ]]; then
echo "A selected verification suite did not pass: ${result}" >&2
Expand Down
19 changes: 17 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
SHELL := /bin/bash

.PHONY: verify verify-quick verify-pg coverage cli-preview rust-check check-native-layering check-python-boundary check-release-gate \
.PHONY: verify verify-quick verify-pg coverage cli-preview rust-check check-native-layering check-python-boundary check-release-gate check-docs-skill \
check-e2e-shards test-e2e-duckdb test-e2e-duckdb-build-core \
test-e2e-duckdb-build-incremental \
test-e2e-duckdb-cli-data test-e2e-duckdb-cli test-e2e-duckdb-inspection \
Expand Down Expand Up @@ -153,6 +153,7 @@ E2E_DUCKDB_CLI_PATHS := \
tests/e2e/src/sqlbuild/cli/commands/main/audit \
tests/e2e/src/sqlbuild/cli/commands/main/bigquery \
tests/e2e/src/sqlbuild/cli/commands/main/check \
tests/e2e/src/sqlbuild/cli/commands/main/clickhouse \
tests/e2e/src/sqlbuild/cli/commands/main/compile \
tests/e2e/src/sqlbuild/cli/commands/main/databricks \
tests/e2e/src/sqlbuild/cli/commands/main/dbt \
Expand Down Expand Up @@ -459,6 +460,16 @@ test-real-postgres:
exit $$status


test-real-clickhouse:
@mkdir -p /tmp/opencode
@log="/tmp/opencode/test-real-clickhouse-$$(date +%Y%m%d-%H%M%S).log"; \
echo "Logging to $$log"; \
env PYTHONUNBUFFERED=1 TESTCONTAINERS_RYUK_DISABLED=true SQLBUILD_CONCURRENCY=$(SQLBUILD_CONCURRENCY) uv run pytest tests -m "real_warehouse and clickhouse" -vv --color=yes -n auto --dist loadfile 2>&1 | tee "$$log"; \
status=$${PIPESTATUS[0]}; \
echo "TEST_REAL_CLICKHOUSE_EXIT=$$status (log: $$log)" | tee -a "$$log"; \
exit $$status


test-real-sqlserver:
@mkdir -p /tmp/opencode
@log="/tmp/opencode/test-real-sqlserver-$$(date +%Y%m%d-%H%M%S).log"; \
Expand Down Expand Up @@ -626,7 +637,7 @@ verify-quick:
exit $$status


check-ci: check-e2e-shards check-native-layering check-python-boundary check-release-gate rust-lint
check-ci: check-e2e-shards check-native-layering check-python-boundary check-release-gate check-docs-skill rust-lint
uv run ruff format --check .
uv run ruff check .
uv run ty check src tests
Expand All @@ -646,6 +657,10 @@ check-release-gate:
uv run python -m scripts.check_release_gate


check-docs-skill:
uv run python -m scripts.check_docs_skill


check-pr-metadata:
@test -n "$(PR_TITLE)" || { echo "PR_TITLE is required" >&2; exit 2; }
@test -n "$(PR_BODY_FILE)" || { echo "PR_BODY_FILE is required" >&2; exit 2; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1455,8 +1455,21 @@ fn analyze_and_resolve_sql(
replacements.insert((function_name.as_str(), referenced_name.as_str()), target);
}
}
let relations_resolved =
replace_relation_markers(request.query_sql, &replacements, request.patterns);
let functions_resolved = resolve_function_calls(
&resolve_function_calls(
&relations_resolved,
request.functions,
false,
request.patterns,
)?,
request.functions,
true,
request.patterns,
)?;
let cte_body_sql = strip_statement_terminators(
&replace_relation_markers(request.query_sql, &replacements, request.patterns),
&functions_resolved,
SliceDialect::new(Some(request.dialect_name)),
)
.to_string();
Expand Down
2 changes: 2 additions & 0 deletions crates/sqlbuild-analysis/src/compiler/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#[path = "tests/helpers.rs"]
mod helpers;
#[path = "tests/planning/helpers.rs"]
mod planning_helpers;
#[path = "tests/test_scenario_expected_columns.rs"]
mod scenario_expected_columns;
#[path = "tests/test_scenario_helper_ctes.rs"]
Expand Down
2 changes: 1 addition & 1 deletion crates/sqlbuild-analysis/src/compiler/tests/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1559,7 +1559,7 @@ pub(super) fn snowflake_function_synonyms_stay_as_authored() -> bool {
&& !sql.contains("SUBSTRING")
}

fn generic_lexical_syntax() -> Value {
pub(crate) fn generic_lexical_syntax() -> Value {
json!({
"backslashEscapeQuotes": [],
"escapeStringPrefix": false,
Expand Down
31 changes: 31 additions & 0 deletions crates/sqlbuild-analysis/src/compiler/tests/planning/helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use serde_json::{Value, json};

use crate::compiler::tests::helpers::{generic_lexical_syntax, plan_json};

/// The rendered SQL of a model test whose real model calls a UDF in its projection.
pub(crate) fn projected_udf_call_sql(dialect: &str) -> String {
let request = json!({
"lexicalSyntax": generic_lexical_syntax(),
"models": [
{"name": "orders", "querySql": "SELECT o.order_id, __udf(\"is_open\")(o.status) AS is_open FROM __source(\"raw_orders\") AS o"}
],
"functions": [{"name": "is_open", "udfPrefix": "analytics__is_open", "udfSuffix": ""}],
"tests": [{
"name": "orders_case", "fileLabel": "tests/orders.sql",
"payload": {
"kind": "model",
"authoredCtes": [{"name": "__source__raw_orders", "sqlBody": "SELECT 1 AS order_id, 'open' AS status"}],
"expectedCtes": [{"name": "__expected__orders", "sqlBody": "SELECT 1 AS order_id, TRUE AS is_open"}],
"expectedModelNames": ["orders"]
}
}],
"sqlAnalysisEnabled": true, "sqlAnalysisDialect": dialect
});
let response: Value =
serde_json::from_str(&plan_json(&request.to_string()).expect("planning succeeds"))
.expect("valid JSON");
response["artifacts"][0]["sql"]
.as_str()
.expect("rendered SQL")
.to_owned()
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ use crate::compiler::tests::helpers::{
unicode_cte_after_leading_with_preserves_identifier,
unresolved_reference_fast_rejection_preserves_warning, upstream_fallback_resolves,
};
use crate::compiler::tests::planning_helpers::projected_udf_call_sql;
use crate::compiler::tests::test_types::{
GlueErrorProjectionTestCase, GluePlanParityTestCase, GluePlannerErrorTestCase,
SqlTestPlanningTestCase,
ProjectedUdfTestCase, SqlTestPlanningTestCase,
};

/// A mocked model test, a test missing its source mock and a UDF test over two models.
Expand Down Expand Up @@ -324,3 +325,33 @@ fn given_plan_warnings_when_projecting_errors_then_each_distinct_error_message_i
);
}
}

#[test]
fn given_real_model_calling_udf_in_projection_when_planning_then_call_targets_the_function() {
let test_cases = [
ProjectedUdfTestCase {
description: "DuckDB falls back to textual resolution",
dialect: "duckdb",
expected_call: "analytics__is_open(o.status)",
},
ProjectedUdfTestCase {
description: "ClickHouse parses the marker as a parametric call",
dialect: "clickhouse",
expected_call: "analytics__is_open(o.status)",
},
];

for test_case in test_cases {
let sql = projected_udf_call_sql(test_case.dialect);

assert_eq!(
(
sql.contains(test_case.expected_call),
sql.contains("__udf(")
),
(true, false),
"{}: {sql}",
test_case.description
);
}
}
6 changes: 6 additions & 0 deletions crates/sqlbuild-analysis/src/compiler/tests/test_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,9 @@ pub(crate) struct ScenarioHelperShadowTestCase {
pub(crate) check_sql: &'static str,
pub(crate) expected_rejected: bool,
}

pub(super) struct ProjectedUdfTestCase {
pub(super) description: &'static str,
pub(super) dialect: &'static str,
pub(super) expected_call: &'static str,
}
7 changes: 6 additions & 1 deletion crates/sqlbuild-analysis/src/planner_identity/constants.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Planner identity constants shared with the Python planner.

/// Model config keys whose values participate in version identity, in Python's insertion order after `cursor_inputs`.
pub const VERSION_IDENTITY_CONFIG_KEYS: [&str; 34] = [
pub const VERSION_IDENTITY_CONFIG_KEYS: [&str; 39] = [
"append_cursor_inclusive",
"batch_size",
"check_columns",
Expand Down Expand Up @@ -36,6 +36,11 @@ pub const VERSION_IDENTITY_CONFIG_KEYS: [&str; 34] = [
"updated_at",
"valid_from_column",
"valid_to_column",
"engine",
"order_by",
"partition_by",
"primary_key",
"table_settings",
];
/// Model config key of cursor inputs, recorded before every other identity key.
pub const CURSOR_INPUTS_CONFIG_KEY: &str = "cursor_inputs";
Expand Down
12 changes: 12 additions & 0 deletions crates/sqlbuild-analysis/src/planner_strategy/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,15 @@ pub const PERMANENT_TABLE_TYPE: &str = "permanent";
pub const TRANSIENT_TABLE_DDL: &str = "TRANSIENT TABLE";
/// The permanent table DDL replacing the transient keyword.
pub const PERMANENT_TABLE_DDL: &str = "TABLE";
/// The warning code of an incremental model whose table keeps an outdated physical layout.
pub const PHYSICAL_LAYOUT_PENDING_WARNING_CODE: &str = "PHYSICAL_LAYOUT_PENDING";
/// Model config keys of a table's physical layout, in the order warnings name them.
pub const PHYSICAL_LAYOUT_CONFIG_KEYS: [&str; 5] = [
"engine",
"order_by",
"partition_by",
"primary_key",
"table_settings",
];
/// The version metadata member holding the model config.
pub const METADATA_CONFIG_MEMBER: &str = "config";
1 change: 1 addition & 0 deletions crates/sqlbuild-analysis/src/planner_strategy/main/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub mod microbatch_limit_warning;
pub mod microbatch_lookback;
pub mod model_plan_action;
pub mod model_warnings;
pub mod physical_layout_warning;
pub mod schema_actions;
pub mod singular_attachment;
pub mod table_type_ddl;
Loading
Loading