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
54 changes: 50 additions & 4 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
duckdb: ${{ steps.scope.outputs.duckdb }}
postgres: ${{ steps.scope.outputs.postgres }}
performance: ${{ steps.scope.outputs.performance }}
format_performance: ${{ steps.scope.outputs.format_performance }}
rules_performance: ${{ steps.scope.outputs.rules_performance }}
steps:
- uses: actions/checkout@v4
Expand All @@ -36,18 +37,20 @@ jobs:
echo "duckdb=$1"
echo "postgres=$2"
echo "performance=$3"
echo "rules_performance=$4"
echo "format_performance=$4"
echo "rules_performance=$5"
} >> "${GITHUB_OUTPUT}"
}

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

duckdb=false
postgres=false
performance=false
format_performance=false
rules_performance=false
broad=false
changed=false
Expand All @@ -70,8 +73,13 @@ jobs:
;;
src/sqlbuild/rule_engine/*|src/sqlbuild/lint/*|scripts/rules_benchmark/*|scripts/benchmark_rules_ci.py|tests/performance/*)
broad=true
format_performance=true
rules_performance=true
;;
tests/e2e/src/sqlbuild/cli/commands/main/format/*)
duckdb=true
format_performance=true
;;
tests/e2e/src/sqlbuild/cli/commands/main/postgres/*|tests/integration/src/sqlbuild/adapters/postgres/*|tests/integration/src/sqlbuild/virtual/state/classes/postgres/*|tests/integration/src/sqlbuild/runtime/execution_history/*)
postgres=true
;;
Expand All @@ -82,6 +90,7 @@ jobs:
;;
.github/workflows/verify.yml|Makefile)
performance=true
format_performance=true
rules_performance=true
;;
.github/*|docs/*|*.md|.gitignore|LICENSE)
Expand All @@ -93,15 +102,21 @@ jobs:
done < <(git diff --name-only -z "${BASE_SHA}" "${HEAD_SHA}")

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

if [[ "${broad}" == "true" ]]; then
duckdb=true
postgres=true
format_performance=true
fi
write_scope "${duckdb}" "${postgres}" "${performance}" "${rules_performance}"
write_scope \
"${duckdb}" \
"${postgres}" \
"${performance}" \
"${format_performance}" \
"${rules_performance}"

lockfile:
name: Lockfile consistency
Expand Down Expand Up @@ -207,6 +222,34 @@ jobs:
- name: Run DuckDB E2E shard
run: make ${{ matrix.target }}

format_performance:
name: Formatter performance guards
needs: [changes, lockfile]
if: needs.changes.outputs.format_performance == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 10
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 --python 3.12
- name: Run formatter performance guards
run: >-
uv run pytest
tests/e2e/src/sqlbuild/cli/commands/main/format/test_format_performance.py
-n auto --dist loadfile -m performance

performance:
name: Performance guards
needs: [changes, lockfile]
Expand Down Expand Up @@ -371,6 +414,7 @@ jobs:
lockfile,
checks,
e2e_shards,
format_performance,
performance,
cold_compile_performance,
rules_performance,
Expand All @@ -384,6 +428,7 @@ jobs:
LOCKFILE_RESULT: ${{ needs.lockfile.result }}
CHECKS_RESULT: ${{ needs.checks.result }}
E2E_SHARDS_RESULT: ${{ needs.e2e_shards.result }}
FORMAT_PERFORMANCE_RESULT: ${{ needs.format_performance.result }}
PERFORMANCE_RESULT: ${{ needs.performance.result }}
COLD_COMPILE_PERFORMANCE_RESULT: ${{ needs.cold_compile_performance.result }}
RULES_PERFORMANCE_RESULT: ${{ needs.rules_performance.result }}
Expand All @@ -397,6 +442,7 @@ jobs:
fi
for result in \
"${E2E_SHARDS_RESULT}" \
"${FORMAT_PERFORMANCE_RESULT}" \
"${PERFORMANCE_RESULT}" \
"${COLD_COMPILE_PERFORMANCE_RESULT}" \
"${RULES_PERFORMANCE_RESULT}" \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ fn format_sql_json(py: Python<'_>, request_json: &str) -> PyResult<String> {
.map_err(value_error)
}

#[pyfunction]
fn format_sql_batch_json(py: Python<'_>, request_json: &str) -> PyResult<String> {
py.detach(|| crate::sql_lint::main::batch_formatter::format_batch_json(request_json))
.map_err(value_error)
}

#[pyfunction(name = "validate_sql_with_schema_json")]
fn schema_validation_json(py: Python<'_>, request_json: &str) -> PyResult<String> {
py.detach(|| crate::semantic_validation::main::validation_json(request_json))
Expand Down Expand Up @@ -255,6 +261,7 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
module.add_function(wrap_pyfunction!(evaluate_json, module)?)?;
module.add_function(wrap_pyfunction!(lint_sql_json, module)?)?;
module.add_function(wrap_pyfunction!(format_sql_json, module)?)?;
module.add_function(wrap_pyfunction!(format_sql_batch_json, module)?)?;
module.add_function(wrap_pyfunction!(schema_validation_json, module)?)?;
module.add_function(wrap_pyfunction!(schema_validations_json, module)?)?;
module.add_function(wrap_pyfunction!(analyze_sql_uses_json, module)?)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::str::FromStr;

use polyglot_sql::tokens::Token;
use polyglot_sql::{Dialect, DialectType, format_by_name};
use rayon::iter::{IntoParallelIterator, ParallelIterator};

use crate::sql_lint::constants::{
CAST_TYPE_SEPARATOR_KEYWORD, CLOSE_PARENTHESIS, LINT_API_VERSION, OPEN_PARENTHESIS,
Expand All @@ -19,6 +20,14 @@ pub(crate) fn format_json_impl(request_json: &str) -> Result<String, String> {
serde_json::to_string(&response).map_err(|error| error.to_string())
}

pub(crate) fn format_batch_json_impl(request_json: &str) -> Result<String, String> {
let requests: Vec<FormatRequest> =
serde_json::from_str(request_json).map_err(|error| error.to_string())?;
let responses: Result<Vec<FormatResponse>, String> =
requests.into_par_iter().map(format_sql).collect();
serde_json::to_string(&responses?).map_err(|error| error.to_string())
}

fn format_sql(request: FormatRequest) -> Result<FormatResponse, String> {
if request.version != LINT_API_VERSION {
return Err(format!(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use crate::sql_lint::_helpers::formatter::format_batch_json_impl;

pub(crate) fn format_batch_json(request_json: &str) -> Result<String, String> {
format_batch_json_impl(request_json)
}
1 change: 1 addition & 0 deletions crates/sqlbuild-rules-native/src/sql_lint/main/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub(crate) mod batch_formatter;
pub(crate) mod catalogue;
pub(crate) mod engine;
pub(crate) mod formatter;
44 changes: 44 additions & 0 deletions crates/sqlbuild-rules-native/src/sql_lint/tests/test_sql_lint.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use serde_json::{Value, json};

use crate::sql_lint::main::batch_formatter::format_batch_json;
use crate::sql_lint::main::engine::lint_json;
use crate::sql_lint::main::formatter::format_json;
use crate::sql_lint::tests::{helpers, test_types};
Expand Down Expand Up @@ -1913,6 +1914,49 @@ fn given_format_cases_when_formatting_then_output_matches() -> Result<(), String
Ok(())
}

#[test]
fn given_ordered_sql_batch_when_formatting_then_outputs_are_parallel_and_ordered()
-> Result<(), String> {
let test_cases = [
test_types::FormatTestCase {
description: "first order query",
sql: "select order_id from orders where order_id=1",
expected_sql: "SELECT\n order_id\nFROM orders\nWHERE\n order_id = 1",
expected_changed: true,
},
test_types::FormatTestCase {
description: "second product query",
sql: "select product_id from products where product_id=2",
expected_sql: "SELECT\n product_id\nFROM products\nWHERE\n product_id = 2",
expected_changed: true,
},
];
let requests: Vec<Value> = test_cases
.iter()
.map(|test_case| json!({"version": 1, "sql": test_case.sql, "dialect": "snowflake"}))
.collect();

let response =
format_batch_json(&serde_json::to_string(&requests).map_err(|error| error.to_string())?)?;
let payloads: Vec<Value> =
serde_json::from_str(&response).map_err(|error| error.to_string())?;

assert_eq!(payloads.len(), test_cases.len());
let mut payload_iter = payloads.iter();
for test_case in &test_cases {
let payload = payload_iter
.next()
.ok_or_else(|| format!("{}: batch response omitted an item", test_case.description))?;
assert_eq!(
payload["sql"], test_case.expected_sql,
"{}",
test_case.description
);
assert_eq!(payload["changed"], test_case.expected_changed);
}
Ok(())
}

#[test]
fn given_unknown_rule_when_linting_then_request_is_rejected() -> Result<(), String> {
let test_cases = [test_types::InvalidRuleTestCase {
Expand Down
1 change: 1 addition & 0 deletions src/sqlbuild/_native.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
def catalogue_json() -> str: ...
def evaluate_json(request_json: str) -> str: ...
def format_sql_json(request_json: str) -> str: ...
def format_sql_batch_json(request_json: str) -> str: ...
def lint_sql_json(request_json: str) -> str: ...
def load_config_json(project_dir: str) -> str: ...
def selected_codes_json(request_json: str) -> str: ...
Expand Down
1 change: 1 addition & 0 deletions src/sqlbuild/cli/commands/_helpers/entry/dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,7 @@ def _dispatch_lint_format_command(
exclude=tuple(args.exclude),
check=args.format_check,
diff=args.format_diff,
fixtures_only=args.format_fixtures_only,
json_output=args.json,
no_color=args.no_color,
)
Expand Down
21 changes: 11 additions & 10 deletions src/sqlbuild/cli/commands/_helpers/entry/lazy_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,16 +247,17 @@ def build_lazy_cli_handlers() -> CliEntrypointHandlers:
targets=targets,
force=force,
),
run_format=lambda project_dir, select, exclude, check, diff, json_output, no_color: lazy[
"format"
](
project_dir=project_dir,
select=select,
exclude=exclude,
check=check,
diff=diff,
json_output=json_output,
no_color=no_color,
run_format=lambda project_dir, select, exclude, check, diff, fixtures_only, json_output, no_color: ( # noqa: E501
lazy["format"](
project_dir=project_dir,
select=select,
exclude=exclude,
check=check,
diff=diff,
fixtures_only=fixtures_only,
json_output=json_output,
no_color=no_color,
)
),
run_scenario=lazy["scenario"],
run_scenario_capture=lazy["scenario_capture"],
Expand Down
6 changes: 6 additions & 0 deletions src/sqlbuild/cli/commands/_helpers/entry/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,12 @@ def _add_quality_parsers(
format_parser.add_argument("--json", action="store_true", default=False)
format_parser.add_argument("--check", dest="format_check", action="store_true")
format_parser.add_argument("--diff", dest="format_diff", action="store_true")
format_parser.add_argument(
"--fixtures-only",
dest="format_fixtures_only",
action="store_true",
help="only remove redundant typed-null SQL test fixture columns",
)
_ = add_select_args(format_parser)


Expand Down
Loading
Loading