diff --git a/cases/generated/zoned_texts/column_comparisons.toml b/cases/generated/zoned_texts/column_comparisons.toml index c8d7e73ad..c4cd16ce4 100644 --- a/cases/generated/zoned_texts/column_comparisons.toml +++ b/cases/generated/zoned_texts/column_comparisons.toml @@ -110,6 +110,7 @@ query = "SELECT z = '2024-01-01 00:00:00'::TIMESTAMP_NTZ AS r FROM input ORDER B order = "ordered" tags = ["null", "boundary"] origin = "generated: zoned_texts@1" +expected_divergence = "timestamp_tz_submicro" [[case]] id = "timestamp_tz_column_gte_date" @@ -233,6 +234,7 @@ query = "SELECT z = '2024-01-01 00:00:00'::TIMESTAMP_NTZ AS r FROM input ORDER B order = "ordered" tags = ["null", "boundary"] origin = "generated: zoned_texts@1" +expected_divergence = "timestamp_tz_submicro" [[case]] id = "timestamp_ltz_column_gte_date" diff --git a/cases/temporal/naive_local_comparison.toml b/cases/temporal/naive_local_comparison.toml new file mode 100644 index 000000000..63b7cb692 --- /dev/null +++ b/cases/temporal/naive_local_comparison.toml @@ -0,0 +1,87 @@ +# A TIMESTAMP_NTZ compared with a TIMESTAMP_LTZ (a column, CURRENT_TIMESTAMP() or DATEADD of it) in +# the UTC session, the incremental and freshness filters compiled models write. +category = "temporal" +construct = "node:lte" +session_profile = "utc-v1" + +[[case]] +id = "ntz_beside_ltz_columns" +description = "Every comparison of a naive timestamp with a local-zone one, equal, a microsecond apart and across offsets." +columns = [{ name = "n", type = "TIMESTAMP_NTZ(9)" }, { name = "l", type = "TIMESTAMP_LTZ(9)" }] +rows = [ + ["'2024-06-01 10:00:00'", "'2024-06-01 10:00:00 +00:00'"], + ["'2024-06-01 10:00:00'", "'2024-06-01 12:00:00 +02:00'"], + ["'2024-06-01 10:00:00.000001'", "'2024-06-01 10:00:00 +00:00'"], + ["'2024-06-01 09:59:59.999999'", "'2024-06-01 10:00:00 +00:00'"], + ["'2024-06-01 10:00:00'", "'2024-06-01 05:00:00 -05:00'"], + ["'1969-12-31 23:59:59.5'", "'1970-01-01 00:00:00 +00:00'"], + ["NULL", "'2024-06-01 10:00:00 +00:00'"], +] +expression = "ARRAY_CONSTRUCT(n = l, n <> l, n < l, n <= l, n > l, n >= l, l < n)" +expected_divergence = "json_fidelity" +tags = ["null", "boundary"] + +[[case]] +id = "ntz_millisecond_beside_ltz" +description = "A TIMESTAMP_NTZ(3) beside a TIMESTAMP_LTZ with microseconds compares at full precision." +columns = [{ name = "n", type = "TIMESTAMP_NTZ(3)" }, { name = "l", type = "TIMESTAMP_LTZ(9)" }] +rows = [ + ["'2024-06-01 10:00:00.123'", "'2024-06-01 10:00:00.123 +00:00'"], + ["'2024-06-01 10:00:00.123'", "'2024-06-01 10:00:00.123001 +00:00'"], + ["'2024-06-01 10:00:00.124'", "'2024-06-01 10:00:00.123999 +00:00'"], +] +expression = "ARRAY_CONSTRUCT(n = l, n < l, n > l)" +tags = ["boundary"] + +[[case]] +id = "ntz_at_most_current_timestamp" +description = "A past naive timestamp is at most CURRENT_TIMESTAMP(); a far-future one is not." +columns = [{ name = "updated_at", type = "TIMESTAMP_NTZ(9)" }] +rows = [ + ["'2020-01-01 00:00:00'"], + ["'2199-12-31 23:59:59'"], + ["NULL"], +] +expression = "updated_at <= CURRENT_TIMESTAMP()" +tags = ["null"] + +[[case]] +id = "ntz_after_dateadd_of_current_timestamp" +description = "The incremental filter updated_at >= DATEADD(day, -3, CURRENT_TIMESTAMP()) over past and future rows." +columns = [{ name = "updated_at", type = "TIMESTAMP_NTZ(9)" }] +rows = [ + ["'2020-01-01 00:00:00'"], + ["'2199-12-31 23:59:59'"], + ["NULL"], +] +expression = "updated_at >= DATEADD(day, -3, CURRENT_TIMESTAMP())" +tags = ["null"] + +[[case]] +id = "dateadd_of_ltz_column" +description = "DATEADD of a local-zone column in DAY, HOUR, MONTH and MINUTE, compared with naive values." +columns = [{ name = "l", type = "TIMESTAMP_LTZ(9)" }] +rows = [ + ["'2024-01-31 23:30:00 +00:00'"], + ["'2024-02-29 01:00:00 +05:30'"], + ["'2024-03-31 00:00:00.000001 -08:00'"], + ["NULL"], +] +expression = "ARRAY_CONSTRUCT(DATEADD(day, -3, l) = '2024-01-28 23:30:00'::TIMESTAMP_NTZ, TO_VARCHAR(DATEADD(hour, 5, l)::TIMESTAMP_NTZ), TO_VARCHAR(DATEADD(month, 1, l)::TIMESTAMP_NTZ), TO_VARCHAR(DATEADD(minute, -90, l)::TIMESTAMP_NTZ))" +expected_divergence = "json_fidelity" +tags = ["null", "boundary"] + +[[case]] +id = "datediff_subday_of_dates" +description = "DATEDIFF in HOUR, MINUTE and SECOND with a DATE operand, read as its midnight." +columns = [{ name = "d", type = "DATE" }, { name = "t", type = "TIMESTAMP_NTZ(9)" }] +rows = [ + ["'2024-06-01'", "'2024-06-01 10:30:00'"], + ["'2024-06-01'", "'2024-05-31 23:59:59.999999999'"], + ["'2024-02-28'", "'2024-03-01 00:00:00'"], + ["'1969-12-31'", "'1970-01-01 00:00:01'"], + ["NULL", "'2024-06-01 10:30:00'"], +] +expression = "ARRAY_CONSTRUCT(DATEDIFF(hour, d, t), DATEDIFF(minute, t, d), DATEDIFF(second, d, t), DATEDIFF(hour, d, '2024-06-03'::DATE))" +expected_divergence = "json_fidelity" +tags = ["null", "boundary"] diff --git a/cases/temporal/naive_zoned_comparison.toml b/cases/temporal/naive_zoned_comparison.toml new file mode 100644 index 000000000..a785046e7 --- /dev/null +++ b/cases/temporal/naive_zoned_comparison.toml @@ -0,0 +1,71 @@ +category = "temporal" +construct = "node:lte" +session_profile = "utc-v1" + +[[case]] +id = "ntz_beside_utc_tz" +description = "Every comparison of a naive timestamp with CONVERT_TIMEZONE('UTC', tz), equal, a microsecond apart and across source offsets." +columns = [{ name = "n", type = "TIMESTAMP_NTZ(9)" }, { name = "tz", type = "TIMESTAMP_TZ(9)" }] +rows = [ + ["'2024-06-01 10:00:00'", "'2024-06-01 10:00:00 +00:00'"], + ["'2024-06-01 10:00:00'", "'2024-06-01 12:00:00 +02:00'"], + ["'2024-06-01 10:00:00.000001'", "'2024-06-01 10:00:00 +00:00'"], + ["'2024-06-01 09:59:59.999999'", "'2024-06-01 10:00:00 +00:00'"], + ["'2024-06-01 10:00:00'", "'2024-06-01 05:00:00 -05:00'"], + ["'1969-12-31 23:59:59.5'", "'1970-01-01 00:00:00 +00:00'"], + ["NULL", "'2024-06-01 10:00:00 +00:00'"], +] +expression = "ARRAY_CONSTRUCT(n = CONVERT_TIMEZONE('UTC', tz), n <> CONVERT_TIMEZONE('UTC', tz), n < CONVERT_TIMEZONE('UTC', tz), n <= CONVERT_TIMEZONE('UTC', tz), n > CONVERT_TIMEZONE('UTC', tz), n >= CONVERT_TIMEZONE('UTC', tz), CONVERT_TIMEZONE('UTC', tz) < n)" +expected_divergence = "json_fidelity" +tags = ["null", "boundary"] + +[[case]] +id = "ntz_beside_offset_tz_column" +description = "A naive timestamp compared with a TIMESTAMP_TZ column at a non-UTC offset, without CONVERT_TIMEZONE: whether Snowflake compares instants or wall times." +columns = [{ name = "n", type = "TIMESTAMP_NTZ(9)" }, { name = "tz", type = "TIMESTAMP_TZ(9)" }] +rows = [ + ["'2024-06-01 10:00:00'", "'2024-06-01 12:00:00 +02:00'"], + ["'2024-06-01 12:00:00'", "'2024-06-01 12:00:00 +02:00'"], + ["'2024-06-01 10:00:00'", "'2024-06-01 05:00:00 -05:00'"], +] +expression = "ARRAY_CONSTRUCT(n = tz, n < tz, n > tz)" +tags = ["boundary"] + +[[case]] +id = "ntz_at_most_utc_current_timestamp" +description = "dbt's incremental upper bound: a past naive timestamp is at most CONVERT_TIMEZONE('UTC', CURRENT_TIMESTAMP()); a far-future one is not." +columns = [{ name = "updated_at", type = "TIMESTAMP_NTZ(9)" }] +rows = [ + ["'2020-01-01 00:00:00'"], + ["'2199-12-31 23:59:59'"], + ["NULL"], +] +expression = "updated_at <= CONVERT_TIMEZONE('UTC', CURRENT_TIMESTAMP())" +tags = ["null"] + +[[case]] +id = "ntz_beside_zoned_past_nanosecond_range" +description = "A naive TIMESTAMP_NTZ(9) compared with TIMESTAMP_TZ and TIMESTAMP_LTZ sentinels past 2262, which DuckDB cannot widen to nanoseconds." +columns = [{ name = "n", type = "TIMESTAMP_NTZ(9)" }, { name = "tz", type = "TIMESTAMP_TZ(9)" }, { name = "l", type = "TIMESTAMP_LTZ(9)" }] +rows = [ + ["'2024-06-01 10:00:00'", "'9999-12-31 00:00:00 +00:00'", "'9999-12-31 00:00:00 +00:00'"], + ["'2024-06-01 10:00:00'", "'2300-01-01 12:00:00 +02:00'", "'2300-01-01 12:00:00 +00:00'"], + ["'2024-06-01 10:00:00'", "'2024-06-01 12:00:00 +02:00'", "'2024-06-01 10:00:00 +00:00'"], +] +expression = "ARRAY_CONSTRUCT(n < tz, n = tz, n < l, n >= l)" +expected_divergence = "timestamp_range" +tags = ["boundary"] + +[[case]] +id = "ntz_between_zoned_bounds" +description = "A naive timestamp BETWEEN a TIMESTAMP_TZ and a TIMESTAMP_LTZ bound, and a zoned value BETWEEN naive bounds." +columns = [{ name = "n", type = "TIMESTAMP_NTZ(9)" }, { name = "tz", type = "TIMESTAMP_TZ(9)" }, { name = "l", type = "TIMESTAMP_LTZ(9)" }] +rows = [ + ["'2024-06-01 10:00:00'", "'2024-06-01 11:00:00 +02:00'", "'2024-06-01 11:00:00 +00:00'"], + ["'2024-06-01 10:00:00'", "'2024-06-01 12:00:00 +02:00'", "'2024-06-01 10:00:00 +00:00'"], + ["'2024-06-01 10:00:00'", "'2024-06-01 13:00:00 +02:00'", "'2024-06-01 09:00:00 +00:00'"], + ["NULL", "'2024-06-01 10:00:00 +00:00'", "'2024-06-01 10:00:00 +00:00'"], +] +query = "SELECT n BETWEEN tz AND l AS a, tz BETWEEN n AND '2024-06-01 10:30:00'::TIMESTAMP_NTZ AS b FROM input ORDER BY ordinal" +order = "ordered" +tags = ["null", "boundary"] diff --git a/corpora/basics/queries.toml b/corpora/basics/queries.toml index 9f90d9954..003f6bbb5 100644 --- a/corpora/basics/queries.toml +++ b/corpora/basics/queries.toml @@ -14565,29 +14565,34 @@ expect_repository = "refused SQ0003 node:gte at 42..87" [[query]] category = "last_days" sql = '''SELECT order_id FROM orders WHERE ordered_at >= DATEADD(day, -30, CURRENT_TIMESTAMP())''' -expect = "refused SQ0003 fn:DATEADD at 48..86" -expect_repository = "refused SQ0003 fn:DATEADD at 48..86" +expect = "refused SQ0008 fn:DATEADD at 48..86" +expect_repository = "translated" +result_repository = "0 rows e3b0c44298fc1c14" [[query]] category = "last_days" sql = '''select /* c */ order_id from "ORDERS" where ordered_at >= dateadd(day, -30, current_timestamp())''' -expect = "refused SQ0003 fn:DATEADD at 58..96" -expect_repository = "refused SQ0003 fn:DATEADD at 58..96" +expect = "refused SQ0008 fn:DATEADD at 58..96" +expect_repository = "translated" +result_repository = "0 rows e3b0c44298fc1c14" [[query]] category = "last_days" sql = '''SELECT /* c */ ORDER_ID FROM ORDERS WHERE ORDERED_AT >= DATEADD(DAY, -30, CURRENT_TIMESTAMP())''' -expect = "refused SQ0003 fn:DATEADD at 60..98" -expect_repository = "refused SQ0003 fn:DATEADD at 60..98" +expect = "refused SQ0008 fn:DATEADD at 60..98" +expect_repository = "translated" +result_repository = "0 rows e3b0c44298fc1c14" [[query]] category = "last_days" sql = '''select order_id from orders where ordered_at >= DATEADD(day, -30, CURRENT_TIMESTAMP())''' -expect = "refused SQ0003 fn:DATEADD at 48..86" -expect_repository = "refused SQ0003 fn:DATEADD at 48..86" +expect = "refused SQ0008 fn:DATEADD at 48..86" +expect_repository = "translated" +result_repository = "0 rows e3b0c44298fc1c14" [[query]] category = "last_days" sql = '''SeLect ordEr_Id FrOM "ORDERS" wHERe oRDeRed_at >= DAteAdD(DAY, -30, cURrENt_tImEsTamP())''' -expect = "refused SQ0003 fn:DATEADD at 54..92" -expect_repository = "refused SQ0003 fn:DATEADD at 54..92" +expect = "refused SQ0008 fn:DATEADD at 54..92" +expect_repository = "translated" +result_repository = "0 rows e3b0c44298fc1c14" diff --git a/corpora/everyday-models/queries.toml b/corpora/everyday-models/queries.toml index aad2a7303..b3fe98972 100644 --- a/corpora/everyday-models/queries.toml +++ b/corpora/everyday-models/queries.toml @@ -150,8 +150,8 @@ sql = '''select _loaded_at from raw_orders where _loaded_at >= dateadd(day, -3, current_timestamp())''' -expect = "refused SQ0003 fn:DATEADD at 152..189" -expect_repository = "refused SQ0003 fn:DATEADD at 152..189" +expect = "refused SQ0008 fn:DATEADD at 152..189" +expect_repository = "translated" [[query]] category = "dbt_staging" @@ -4066,8 +4066,8 @@ category = "dbt_tests" sql = '''select max(updated_at) as max_updated_at from stg_orders having max(updated_at) < dateadd(hour, -24, current_timestamp())''' -expect = "refused SQ0003 fn:DATEADD at 82..121" -expect_repository = "refused SQ0003 fn:DATEADD at 82..121" +expect = "refused SQ0008 fn:DATEADD at 82..121" +expect_repository = "translated" [[query]] category = "dbt_tests" @@ -4704,8 +4704,8 @@ sql = '''select * from stg_orders where updated_at > (select max(updated_at) from fct_orders) and updated_at <= current_timestamp()''' -expect = "refused SQ0003 node:lte at 91..124" -expect_repository = "refused SQ0003 node:lte at 91..124" +expect = "refused SQ0008 node:lte at 91..124" +expect_repository = "translated" [[query]] category = "dbt_incremental" @@ -4797,8 +4797,8 @@ expect_repository = "translated" category = "dates" sql = '''select order_id, timestampdiff(hour, order_date, updated_at) as hours_to_update from stg_orders''' -expect = "refused SQ0003 fn:TIMESTAMPDIFF at 17..60" -expect_repository = "refused SQ0003 fn:TIMESTAMPDIFF at 17..60" +expect = "translated" +expect_repository = "translated" [[query]] category = "strings" @@ -6091,7 +6091,7 @@ sql = '''select * from analytics.dbt_dev.fct_orders where updated_at > (select max(updated_at) from analytics.dbt_dev.inc_event_time) and updated_at <= convert_timezone('UTC', current_timestamp())''' expect = "refused SQ0008 fn:CONVERT_TIMEZONE at 145..189" -expect_repository = "refused SQ0003 node:lte at 131..189" +expect_repository = "translated" [[query]] category = "dbt_incremental" diff --git a/corpora/everyday/queries.toml b/corpora/everyday/queries.toml index 41384201a..be5ecc792 100644 --- a/corpora/everyday/queries.toml +++ b/corpora/everyday/queries.toml @@ -1506,8 +1506,8 @@ expect_repository = "refused SQ0003 node:gte at 34..79" [[query]] category = "dates" sql = '''SELECT order_id FROM orders WHERE ordered_at >= DATEADD(day, -30, CURRENT_TIMESTAMP())''' -expect = "refused SQ0003 fn:DATEADD at 48..86" -expect_repository = "refused SQ0003 fn:DATEADD at 48..86" +expect = "refused SQ0008 fn:DATEADD at 48..86" +expect_repository = "translated" [[query]] category = "dates" diff --git a/coverage/translation/results-baseline.json b/coverage/translation/results-baseline.json index 8e22ff07d..36c84325e 100644 --- a/coverage/translation/results-baseline.json +++ b/coverage/translation/results-baseline.json @@ -6277,14 +6277,14 @@ "generated/zoned_texts/cast_tz.toml#cast_tz_wall": "declared_divergence", "generated/zoned_texts/cast_tz.toml#cast_tz_year": "declared_divergence", "generated/zoned_texts/column_comparisons.toml#timestamp_ltz_column_between_texts": "declared_divergence", - "generated/zoned_texts/column_comparisons.toml#timestamp_ltz_column_eq_ntz": "refused", + "generated/zoned_texts/column_comparisons.toml#timestamp_ltz_column_eq_ntz": "declared_divergence", "generated/zoned_texts/column_comparisons.toml#timestamp_ltz_column_eq_offset_text": "declared_divergence", "generated/zoned_texts/column_comparisons.toml#timestamp_ltz_column_eq_t_text": "refused", "generated/zoned_texts/column_comparisons.toml#timestamp_ltz_column_gte_date": "refused", "generated/zoned_texts/column_comparisons.toml#timestamp_ltz_column_gte_naive_text": "declared_divergence", "generated/zoned_texts/column_comparisons.toml#timestamp_ltz_column_lt_date_text": "declared_divergence", "generated/zoned_texts/column_comparisons.toml#timestamp_tz_column_between_texts": "declared_divergence", - "generated/zoned_texts/column_comparisons.toml#timestamp_tz_column_eq_ntz": "refused", + "generated/zoned_texts/column_comparisons.toml#timestamp_tz_column_eq_ntz": "declared_divergence", "generated/zoned_texts/column_comparisons.toml#timestamp_tz_column_eq_offset_text": "declared_divergence", "generated/zoned_texts/column_comparisons.toml#timestamp_tz_column_eq_t_text": "refused", "generated/zoned_texts/column_comparisons.toml#timestamp_tz_column_gte_date": "refused", @@ -7484,6 +7484,17 @@ "temporal/last_day.toml#last_day_month_unit": "refused", "temporal/last_day.toml#last_day_of_dates": "matching", "temporal/last_day.toml#last_day_of_timestamps": "matching", + "temporal/naive_local_comparison.toml#dateadd_of_ltz_column": "declared_divergence", + "temporal/naive_local_comparison.toml#datediff_subday_of_dates": "declared_divergence", + "temporal/naive_local_comparison.toml#ntz_after_dateadd_of_current_timestamp": "matching", + "temporal/naive_local_comparison.toml#ntz_at_most_current_timestamp": "matching", + "temporal/naive_local_comparison.toml#ntz_beside_ltz_columns": "declared_divergence", + "temporal/naive_local_comparison.toml#ntz_millisecond_beside_ltz": "matching", + "temporal/naive_zoned_comparison.toml#ntz_at_most_utc_current_timestamp": "matching", + "temporal/naive_zoned_comparison.toml#ntz_beside_offset_tz_column": "matching", + "temporal/naive_zoned_comparison.toml#ntz_beside_utc_tz": "declared_divergence", + "temporal/naive_zoned_comparison.toml#ntz_beside_zoned_past_nanosecond_range": "declared_divergence", + "temporal/naive_zoned_comparison.toml#ntz_between_zoned_bounds": "matching", "temporal/parse_followups.toml#dateadd_amount_above_int32": "matching", "temporal/parse_followups.toml#dateadd_amount_below_int32": "matching", "temporal/parse_followups.toml#dateadd_amount_int32_bounds": "declared_divergence", @@ -9370,10 +9381,10 @@ "translation/functions/current-time.current-time.toml#t003_precision_range": "refused", "translation/functions/current-time.current-time.toml#t004_null_none": "refused", "translation/functions/current-timestamp.current-timestamp.syntax.toml#t001_type": "refused", - "translation/functions/current-timestamp.current-timestamp.syntax.toml#t002_null_none": "refused", + "translation/functions/current-timestamp.current-timestamp.syntax.toml#t002_null_none": "matching", "translation/functions/current-timestamp.current-timestamp.toml#t001_type": "refused", "translation/functions/current-timestamp.current-timestamp.toml#t002_precision": "refused", - "translation/functions/current-timestamp.current-timestamp.toml#t003_null_none": "refused", + "translation/functions/current-timestamp.current-timestamp.toml#t003_null_none": "matching", "translation/functions/datasketches-hll-accumulate.toml#t001_estimate": "refused", "translation/functions/datasketches-hll-accumulate.toml#t002_float": "refused", "translation/functions/datasketches-hll-accumulate.toml#t003_max_log_k": "refused", @@ -9439,7 +9450,7 @@ "translation/functions/datediff.datediff.toml#t001_date": "matching", "translation/functions/datediff.datediff.toml#t002_timestamp_ntz": "matching", "translation/functions/datediff.datediff.toml#t003_time": "matching", - "translation/functions/datediff.datediff.toml#t004_mixed": "refused", + "translation/functions/datediff.datediff.toml#t004_mixed": "matching", "translation/functions/datediff.datediff.toml#t005_null_date_or_time_expr1": "matching", "translation/functions/datediff.datediff.toml#t006_null_date_or_time_expr2": "matching", "translation/functions/datediff.datediff.toml#t007_leap": "matching", @@ -11263,7 +11274,7 @@ "translation/functions/timediff.toml#t001_date": "matching", "translation/functions/timediff.toml#t002_timestamp_ntz": "matching", "translation/functions/timediff.toml#t003_time": "matching", - "translation/functions/timediff.toml#t004_mixed": "refused", + "translation/functions/timediff.toml#t004_mixed": "matching", "translation/functions/timediff.toml#t005_null_date_or_time_expr1": "matching", "translation/functions/timediff.toml#t006_null_date_or_time_expr2": "matching", "translation/functions/timediff.toml#t007_leap": "matching", @@ -11335,7 +11346,7 @@ "translation/functions/timestampdiff.toml#t001_date": "matching", "translation/functions/timestampdiff.toml#t002_timestamp_ntz": "matching", "translation/functions/timestampdiff.toml#t003_time": "matching", - "translation/functions/timestampdiff.toml#t004_mixed": "refused", + "translation/functions/timestampdiff.toml#t004_mixed": "matching", "translation/functions/timestampdiff.toml#t005_null_date_or_time_expr1": "matching", "translation/functions/timestampdiff.toml#t006_null_date_or_time_expr2": "matching", "translation/functions/timestampdiff.toml#t007_leap": "matching", @@ -13458,7 +13469,7 @@ "sf.functions.current-time.current-time": "refused", "sf.functions.current-time.current-time.syntax": "refused", "sf.functions.current-timestamp.current-timestamp": "refused", - "sf.functions.current-timestamp.current-timestamp.syntax": "unmeasured", + "sf.functions.current-timestamp.current-timestamp.syntax": "unmet_obligations", "sf.functions.current-transaction": "not_translatable", "sf.functions.current-user.current-user": "not_translatable", "sf.functions.current-user.current-user.syntax": "not_translatable", @@ -16354,10 +16365,10 @@ "sf.functions.current-time.current-time.t003": "unmet", "sf.functions.current-time.current-time.t004": "unmet", "sf.functions.current-timestamp.current-timestamp.syntax.t001": "unmet", - "sf.functions.current-timestamp.current-timestamp.syntax.t002": "unmet", + "sf.functions.current-timestamp.current-timestamp.syntax.t002": "met", "sf.functions.current-timestamp.current-timestamp.t001": "unmet", "sf.functions.current-timestamp.current-timestamp.t002": "unmet", - "sf.functions.current-timestamp.current-timestamp.t003": "unmet", + "sf.functions.current-timestamp.current-timestamp.t003": "met", "sf.functions.datasketches-hll-accumulate.t001": "unmet", "sf.functions.datasketches-hll-accumulate.t002": "unmet", "sf.functions.datasketches-hll-accumulate.t003": "unmet", @@ -16424,7 +16435,7 @@ "sf.functions.datediff.datediff.t001": "met", "sf.functions.datediff.datediff.t002": "met", "sf.functions.datediff.datediff.t003": "met", - "sf.functions.datediff.datediff.t004": "unmet", + "sf.functions.datediff.datediff.t004": "met", "sf.functions.datediff.datediff.t005": "met", "sf.functions.datediff.datediff.t006": "met", "sf.functions.datediff.datediff.t007": "met", @@ -18246,7 +18257,7 @@ "sf.functions.timediff.t001": "met", "sf.functions.timediff.t002": "met", "sf.functions.timediff.t003": "met", - "sf.functions.timediff.t004": "unmet", + "sf.functions.timediff.t004": "met", "sf.functions.timediff.t005": "met", "sf.functions.timediff.t006": "met", "sf.functions.timediff.t007": "met", @@ -18318,7 +18329,7 @@ "sf.functions.timestampdiff.t001": "met", "sf.functions.timestampdiff.t002": "met", "sf.functions.timestampdiff.t003": "met", - "sf.functions.timestampdiff.t004": "unmet", + "sf.functions.timestampdiff.t004": "met", "sf.functions.timestampdiff.t005": "met", "sf.functions.timestampdiff.t006": "met", "sf.functions.timestampdiff.t007": "met", diff --git a/crates/sqlquack-duckdb/src/temporal/constants.rs b/crates/sqlquack-duckdb/src/temporal/constants.rs index 8380f8848..b403b13a6 100644 --- a/crates/sqlquack-duckdb/src/temporal/constants.rs +++ b/crates/sqlquack-duckdb/src/temporal/constants.rs @@ -170,3 +170,5 @@ pub const NANOSECOND_DATE_RANGE: (&str, &str) = ("1677-09-22", "2262-04-10"); pub const DUCKDB_CURRENT_DATE: &str = "current_date()"; /// DuckDB's current timestamp as a call, which an alias `CURRENT_TIMESTAMP` cannot capture. pub const DUCKDB_CURRENT_TIMESTAMP: &str = "get_current_timestamp()"; +/// The precision a DATE is read at, as its midnight, when DATEDIFF counts units shorter than a day. +pub const MIDNIGHT_PRECISION: u8 = 0; diff --git a/crates/sqlquack-duckdb/src/temporal/main/date_diff_value.rs b/crates/sqlquack-duckdb/src/temporal/main/date_diff_value.rs index 9ed9dca52..32b1d75ce 100644 --- a/crates/sqlquack-duckdb/src/temporal/main/date_diff_value.rs +++ b/crates/sqlquack-duckdb/src/temporal/main/date_diff_value.rs @@ -5,6 +5,7 @@ use sqlquack_snowflake::typing::models::SnowflakeType; use crate::blocks::constants::DATE_DIFF; use crate::temporal::_helpers::timestamps::{epoch_units, floor_divide, stored_digits}; +use crate::temporal::constants::MIDNIGHT_PRECISION; use crate::temporal::main::truncate_timestamp_to_date::truncate_timestamp_to_date; /// Boundaries of the unit named by its canonical Snowflake name crossed from `start` to `end`. @@ -41,6 +42,16 @@ pub fn date_diff_value( _ => 0, }; let units = |sql: &str, source: SnowflakeType| -> Result { + let (sql, source) = match source { + SnowflakeType::Date => ( + format!("CAST({sql} AS TIMESTAMP)"), + SnowflakeType::TimestampNtz { + precision: MIDNIGHT_PRECISION, + }, + ), + other => (sql.to_owned(), other), + }; + let sql = sql.as_str(); let SnowflakeType::TimestampNtz { precision } = source else { return Err(format!( "DATEDIFF in {unit} of {source} has no proven conversion" diff --git a/crates/sqlquack-rules/src/catalogue/constants.rs b/crates/sqlquack-rules/src/catalogue/constants.rs index ec3eaeb93..339fc6b6b 100644 --- a/crates/sqlquack-rules/src/catalogue/constants.rs +++ b/crates/sqlquack-rules/src/catalogue/constants.rs @@ -57,6 +57,8 @@ use crate::conversion::constants::{ }; use crate::conversion::models::RuleSpec; +/// A TIMESTAMP_NTZ compared with a TIMESTAMP_LTZ or TIMESTAMP_TZ in the UTC session. +pub const NAIVE_LOCAL_COMPARISON: &str = "rule.naive_local_comparison"; /// Every rule. pub const RULES: &[RuleSpec] = &[ RuleSpec { @@ -317,6 +319,20 @@ pub const RULES: &[RuleSpec] = &[ snowflake: &[NUMBER_OUT_OF_RANGE], }], }, + RuleSpec { + id: NAIVE_LOCAL_COMPARISON, + constructs: &[ + "node:eq", + "node:neq", + "node:lt", + "node:lte", + "node:gt", + "node:gte", + "node:between", + ], + conditions: "A TIMESTAMP_NTZ beside a TIMESTAMP_LTZ, such as CURRENT_TIMESTAMP() or DATEADD of it, or beside a TIMESTAMP_TZ at any offset, such as CONVERT_TIMEZONE('UTC', CURRENT_TIMESTAMP()): Snowflake reads the naive value in the session time zone and compares instants, as recorded, so the zoned value is read as its wall time in UTC, which relies on session_timezone_utc, and on timestamp_range at precisions above microseconds.", + failures: &[], + }, RuleSpec { id: DATE_LITERAL_COMPARISON, constructs: &[ @@ -547,7 +563,7 @@ pub const RULES: &[RuleSpec] = &[ "fn:TIMEDIFF", "fn:TIMESTAMPDIFF", ], - conditions: "DATE or TIMESTAMP_NTZ values. DATE_TRUNC to YEAR, QUARTER, MONTH, WEEK (Monday) or DAY, and for timestamps HOUR, MINUTE or SECOND, keeping the type. DATEADD of a NUMBER(p,0) amount (to text read as TIMESTAMP_NTZ(9) as CAST reads it, as recorded), which fails beyond 2^53 as Snowflake's error 100046, of YEAR, QUARTER, MONTH (clamped to the month's end), WEEK or DAY, and for timestamps HOUR, MINUTE, SECOND, MILLISECOND or MICROSECOND, keeping the type and its nanoseconds; a result beyond DuckDB's range (for precision 7-9 its nanosecond range) raises timestamp_range. DATEDIFF between two DATEs or two timestamps counts YEAR, QUARTER, MONTH and DAY boundaries of the dates, and for timestamps HOUR, MINUTE, SECOND, MILLISECOND or MICROSECOND boundaries toward the past. Between two TIMESTAMP_TZ values at any offsets, MINUTE and SECOND boundaries are counted between their UTC instants, which every whole-minute offset shares, as recorded; other units of such values refuse, as Snowflake counts DAY boundaries in each value's own offset.", + conditions: "DATE or TIMESTAMP_NTZ values. DATE_TRUNC to YEAR, QUARTER, MONTH, WEEK (Monday) or DAY, and for timestamps HOUR, MINUTE or SECOND, keeping the type. DATEADD of a NUMBER(p,0) amount (to text read as TIMESTAMP_NTZ(9) as CAST reads it, as recorded), which fails beyond 2^53 as Snowflake's error 100046, of YEAR, QUARTER, MONTH (clamped to the month's end), WEEK or DAY, and for timestamps HOUR, MINUTE, SECOND, MILLISECOND or MICROSECOND, keeping the type and its nanoseconds; a result beyond DuckDB's range (for precision 7-9 its nanosecond range) raises timestamp_range. DATEDIFF between two DATEs or two timestamps counts YEAR, QUARTER, MONTH and DAY boundaries of the dates, and for timestamps HOUR, MINUTE, SECOND, MILLISECOND or MICROSECOND boundaries toward the past, a DATE beside a timestamp or another DATE read as its midnight for those units. Between two TIMESTAMP_TZ values at any offsets, MINUTE and SECOND boundaries are counted between their UTC instants, which every whole-minute offset shares, as recorded; other units of such values refuse, as Snowflake counts DAY boundaries in each value's own offset.", failures: &[FailureMode { kind: ErrorKind::NumericOverflow, snowflake: &[NUMBER_OUT_OF_RANGE], @@ -1620,7 +1636,7 @@ pub const RULES: &[RuleSpec] = &[ "fn:TIMEDIFF", "fn:TIMESTAMPDIFF", ], - conditions: "DATE_TRUNC and DATEADD of a TIMESTAMP_TZ known to hold the UTC offset, +00:00 (from CONVERT_TIMEZONE('UTC', x), a cast of a DATE or naive timestamp in the UTC session, or values chosen from such), computed on its UTC wall time and read back at +00:00, keeping its type; DATEDIFF of two such values from their UTC wall times. A UTC-offset value against a naive one, and zoned values of unknown offset, refuse.", + conditions: "DATE_TRUNC and DATEADD of a TIMESTAMP_TZ known to hold the UTC offset, +00:00 (from CONVERT_TIMEZONE('UTC', x), a cast of a DATE or naive timestamp in the UTC session, or values chosen from such), computed on its UTC wall time and read back at +00:00, keeping its type; DATEADD of a TIMESTAMP_LTZ the same way, which relies on session_timezone_utc; DATEDIFF of two such values from their UTC wall times. A UTC-offset value against a naive one, and zoned values of unknown offset, refuse.", failures: &[], }, RuleSpec { diff --git a/crates/sqlquack-rules/src/conversion/_helpers/expressions/operators.rs b/crates/sqlquack-rules/src/conversion/_helpers/expressions/operators.rs index 42757da02..13fa661e9 100644 --- a/crates/sqlquack-rules/src/conversion/_helpers/expressions/operators.rs +++ b/crates/sqlquack-rules/src/conversion/_helpers/expressions/operators.rs @@ -16,6 +16,7 @@ use sqlquack_duckdb::semi_structured::main::json_output::json_output; use sqlquack_duckdb::semi_structured::main::json_text::json_text; use sqlquack_duckdb::temporal::main::date_add_amount::date_add_amount; use sqlquack_duckdb::temporal::main::date_add_value::date_add_value; +use sqlquack_duckdb::temporal::main::utc_wall_time::utc_wall_time; use sqlquack_duckdb::text::constants::TEXT_NUMBER_SCALE; use sqlquack_duckdb::text::main::compared_number_range::compared_number_range; use sqlquack_duckdb::text::main::compared_number_text::compared_number_text; @@ -40,13 +41,14 @@ use crate::conversion::_helpers::support::typed_leaves::compared_by_text; use crate::conversion::constants::DATE_NAME; use sqlquack_duckdb::blocks::constants::NANOSECOND_DIGITS; +use crate::catalogue::constants::NAIVE_LOCAL_COMPARISON; use crate::conversion::constants::{ COMPARISON, DATE_DAY_ARITHMETIC, DATE_LITERAL_COMPARISON, DAY_UNIT, DECIMAL_ARITHMETIC, - DECIMAL_DIVISION, DECIMAL_MODULO, FLOAT_ARITHMETIC, FLOAT_MODULO, LOGIC, MOD_PAIR, - NULL_ARITHMETIC, NULL_SAFE_COMPARISON, NULL_SUM_PRECISION, TEXT_NUMBER_COMPARISON, + DECIMAL_DIVISION, DECIMAL_MODULO, FLOAT_ARITHMETIC, FLOAT_MODULO, LOGIC, MICROSECOND_DIGITS, + MOD_PAIR, NULL_ARITHMETIC, NULL_SAFE_COMPARISON, NULL_SUM_PRECISION, TEXT_NUMBER_COMPARISON, VARIANT_EQUALITY, }; -use sqlquack_policy::policy::constants::{NUMERIC_RANGE, SESSION_TIMEZONE_UTC}; +use sqlquack_policy::policy::constants::{NUMERIC_RANGE, SESSION_TIMEZONE_UTC, TIMESTAMP_RANGE}; /// Characters in a `YYYY-MM-DD` date. const ISO_DATE_LENGTH: usize = 10; @@ -592,6 +594,20 @@ pub(crate) fn comparable( let left = zoned_literal(left, construct, context)?; return Ok((left, right)); } + ( + SnowflakeType::TimestampNtz { .. }, + SnowflakeType::TimestampLtz { precision } | SnowflakeType::TimestampTz { precision }, + ) => { + let right = local_as_naive(right, precision, construct, context)?; + return comparable(left, right, construct, context); + } + ( + SnowflakeType::TimestampLtz { precision } | SnowflakeType::TimestampTz { precision }, + SnowflakeType::TimestampNtz { .. }, + ) => { + let left = local_as_naive(left, precision, construct, context)?; + return comparable(left, right, construct, context); + } (SnowflakeType::Varchar, number @ SnowflakeType::Number { .. }) => { let text = number_text(left, (number, &right.sql), construct, context)?; return Ok((text, number_range(right, context))); @@ -617,6 +633,25 @@ pub(crate) fn comparable( } } +/// A TIMESTAMP_LTZ or TIMESTAMP_TZ beside a TIMESTAMP_NTZ, as its instant's UTC wall time. +fn local_as_naive( + emitted: Emitted, + precision: u8, + construct: &Construct, + context: &Context<'_>, +) -> Result { + let log = context.log; + let _rule = log.rule(NAIVE_LOCAL_COMPARISON); + diverge(context, SESSION_TIMEZONE_UTC, construct)?; + if precision > MICROSECOND_DIGITS { + diverge(context, TIMESTAMP_RANGE, construct)?; + } + let naive = SnowflakeType::TimestampNtz { precision }; + let sql = cast(&utc_wall_time(&emitted.sql, log), naive, log) + .map_err(|reason| outside_conditions(construct.clone(), reason))?; + Ok(Emitted::new(sql, naive)) +} + /// A NUMBER compared with text, raising where Snowflake's comparison overflows. fn number_range(number: Emitted, context: &Context<'_>) -> Emitted { match number.data_type { diff --git a/crates/sqlquack-rules/src/conversion/_helpers/expressions/temporal_functions.rs b/crates/sqlquack-rules/src/conversion/_helpers/expressions/temporal_functions.rs index 4ac1d04b8..4d190414c 100644 --- a/crates/sqlquack-rules/src/conversion/_helpers/expressions/temporal_functions.rs +++ b/crates/sqlquack-rules/src/conversion/_helpers/expressions/temporal_functions.rs @@ -484,6 +484,15 @@ fn shift( }; let (operand, zoned) = match operand.data_type { SnowflakeType::Time { .. } => (operand, None), + local @ SnowflakeType::TimestampLtz { precision } => { + let _zoned = site.context.log.rule(UTC_OFFSET_ARITHMETIC); + diverge(site.context, SESSION_TIMEZONE_UTC, construct)?; + let wall = SnowflakeType::TimestampNtz { + precision: precision.min(MICROSECOND_DIGITS), + }; + let sql = utc_wall_time(&operand.sql, site.context.log); + (Emitted::new(sql, wall), Some(local)) + } _ => temporal_or_refused(operand, construct, site.context)?, }; let amount = Emitted::new( diff --git a/crates/sqlquack/src/analysis/tests/test_session_timezone.rs b/crates/sqlquack/src/analysis/tests/test_session_timezone.rs index 28fdb2a88..be7ce94bc 100644 --- a/crates/sqlquack/src/analysis/tests/test_session_timezone.rs +++ b/crates/sqlquack/src/analysis/tests/test_session_timezone.rs @@ -1,11 +1,21 @@ use sqlquack_core::refusals::models::RefusalCategory; -use crate::analysis::tests::helpers::{relying_on_utc_session, session_outcome}; -use crate::analysis::tests::test_types::{DivergenceModeOutcome, SessionTimeZoneTestCase}; +use crate::analysis::tests::helpers::{ + relying_on_utc_session, session_outcome, shop_first_outcome, +}; +use crate::analysis::tests::test_types::{ + DivergenceModeOutcome, FirstValueTestCase, SessionTimeZoneTestCase, +}; const NAIVE_TO_ZONED: &str = "SELECT '2024-03-10 22:30:00'::TIMESTAMP_NTZ(6)::TIMESTAMP_TZ AS placed_at"; +const NAIVE_BESIDE_LOCAL: &str = "SELECT '2024-06-01 10:00:00'::TIMESTAMP_NTZ < \ + '2024-06-01 12:00:00 +02:00'::TIMESTAMP_LTZ AS v"; + +const NAIVE_BESIDE_MICROSECOND_LOCAL: &str = "SELECT '2024-06-01 10:00:00'::TIMESTAMP_NTZ < \ + '2024-06-01 12:00:00 +02:00'::TIMESTAMP_LTZ(6) AS v"; + #[test] fn given_session_dependent_statements_when_converting_then_they_rely_on_a_utc_session() { let test_cases = [ @@ -72,6 +82,53 @@ fn given_session_dependent_statements_when_converting_then_they_rely_on_a_utc_se utc_session: false, expected_outcome: relying_on_utc_session(&["fn:CONVERT_TIMEZONE"]), }, + SessionTimeZoneTestCase { + description: "a naive timestamp compared with a nanosecond local one relies on it, \ + and on the nanosecond range", + sql: NAIVE_BESIDE_LOCAL, + strict: false, + utc_session: false, + expected_outcome: DivergenceModeOutcome { + converted: true, + divergences: vec![ + ( + "session_timezone_utc".to_owned(), + vec!["node:lt".to_owned()], + ), + ("timestamp_range".to_owned(), vec!["node:lt".to_owned()]), + ], + categories: vec![], + }, + }, + SessionTimeZoneTestCase { + description: "a naive timestamp compared with a microsecond local one relies only on it", + sql: NAIVE_BESIDE_MICROSECOND_LOCAL, + strict: false, + utc_session: false, + expected_outcome: relying_on_utc_session(&["node:lt"]), + }, + SessionTimeZoneTestCase { + description: "strict mode with a confirmed UTC session still refuses the nanosecond range", + sql: NAIVE_BESIDE_LOCAL, + strict: true, + utc_session: true, + expected_outcome: DivergenceModeOutcome { + converted: false, + divergences: vec![], + categories: vec![RefusalCategory::DivergenceRefused], + }, + }, + SessionTimeZoneTestCase { + description: "strict mode refuses that comparison without a confirmed UTC session", + sql: NAIVE_BESIDE_LOCAL, + strict: true, + utc_session: false, + expected_outcome: DivergenceModeOutcome { + converted: false, + divergences: vec![], + categories: vec![RefusalCategory::DivergenceRefused], + }, + }, ]; for test_case in test_cases { assert_eq!( @@ -82,3 +139,66 @@ fn given_session_dependent_statements_when_converting_then_they_rely_on_a_utc_se ); } } + +#[test] +fn given_naive_and_local_timestamps_when_converting_then_they_compare_as_utc_wall_times() { + let test_cases = [ + FirstValueTestCase { + description: "a local value at +02:00 equals the naive value of its UTC wall time", + sql: "SELECT '2024-06-01 10:00:00'::TIMESTAMP_NTZ = \ + '2024-06-01 12:00:00 +02:00'::TIMESTAMP_LTZ AS v", + expected_outcome: Ok("true"), + }, + FirstValueTestCase { + description: "the local side keeps its microseconds beside a millisecond naive value", + sql: "SELECT '2024-06-01 10:00:00.123'::TIMESTAMP_NTZ(3) < \ + '2024-06-01 05:00:00.123001 -05:00'::TIMESTAMP_LTZ(9) AS v", + expected_outcome: Ok("true"), + }, + FirstValueTestCase { + description: "a past naive timestamp is at most CURRENT_TIMESTAMP()", + sql: "SELECT '2020-01-01 00:00:00'::TIMESTAMP_NTZ <= CURRENT_TIMESTAMP() AS v", + expected_outcome: Ok("true"), + }, + FirstValueTestCase { + description: "the incremental filter against DATEADD of CURRENT_TIMESTAMP()", + sql: "SELECT '2199-01-01 00:00:00'::TIMESTAMP_NTZ >= \ + DATEADD(day, -3, CURRENT_TIMESTAMP()) AS v", + expected_outcome: Ok("true"), + }, + FirstValueTestCase { + description: "a naive timestamp four days back is outside a three-day window", + sql: "SELECT DATEADD(day, -4, CURRENT_TIMESTAMP())::TIMESTAMP_NTZ >= \ + DATEADD(day, -3, CURRENT_TIMESTAMP()) AS v", + expected_outcome: Ok("false"), + }, + FirstValueTestCase { + description: "DATEDIFF in hours reads a DATE as its midnight", + sql: "SELECT DATEDIFF(hour, '1969-12-31'::DATE, '1970-01-01 01:00:00'::TIMESTAMP_NTZ) \ + AS v", + expected_outcome: Ok("25"), + }, + FirstValueTestCase { + description: "DATEDIFF in hours to the hour before a DATE's midnight is -1", + sql: "SELECT DATEDIFF(hour, '2024-06-02'::DATE, '2024-06-01 23:59:59'::TIMESTAMP_NTZ) \ + AS v", + expected_outcome: Ok("-1"), + }, + FirstValueTestCase { + description: "DATEDIFF in minutes between two DATEs", + sql: "SELECT DATEDIFF(minute, '2024-06-01'::DATE, '2024-06-02'::DATE) AS v", + expected_outcome: Ok("1440"), + }, + ]; + for test_case in test_cases { + assert_eq!( + shop_first_outcome(test_case.sql, test_case.expected_outcome), + test_case + .expected_outcome + .map(str::to_owned) + .map_err(str::to_owned), + "{}", + test_case.description + ); + } +} diff --git a/evidence/results/2b87658656f254923348f2d90be14665d90a1909f076c6a39fe4d430f1ce3d69.arrow b/evidence/results/2b87658656f254923348f2d90be14665d90a1909f076c6a39fe4d430f1ce3d69.arrow new file mode 100644 index 000000000..c14f26857 Binary files /dev/null and b/evidence/results/2b87658656f254923348f2d90be14665d90a1909f076c6a39fe4d430f1ce3d69.arrow differ diff --git a/evidence/results/2b87658656f254923348f2d90be14665d90a1909f076c6a39fe4d430f1ce3d69.json b/evidence/results/2b87658656f254923348f2d90be14665d90a1909f076c6a39fe4d430f1ce3d69.json new file mode 100644 index 000000000..57fb29504 --- /dev/null +++ b/evidence/results/2b87658656f254923348f2d90be14665d90a1909f076c6a39fe4d430f1ce3d69.json @@ -0,0 +1,61 @@ +{ + "arrow_sha256": "185438aa5c54a46e8f93e26052aee6c9851b0d116ea3c59fc6cff4419ef945d7", + "case_hash": "2b87658656f254923348f2d90be14665d90a1909f076c6a39fe4d430f1ce3d69", + "columns": [ + { + "internal_size": null, + "name": "VALUE", + "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": "8dd8205e8ee2891390da991f5926d6adecd533dfa18ce14f940856d47aaa892f", + "recorded_at": "2026-11-06T10:50:38.322420+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/4847627d6a2122542b24fe9aa765d15da1b199d789026666df673a6ca9550014.arrow b/evidence/results/4847627d6a2122542b24fe9aa765d15da1b199d789026666df673a6ca9550014.arrow new file mode 100644 index 000000000..464206cf8 Binary files /dev/null and b/evidence/results/4847627d6a2122542b24fe9aa765d15da1b199d789026666df673a6ca9550014.arrow differ diff --git a/evidence/results/4847627d6a2122542b24fe9aa765d15da1b199d789026666df673a6ca9550014.json b/evidence/results/4847627d6a2122542b24fe9aa765d15da1b199d789026666df673a6ca9550014.json new file mode 100644 index 000000000..96933c2cb --- /dev/null +++ b/evidence/results/4847627d6a2122542b24fe9aa765d15da1b199d789026666df673a6ca9550014.json @@ -0,0 +1,61 @@ +{ + "arrow_sha256": "e98af85b95aba3afa0535ba022714508df55665e2ece531731c104ce93bfe265", + "case_hash": "4847627d6a2122542b24fe9aa765d15da1b199d789026666df673a6ca9550014", + "columns": [ + { + "internal_size": null, + "name": "VALUE", + "nullable": true, + "precision": null, + "scale": null, + "type_code": 10, + "type_name": "ARRAY" + } + ], + "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": "d109d43ce82f44fe9037384a6690017c31e804dfd9ec096e1a6e32a986b1c56e", + "recorded_at": "2026-11-06T20:46:16.623971+00:00", + "recorder": "sqlquack-record 0.1.0", + "row_count": 7, + "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/4a0b34afce04e95a1b2ca94c3a85e107ee04ad055a4af7b8dd45df2a891e6b78.arrow b/evidence/results/4a0b34afce04e95a1b2ca94c3a85e107ee04ad055a4af7b8dd45df2a891e6b78.arrow new file mode 100644 index 000000000..a688b5002 Binary files /dev/null and b/evidence/results/4a0b34afce04e95a1b2ca94c3a85e107ee04ad055a4af7b8dd45df2a891e6b78.arrow differ diff --git a/evidence/results/4a0b34afce04e95a1b2ca94c3a85e107ee04ad055a4af7b8dd45df2a891e6b78.json b/evidence/results/4a0b34afce04e95a1b2ca94c3a85e107ee04ad055a4af7b8dd45df2a891e6b78.json new file mode 100644 index 000000000..8a736b7ae --- /dev/null +++ b/evidence/results/4a0b34afce04e95a1b2ca94c3a85e107ee04ad055a4af7b8dd45df2a891e6b78.json @@ -0,0 +1,61 @@ +{ + "arrow_sha256": "e8fde308995cb37ae6dc41043946e50abdc9afc23f06eab5abf5b201ef309457", + "case_hash": "4a0b34afce04e95a1b2ca94c3a85e107ee04ad055a4af7b8dd45df2a891e6b78", + "columns": [ + { + "internal_size": null, + "name": "VALUE", + "nullable": true, + "precision": null, + "scale": null, + "type_code": 10, + "type_name": "ARRAY" + } + ], + "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": "48d558eaa9c1af96c62351d05c7e9045a03b15e98c16c2713a5339237c47391d", + "recorded_at": "2026-11-06T10:50:38.322420+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/5a1b4c80d91d82d07126c1ba580210ae772771572eb154db3c0a0eac0024b93b.arrow b/evidence/results/5a1b4c80d91d82d07126c1ba580210ae772771572eb154db3c0a0eac0024b93b.arrow new file mode 100644 index 000000000..3fdf1f527 Binary files /dev/null and b/evidence/results/5a1b4c80d91d82d07126c1ba580210ae772771572eb154db3c0a0eac0024b93b.arrow differ diff --git a/evidence/results/5a1b4c80d91d82d07126c1ba580210ae772771572eb154db3c0a0eac0024b93b.json b/evidence/results/5a1b4c80d91d82d07126c1ba580210ae772771572eb154db3c0a0eac0024b93b.json new file mode 100644 index 000000000..ca202f9ee --- /dev/null +++ b/evidence/results/5a1b4c80d91d82d07126c1ba580210ae772771572eb154db3c0a0eac0024b93b.json @@ -0,0 +1,70 @@ +{ + "arrow_sha256": "8fb7c2e3c6229061420e56660e1755df17895af2ba77873641ad97c48b4bc096", + "case_hash": "5a1b4c80d91d82d07126c1ba580210ae772771572eb154db3c0a0eac0024b93b", + "columns": [ + { + "internal_size": null, + "name": "A", + "nullable": true, + "precision": null, + "scale": null, + "type_code": 13, + "type_name": "BOOLEAN" + }, + { + "internal_size": null, + "name": "B", + "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": "8db9e137833ba763bbec5e6afa43f20f24a11a6e395fb85f9cfe60b902a1d48f", + "recorded_at": "2026-11-06T21:34:15.187868+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/b25def58913a26c02294ce05b560dec1a7cd43312dc98f7d3df38381cf2702af.arrow b/evidence/results/b25def58913a26c02294ce05b560dec1a7cd43312dc98f7d3df38381cf2702af.arrow new file mode 100644 index 000000000..b9fd14c4f Binary files /dev/null and b/evidence/results/b25def58913a26c02294ce05b560dec1a7cd43312dc98f7d3df38381cf2702af.arrow differ diff --git a/evidence/results/b25def58913a26c02294ce05b560dec1a7cd43312dc98f7d3df38381cf2702af.json b/evidence/results/b25def58913a26c02294ce05b560dec1a7cd43312dc98f7d3df38381cf2702af.json new file mode 100644 index 000000000..229c361fa --- /dev/null +++ b/evidence/results/b25def58913a26c02294ce05b560dec1a7cd43312dc98f7d3df38381cf2702af.json @@ -0,0 +1,61 @@ +{ + "arrow_sha256": "95a7ee28c3733134197fad14721aa29cf53acd23420a332f6bcacc4405170783", + "case_hash": "b25def58913a26c02294ce05b560dec1a7cd43312dc98f7d3df38381cf2702af", + "columns": [ + { + "internal_size": null, + "name": "VALUE", + "nullable": true, + "precision": null, + "scale": null, + "type_code": 10, + "type_name": "ARRAY" + } + ], + "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": "ee615ba6a6499eb2eb62890e56613bf47eae3875e4d66b6622efb554c8707310", + "recorded_at": "2026-11-06T10:50:38.322420+00:00", + "recorder": "sqlquack-record 0.1.0", + "row_count": 5, + "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/b5a431e5c5268eb4225aabe13a3d1dc5c19f73ad715847a2fa441c03ad85e35e.arrow b/evidence/results/b5a431e5c5268eb4225aabe13a3d1dc5c19f73ad715847a2fa441c03ad85e35e.arrow new file mode 100644 index 000000000..c14f26857 Binary files /dev/null and b/evidence/results/b5a431e5c5268eb4225aabe13a3d1dc5c19f73ad715847a2fa441c03ad85e35e.arrow differ diff --git a/evidence/results/b5a431e5c5268eb4225aabe13a3d1dc5c19f73ad715847a2fa441c03ad85e35e.json b/evidence/results/b5a431e5c5268eb4225aabe13a3d1dc5c19f73ad715847a2fa441c03ad85e35e.json new file mode 100644 index 000000000..9369a9e87 --- /dev/null +++ b/evidence/results/b5a431e5c5268eb4225aabe13a3d1dc5c19f73ad715847a2fa441c03ad85e35e.json @@ -0,0 +1,61 @@ +{ + "arrow_sha256": "185438aa5c54a46e8f93e26052aee6c9851b0d116ea3c59fc6cff4419ef945d7", + "case_hash": "b5a431e5c5268eb4225aabe13a3d1dc5c19f73ad715847a2fa441c03ad85e35e", + "columns": [ + { + "internal_size": null, + "name": "VALUE", + "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": "09e1e024cb2942b8a38e044600254d4547af79e592410cfdd6fc2996e418dafa", + "recorded_at": "2026-11-06T20:46:16.623971+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/bb7bc504565d556a3338907aa8513a5e28adc294520cafffbe751b238f0c81eb.arrow b/evidence/results/bb7bc504565d556a3338907aa8513a5e28adc294520cafffbe751b238f0c81eb.arrow new file mode 100644 index 000000000..5be3962d0 Binary files /dev/null and b/evidence/results/bb7bc504565d556a3338907aa8513a5e28adc294520cafffbe751b238f0c81eb.arrow differ diff --git a/evidence/results/bb7bc504565d556a3338907aa8513a5e28adc294520cafffbe751b238f0c81eb.json b/evidence/results/bb7bc504565d556a3338907aa8513a5e28adc294520cafffbe751b238f0c81eb.json new file mode 100644 index 000000000..675abb08d --- /dev/null +++ b/evidence/results/bb7bc504565d556a3338907aa8513a5e28adc294520cafffbe751b238f0c81eb.json @@ -0,0 +1,61 @@ +{ + "arrow_sha256": "0ba7f53c2ead942073a7ccf7c556af301ca64e6ebb87529a7faae0c68cf48413", + "case_hash": "bb7bc504565d556a3338907aa8513a5e28adc294520cafffbe751b238f0c81eb", + "columns": [ + { + "internal_size": null, + "name": "VALUE", + "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": "262a38743ace864bba29c62c964298d1c52d2acde32933c90ab90cdefe1856d8", + "recorded_at": "2026-11-06T10:50:38.322420+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/da9bcba92cbdbae85a17e271c4080dc0c3f702f134b85fa937f7b2cf7abf2591.arrow b/evidence/results/da9bcba92cbdbae85a17e271c4080dc0c3f702f134b85fa937f7b2cf7abf2591.arrow new file mode 100644 index 000000000..f039244c9 Binary files /dev/null and b/evidence/results/da9bcba92cbdbae85a17e271c4080dc0c3f702f134b85fa937f7b2cf7abf2591.arrow differ diff --git a/evidence/results/da9bcba92cbdbae85a17e271c4080dc0c3f702f134b85fa937f7b2cf7abf2591.json b/evidence/results/da9bcba92cbdbae85a17e271c4080dc0c3f702f134b85fa937f7b2cf7abf2591.json new file mode 100644 index 000000000..023ee837a --- /dev/null +++ b/evidence/results/da9bcba92cbdbae85a17e271c4080dc0c3f702f134b85fa937f7b2cf7abf2591.json @@ -0,0 +1,61 @@ +{ + "arrow_sha256": "1ff7a750f1062f3178c14b0a9835965d0d871bda92fe0611c4b895c7a8ef55dc", + "case_hash": "da9bcba92cbdbae85a17e271c4080dc0c3f702f134b85fa937f7b2cf7abf2591", + "columns": [ + { + "internal_size": null, + "name": "VALUE", + "nullable": true, + "precision": null, + "scale": null, + "type_code": 10, + "type_name": "ARRAY" + } + ], + "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": "1da7fe8d0fb3ebdf79560077eeae89a2074bba65bf9967a53448bc3cea710a50", + "recorded_at": "2026-11-06T20:46:16.623971+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/e0486d75b1aa140b5c61911aef091e4a658cffb88d41e97131716ee179713eb1.arrow b/evidence/results/e0486d75b1aa140b5c61911aef091e4a658cffb88d41e97131716ee179713eb1.arrow new file mode 100644 index 000000000..464206cf8 Binary files /dev/null and b/evidence/results/e0486d75b1aa140b5c61911aef091e4a658cffb88d41e97131716ee179713eb1.arrow differ diff --git a/evidence/results/e0486d75b1aa140b5c61911aef091e4a658cffb88d41e97131716ee179713eb1.json b/evidence/results/e0486d75b1aa140b5c61911aef091e4a658cffb88d41e97131716ee179713eb1.json new file mode 100644 index 000000000..16a91589b --- /dev/null +++ b/evidence/results/e0486d75b1aa140b5c61911aef091e4a658cffb88d41e97131716ee179713eb1.json @@ -0,0 +1,61 @@ +{ + "arrow_sha256": "e98af85b95aba3afa0535ba022714508df55665e2ece531731c104ce93bfe265", + "case_hash": "e0486d75b1aa140b5c61911aef091e4a658cffb88d41e97131716ee179713eb1", + "columns": [ + { + "internal_size": null, + "name": "VALUE", + "nullable": true, + "precision": null, + "scale": null, + "type_code": 10, + "type_name": "ARRAY" + } + ], + "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": "4f27046491d9d1d5c166a930fc3fa9f3aa770cfde9d7f33605e7b650408e51a6", + "recorded_at": "2026-11-06T10:50:38.322420+00:00", + "recorder": "sqlquack-record 0.1.0", + "row_count": 7, + "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/f8b693ec5e28961d189d3aa6fe68e451c7dfac66e05406624c0744301434c1ee.arrow b/evidence/results/f8b693ec5e28961d189d3aa6fe68e451c7dfac66e05406624c0744301434c1ee.arrow new file mode 100644 index 000000000..267070c0e Binary files /dev/null and b/evidence/results/f8b693ec5e28961d189d3aa6fe68e451c7dfac66e05406624c0744301434c1ee.arrow differ diff --git a/evidence/results/f8b693ec5e28961d189d3aa6fe68e451c7dfac66e05406624c0744301434c1ee.json b/evidence/results/f8b693ec5e28961d189d3aa6fe68e451c7dfac66e05406624c0744301434c1ee.json new file mode 100644 index 000000000..23ef09ca4 --- /dev/null +++ b/evidence/results/f8b693ec5e28961d189d3aa6fe68e451c7dfac66e05406624c0744301434c1ee.json @@ -0,0 +1,61 @@ +{ + "arrow_sha256": "f071394be88e4a33ad08622f428b264b74999720ae6c5966d3a3e289e90474be", + "case_hash": "f8b693ec5e28961d189d3aa6fe68e451c7dfac66e05406624c0744301434c1ee", + "columns": [ + { + "internal_size": null, + "name": "VALUE", + "nullable": true, + "precision": null, + "scale": null, + "type_code": 10, + "type_name": "ARRAY" + } + ], + "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": "a904ab31f57de597b42d1fba15d519f358635169eb443a799e4955427e0e28d3", + "recorded_at": "2026-11-06T21:34:15.187868+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/faf3cf5525a8d16b201b026c2e18d7d798b1d4d99e9aca9df247e6b19a4080ef.arrow b/evidence/results/faf3cf5525a8d16b201b026c2e18d7d798b1d4d99e9aca9df247e6b19a4080ef.arrow new file mode 100644 index 000000000..a4f2fb6f3 Binary files /dev/null and b/evidence/results/faf3cf5525a8d16b201b026c2e18d7d798b1d4d99e9aca9df247e6b19a4080ef.arrow differ diff --git a/evidence/results/faf3cf5525a8d16b201b026c2e18d7d798b1d4d99e9aca9df247e6b19a4080ef.json b/evidence/results/faf3cf5525a8d16b201b026c2e18d7d798b1d4d99e9aca9df247e6b19a4080ef.json new file mode 100644 index 000000000..2c60c497c --- /dev/null +++ b/evidence/results/faf3cf5525a8d16b201b026c2e18d7d798b1d4d99e9aca9df247e6b19a4080ef.json @@ -0,0 +1,61 @@ +{ + "arrow_sha256": "3c24510e2f154e4ceb8dfe3b76ee7a1c12b9025a3dc5ed14869b58f4683970a2", + "case_hash": "faf3cf5525a8d16b201b026c2e18d7d798b1d4d99e9aca9df247e6b19a4080ef", + "columns": [ + { + "internal_size": null, + "name": "VALUE", + "nullable": true, + "precision": null, + "scale": null, + "type_code": 10, + "type_name": "ARRAY" + } + ], + "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": "1817bc1a2effe1b9ca3ffd3a44e339d789158d7a3768279eaa7ea99dbf35d419", + "recorded_at": "2026-11-06T10:50:38.322420+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/scripts/family_cases/_helpers/zoned_texts.py b/scripts/family_cases/_helpers/zoned_texts.py index c5d4f2eca..347c00dfc 100644 --- a/scripts/family_cases/_helpers/zoned_texts.py +++ b/scripts/family_cases/_helpers/zoned_texts.py @@ -27,9 +27,9 @@ ZONED_SPELLING_EDGES, ZONED_STRICT_EDGES_DEFERRED, ZONED_STRICT_FORMS, + ZONED_SUBMICRO_COMPARISONS, ZONED_SUBMICRO_DIVERGENCE, ZONED_SUBMICRO_PROJECTIONS, - ZONED_TEXT_COMPARISONS, ZONED_TEXTS, ZONED_WALL_TIMES, ) @@ -185,7 +185,7 @@ def comparison_case(*, column_type: str, comparison: tuple[str, str]) -> Case: order="ordered", tags=("null", "boundary"), expected_divergence=( - ZONED_SUBMICRO_DIVERGENCE if comparison_slug in ZONED_TEXT_COMPARISONS else None + ZONED_SUBMICRO_DIVERGENCE if comparison_slug in ZONED_SUBMICRO_COMPARISONS else None ), ) diff --git a/scripts/family_cases/constants.py b/scripts/family_cases/constants.py index ba5e0e3b9..ec5b0801b 100644 --- a/scripts/family_cases/constants.py +++ b/scripts/family_cases/constants.py @@ -140,6 +140,6 @@ "date": "variant_typed_values", "ntz": "variant_typed_values", } -ZONED_TEXT_COMPARISONS: frozenset[str] = frozenset( - {"gte_naive_text", "lt_date_text", "eq_offset_text", "between_texts"} +ZONED_SUBMICRO_COMPARISONS: frozenset[str] = frozenset( + {"gte_naive_text", "lt_date_text", "eq_offset_text", "between_texts", "eq_ntz"} ) diff --git a/sqlquack.toml b/sqlquack.toml index 8fa1f25dd..43908c3f1 100644 --- a/sqlquack.toml +++ b/sqlquack.toml @@ -384,7 +384,7 @@ failure = "value" reason = "A TIMESTAMP_TZ source column's values carry their own offsets, which DuckDB does not keep. Where the schema declares a column's offset (only +00:00 is supported), operations depending on the offset, such as its wall time, date or parts, convert as for a value known to hold +00:00, and rely on `declared_offset:`. A value with another offset gives a wrong result silently. Strict mode refuses it unless the caller confirms the declared offsets." [divergences.session_timezone_utc] -constructs = ["node:cast", "node:try_cast", "fn:TRY_TO_TIMESTAMP_TZ", "fn:CONVERT_TIMEZONE", "fn:CURRENT_DATE", "fn:CURRENT_TIMESTAMP", "fn:DATE_PART", "node:extract", "node:year", "node:quarter", "node:month", "node:day", "node:hour", "node:minute", "node:second", "fn:TO_VARIANT", "fn:OBJECT_CONSTRUCT", "fn:OBJECT_CONSTRUCT_KEEP_NULL", "fn:OBJECT_INSERT", "fn:ARRAY_CONSTRUCT", "node:map_func", "fn:TO_TIMESTAMP_TZ", "fn:TO_TIMESTAMP_LTZ", "fn:TRY_TO_TIMESTAMP_LTZ", "node:eq", "node:neq", "node:lt", "node:lte", "node:gt", "node:gte", "node:between"] +constructs = ["node:cast", "node:try_cast", "fn:TRY_TO_TIMESTAMP_TZ", "fn:CONVERT_TIMEZONE", "fn:CURRENT_DATE", "fn:CURRENT_TIMESTAMP", "fn:DATEADD", "fn:TIMEADD", "fn:TIMESTAMPADD", "fn:DATE_PART", "node:extract", "node:year", "node:quarter", "node:month", "node:day", "node:hour", "node:minute", "node:second", "fn:TO_VARIANT", "fn:OBJECT_CONSTRUCT", "fn:OBJECT_CONSTRUCT_KEEP_NULL", "fn:OBJECT_INSERT", "fn:ARRAY_CONSTRUCT", "node:map_func", "fn:TO_TIMESTAMP_TZ", "fn:TO_TIMESTAMP_LTZ", "fn:TRY_TO_TIMESTAMP_LTZ", "node:eq", "node:neq", "node:lt", "node:lte", "node:gt", "node:gte", "node:between"] assumption = true default = "warn" strict = "refuse" @@ -434,7 +434,7 @@ failure = "loud" reason = "User decision 2026-10-25: a DATE, TIMESTAMP or TIME inside a VARIANT keeps its type in Snowflake, which DuckDB JSON cannot. The converted SQL stores the JSON string Snowflake writes for the value (batch 8 recorded YYYY-MM-DD, YYYY-MM-DD HH:MI:SS.FFF and a ' Z' suffix for TIMESTAMP_LTZ in UTC), raising where that text drops digits or an offset. TYPEOF and IS_DATE-style reads of a string in such a text raise rather than answer VARCHAR or false. TIMESTAMP_TZ (its offset is not kept), TIME and BINARY (its hex text is indistinguishable from plain strings) stay refused." [divergences.timestamp_range] -constructs = ["node:cast", "node:try_cast", "node:add", "node:sub", "fn:DATEADD", "fn:TIMEADD", "fn:TIMESTAMPADD", "fn:ADD_MONTHS", "fn:CONVERT_TIMEZONE", "fn:TO_TIMESTAMP", "fn:TO_TIMESTAMP_NTZ", "fn:TRY_TO_TIMESTAMP", "fn:TRY_TO_TIMESTAMP_NTZ", "fn:TRY_TO_TIMESTAMP_TZ", "fn:TIMESTAMP_NTZ_FROM_PARTS", "node:eq", "node:neq", "node:lt", "node:lte", "node:gt", "node:gte", "node:in", "fn:TO_TIMESTAMP_TZ", "fn:TO_TIMESTAMP_LTZ", "fn:TRY_TO_TIMESTAMP_LTZ"] +constructs = ["node:cast", "node:try_cast", "node:add", "node:sub", "fn:DATEADD", "fn:TIMEADD", "fn:TIMESTAMPADD", "fn:ADD_MONTHS", "fn:CONVERT_TIMEZONE", "fn:TO_TIMESTAMP", "fn:TO_TIMESTAMP_NTZ", "fn:TRY_TO_TIMESTAMP", "fn:TRY_TO_TIMESTAMP_NTZ", "fn:TRY_TO_TIMESTAMP_TZ", "fn:TIMESTAMP_NTZ_FROM_PARTS", "node:eq", "node:neq", "node:lt", "node:lte", "node:gt", "node:gte", "node:between", "node:in", "fn:TO_TIMESTAMP_TZ", "fn:TO_TIMESTAMP_LTZ", "fn:TRY_TO_TIMESTAMP_LTZ"] default = "warn" strict = "refuse" failure = "loud"