Skip to content

Commit c2e78ff

Browse files
committed
Merge wip/engine-free-functions: convert SQL function bodies without DuckDB's engine
2 parents a201b39 + 4d47519 commit c2e78ff

15 files changed

Lines changed: 102 additions & 15 deletions

File tree

‎crates/sqlquack-convert/Cargo.toml‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@ sqlquack-duckdb.workspace = true
1717
sqlquack-policy.workspace = true
1818
sqlquack-rules.workspace = true
1919
sqlquack-semantics.workspace = true
20-
sqlquack-syntax.workspace = true
21-
22-
[dev-dependencies]
2320
sqlquack-snowflake.workspace = true
21+
sqlquack-syntax.workspace = true
2422

2523
[lints]
2624
workspace = true

crates/sqlquack-convert/src/analysis/main/declared_refusals.rs renamed to crates/sqlquack-convert/src/analysis/_helpers/declared_refusals.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use sqlquack_core::refusals::types::ConstructPolicy;
99
use sqlquack_syntax::Expression;
1010

1111
/// The constructs the policy declares inexpressible, then the nodes of `source` Snowflake rejects.
12-
pub fn declared_refusals(
12+
pub(crate) fn declared_refusals(
1313
statement: &Expression,
1414
occurrences: &[Occurrence],
1515
policy: &dyn ConstructPolicy,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//! Helpers for the conversion pipeline.
22
3+
pub(crate) mod declared_refusals;
34
pub(crate) mod override_calls;
45
pub(crate) mod volatile_calls;

‎crates/sqlquack-convert/src/analysis/constants.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@
22
33
/// The parsed tree's keys for a named call, scalar or aggregate.
44
pub const CALL_NODE_KINDS: &[&str] = &["function", "aggregate_function"];
5+
6+
/// The column a scalar SQL function body is converted as.
7+
pub const SCALAR_RESULT_COLUMN: &str = "RESULT";

‎crates/sqlquack-convert/src/analysis/main/analyze.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ use sqlquack_rules::conversion::main::within_budget::within_budget;
1717
use sqlquack_rules::conversion::models::{ConversionOptions, Converted};
1818
use sqlquack_semantics::types::models::Schema;
1919

20+
use crate::analysis::_helpers::declared_refusals::declared_refusals;
2021
use crate::analysis::_helpers::override_calls::overrides_applied;
2122
use crate::analysis::_helpers::volatile_calls::volatile_calls_kept;
22-
use crate::analysis::main::declared_refusals::declared_refusals;
2323
use crate::analysis::main::placed::placed;
2424
use crate::analysis::models::Analysis;
2525

crates/sqlquack/src/analysis/main/analyze_function.rs renamed to crates/sqlquack-convert/src/analysis/main/analyze_function.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ use sqlquack_rules::conversion::models::{ConversionOptions, ConvertedFunction};
1313
use sqlquack_semantics::types::models::{FunctionReturns, FunctionSpec, Schema};
1414
use sqlquack_snowflake::typing::models::SnowflakeType;
1515

16+
use crate::analysis::_helpers::declared_refusals::declared_refusals;
1617
use crate::analysis::constants::SCALAR_RESULT_COLUMN;
17-
use sqlquack_convert::analysis::main::declared_refusals::declared_refusals;
18-
use sqlquack_convert::analysis::main::placed::placed;
18+
use crate::analysis::main::placed::placed;
1919

2020
/// Converts `body` as `spec` declares it; a NUMBER scalar's body must be of the declared type.
2121
pub fn analyze_function(
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
pub mod analyze;
2-
pub mod declared_refusals;
2+
pub mod analyze_function;
33
pub mod placed;

‎crates/sqlquack-convert/src/analysis/tests.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#[path = "tests/test_analyze_function.rs"]
2+
mod test_analyze_function;
13
#[path = "tests/test_override_calls.rs"]
24
mod test_override_calls;
35
#[path = "tests/test_types.rs"]
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use sqlquack_core::parsing::models::SourceDialect;
2+
use sqlquack_core::refusals::models::RefusalCategory;
3+
use sqlquack_rules::catalogue::main::builtin_catalogue::builtin_catalogue;
4+
use sqlquack_rules::conversion::models::ConversionOptions;
5+
use sqlquack_semantics::types::models::{
6+
ColumnSpec, FunctionLanguage, FunctionReturns, FunctionSpec, FunctionTraits, Schema,
7+
};
8+
use sqlquack_snowflake::typing::models::SnowflakeType;
9+
10+
use crate::analysis::main::analyze_function::analyze_function;
11+
use crate::analysis::tests::test_types::AnalyzeFunctionTestCase;
12+
13+
#[test]
14+
fn given_a_sql_function_body_when_converted_without_the_engine_then_it_is_a_macro_body_or_refused()
15+
{
16+
let amount = SnowflakeType::Number {
17+
precision: 10,
18+
scale: 2,
19+
};
20+
let spec = FunctionSpec {
21+
language: FunctionLanguage::Sql,
22+
arguments: vec![ColumnSpec::new("AMOUNT", amount)],
23+
returns: FunctionReturns::Scalar(amount),
24+
traits: FunctionTraits::default(),
25+
};
26+
let test_cases = [
27+
AnalyzeFunctionTestCase {
28+
description: "a NUMBER scalar body converts, its parameter cast to the declared type",
29+
body: "CAST(amount * 2 AS NUMBER(10,2))",
30+
expected_parameters: &["p1"],
31+
expected_body_has: Some("SELECT CAST(p1 AS DECIMAL(10,2)) AS \"AMOUNT\""),
32+
expected_refusals: &[],
33+
},
34+
AnalyzeFunctionTestCase {
35+
description: "a body naming no parameter or column is refused as unresolved",
36+
body: "missing + 1",
37+
expected_parameters: &[],
38+
expected_body_has: None,
39+
expected_refusals: &[RefusalCategory::UnresolvedName],
40+
},
41+
];
42+
for test_case in test_cases {
43+
let outcome = analyze_function(
44+
(test_case.body, &spec),
45+
SourceDialect::Snowflake,
46+
&builtin_catalogue(),
47+
(&Schema::default(), &ConversionOptions::default()),
48+
);
49+
let parameters = outcome
50+
.as_ref()
51+
.map(|converted| converted.parameters.clone())
52+
.unwrap_or_default();
53+
let body_has = test_case.expected_body_has.filter(|fragment| {
54+
outcome
55+
.as_ref()
56+
.is_ok_and(|converted| converted.body.contains(fragment))
57+
});
58+
let refusals: Vec<RefusalCategory> = outcome
59+
.err()
60+
.unwrap_or_default()
61+
.iter()
62+
.map(|refusal| refusal.category)
63+
.collect();
64+
assert_eq!(
65+
(parameters, body_has, refusals.as_slice()),
66+
(
67+
test_case
68+
.expected_parameters
69+
.iter()
70+
.map(|name| (*name).to_owned())
71+
.collect(),
72+
test_case.expected_body_has,
73+
test_case.expected_refusals
74+
),
75+
"{}",
76+
test_case.description
77+
);
78+
}
79+
}

‎crates/sqlquack-convert/src/analysis/tests/test_types.rs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,11 @@ pub(crate) struct OverrideCallsTestCase {
44
pub(crate) emitted: &'static str,
55
pub(crate) expected_refused: bool,
66
}
7+
8+
pub(crate) struct AnalyzeFunctionTestCase {
9+
pub(crate) description: &'static str,
10+
pub(crate) body: &'static str,
11+
pub(crate) expected_parameters: &'static [&'static str],
12+
pub(crate) expected_body_has: Option<&'static str>,
13+
pub(crate) expected_refusals: &'static [sqlquack_core::refusals::models::RefusalCategory],
14+
}

0 commit comments

Comments
 (0)