Skip to content

Commit 9a8e410

Browse files
authored
feat(native): run metadata checks natively behind the preview engine (#1058)
* feat(native): run metadata checks natively behind the preview engine * test(native): keep semantic checks tied to the type system tier and rename the CLI semantic test
1 parent b930183 commit 9a8e410

54 files changed

Lines changed: 2356 additions & 70 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/sqlbuild-analysis/src/semantic_checks/_helpers/columns.rs renamed to crates/sqlbuild-analysis/src/semantic_checks/_helpers/explanation/columns.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use std::cmp::Ordering;
44

5-
use crate::semantic_checks::_helpers::text::{ascii, casefold};
5+
use crate::semantic_checks::_helpers::sql_text::text::{ascii, casefold};
66
use crate::semantic_checks::constants::{MAX_EDIT_DISTANCE, MIN_ABBREVIATION_LENGTH};
77
use crate::semantic_checks::models::SemanticDeferral;
88

crates/sqlbuild-analysis/src/semantic_checks/_helpers/completion.rs renamed to crates/sqlbuild-analysis/src/semantic_checks/_helpers/explanation/completion.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,21 @@ use std::sync::LazyLock;
55

66
use regex::Regex;
77

8-
use crate::semantic_checks::_helpers::bindings::{BindingOwner, binding_positions};
9-
use crate::semantic_checks::_helpers::columns::closest_column;
10-
use crate::semantic_checks::_helpers::explain::{
8+
use crate::semantic_checks::_helpers::explanation::columns::closest_column;
9+
use crate::semantic_checks::_helpers::explanation::explain::{
1110
Binary, ExplainContext, ModelEvidence, Shapes, binary_index, explain_model, is_explained_code,
1211
is_type_code,
1312
};
14-
use crate::semantic_checks::_helpers::messages::{
13+
use crate::semantic_checks::_helpers::explanation::messages::{
1514
compiled, missing_column, pattern, semantic_help, sentence_message,
1615
};
17-
use crate::semantic_checks::_helpers::opt_outs::opt_out_diagnostic;
18-
use crate::semantic_checks::_helpers::parsed_sql::{ParsedModelFacts, parsed_model_facts};
19-
use crate::semantic_checks::_helpers::poison::Poison;
20-
use crate::semantic_checks::_helpers::text::{LineIndex, ascii, plain_text};
16+
use crate::semantic_checks::_helpers::explanation::opt_outs::opt_out_diagnostic;
17+
use crate::semantic_checks::_helpers::recovery::bindings::{BindingOwner, binding_positions};
18+
use crate::semantic_checks::_helpers::recovery::poison::Poison;
19+
use crate::semantic_checks::_helpers::sql_text::parsed_sql::{
20+
ParsedModelFacts, parsed_model_facts,
21+
};
22+
use crate::semantic_checks::_helpers::sql_text::text::{LineIndex, ascii, plain_text};
2123
use crate::semantic_checks::constants::{
2224
MODEL_RESOURCE_TYPE, SEMANTIC_CODE_PREFIX, SQL_TEST_COLUMN_CODE, SQL_TEST_COLUMN_PATTERN,
2325
UNKNOWN_COLUMN_CODE,

crates/sqlbuild-analysis/src/semantic_checks/_helpers/explain.rs renamed to crates/sqlbuild-analysis/src/semantic_checks/_helpers/explanation/explain.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ use std::sync::LazyLock;
55

66
use regex::Regex;
77

8-
use crate::semantic_checks::_helpers::columns::{closest_column, ordered_columns};
9-
use crate::semantic_checks::_helpers::messages::{
8+
use crate::semantic_checks::_helpers::explanation::columns::{closest_column, ordered_columns};
9+
use crate::semantic_checks::_helpers::explanation::messages::{
1010
comparison_help, compiled, missing_column, pattern, semantic_help, sentence_message, type_words,
1111
};
12-
use crate::semantic_checks::_helpers::text::{
12+
use crate::semantic_checks::_helpers::sql_text::text::{
1313
LineIndex, ascii, last_newline_before, newlines_before, prefix,
1414
};
1515
use crate::semantic_checks::constants::{

crates/sqlbuild-analysis/src/semantic_checks/_helpers/messages.rs renamed to crates/sqlbuild-analysis/src/semantic_checks/_helpers/explanation/messages.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::sync::LazyLock;
44

55
use regex::{Captures, Regex};
66

7-
use crate::semantic_checks::_helpers::text::ascii;
7+
use crate::semantic_checks::_helpers::sql_text::text::ascii;
88
use crate::semantic_checks::constants::{
99
BIGQUERY_DIALECT, COMPARISON_CODE, CONTEXT_SUFFIX_PATTERN, DATE_TYPE, MISSING_PATTERN,
1010
QUOTED_PIECE_PATTERN, SEMANTIC_HELP, TIMESTAMP_TYPE, TYPE_WORDS_PATTERN,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub(crate) mod columns;
2+
pub(crate) mod completion;
3+
pub(crate) mod explain;
4+
pub(crate) mod messages;
5+
pub(crate) mod opt_outs;

crates/sqlbuild-analysis/src/semantic_checks/_helpers/opt_outs.rs renamed to crates/sqlbuild-analysis/src/semantic_checks/_helpers/explanation/opt_outs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::sync::LazyLock;
44

55
use regex::Regex;
66

7-
use crate::semantic_checks::_helpers::messages::{compiled, pattern};
7+
use crate::semantic_checks::_helpers::explanation::messages::{compiled, pattern};
88
use crate::semantic_checks::constants::{
99
ADDITIONAL_HELP_SEPARATOR, FINDING_KINDS, PROJECT_CONFIG_FILENAME, REQUIRE_SQL_ANALYSIS_KEY,
1010
SETTING_SNIPPET_INDENT, SETTINGS_SECTION,
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//! Python's `_polyglot_expression_type` for one function argument expression.
2+
3+
use std::collections::HashMap;
4+
5+
use polyglot_sql::{DataType, Expression};
6+
use serde_json::Value;
7+
8+
use crate::semantic_checks::_helpers::sql_text::text::ascii;
9+
use crate::semantic_checks::constants::{
10+
BOOLEAN_RESULT_KINDS, BOOLEAN_TYPE_NAME, COLUMN_KIND, CUSTOM_TYPE_NAME, DECIMAL_TYPE_NAME,
11+
KNOWN_CAST_TYPE_NAMES, TIMESTAMP_KIND, TIMESTAMP_WITH_TIME_ZONE_TYPE_NAME, VARCHAR_DATA_TYPES,
12+
};
13+
use crate::semantic_checks::models::SemanticDeferral;
14+
15+
/// The type Python infers for `expression`, or None.
16+
pub(crate) fn expression_type(
17+
expression: &Expression,
18+
return_types: &HashMap<String, String>,
19+
) -> Result<Option<String>, SemanticDeferral> {
20+
let kind: &str = expression.variant_name();
21+
let name: &str = expression.get_name();
22+
let function_name: &str = if name.is_empty() { kind } else { name };
23+
if kind != COLUMN_KIND
24+
&& let Some(declared) = return_types.get(&ascii(function_name)?.to_ascii_uppercase())
25+
{
26+
return Ok(Some(declared.clone()));
27+
}
28+
if BOOLEAN_RESULT_KINDS.contains(&kind) {
29+
return Ok(Some(BOOLEAN_TYPE_NAME.to_owned()));
30+
}
31+
match expression {
32+
Expression::Cast(cast) | Expression::TryCast(cast) => cast_type(&cast.to),
33+
_ => Ok(None),
34+
}
35+
}
36+
37+
/// Python's reading of a cast's serialised `to` payload.
38+
fn cast_type(data_type: &DataType) -> Result<Option<String>, SemanticDeferral> {
39+
let target: Value =
40+
serde_json::to_value(data_type).map_err(|_| SemanticDeferral::NativeFailure)?;
41+
let Some(raw_type) = target
42+
.get("data_type")
43+
.and_then(Value::as_str)
44+
.filter(|raw| !raw.is_empty())
45+
else {
46+
return Ok(None);
47+
};
48+
let raw_type: &str = ascii(raw_type)?;
49+
let custom_name: Option<&str> = target
50+
.get("name")
51+
.and_then(Value::as_str)
52+
.filter(|name| !name.is_empty());
53+
if raw_type.eq_ignore_ascii_case(CUSTOM_TYPE_NAME)
54+
&& let Some(name) = custom_name
55+
{
56+
return Ok(Some(ascii(name)?.to_ascii_uppercase()));
57+
}
58+
let lowered: String = raw_type.to_ascii_lowercase();
59+
if lowered == TIMESTAMP_KIND && target.get("timezone") == Some(&Value::Bool(true)) {
60+
return Ok(Some(TIMESTAMP_WITH_TIME_ZONE_TYPE_NAME.to_owned()));
61+
}
62+
let type_name: String = KNOWN_CAST_TYPE_NAMES
63+
.iter()
64+
.find(|(raw, _)| *raw == raw_type)
65+
.map_or_else(
66+
|| raw_type.replace('_', " ").to_ascii_uppercase(),
67+
|(_, known)| (*known).to_owned(),
68+
);
69+
if VARCHAR_DATA_TYPES.contains(&lowered.as_str())
70+
&& let Some(length) = integer(&target, "length")
71+
{
72+
return Ok(Some(format!("VARCHAR({length})")));
73+
}
74+
if type_name == DECIMAL_TYPE_NAME
75+
&& let Some(precision) = integer(&target, "precision")
76+
{
77+
return Ok(Some(match integer(&target, "scale") {
78+
Some(scale) => format!("DECIMAL({precision}, {scale})"),
79+
None => format!("DECIMAL({precision})"),
80+
}));
81+
}
82+
Ok(Some(type_name))
83+
}
84+
85+
/// Python's `isinstance(target.get(key), int)` value.
86+
fn integer(target: &Value, key: &str) -> Option<i128> {
87+
let value: &Value = target.get(key)?;
88+
value
89+
.as_i64()
90+
.map(i128::from)
91+
.or_else(|| value.as_u64().map(i128::from))
92+
}

0 commit comments

Comments
 (0)