flowscope_core/generated/
function_rules.rs

1//! Function argument handling rules per dialect.
2//!
3//! Generated from dialect_behavior.toml
4//!
5//! This module provides dialect-aware rules for function argument handling,
6//! particularly for date/time functions where certain arguments are keywords
7//! (like `YEAR`, `MONTH`) rather than column references.
8
9use crate::Dialect;
10
11/// Returns argument indices to skip when extracting column references from a function call.
12///
13/// Certain SQL functions take keyword arguments (e.g., `DATEDIFF(YEAR, start, end)` in Snowflake)
14/// that should not be treated as column references during lineage analysis. This function
15/// returns the indices of such arguments for the given function and dialect.
16///
17/// # Arguments
18///
19/// * `dialect` - The SQL dialect being analyzed
20/// * `func_name` - The function name (case-insensitive, underscore-insensitive)
21///
22/// # Returns
23///
24/// A slice of argument indices (0-based) to skip. Returns an empty slice for
25/// unknown functions or functions without skip rules.
26///
27/// # Example
28///
29/// ```ignore
30/// // In Snowflake, DATEDIFF takes a unit as the first argument
31/// let skip = skip_args_for_function(Dialect::Snowflake, "DATEDIFF");
32/// assert_eq!(skip, &[0]); // Skip first argument (the unit)
33///
34/// // Both DATEADD and DATE_ADD match the same rules
35/// let skip1 = skip_args_for_function(Dialect::Snowflake, "DATEADD");
36/// let skip2 = skip_args_for_function(Dialect::Snowflake, "DATE_ADD");
37/// assert_eq!(skip1, skip2);
38/// ```
39pub fn skip_args_for_function(dialect: Dialect, func_name: &str) -> &'static [usize] {
40    // Normalize: lowercase and remove underscores to handle both DATEADD and DATE_ADD variants
41    let func_normalized: String = func_name
42        .chars()
43        .filter(|c| *c != '_')
44        .map(|c| c.to_ascii_lowercase())
45        .collect();
46    match func_normalized.as_str() {
47        "datediff" => match dialect {
48            Dialect::Bigquery => &[],
49            Dialect::Databricks => &[],
50            Dialect::Duckdb => &[],
51            Dialect::Hive => &[],
52            Dialect::Mssql => &[0],
53            Dialect::Mysql => &[],
54            Dialect::Redshift => &[0],
55            Dialect::Snowflake => &[0],
56            _ => &[],
57        },
58        "dateadd" => match dialect {
59            Dialect::Bigquery => &[],
60            Dialect::Hive => &[],
61            Dialect::Mssql => &[0],
62            Dialect::Mysql => &[],
63            Dialect::Postgres => &[],
64            Dialect::Snowflake => &[0],
65            _ => &[],
66        },
67        "datepart" => match dialect {
68            Dialect::Postgres => &[0],
69            Dialect::Redshift => &[0],
70            Dialect::Snowflake => &[0],
71            _ => &[],
72        },
73        "datetrunc" => match dialect {
74            Dialect::Bigquery => &[1],
75            Dialect::Databricks => &[0],
76            Dialect::Duckdb => &[0],
77            Dialect::Postgres => &[0],
78            Dialect::Redshift => &[0],
79            Dialect::Snowflake => &[0],
80            _ => &[],
81        },
82        "extract" => &[0],
83        "timestampadd" => match dialect {
84            Dialect::Bigquery => &[1],
85            Dialect::Snowflake => &[0],
86            _ => &[],
87        },
88        "timestampsub" => match dialect {
89            Dialect::Bigquery => &[1],
90            _ => &[],
91        },
92        _ => &[],
93    }
94}
95
96
97/// NULL ordering behavior in ORDER BY.
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum NullOrdering {
100    /// NULLs sort as larger than all other values (NULLS LAST for ASC)
101    NullsAreLarge,
102    /// NULLs sort as smaller than all other values (NULLS FIRST for ASC)
103    NullsAreSmall,
104    /// NULLs always sort last regardless of ASC/DESC
105    NullsAreLast,
106}
107
108impl Dialect {
109    /// Get the default NULL ordering behavior for this dialect.
110    pub const fn null_ordering(&self) -> NullOrdering {
111        match self {
112            Dialect::Bigquery => NullOrdering::NullsAreSmall,
113            Dialect::Clickhouse => NullOrdering::NullsAreLast,
114            Dialect::Databricks => NullOrdering::NullsAreSmall,
115            Dialect::Duckdb => NullOrdering::NullsAreLast,
116            Dialect::Hive => NullOrdering::NullsAreSmall,
117            Dialect::Mssql => NullOrdering::NullsAreSmall,
118            Dialect::Mysql => NullOrdering::NullsAreSmall,
119            Dialect::Postgres => NullOrdering::NullsAreLarge,
120            Dialect::Redshift => NullOrdering::NullsAreLarge,
121            Dialect::Snowflake => NullOrdering::NullsAreLarge,
122            Dialect::Sqlite => NullOrdering::NullsAreSmall,
123            _ => NullOrdering::NullsAreLast,
124        }
125    }
126
127    /// Whether this dialect supports implicit UNNEST (no CROSS JOIN needed).
128    pub const fn supports_implicit_unnest(&self) -> bool {
129        matches!(self, Dialect::Bigquery | Dialect::Redshift)
130    }
131}