@@ -8,10 +8,10 @@ use polyglot_sql::traversal::ExpressionWalk;
88use serde_json:: { Map , Value } ;
99
1010use crate :: assembly:: analysis_session:: _helpers:: dict_walk:: {
11- PyValue , alias_or_name, arg, children, column_table_name, direct_tables, expression_args ,
12- find_all, is_star, kind, name, name_payload, output_name, parse_one, projected_expression ,
13- py_truthy, python_int, raw_name_payload, set_operation, to_dict, top_level_ctes ,
14- unwrap_annotations, where_clause,
11+ Assertion , PyValue , alias_or_name, arg, children, column_table_name, direct_tables,
12+ expression_args , find_all, is_star, kind, name, name_payload, output_name, parse_one,
13+ projected_expression , py_truthy, python_int, raw_name_payload, set_operation, to_dict,
14+ top_level_ctes , typed_annotation , unwrap_annotations, where_clause,
1515} ;
1616use crate :: assembly:: analysis_session:: _helpers:: mappings:: ShapeTable ;
1717use crate :: assembly:: analysis_session:: constants:: {
@@ -23,7 +23,7 @@ use crate::assembly::analysis_session::constants::{
2323 NULLABLE_NULLABILITY , NULLIF_FUNCTION_NAME , POLYGLOT_TYPE_NAMES , PYTHON_RULE , SAFE_CAST_RULE ,
2424 SELECT_AST_KIND , SET_OPERATION_AST_KINDS , STRING_LITERAL_TYPE , SUBSTRING_AST_KIND ,
2525 TABLE_AST_KIND , TEXT_TYPE , TIMESTAMP_TYPE_NAME , TIMESTAMP_TZ_TYPE , TRY_CAST_AST_KIND ,
26- TYPE_PASSTHROUGH_AST_KINDS , UNKNOWN_NULLABILITY , VARCHAR_DATA_TYPES , WILDCARD ,
26+ TYPE_PASSTHROUGH_AST_KINDS , UNKNOWN_NULLABILITY , UNKNOWN_TYPE , VARCHAR_DATA_TYPES , WILDCARD ,
2727} ;
2828use crate :: assembly:: analysis_session:: constants:: {
2929 CONFIDENCE_HIGH , CONFIDENCE_MEDIUM , CONFIDENCE_UNKNOWN , TRANSFORM_AGGREGATION , TRANSFORM_CAST ,
@@ -35,8 +35,6 @@ use crate::assembly::analysis_session::types::{Pairs, Shapes};
3535use crate :: nullability_rules:: main:: conditional_result_nullability:: conditional_result_nullability;
3636use crate :: nullability_rules:: main:: first_arg_nullability:: first_arg_nullability;
3737use crate :: nullability_rules:: main:: safe_cast_nullability:: safe_cast_nullability;
38- use crate :: sql_macros:: main:: sql_macro_declared_type:: sql_macro_declared_type;
39- use crate :: sql_macros:: models:: DeclaredOutput ;
4038use crate :: type_system:: main:: normalize_type:: normalize_type;
4139use crate :: type_system:: models:: { NormalizedType , TypeFamily } ;
4240
@@ -60,8 +58,6 @@ pub(crate) struct RecoveryProfile<'a> {
6058 pub ( crate ) rules : Option < & ' a Pairs > ,
6159 /// Runs the adapter's own rules, those with the `python` rule id.
6260 pub ( crate ) callback : Option < & ' a NullabilityCallback > ,
63- /// The model's select items that are one SQL macro call each, typed by its `returns`.
64- pub ( crate ) declared : & ' a [ DeclaredOutput ] ,
6561}
6662
6763/// One enrichment's recovery request.
@@ -133,108 +129,6 @@ pub(crate) struct LegacyAnalysis {
133129 pub ( crate ) lineage : Vec < LineageRow > ,
134130}
135131
136- /// The model query's output types its SQL macro call items change, given the relation types.
137- pub ( crate ) fn declared_output_types (
138- cleaned_sql : & str ,
139- types : & ShapeTable ,
140- profile : & RecoveryProfile < ' _ > ,
141- ) -> Fact < Vec < ( String , String ) > > {
142- let Some ( parsed) = parse_one ( cleaned_sql, profile. dialect ) ? else {
143- return Ok ( Vec :: new ( ) ) ;
144- } ;
145- let facts: Relations = referenced_types ( & parsed, types) ;
146- let declared: Vec < ( String , String ) > = Context {
147- profile,
148- branches : declared_branches ( & parsed) ,
149- }
150- . query_types ( & parsed, & facts) ?;
151- let plain: Vec < ( String , String ) > = Context {
152- profile,
153- branches : Vec :: new ( ) ,
154- }
155- . query_types ( & parsed, & facts) ?;
156- let main: HashSet < String > = main_output_columns ( & parsed, profile. declared ) ;
157- Ok ( declared
158- . into_iter ( )
159- . filter ( |( name, data_type) | {
160- dict_get ( & plain, name) != Some ( data_type) || main. contains ( & casefold ( name) )
161- } )
162- . collect ( ) )
163- }
164-
165- /// Each select of the main query and top-level CTEs by address, with its scope and branch.
166- fn declared_branches ( root : & Expression ) -> Vec < Branch > {
167- let mut branches: Vec < Branch > = Vec :: new ( ) ;
168- for ( cte_name, has_column_aliases, body) in top_level_ctes ( root) {
169- if !has_column_aliases
170- && unwrap_annotations ( Some ( body) ) . is_some_and ( |body| top_level_ctes ( body) . is_empty ( ) )
171- {
172- for ( branch, select) in set_branches ( Some ( body) ) . into_iter ( ) . enumerate ( ) {
173- branches. push ( (
174- std:: ptr:: from_ref ( select) ,
175- Some ( cte_name. to_owned ( ) ) ,
176- branch,
177- ) ) ;
178- }
179- }
180- }
181- for ( branch, select) in set_branches ( Some ( root) ) . into_iter ( ) . enumerate ( ) {
182- branches. push ( ( std:: ptr:: from_ref ( select) , None , branch) ) ;
183- }
184- branches
185- }
186-
187- /// The folded main-query output columns its SQL macro call items stand in, in any branch.
188- fn main_output_columns ( root : & Expression , declared : & [ DeclaredOutput ] ) -> HashSet < String > {
189- let selects: Vec < & Expression > = set_branches ( Some ( root) ) ;
190- let by_name: bool = set_operation ( root) . is_some_and ( |( _, _, by_name) | by_name) ;
191- let Some ( first) = selects. first ( ) else {
192- return HashSet :: new ( ) ;
193- } ;
194- let first_names: Vec < & str > = projection_names ( first) ;
195- let mut columns: HashSet < String > = HashSet :: new ( ) ;
196- for output in declared. iter ( ) . filter ( |output| output. cte . is_none ( ) ) {
197- if by_name {
198- columns. insert ( casefold ( & output. name ) ) ;
199- continue ;
200- }
201- let Some ( select) = selects. get ( output. branch ) else {
202- continue ;
203- } ;
204- let position: Option < usize > = projection_names ( select)
205- . iter ( )
206- . position ( |name| name. eq_ignore_ascii_case ( & output. name ) ) ;
207- if let Some ( column) = position. and_then ( |position| first_names. get ( position) ) {
208- columns. insert ( casefold ( column) ) ;
209- }
210- }
211- columns
212- }
213-
214- /// The output name of each of `select`'s projections, in order.
215- fn projection_names ( select : & Expression ) -> Vec < & str > {
216- select
217- . get_expressions ( )
218- . iter ( )
219- . map ( |projection| output_name ( unwrap_annotations ( Some ( projection) ) ) )
220- . collect ( )
221- }
222-
223- /// The selects of `node`'s set operation, left to right, or `node` itself.
224- fn set_branches ( node : Option < & Expression > ) -> Vec < & Expression > {
225- let Some ( node) = unwrap_annotations ( node) else {
226- return Vec :: new ( ) ;
227- } ;
228- match set_operation ( node) {
229- Some ( ( left, right, _) ) => {
230- let mut branches: Vec < & Expression > = set_branches ( Some ( left) ) ;
231- branches. extend ( set_branches ( Some ( right) ) ) ;
232- branches
233- }
234- None => vec ! [ node] ,
235- }
236- }
237-
238132/// Python's legacy analysis of a model the native engine handed back, or why it defers.
239133pub ( crate ) fn legacy_analysis ( input : & LegacyInput < ' _ > ) -> Fact < LegacyAnalysis > {
240134 let Some ( parsed) = parse_one ( input. cleaned_sql , input. profile . dialect ) ? else {
@@ -272,7 +166,6 @@ pub(crate) fn legacy_analysis(input: &LegacyInput<'_>) -> Fact<LegacyAnalysis> {
272166 let unqualified: Option < & ( String , String ) > = single_resource ( & resources) ;
273167 let context = Context {
274168 profile : & input. profile ,
275- branches : Vec :: new ( ) ,
276169 } ;
277170 let ( cte_types, cte_nullability) = if input. recover {
278171 (
@@ -543,7 +436,6 @@ fn passthrough_facts(
543436 }
544437 let context = Context {
545438 profile : & input. profile ,
546- branches : Vec :: new ( ) ,
547439 } ;
548440 recovery. types = context. passthrough_types ( root, & input_types ( input) ) ?;
549441 recovery. nullability = context. passthrough_nullability ( root, & input_nullability ( input) ) ?;
@@ -574,21 +466,24 @@ fn direct_cte_outputs(root: &Expression, cte_names: &HashSet<String>) -> HashSet
574466 outputs
575467}
576468
577- /// A select's address, its top-level CTE or `None` for the main query, and its branch.
578- type Branch = ( * const Expression , Option < String > , usize ) ;
579-
580469/// The profile every resolver reads.
581470struct Context < ' a , ' b > {
582471 profile : & ' a RecoveryProfile < ' b > ,
583- /// The selects whose SQL macro call items take their declared types.
584- branches : Vec < Branch > ,
585472}
586473
587474impl Context < ' _ , ' _ > {
588- /// The model query's output types, through its top-level CTEs.
589- fn query_types ( & self , root : & Expression , facts : & Relations ) -> Fact < Vec < ( String , String ) > > {
475+ /// Python's `_polyglot_cte_passthrough_types_from_parsed`.
476+ fn passthrough_types (
477+ & self ,
478+ root : & Expression ,
479+ facts : & Relations ,
480+ ) -> Fact < Vec < ( String , String ) > > {
481+ let ctes: Vec < ( & str , bool , & Expression ) > = top_level_ctes ( root) ;
482+ if ctes. is_empty ( ) {
483+ return Ok ( Vec :: new ( ) ) ;
484+ }
590485 let mut relations: Relations = referenced_facts ( root, facts) ;
591- for ( cte_name, has_column_aliases, body) in top_level_ctes ( root ) {
486+ for ( cte_name, has_column_aliases, body) in ctes {
592487 if has_column_aliases {
593488 continue ;
594489 }
@@ -604,52 +499,6 @@ impl Context<'_, '_> {
604499 self . direct_select_types ( Some ( root) , & relations)
605500 }
606501
607- /// Python's `_polyglot_cte_passthrough_types_from_parsed`.
608- fn passthrough_types (
609- & self ,
610- root : & Expression ,
611- facts : & Relations ,
612- ) -> Fact < Vec < ( String , String ) > > {
613- if top_level_ctes ( root) . is_empty ( ) {
614- return Ok ( Vec :: new ( ) ) ;
615- }
616- self . query_types ( root, facts)
617- }
618-
619- /// `types` of `select` with each of its SQL macro call items typed by its `returns`.
620- fn declared_types (
621- & self ,
622- select : & Expression ,
623- mut types : Vec < ( String , String ) > ,
624- ) -> Vec < ( String , String ) > {
625- let address: * const Expression = std:: ptr:: from_ref ( select) ;
626- let Some ( ( _, cte, branch) ) = self
627- . branches
628- . iter ( )
629- . find ( |( branch_select, _, _) | * branch_select == address)
630- else {
631- return types;
632- } ;
633- for output in self . profile . declared {
634- let key: String = types
635- . iter ( )
636- . map ( |( name, _) | name)
637- . find ( |name| name. eq_ignore_ascii_case ( & output. name ) )
638- . cloned ( )
639- . unwrap_or_else ( || output. name . clone ( ) ) ;
640- let inferred: Option < & str > = dict_get ( & types, & key) . map ( String :: as_str) ;
641- if let Some ( declared) = sql_macro_declared_type (
642- self . profile . declared ,
643- ( cte. as_deref ( ) , * branch, & output. name ) ,
644- inferred,
645- self . profile . dialect ,
646- ) {
647- types = dict_set ( types, & key, declared) ;
648- }
649- }
650- types
651- }
652-
653502 /// Python's `_polyglot_select_output_types`.
654503 fn select_output_types (
655504 & self ,
@@ -840,7 +689,7 @@ impl Context<'_, '_> {
840689 inferred = dict_set ( inferred, output, inferred_type) ;
841690 }
842691 }
843- Ok ( self . declared_types ( select , inferred) )
692+ Ok ( inferred)
844693 }
845694
846695 /// Python's `_polyglot_direct_expression_type`.
@@ -849,6 +698,24 @@ impl Context<'_, '_> {
849698 expression : Option < & Expression > ,
850699 by_alias : & Relations ,
851700 ) -> Fact < Option < String > > {
701+ if let Some ( ( asserted, inner) ) = typed_annotation ( expression) {
702+ if let Some ( inferred) = self
703+ . expression_type_in ( Some ( inner) , by_alias) ?
704+ . filter ( |inferred| !upper ( inferred) . contains ( UNKNOWN_TYPE ) )
705+ {
706+ return Ok ( Some ( inferred) ) ;
707+ }
708+ return match asserted {
709+ Assertion :: Fixed ( asserted) => {
710+ let target: Value =
711+ serde_json:: to_value ( asserted) . map_err ( |error| error. to_string ( ) ) ?;
712+ Ok ( target. as_object ( ) . and_then ( cast_target_type) )
713+ }
714+ Assertion :: Like ( like) => Ok ( self
715+ . expression_type_in ( Some ( like) , by_alias) ?
716+ . filter ( |like| !upper ( like) . contains ( UNKNOWN_TYPE ) ) ) ,
717+ } ;
718+ }
852719 let expression: Option < & Expression > = unwrap_annotations ( expression) ;
853720 let node_kind: & str = kind ( expression) ;
854721 if let Some ( inferred) = self . expression_type ( expression) ? {
@@ -1070,6 +937,7 @@ impl Context<'_, '_> {
1070937 aliases : & [ ( String , & ' static str ) ] ,
1071938 scoped : & NullRelations ,
1072939 ) -> Fact < & ' static str > {
940+ let expression: Option < & Expression > = unwrap_annotations ( expression) ;
1073941 let node_kind: & str = kind ( expression) ;
1074942 match node_kind {
1075943 NULL_AST_KIND => return Ok ( NULLABLE_NULLABILITY ) ,
@@ -1101,6 +969,7 @@ impl Context<'_, '_> {
1101969
1102970 /// Python's `_infer_polyglot_shallow_nullability`.
1103971 fn shallow_nullability ( & self , expression : Option < & Expression > ) -> Fact < & ' static str > {
972+ let expression: Option < & Expression > = unwrap_annotations ( expression) ;
1104973 let node_kind: & str = kind ( expression) ;
1105974 match node_kind {
1106975 NULL_AST_KIND => return Ok ( NULLABLE_NULLABILITY ) ,
0 commit comments