diff --git a/cases/relational/pivot_unaliased.toml b/cases/relational/pivot_unaliased.toml new file mode 100644 index 000000000..8597d8939 --- /dev/null +++ b/cases/relational/pivot_unaliased.toml @@ -0,0 +1,60 @@ +# PIVOT without an alias column list: the names Snowflake gives the key columns, as dbt pivot +# models leave them. +category = "relational" +construct = "node:pivot" +session_profile = "utc-v1" + +[[case]] +id = "pivot_star_without_alias_list" +description = "SELECT * over SUM(total) FOR channel IN ('web', 'store') with no alias list. Catches: key columns named web or WEB instead of the quoted text, a missing or NULL key, keys outside the list dropped, and the grouping column kept." +columns = [{ name = "order_id", type = "NUMBER(38,0)" }, { name = "channel", type = "VARCHAR" }, { name = "total", type = "NUMBER(12,2)" }] +rows = [ + ["1", "'web'", "10.50"], + ["1", "'web'", "2.25"], + ["1", "'store'", "4.00"], + ["2", "'store'", "NULL"], + ["2", "'phone'", "9.99"], + ["3", "NULL", "1.00"], + ["3", "'WEB'", "3.00"], +] +query = "SELECT * FROM (SELECT order_id, channel, total FROM input) PIVOT(SUM(total) FOR channel IN ('web', 'store')) ORDER BY order_id" +order = "ordered" +tags = ["null", "duplicates", "missing-key", "case-sensitive-key"] + +[[case]] +id = "pivot_quoted_key_reference" +description = "The pivoted key columns read back by their quoted names, \"'web'\" and \"'store'\", through a table alias without a column list. Catches: the names not matching the quoted key text." +columns = [{ name = "order_id", type = "NUMBER(38,0)" }, { name = "channel", type = "VARCHAR" }, { name = "total", type = "NUMBER(12,2)" }] +rows = [ + ["1", "'web'", "10.50"], + ["1", "'store'", "4.00"], + ["2", "'web'", "1.00"], +] +query = "SELECT p.order_id, p.\"'web'\" AS web_total, p.\"'store'\" AS store_total FROM (SELECT order_id, channel, total FROM input) PIVOT(SUM(total) FOR channel IN ('web', 'store')) AS p ORDER BY p.order_id" +order = "ordered" +tags = ["null", "missing-key"] + +[[case]] +id = "pivot_mixed_case_keys_without_alias_list" +description = "Keys with upper case, a space and an underscore, under MAX over BOOLEAN and no alias list. Catches: key names upper-cased, spaces changed, or the quotes dropped." +columns = [{ name = "customer_id", type = "VARCHAR" }, { name = "consent", type = "VARCHAR" }, { name = "granted", type = "BOOLEAN" }] +rows = [ + ["'c-1'", "'Email Opt'", "TRUE"], + ["'c-1'", "'SMS_OPT'", "FALSE"], + ["'c-2'", "'Email Opt'", "NULL"], + ["'c-2'", "'other'", "TRUE"], +] +query = "SELECT * FROM (SELECT customer_id, consent, granted FROM input) PIVOT(MAX(granted) FOR consent IN ('Email Opt', 'SMS_OPT')) ORDER BY customer_id" +order = "ordered" +tags = ["null", "missing-key", "case-sensitive-key"] + +[[case]] +id = "pivot_without_alias_list_over_no_rows" +description = "A PIVOT without an alias list over no rows. Catches: a row of NULLs instead of no rows, and the column names and types still described." +columns = [{ name = "order_id", type = "NUMBER(38,0)" }, { name = "channel", type = "VARCHAR" }, { name = "total", type = "NUMBER(12,2)" }] +rows = [ + ["1", "'web'", "10.50"], +] +query = "SELECT * FROM (SELECT order_id, channel, total FROM input WHERE total < 0) PIVOT(SUM(total) FOR channel IN ('web', 'store'))" +order = "bag" +tags = ["empty-input"] diff --git a/cases/semi-structured/table_flatten.toml b/cases/semi-structured/table_flatten.toml new file mode 100644 index 000000000..c332b66d8 --- /dev/null +++ b/cases/semi-structured/table_flatten.toml @@ -0,0 +1,33 @@ +# FLATTEN written as TABLE(FLATTEN(...)) after a comma, with a positional input, as dbt models +# write it: the same rows as LATERAL FLATTEN(input => ...). +category = "semi-structured" +construct = "fn:FLATTEN" +session_profile = "utc-v1" + +[[case]] +id = "table_flatten_positional_array" +description = "TABLE(FLATTEN(t.tags)) over an ARRAY column with a positional input: one row per element. Catches: the input not correlated to its row, empty arrays or NULL giving rows, or elements out of order." +columns = [{ name = "ticket_id", type = "NUMBER(38,0)" }, { name = "tags", type = "ARRAY" }] +rows = [ + ["1", "ARRAY_CONSTRUCT('billing', 'urgent')"], + ["2", "ARRAY_CONSTRUCT()"], + ["3", "NULL"], + ["4", "ARRAY_CONSTRUCT('refund')"], +] +query = "SELECT t.ticket_id, f.index, f.value::STRING AS tag FROM input t, TABLE(FLATTEN(t.tags)) f ORDER BY t.ticket_id, f.index" +order = "ordered" +tags = ["null", "array-index"] + +[[case]] +id = "table_flatten_named_variant_path" +description = "TABLE(FLATTEN(input => v:items)) over a VARIANT path, with KEY, PATH and VALUE read back. Catches: the named input ignored, objects read as arrays, or JSON null and missing paths giving rows." +columns = [{ name = "k", type = "NUMBER(2,0)" }, { name = "v", type = "VARIANT" }] +rows = [ + ["1", "PARSE_JSON('{\"items\":[{\"sku\":\"A-10\"},{\"sku\":\"B-20\"}]}')"], + ["2", "PARSE_JSON('{\"items\":{\"b\":1,\"a\":2}}')"], + ["3", "PARSE_JSON('{\"items\":null}')"], + ["4", "PARSE_JSON('{}')"], +] +query = "SELECT i.k, f.index, f.key, f.path, f.value::VARCHAR AS value FROM input i, TABLE(FLATTEN(input => i.v:items)) f ORDER BY i.k, f.index, f.key" +order = "ordered" +tags = ["null", "json-null", "missing-key", "nested", "array-index", "case-sensitive-key"] diff --git a/crates/sqlquack-rules/src/catalogue/constants.rs b/crates/sqlquack-rules/src/catalogue/constants.rs index c6bab09bc..654427cf2 100644 --- a/crates/sqlquack-rules/src/catalogue/constants.rs +++ b/crates/sqlquack-rules/src/catalogue/constants.rs @@ -993,7 +993,7 @@ pub const RULES: &[RuleSpec] = &[ RuleSpec { id: FLATTEN_RULE, constructs: &["node:lateral", "fn:FLATTEN"], - conditions: "LATERAL FLATTEN of a VARIANT input after another FROM source or after JOIN ... ON clauses, as a comma or CROSS JOIN source, with OUTER => a boolean literal at most: INDEX and PATH [i] for array elements, KEY and PATH for object keys (raising for keys that are not plain names), VALUE and THIS; scalars, JSON null and NULL give no rows. Under OUTER => TRUE an input with no rows gives one row: PATH '' and THIS the input for an empty container MODE takes, every column NULL otherwise (JSON null, NULL, a missing path, a scalar, a kind MODE skips), as recorded. MODE => 'ARRAY' or 'OBJECT' without RECURSIVE takes only that kind. RECURSIVE => TRUE walks every depth with keys in byte order, writing a key of letters, digits, `_`, spaces and dots that is not a plain name as ['key'] and raising for any other, MODE => 'BOTH', 'OBJECT' or 'ARRAY' choosing what it descends into, a scalar input giving one row of itself under BOTH and raising under OBJECT or ARRAY; RECURSIVE with OUTER, SEQ and PATH => are not converted.", + conditions: "LATERAL FLATTEN, or TABLE(FLATTEN(...)) with an alias and no column list, which correlates the same way, of a VARIANT input after another FROM source or after JOIN ... ON clauses, as a comma or CROSS JOIN source, with OUTER => a boolean literal at most: INDEX and PATH [i] for array elements, KEY and PATH for object keys (raising for keys that are not plain names), VALUE and THIS; scalars, JSON null and NULL give no rows. Under OUTER => TRUE an input with no rows gives one row: PATH '' and THIS the input for an empty container MODE takes, every column NULL otherwise (JSON null, NULL, a missing path, a scalar, a kind MODE skips), as recorded. MODE => 'ARRAY' or 'OBJECT' without RECURSIVE takes only that kind. RECURSIVE => TRUE walks every depth with keys in byte order, writing a key of letters, digits, `_`, spaces and dots that is not a plain name as ['key'] and raising for any other, MODE => 'BOTH', 'OBJECT' or 'ARRAY' choosing what it descends into, a scalar input giving one row of itself under BOTH and raising under OBJECT or ARRAY; RECURSIVE with OUTER, SEQ and PATH => are not converted.", failures: &[], }, RuleSpec { diff --git a/crates/sqlquack-rules/src/conversion/_helpers/queries/flatten.rs b/crates/sqlquack-rules/src/conversion/_helpers/queries/flatten.rs index 48fe37a56..20075e906 100644 --- a/crates/sqlquack-rules/src/conversion/_helpers/queries/flatten.rs +++ b/crates/sqlquack-rules/src/conversion/_helpers/queries/flatten.rs @@ -27,7 +27,7 @@ use sqlquack_semantics::scopes::models::ScopeRelation; use sqlquack_semantics::types::models::{ColumnSpec, Holding}; /// The name of the table function LATERAL converts. -const FLATTEN_NAME: &str = "FLATTEN"; +pub(crate) const FLATTEN_NAME: &str = "FLATTEN"; /// A converted LATERAL FLATTEN: its join text and the relation it brings into scope. pub(crate) struct Flattened { diff --git a/crates/sqlquack-rules/src/conversion/_helpers/queries/sources.rs b/crates/sqlquack-rules/src/conversion/_helpers/queries/sources.rs index d80e69818..6bc534179 100644 --- a/crates/sqlquack-rules/src/conversion/_helpers/queries/sources.rs +++ b/crates/sqlquack-rules/src/conversion/_helpers/queries/sources.rs @@ -15,7 +15,7 @@ use crate::conversion::_helpers::expressions::project_functions::{exact_argument use crate::conversion::_helpers::expressions::scalar::convert_expression; use crate::conversion::_helpers::expressions::semi_structured::guarded_column; use crate::conversion::_helpers::queries::clauses::condition_sql; -use crate::conversion::_helpers::queries::flatten::lateral_flatten; +use crate::conversion::_helpers::queries::flatten::{FLATTEN_NAME, lateral_flatten}; use crate::conversion::_helpers::queries::pivot::pivot_subquery; use crate::conversion::_helpers::queries::query::{convert_query, require_distinct_names}; use crate::conversion::_helpers::support::collecting::{ @@ -57,7 +57,7 @@ pub(crate) fn convert_from( let sources = list(from, "expressions"); let separator = match sources .iter() - .any(|source| source.get(LATERAL_KEY).is_some()) + .any(|source| lateral_payload(source).is_some()) { true => " CROSS JOIN ", false => ", ", @@ -65,13 +65,13 @@ pub(crate) fn convert_from( let mut sql = String::from(" FROM "); for (position, source) in sources.iter().enumerate() { let before = events(); - if let Some(lateral) = source.get(LATERAL_KEY) { + if let Some(lateral) = lateral_payload(source) { let flattened = match position { 0 => Err(missing( node_construct(LATERAL_KEY), "LATERAL as the first FROM source".to_owned(), )), - _ => lateral_flatten(lateral, &relations, outer, context) + _ => lateral_flatten(&lateral, &relations, outer, context) .map(|flattened| (flattened.sql, flattened.relation)), } .map_err(|refusal| context.locator.placed(refusal, source)); @@ -161,8 +161,8 @@ fn convert_join( .get("match_condition") .filter(|matching| !matching.is_null()); let before = events(); - if let (Some(lateral), "Implicit" | "Cross", None) = (source.get(LATERAL_KEY), kind, on) { - let flattened = lateral_flatten(lateral, earlier, outer, context) + if let (Some(lateral), "Implicit" | "Cross", None) = (lateral_payload(source), kind, on) { + let flattened = lateral_flatten(&lateral, earlier, outer, context) .map(|flattened| (flattened.sql, flattened.relation)); return recovered(flattened, source, (before, earlier.len())); } @@ -492,6 +492,28 @@ fn source_alias(source: &Value) -> Option { } } +/// The LATERAL payload of `LATERAL f(...) alias` or `TABLE(FLATTEN(...)) alias`, correlated alike. +fn lateral_payload(source: &Value) -> Option { + if let Some(lateral) = source.get(LATERAL_KEY) { + return Some(lateral.clone()); + } + let ("alias", aliased) = node_kind(source)? else { + return None; + }; + let call = table_call(aliased.get("this")?)?; + let flatten = call + .get("name") + .and_then(Value::as_str) + .is_some_and(|name| name.eq_ignore_ascii_case(FLATTEN_NAME)); + let alias = identifier_name(aliased.get("alias")?)?; + let unlisted = aliased + .get("column_aliases") + .and_then(Value::as_array) + .is_none_or(Vec::is_empty); + (flatten && unlisted && alias == alias.to_ascii_uppercase()) + .then(|| json!({ "this": { "function": call }, "alias": alias })) +} + fn lateral_subquery(source: &Value) -> bool { matches!(node_kind(source), Some(("subquery", payload)) if payload.get(LATERAL_KEY).and_then(Value::as_bool) == Some(true)) diff --git a/evidence/results/21d4c9c5a220840574004b4e581286b3ed15137cb1456ce3df3c697673e01e5d.arrow b/evidence/results/21d4c9c5a220840574004b4e581286b3ed15137cb1456ce3df3c697673e01e5d.arrow new file mode 100644 index 000000000..05c478201 Binary files /dev/null and b/evidence/results/21d4c9c5a220840574004b4e581286b3ed15137cb1456ce3df3c697673e01e5d.arrow differ diff --git a/evidence/results/21d4c9c5a220840574004b4e581286b3ed15137cb1456ce3df3c697673e01e5d.json b/evidence/results/21d4c9c5a220840574004b4e581286b3ed15137cb1456ce3df3c697673e01e5d.json new file mode 100644 index 000000000..7548f6519 --- /dev/null +++ b/evidence/results/21d4c9c5a220840574004b4e581286b3ed15137cb1456ce3df3c697673e01e5d.json @@ -0,0 +1,79 @@ +{ + "arrow_sha256": "aad895c9f4aeb32525580622b70d94b39b44b5c17b1f117270b89248d608b50a", + "case_hash": "21d4c9c5a220840574004b4e581286b3ed15137cb1456ce3df3c697673e01e5d", + "columns": [ + { + "internal_size": null, + "name": "ORDER_ID", + "nullable": false, + "precision": 38, + "scale": 0, + "type_code": 0, + "type_name": "FIXED" + }, + { + "internal_size": null, + "name": "'web'", + "nullable": true, + "precision": 24, + "scale": 2, + "type_code": 0, + "type_name": "FIXED" + }, + { + "internal_size": null, + "name": "'store'", + "nullable": true, + "precision": 24, + "scale": 2, + "type_code": 0, + "type_name": "FIXED" + } + ], + "connector": "snowflake-connector-python 4.7.2", + "duckdb_version": "v2.0.0", + "effective_parameters": { + "BINARY_INPUT_FORMAT": "HEX", + "BINARY_OUTPUT_FORMAT": "HEX", + "DATE_INPUT_FORMAT": "AUTO", + "DATE_OUTPUT_FORMAT": "YYYY-MM-DD", + "DEFAULT_NULL_ORDERING": "LAST", + "GEOGRAPHY_OUTPUT_FORMAT": "GeoJSON", + "GEOMETRY_OUTPUT_FORMAT": "GeoJSON", + "JSON_INDENT": "2", + "QUOTED_IDENTIFIERS_IGNORE_CASE": "false", + "STRICT_JSON_OUTPUT": "false", + "TIMESTAMP_DAY_IS_ALWAYS_24H": "false", + "TIMESTAMP_INPUT_FORMAT": "AUTO", + "TIMESTAMP_LTZ_OUTPUT_FORMAT": "", + "TIMESTAMP_NTZ_OUTPUT_FORMAT": "YYYY-MM-DD HH24:MI:SS.FF3", + "TIMESTAMP_OUTPUT_FORMAT": "YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM", + "TIMESTAMP_TYPE_MAPPING": "TIMESTAMP_NTZ", + "TIMESTAMP_TZ_OUTPUT_FORMAT": "", + "TIMEZONE": "UTC", + "TIME_INPUT_FORMAT": "AUTO", + "TIME_OUTPUT_FORMAT": "HH24:MI:SS", + "TWO_DIGIT_CENTURY_START": "1970", + "USE_CACHED_RESULT": "false", + "WEEK_OF_YEAR_POLICY": "0", + "WEEK_START": "0" + }, + "error": null, + "format": 1, + "outcome": "rows", + "parameters_sha256": "d094d15417903748055eca8fde8cf2884751253939f05ad5ad8d9bf14299ac61", + "query_id_sha256": "c7c2819c5d2f770f5aa606d95c7ddaf4e8ab9f644632588cfa4cc67f280c2b6f", + "recorded_at": "2026-11-06T06:09:54.301398+00:00", + "recorder": "sqlquack-record 0.1.0", + "row_count": 0, + "session_parameters": { + "DEFAULT_NULL_ORDERING": "LAST", + "QUOTED_IDENTIFIERS_IGNORE_CASE": false, + "TIMESTAMP_TYPE_MAPPING": "TIMESTAMP_NTZ", + "TIMEZONE": "UTC", + "USE_CACHED_RESULT": false, + "WEEK_OF_YEAR_POLICY": 0, + "WEEK_START": 0 + }, + "warehouse_version": "10.40.102" +} diff --git a/evidence/results/542612dfa7749f96531af74a063f43b3e3f348f16c50622e0f30830b74f7cf1f.arrow b/evidence/results/542612dfa7749f96531af74a063f43b3e3f348f16c50622e0f30830b74f7cf1f.arrow new file mode 100644 index 000000000..dde2c235a Binary files /dev/null and b/evidence/results/542612dfa7749f96531af74a063f43b3e3f348f16c50622e0f30830b74f7cf1f.arrow differ diff --git a/evidence/results/542612dfa7749f96531af74a063f43b3e3f348f16c50622e0f30830b74f7cf1f.json b/evidence/results/542612dfa7749f96531af74a063f43b3e3f348f16c50622e0f30830b74f7cf1f.json new file mode 100644 index 000000000..56af05b64 --- /dev/null +++ b/evidence/results/542612dfa7749f96531af74a063f43b3e3f348f16c50622e0f30830b74f7cf1f.json @@ -0,0 +1,97 @@ +{ + "arrow_sha256": "e16ca5668f551f81428efd0c80c46ce0f55b5d5726c3ff1231b1fed30c8af227", + "case_hash": "542612dfa7749f96531af74a063f43b3e3f348f16c50622e0f30830b74f7cf1f", + "columns": [ + { + "internal_size": null, + "name": "K", + "nullable": false, + "precision": 2, + "scale": 0, + "type_code": 0, + "type_name": "FIXED" + }, + { + "internal_size": null, + "name": "INDEX", + "nullable": true, + "precision": 38, + "scale": 0, + "type_code": 0, + "type_name": "FIXED" + }, + { + "internal_size": 134217728, + "name": "KEY", + "nullable": true, + "precision": null, + "scale": null, + "type_code": 2, + "type_name": "TEXT" + }, + { + "internal_size": 134217728, + "name": "PATH", + "nullable": true, + "precision": null, + "scale": null, + "type_code": 2, + "type_name": "TEXT" + }, + { + "internal_size": 134217728, + "name": "VALUE", + "nullable": true, + "precision": null, + "scale": null, + "type_code": 2, + "type_name": "TEXT" + } + ], + "connector": "snowflake-connector-python 4.7.2", + "duckdb_version": "v2.0.0", + "effective_parameters": { + "BINARY_INPUT_FORMAT": "HEX", + "BINARY_OUTPUT_FORMAT": "HEX", + "DATE_INPUT_FORMAT": "AUTO", + "DATE_OUTPUT_FORMAT": "YYYY-MM-DD", + "DEFAULT_NULL_ORDERING": "LAST", + "GEOGRAPHY_OUTPUT_FORMAT": "GeoJSON", + "GEOMETRY_OUTPUT_FORMAT": "GeoJSON", + "JSON_INDENT": "2", + "QUOTED_IDENTIFIERS_IGNORE_CASE": "false", + "STRICT_JSON_OUTPUT": "false", + "TIMESTAMP_DAY_IS_ALWAYS_24H": "false", + "TIMESTAMP_INPUT_FORMAT": "AUTO", + "TIMESTAMP_LTZ_OUTPUT_FORMAT": "", + "TIMESTAMP_NTZ_OUTPUT_FORMAT": "YYYY-MM-DD HH24:MI:SS.FF3", + "TIMESTAMP_OUTPUT_FORMAT": "YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM", + "TIMESTAMP_TYPE_MAPPING": "TIMESTAMP_NTZ", + "TIMESTAMP_TZ_OUTPUT_FORMAT": "", + "TIMEZONE": "UTC", + "TIME_INPUT_FORMAT": "AUTO", + "TIME_OUTPUT_FORMAT": "HH24:MI:SS", + "TWO_DIGIT_CENTURY_START": "1970", + "USE_CACHED_RESULT": "false", + "WEEK_OF_YEAR_POLICY": "0", + "WEEK_START": "0" + }, + "error": null, + "format": 1, + "outcome": "rows", + "parameters_sha256": "d094d15417903748055eca8fde8cf2884751253939f05ad5ad8d9bf14299ac61", + "query_id_sha256": "c6acf3f6ecb1dc565f427f676461e985eeb4886484841bc8d1f686f4c992c03e", + "recorded_at": "2026-11-06T06:09:54.301398+00:00", + "recorder": "sqlquack-record 0.1.0", + "row_count": 4, + "session_parameters": { + "DEFAULT_NULL_ORDERING": "LAST", + "QUOTED_IDENTIFIERS_IGNORE_CASE": false, + "TIMESTAMP_TYPE_MAPPING": "TIMESTAMP_NTZ", + "TIMEZONE": "UTC", + "USE_CACHED_RESULT": false, + "WEEK_OF_YEAR_POLICY": 0, + "WEEK_START": 0 + }, + "warehouse_version": "10.40.102" +} diff --git a/evidence/results/7c45d391d4c1b047174ace42c9996d06ae71f8d5518c19c6903fae1b42467c50.arrow b/evidence/results/7c45d391d4c1b047174ace42c9996d06ae71f8d5518c19c6903fae1b42467c50.arrow new file mode 100644 index 000000000..7fcd89ccb Binary files /dev/null and b/evidence/results/7c45d391d4c1b047174ace42c9996d06ae71f8d5518c19c6903fae1b42467c50.arrow differ diff --git a/evidence/results/7c45d391d4c1b047174ace42c9996d06ae71f8d5518c19c6903fae1b42467c50.json b/evidence/results/7c45d391d4c1b047174ace42c9996d06ae71f8d5518c19c6903fae1b42467c50.json new file mode 100644 index 000000000..cf35b908e --- /dev/null +++ b/evidence/results/7c45d391d4c1b047174ace42c9996d06ae71f8d5518c19c6903fae1b42467c50.json @@ -0,0 +1,79 @@ +{ + "arrow_sha256": "2cdfc5f0d80bd6fd45f928d642c14eb08eb5fef8bba7fb4ec57379dda451b1e2", + "case_hash": "7c45d391d4c1b047174ace42c9996d06ae71f8d5518c19c6903fae1b42467c50", + "columns": [ + { + "internal_size": 134217728, + "name": "CUSTOMER_ID", + "nullable": false, + "precision": null, + "scale": null, + "type_code": 2, + "type_name": "TEXT" + }, + { + "internal_size": null, + "name": "'Email Opt'", + "nullable": true, + "precision": null, + "scale": null, + "type_code": 13, + "type_name": "BOOLEAN" + }, + { + "internal_size": null, + "name": "'SMS_OPT'", + "nullable": true, + "precision": null, + "scale": null, + "type_code": 13, + "type_name": "BOOLEAN" + } + ], + "connector": "snowflake-connector-python 4.7.2", + "duckdb_version": "v2.0.0", + "effective_parameters": { + "BINARY_INPUT_FORMAT": "HEX", + "BINARY_OUTPUT_FORMAT": "HEX", + "DATE_INPUT_FORMAT": "AUTO", + "DATE_OUTPUT_FORMAT": "YYYY-MM-DD", + "DEFAULT_NULL_ORDERING": "LAST", + "GEOGRAPHY_OUTPUT_FORMAT": "GeoJSON", + "GEOMETRY_OUTPUT_FORMAT": "GeoJSON", + "JSON_INDENT": "2", + "QUOTED_IDENTIFIERS_IGNORE_CASE": "false", + "STRICT_JSON_OUTPUT": "false", + "TIMESTAMP_DAY_IS_ALWAYS_24H": "false", + "TIMESTAMP_INPUT_FORMAT": "AUTO", + "TIMESTAMP_LTZ_OUTPUT_FORMAT": "", + "TIMESTAMP_NTZ_OUTPUT_FORMAT": "YYYY-MM-DD HH24:MI:SS.FF3", + "TIMESTAMP_OUTPUT_FORMAT": "YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM", + "TIMESTAMP_TYPE_MAPPING": "TIMESTAMP_NTZ", + "TIMESTAMP_TZ_OUTPUT_FORMAT": "", + "TIMEZONE": "UTC", + "TIME_INPUT_FORMAT": "AUTO", + "TIME_OUTPUT_FORMAT": "HH24:MI:SS", + "TWO_DIGIT_CENTURY_START": "1970", + "USE_CACHED_RESULT": "false", + "WEEK_OF_YEAR_POLICY": "0", + "WEEK_START": "0" + }, + "error": null, + "format": 1, + "outcome": "rows", + "parameters_sha256": "d094d15417903748055eca8fde8cf2884751253939f05ad5ad8d9bf14299ac61", + "query_id_sha256": "01451c6abd9c269aeee06b0ddb718368f472fdaeb0d09a65a89ba1293497287e", + "recorded_at": "2026-11-06T06:09:54.301398+00:00", + "recorder": "sqlquack-record 0.1.0", + "row_count": 2, + "session_parameters": { + "DEFAULT_NULL_ORDERING": "LAST", + "QUOTED_IDENTIFIERS_IGNORE_CASE": false, + "TIMESTAMP_TYPE_MAPPING": "TIMESTAMP_NTZ", + "TIMEZONE": "UTC", + "USE_CACHED_RESULT": false, + "WEEK_OF_YEAR_POLICY": 0, + "WEEK_START": 0 + }, + "warehouse_version": "10.40.102" +} diff --git a/evidence/results/9b9e4ce500d7a86b5e5a0da9652e48fa2336d677f65f0e0921cc04a04b92d05f.arrow b/evidence/results/9b9e4ce500d7a86b5e5a0da9652e48fa2336d677f65f0e0921cc04a04b92d05f.arrow new file mode 100644 index 000000000..1a2102ebe Binary files /dev/null and b/evidence/results/9b9e4ce500d7a86b5e5a0da9652e48fa2336d677f65f0e0921cc04a04b92d05f.arrow differ diff --git a/evidence/results/9b9e4ce500d7a86b5e5a0da9652e48fa2336d677f65f0e0921cc04a04b92d05f.json b/evidence/results/9b9e4ce500d7a86b5e5a0da9652e48fa2336d677f65f0e0921cc04a04b92d05f.json new file mode 100644 index 000000000..cadc86969 --- /dev/null +++ b/evidence/results/9b9e4ce500d7a86b5e5a0da9652e48fa2336d677f65f0e0921cc04a04b92d05f.json @@ -0,0 +1,79 @@ +{ + "arrow_sha256": "83906f4fd0b02362253a9a0fe43ac0d4eee5e75d68a8ee44af2739e3189a27ed", + "case_hash": "9b9e4ce500d7a86b5e5a0da9652e48fa2336d677f65f0e0921cc04a04b92d05f", + "columns": [ + { + "internal_size": null, + "name": "ORDER_ID", + "nullable": false, + "precision": 38, + "scale": 0, + "type_code": 0, + "type_name": "FIXED" + }, + { + "internal_size": null, + "name": "WEB_TOTAL", + "nullable": true, + "precision": 24, + "scale": 2, + "type_code": 0, + "type_name": "FIXED" + }, + { + "internal_size": null, + "name": "STORE_TOTAL", + "nullable": true, + "precision": 24, + "scale": 2, + "type_code": 0, + "type_name": "FIXED" + } + ], + "connector": "snowflake-connector-python 4.7.2", + "duckdb_version": "v2.0.0", + "effective_parameters": { + "BINARY_INPUT_FORMAT": "HEX", + "BINARY_OUTPUT_FORMAT": "HEX", + "DATE_INPUT_FORMAT": "AUTO", + "DATE_OUTPUT_FORMAT": "YYYY-MM-DD", + "DEFAULT_NULL_ORDERING": "LAST", + "GEOGRAPHY_OUTPUT_FORMAT": "GeoJSON", + "GEOMETRY_OUTPUT_FORMAT": "GeoJSON", + "JSON_INDENT": "2", + "QUOTED_IDENTIFIERS_IGNORE_CASE": "false", + "STRICT_JSON_OUTPUT": "false", + "TIMESTAMP_DAY_IS_ALWAYS_24H": "false", + "TIMESTAMP_INPUT_FORMAT": "AUTO", + "TIMESTAMP_LTZ_OUTPUT_FORMAT": "", + "TIMESTAMP_NTZ_OUTPUT_FORMAT": "YYYY-MM-DD HH24:MI:SS.FF3", + "TIMESTAMP_OUTPUT_FORMAT": "YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM", + "TIMESTAMP_TYPE_MAPPING": "TIMESTAMP_NTZ", + "TIMESTAMP_TZ_OUTPUT_FORMAT": "", + "TIMEZONE": "UTC", + "TIME_INPUT_FORMAT": "AUTO", + "TIME_OUTPUT_FORMAT": "HH24:MI:SS", + "TWO_DIGIT_CENTURY_START": "1970", + "USE_CACHED_RESULT": "false", + "WEEK_OF_YEAR_POLICY": "0", + "WEEK_START": "0" + }, + "error": null, + "format": 1, + "outcome": "rows", + "parameters_sha256": "d094d15417903748055eca8fde8cf2884751253939f05ad5ad8d9bf14299ac61", + "query_id_sha256": "23fbd4e24b967866c4e152b945b6b8e02600648fb2e71b50a94edd92f2bf021f", + "recorded_at": "2026-11-06T06:09:54.301398+00:00", + "recorder": "sqlquack-record 0.1.0", + "row_count": 2, + "session_parameters": { + "DEFAULT_NULL_ORDERING": "LAST", + "QUOTED_IDENTIFIERS_IGNORE_CASE": false, + "TIMESTAMP_TYPE_MAPPING": "TIMESTAMP_NTZ", + "TIMEZONE": "UTC", + "USE_CACHED_RESULT": false, + "WEEK_OF_YEAR_POLICY": 0, + "WEEK_START": 0 + }, + "warehouse_version": "10.40.102" +} diff --git a/evidence/results/e2cbd488488ef8174915de51d18c8d0f770d9832448d0e28f5cabfebda20f9ec.arrow b/evidence/results/e2cbd488488ef8174915de51d18c8d0f770d9832448d0e28f5cabfebda20f9ec.arrow new file mode 100644 index 000000000..9ff597c2e Binary files /dev/null and b/evidence/results/e2cbd488488ef8174915de51d18c8d0f770d9832448d0e28f5cabfebda20f9ec.arrow differ diff --git a/evidence/results/e2cbd488488ef8174915de51d18c8d0f770d9832448d0e28f5cabfebda20f9ec.json b/evidence/results/e2cbd488488ef8174915de51d18c8d0f770d9832448d0e28f5cabfebda20f9ec.json new file mode 100644 index 000000000..732a6f0cb --- /dev/null +++ b/evidence/results/e2cbd488488ef8174915de51d18c8d0f770d9832448d0e28f5cabfebda20f9ec.json @@ -0,0 +1,79 @@ +{ + "arrow_sha256": "53578690d219a8fc9e389871a6353ff33e789552260a9519066a0ecb17cd21f2", + "case_hash": "e2cbd488488ef8174915de51d18c8d0f770d9832448d0e28f5cabfebda20f9ec", + "columns": [ + { + "internal_size": null, + "name": "ORDER_ID", + "nullable": false, + "precision": 38, + "scale": 0, + "type_code": 0, + "type_name": "FIXED" + }, + { + "internal_size": null, + "name": "'web'", + "nullable": true, + "precision": 24, + "scale": 2, + "type_code": 0, + "type_name": "FIXED" + }, + { + "internal_size": null, + "name": "'store'", + "nullable": true, + "precision": 24, + "scale": 2, + "type_code": 0, + "type_name": "FIXED" + } + ], + "connector": "snowflake-connector-python 4.7.2", + "duckdb_version": "v2.0.0", + "effective_parameters": { + "BINARY_INPUT_FORMAT": "HEX", + "BINARY_OUTPUT_FORMAT": "HEX", + "DATE_INPUT_FORMAT": "AUTO", + "DATE_OUTPUT_FORMAT": "YYYY-MM-DD", + "DEFAULT_NULL_ORDERING": "LAST", + "GEOGRAPHY_OUTPUT_FORMAT": "GeoJSON", + "GEOMETRY_OUTPUT_FORMAT": "GeoJSON", + "JSON_INDENT": "2", + "QUOTED_IDENTIFIERS_IGNORE_CASE": "false", + "STRICT_JSON_OUTPUT": "false", + "TIMESTAMP_DAY_IS_ALWAYS_24H": "false", + "TIMESTAMP_INPUT_FORMAT": "AUTO", + "TIMESTAMP_LTZ_OUTPUT_FORMAT": "", + "TIMESTAMP_NTZ_OUTPUT_FORMAT": "YYYY-MM-DD HH24:MI:SS.FF3", + "TIMESTAMP_OUTPUT_FORMAT": "YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM", + "TIMESTAMP_TYPE_MAPPING": "TIMESTAMP_NTZ", + "TIMESTAMP_TZ_OUTPUT_FORMAT": "", + "TIMEZONE": "UTC", + "TIME_INPUT_FORMAT": "AUTO", + "TIME_OUTPUT_FORMAT": "HH24:MI:SS", + "TWO_DIGIT_CENTURY_START": "1970", + "USE_CACHED_RESULT": "false", + "WEEK_OF_YEAR_POLICY": "0", + "WEEK_START": "0" + }, + "error": null, + "format": 1, + "outcome": "rows", + "parameters_sha256": "d094d15417903748055eca8fde8cf2884751253939f05ad5ad8d9bf14299ac61", + "query_id_sha256": "3888bb3369e4f9aafe244f1ad2798a27975402e748d92dedceb04f78850854c9", + "recorded_at": "2026-11-06T06:09:54.301398+00:00", + "recorder": "sqlquack-record 0.1.0", + "row_count": 3, + "session_parameters": { + "DEFAULT_NULL_ORDERING": "LAST", + "QUOTED_IDENTIFIERS_IGNORE_CASE": false, + "TIMESTAMP_TYPE_MAPPING": "TIMESTAMP_NTZ", + "TIMEZONE": "UTC", + "USE_CACHED_RESULT": false, + "WEEK_OF_YEAR_POLICY": 0, + "WEEK_START": 0 + }, + "warehouse_version": "10.40.102" +} diff --git a/evidence/results/ece78351de913a8b2004517ac82edd0a88621a3249368de23e69dc33181b683d.arrow b/evidence/results/ece78351de913a8b2004517ac82edd0a88621a3249368de23e69dc33181b683d.arrow new file mode 100644 index 000000000..db3a278fc Binary files /dev/null and b/evidence/results/ece78351de913a8b2004517ac82edd0a88621a3249368de23e69dc33181b683d.arrow differ diff --git a/evidence/results/ece78351de913a8b2004517ac82edd0a88621a3249368de23e69dc33181b683d.json b/evidence/results/ece78351de913a8b2004517ac82edd0a88621a3249368de23e69dc33181b683d.json new file mode 100644 index 000000000..153804bc9 --- /dev/null +++ b/evidence/results/ece78351de913a8b2004517ac82edd0a88621a3249368de23e69dc33181b683d.json @@ -0,0 +1,79 @@ +{ + "arrow_sha256": "267a8b880888e6609d407f286799f8c0ac964e1e9ffb3cbb5cfb946fce5b0443", + "case_hash": "ece78351de913a8b2004517ac82edd0a88621a3249368de23e69dc33181b683d", + "columns": [ + { + "internal_size": null, + "name": "TICKET_ID", + "nullable": false, + "precision": 38, + "scale": 0, + "type_code": 0, + "type_name": "FIXED" + }, + { + "internal_size": null, + "name": "INDEX", + "nullable": true, + "precision": 38, + "scale": 0, + "type_code": 0, + "type_name": "FIXED" + }, + { + "internal_size": 134217728, + "name": "TAG", + "nullable": true, + "precision": null, + "scale": null, + "type_code": 2, + "type_name": "TEXT" + } + ], + "connector": "snowflake-connector-python 4.7.2", + "duckdb_version": "v2.0.0", + "effective_parameters": { + "BINARY_INPUT_FORMAT": "HEX", + "BINARY_OUTPUT_FORMAT": "HEX", + "DATE_INPUT_FORMAT": "AUTO", + "DATE_OUTPUT_FORMAT": "YYYY-MM-DD", + "DEFAULT_NULL_ORDERING": "LAST", + "GEOGRAPHY_OUTPUT_FORMAT": "GeoJSON", + "GEOMETRY_OUTPUT_FORMAT": "GeoJSON", + "JSON_INDENT": "2", + "QUOTED_IDENTIFIERS_IGNORE_CASE": "false", + "STRICT_JSON_OUTPUT": "false", + "TIMESTAMP_DAY_IS_ALWAYS_24H": "false", + "TIMESTAMP_INPUT_FORMAT": "AUTO", + "TIMESTAMP_LTZ_OUTPUT_FORMAT": "", + "TIMESTAMP_NTZ_OUTPUT_FORMAT": "YYYY-MM-DD HH24:MI:SS.FF3", + "TIMESTAMP_OUTPUT_FORMAT": "YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM", + "TIMESTAMP_TYPE_MAPPING": "TIMESTAMP_NTZ", + "TIMESTAMP_TZ_OUTPUT_FORMAT": "", + "TIMEZONE": "UTC", + "TIME_INPUT_FORMAT": "AUTO", + "TIME_OUTPUT_FORMAT": "HH24:MI:SS", + "TWO_DIGIT_CENTURY_START": "1970", + "USE_CACHED_RESULT": "false", + "WEEK_OF_YEAR_POLICY": "0", + "WEEK_START": "0" + }, + "error": null, + "format": 1, + "outcome": "rows", + "parameters_sha256": "d094d15417903748055eca8fde8cf2884751253939f05ad5ad8d9bf14299ac61", + "query_id_sha256": "50c71e260d074ed287d2ae44a3d876cbbebf9f4850492b837de0975cbbf7766c", + "recorded_at": "2026-11-06T06:09:54.301398+00:00", + "recorder": "sqlquack-record 0.1.0", + "row_count": 3, + "session_parameters": { + "DEFAULT_NULL_ORDERING": "LAST", + "QUOTED_IDENTIFIERS_IGNORE_CASE": false, + "TIMESTAMP_TYPE_MAPPING": "TIMESTAMP_NTZ", + "TIMEZONE": "UTC", + "USE_CACHED_RESULT": false, + "WEEK_OF_YEAR_POLICY": 0, + "WEEK_START": 0 + }, + "warehouse_version": "10.40.102" +}