From d9de7cc70af96f7ec971f9bd633e4ac382c71238 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:28:06 +0000 Subject: [PATCH 1/8] test(benchmarks): add DuckShop edge sources for arrays, dates, amounts and VARIANTs Fixed rows for basket lines, date and time texts, amount checks, VARIANT attribute values and category links, with their staging models and tests. The ticket-note staging model passes its text on for the pattern cases. --- .../edge_cases/stg_edge_amount_checks.sql | 40 ++++ .../edge_cases/stg_edge_basket_lines.sql | 32 +++ .../edge_cases/stg_edge_category_links.sql | 30 +++ .../edge_cases/stg_edge_date_texts.sql | 36 +++ .../edge_cases/stg_edge_ticket_notes.sql | 8 +- .../edge_cases/stg_edge_variant_values.sql | 32 +++ .../project/python/loaders/_edge_cases.py | 221 +++++++++++++++++- .../project/python/loaders/edge_cases.py | 25 ++ .../duckshop/project/sources/edge_cases.yml | 140 +++++++++++ .../test_stg_edge_amount_checks.sql | 27 +++ .../edge_cases/test_stg_edge_basket_lines.sql | 23 ++ .../test_stg_edge_category_links.sql | 21 ++ .../edge_cases/test_stg_edge_date_texts.sql | 27 +++ .../test_stg_edge_variant_values.sql | 21 ++ .../duckshop/tests/test_synthetic_data.py | 5 + 15 files changed, 685 insertions(+), 3 deletions(-) create mode 100644 benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_amount_checks.sql create mode 100644 benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_basket_lines.sql create mode 100644 benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_category_links.sql create mode 100644 benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_date_texts.sql create mode 100644 benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_variant_values.sql create mode 100644 benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_amount_checks.sql create mode 100644 benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_basket_lines.sql create mode 100644 benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_category_links.sql create mode 100644 benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_date_texts.sql create mode 100644 benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_variant_values.sql diff --git a/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_amount_checks.sql b/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_amount_checks.sql new file mode 100644 index 0000000000..a96f28ae2e --- /dev/null +++ b/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_amount_checks.sql @@ -0,0 +1,40 @@ +MODEL ( + description """ + One row per reconciliation check, keyed by a NUMBER(18,0), a NUMBER(38,0) and a text key, with + amounts at scales 2 and 15, a FLOAT ratio and raw JSON number text + """, + tags [edge_case], + columns ( + check_id (type VARCHAR, nullable false, audits [not_null, unique]), + group_code (type VARCHAR), + narrow_key (type NUMBER(18, 0)), + wide_key (type NUMBER(38, 0)), + key_text (type VARCHAR), + amount (type NUMBER(18, 2)), + fine_amount (type NUMBER(20, 15)), + ratio (type FLOAT), + json_text (type VARCHAR), + ), +); + +WITH src AS ( + SELECT * + FROM __source("edge__amount_checks") +), + +final AS ( + SELECT + s.check_id, + s.group_code, + s.narrow_key, + s.wide_key, + s.key_text, + s.amount, + s.fine_amount, + s.ratio, + s.json_text + FROM src AS s +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_basket_lines.sql b/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_basket_lines.sql new file mode 100644 index 0000000000..d95d4b3553 --- /dev/null +++ b/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_basket_lines.sql @@ -0,0 +1,32 @@ +MODEL ( + description """ + One row per basket line with its tag in lower case, the input to the edge models that collect + tags into arrays whose element order Snowflake does not define + """, + tags [edge_case], + columns ( + line_id (type VARCHAR, nullable false, audits [not_null, unique]), + basket_id (type VARCHAR), + line_no (type NUMBER(38, 0)), + sku (type VARCHAR), + tag (type VARCHAR), + ), +); + +WITH src AS ( + SELECT * + FROM __source("edge__basket_lines") +), + +final AS ( + SELECT + s.line_id, + s.basket_id, + s.line_no, + s.sku, + LOWER(TRIM(s.tag))::VARCHAR AS tag + FROM src AS s +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_category_links.sql b/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_category_links.sql new file mode 100644 index 0000000000..fd304cdf2a --- /dev/null +++ b/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_category_links.sql @@ -0,0 +1,30 @@ +MODEL ( + description """ + One row per catalogue category link, with the shelf depth each category is displayed at, for + walking the category tree with CONNECT BY and a recursive CTE + """, + tags [edge_case], + columns ( + category_id (type VARCHAR, nullable false, audits [not_null, unique]), + parent_id (type VARCHAR), + category_name (type VARCHAR), + shelf_depth (type NUMBER(38, 0)), + ), +); + +WITH src AS ( + SELECT * + FROM __source("edge__category_links") +), + +final AS ( + SELECT + s.category_id, + s.parent_id, + s.category_name, + s.shelf_depth + FROM src AS s +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_date_texts.sql b/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_date_texts.sql new file mode 100644 index 0000000000..ccd2f9a5ea --- /dev/null +++ b/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_date_texts.sql @@ -0,0 +1,36 @@ +MODEL ( + description """ + One row per partner date and time text, with the clock columns kept at their declared precisions + so later models can read them as text, zoned times and times of day + """, + tags [edge_case], + columns ( + text_id (type VARCHAR, nullable false, audits [not_null, unique]), + date_text (type VARCHAR), + zoned_text (type VARCHAR), + wall_clock (type TIMESTAMP_NTZ(3)), + wall_clock_text (type VARCHAR), + local_time (type TIME(3)), + fine_time (type TIME(9)), + ), +); + +WITH src AS ( + SELECT * + FROM __source("edge__date_texts") +), + +final AS ( + SELECT + s.text_id, + s.date_text, + s.zoned_text, + s.wall_clock, + s.wall_clock_text, + s.local_time, + s.fine_time + FROM src AS s +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_ticket_notes.sql b/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_ticket_notes.sql index 6fac313799..bd9fd1d85d 100644 --- a/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_ticket_notes.sql +++ b/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_ticket_notes.sql @@ -3,7 +3,7 @@ MODEL ( One row per support ticket note with order references and reasons found by regular expressions, written with escaped and dollar-quoted patterns, a SKU normalised by a macro after an escaped quote, campaign and discount codes matched by pattern lists and escapes, and SKUs split at edge - positions + positions; the note text and tags are passed on as written """, tags [edge_case], columns ( @@ -29,6 +29,8 @@ MODEL ( sku_from_zero (type VARCHAR), sku_last_two (type VARCHAR), sku_before_start (type VARCHAR), + body (type VARCHAR), + tags_text (type VARCHAR), ), ); @@ -67,7 +69,9 @@ final AS ( SPLIT_PART(s.sku, '', 1)::VARCHAR AS sku_part_no_delimiter, SUBSTR(s.sku, 0, 3)::VARCHAR AS sku_from_zero, SUBSTR(s.sku, -2)::VARCHAR AS sku_last_two, - SUBSTR(s.sku, -10, 3)::VARCHAR AS sku_before_start + SUBSTR(s.sku, -10, 3)::VARCHAR AS sku_before_start, + s.body, + s.tags_text FROM src AS s ) diff --git a/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_variant_values.sql b/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_variant_values.sql new file mode 100644 index 0000000000..6699eaf16c --- /dev/null +++ b/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_variant_values.sql @@ -0,0 +1,32 @@ +MODEL ( + description """ + One row per partner attribute value held as VARIANT, never SQL NULL (a missing value arrives as + JSON null), with the raw text beside it + """, + tags [edge_case], + columns ( + value_id (type VARCHAR, nullable false, audits [not_null, unique]), + group_code (type VARCHAR), + sort_key (type NUMBER(38, 0)), + attribute_value (type VARIANT, nullable false, audits [not_null]), + value_text (type VARCHAR), + ), +); + +WITH src AS ( + SELECT * + FROM __source("edge__variant_values") +), + +final AS ( + SELECT + s.value_id, + s.group_code, + s.sort_key, + COALESCE(s.attribute_value, PARSE_JSON('null')) AS attribute_value, + s.value_text + FROM src AS s +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/python/loaders/_edge_cases.py b/benchmarks/duckshop/project/python/loaders/_edge_cases.py index b555839004..0c42460042 100644 --- a/benchmarks/duckshop/project/python/loaders/_edge_cases.py +++ b/benchmarks/duckshop/project/python/loaders/_edge_cases.py @@ -7,7 +7,7 @@ from __future__ import annotations -from datetime import datetime +from datetime import datetime, time from decimal import Decimal Rows = list[dict[str, object]] @@ -490,6 +490,220 @@ def partner_listings() -> Rows: ] +def basket_lines() -> Rows: + lines = [ + # basket, line, sku, tag: B-1 repeats a tag, B-3 holds one line, B-4 has a NULL tag + ("B-1", 1, "MALLARD-01", "bath"), + ("B-1", 2, "TEAL-02", "gift"), + ("B-1", 3, "EIDER-03", "bath"), + ("B-2", 1, "PINTAIL-04", "garden"), + ("B-2", 2, "MALLARD-01", "bath"), + ("B-3", 1, "MERGANSER-05", "pond"), + ("B-4", 1, "TEAL-02", None), + ("B-4", 2, "EIDER-03", "gift"), + ] + return [ + { + "line_id": f"BL-{index + 1:02d}", + "basket_id": basket, + "line_no": line_no, + "sku": sku, + "tag": tag, + "_loaded_at": _at("2026-03-06 00:00:00"), + } + for index, (basket, line_no, sku, tag) in enumerate(lines) + ] + + +def date_texts() -> Rows: + texts = [ + # date text, zoned text, wall clock, wall clock text, local time, fine time + ( + "5.3.2026", + "2026-03-05T23:30:00Z", + "2026-03-05 23:30:00.123", + "2026-03-05 23:30:00.1239", + time(23, 59, 59, 999000), + "23:59:59.9999999", + ), + ( + "2026-3-5", + "2026-03-05 23:30:00+00:00", + "2026-03-05 23:30:00", + "2026-03-05 23:30:00.9", + time(0, 0, 0), + "12:00:00.0000005", + ), + ( + "05/03/26", + "2026-03-05 23:30:00 +0000", + "2026-03-05 23:30:00.120", + "2026-03-05 23:30:00.12", + time(12, 30, 15, 250000), + "00:00:00.000000001", + ), + ( + "2026-03-05T10:00", + "2026-03-06T08:30:00+09:00", + "1969-12-31 23:59:59.999", + "1969-12-31 23:59:59.999", + time(23, 30), + "23:59:59.999999999", + ), + ( + "2026-03-05Z", + "2026-03-05T18:00:00-05:30", + "2026-12-31 23:59:59.999", + "2026-12-31 23:59:59.999000", + time(6, 0, 0, 1000), + "06:00:00.0000001", + ), + ( + "05 Auguſt 2026", + "2026-12-31T23:30:00-05:30", + "2026-03-29 01:30:00", + "2026-03-29 01:30", + None, + None, + ), + ("5 Mar 2026", None, None, None, None, None), + ] + return [ + { + "text_id": f"DT-{index + 1:02d}", + "date_text": date_text, + "zoned_text": zoned, + "wall_clock": None if wall is None else _at(wall), + "wall_clock_text": wall_text, + "local_time": local, + "fine_time": fine, + "_loaded_at": _at("2026-03-06 00:00:00"), + } + for index, (date_text, zoned, wall, wall_text, local, fine) in enumerate(texts) + ] + + +def amount_checks() -> Rows: + checks = [ + # group, narrow key, wide key, key text, amount, fine amount, ratio, JSON text + ( + "G-1", + 7, + 7, + "01", + Decimal("10.00"), + Decimal("0.123456789012345"), + 0.29, + '{"amount": 2.0, "qty": 1.00}', + ), + ( + "G-1", + 12, + 12345678901234567890123, + "1", + Decimal("-0.50"), + Decimal("0.123456789012344"), + 1.005, + "1.0.5", + ), + ( + "G-1", + None, + 12, + "12", + Decimal("2.50"), + Decimal("0.500000000000001"), + 2.675, + '{"a": 1.00.25}', + ), + ( + "G-2", + 123456789012345678, + 123456789012345678, + " 7", + Decimal("0.00"), + Decimal("1.000000000000000"), + -155.5, + "[1.0.5, 2.0]", + ), + ("G-2", 99, None, None, None, None, None, "12345678901234567890123456789012345678.0"), + ( + "G-3", + 5, + 5, + "n/a", + Decimal("999999999999.99"), + Decimal("0.000000000000001"), + 0.57, + "1." + "0" * 40, + ), + ] + return [ + { + "check_id": f"AC-{index + 1:02d}", + "group_code": group, + "narrow_key": narrow, + "wide_key": wide, + "key_text": key_text, + "amount": amount, + "fine_amount": fine, + "ratio": ratio, + "json_text": json_text, + "_loaded_at": _at("2026-03-06 00:00:00"), + } + for index, (group, narrow, wide, key_text, amount, fine, ratio, json_text) in enumerate( + checks + ) + ] + + +def variant_values() -> Rows: + values = [ + # group, sort key, JSON document + ("V-1", 1, "1"), + ("V-1", 2, '"1"'), + ("V-1", 3, "1.0"), + ("V-1", 4, "null"), + ("V-2", 1, '{"a": 1, "b": 2}'), + ("V-2", 2, '{"b": 2, "a": 1}'), + ("V-2", 3, '"2026-03-01"'), + ("V-3", 1, '[1, "a", 2, true, null, 12345678901234567890]'), + ("V-3", 2, '["a", "B", "b", "\\u00e9", "z"]'), + ("V-3", 3, '{"a$b": 1, "a-b": {"@x": 2}, "a[b": [3], "\\u00e9": 4, "plain key": 5}'), + ] + return [ + { + "value_id": f"VV-{index + 1:02d}", + "group_code": group, + "sort_key": sort_key, + "attribute_value": document, + "value_text": document, + "_loaded_at": _at("2026-03-06 00:00:00"), + } + for index, (group, sort_key, document) in enumerate(values) + ] + + +def category_links() -> Rows: + links = [ + ("CAT-1", None, "Ducks", 1), + ("CAT-2", "CAT-1", "Rubber ducks", 2), + ("CAT-3", "CAT-2", "Collectible series", 3), + ("CAT-4", "CAT-1", "Decoys", 2), + ("CAT-5", None, "Pond supplies", 1), + ] + return [ + { + "category_id": category, + "parent_id": parent, + "category_name": name, + "shelf_depth": depth, + "_loaded_at": _at("2026-03-06 00:00:00"), + } + for category, parent, name, depth in links + ] + + DELIVERY_BATCH_VAR = "edge_delivery_batch" """Project variable naming the last delivery-update load to include; "2" (all loads) by default.""" @@ -543,4 +757,9 @@ def delivery_updates(batch: int = 2) -> Rows: "edge__ticket_notes": ticket_notes(), "edge__partner_listings": partner_listings(), "edge__delivery_updates": delivery_updates(), + "edge__basket_lines": basket_lines(), + "edge__date_texts": date_texts(), + "edge__amount_checks": amount_checks(), + "edge__variant_values": variant_values(), + "edge__category_links": category_links(), } diff --git a/benchmarks/duckshop/project/python/loaders/edge_cases.py b/benchmarks/duckshop/project/python/loaders/edge_cases.py index c00991b8ac..522dd0b37f 100644 --- a/benchmarks/duckshop/project/python/loaders/edge_cases.py +++ b/benchmarks/duckshop/project/python/loaders/edge_cases.py @@ -53,6 +53,31 @@ def edge__partner_listings(ctx: LoaderContext) -> object: return _load(ctx, "edge__partner_listings") +@loader +def edge__basket_lines(ctx: LoaderContext) -> object: + return _load(ctx, "edge__basket_lines") + + +@loader +def edge__date_texts(ctx: LoaderContext) -> object: + return _load(ctx, "edge__date_texts") + + +@loader +def edge__amount_checks(ctx: LoaderContext) -> object: + return _load(ctx, "edge__amount_checks") + + +@loader +def edge__variant_values(ctx: LoaderContext) -> object: + return _load(ctx, "edge__variant_values") + + +@loader +def edge__category_links(ctx: LoaderContext) -> object: + return _load(ctx, "edge__category_links") + + @loader def edge__delivery_updates(ctx: LoaderContext) -> object: batch = int(ctx.vars.get(DELIVERY_BATCH_VAR, 2)) # type: ignore[call-overload] diff --git a/benchmarks/duckshop/project/sources/edge_cases.yml b/benchmarks/duckshop/project/sources/edge_cases.yml index 6b7b5795a1..52da7225d8 100644 --- a/benchmarks/duckshop/project/sources/edge_cases.yml +++ b/benchmarks/duckshop/project/sources/edge_cases.yml @@ -271,3 +271,143 @@ sources: nullable: false audits: - not_null + - name: edge__basket_lines + description: >- + Append-only basket lines with a free-form tag per line, collected into arrays whose element + order Snowflake does not define + managed: true + contract: enforced + columns: + - name: line_id + type: VARCHAR + nullable: false + audits: + - not_null + - unique + - name: basket_id + type: VARCHAR + - name: line_no + type: NUMBER(38,0) + - name: sku + type: VARCHAR + - name: tag + type: VARCHAR + - name: _loaded_at + type: TIMESTAMP_NTZ + nullable: false + audits: + - not_null + - name: edge__date_texts + description: >- + Dates, zoned times and clock times as partners write them: unpadded fields, two-digit years, + ISO separators, offsets in several spellings, and times with sub-millisecond digits + managed: true + contract: enforced + columns: + - name: text_id + type: VARCHAR + nullable: false + audits: + - not_null + - unique + - name: date_text + type: VARCHAR + - name: zoned_text + type: VARCHAR + - name: wall_clock + type: TIMESTAMP_NTZ(3) + - name: wall_clock_text + type: VARCHAR + - name: local_time + type: TIME(3) + - name: fine_time + type: TIME(9) + - name: _loaded_at + type: TIMESTAMP_NTZ + nullable: false + audits: + - not_null + - name: edge__amount_checks + description: >- + Reconciliation checks keyed by numbers of two widths and by text, with amounts at several + scales, FLOAT ratios and raw JSON number text + managed: true + contract: enforced + columns: + - name: check_id + type: VARCHAR + nullable: false + audits: + - not_null + - unique + - name: group_code + type: VARCHAR + - name: narrow_key + type: NUMBER(18,0) + - name: wide_key + type: NUMBER(38,0) + - name: key_text + type: VARCHAR + - name: amount + type: NUMBER(18,2) + - name: fine_amount + type: NUMBER(20,15) + - name: ratio + type: FLOAT + - name: json_text + type: VARCHAR + - name: _loaded_at + type: TIMESTAMP_NTZ + nullable: false + audits: + - not_null + - name: edge__variant_values + description: >- + Partner attribute values held as VARIANT, mixing numbers, strings, booleans, JSON nulls, + objects with reordered keys and wide integers, with the raw text beside each + managed: true + contract: enforced + columns: + - name: value_id + type: VARCHAR + nullable: false + audits: + - not_null + - unique + - name: group_code + type: VARCHAR + - name: sort_key + type: NUMBER(38,0) + - name: attribute_value + type: VARIANT + - name: value_text + type: VARCHAR + - name: _loaded_at + type: TIMESTAMP_NTZ + nullable: false + audits: + - not_null + - name: edge__category_links + description: >- + Catalogue category links for walking the category tree, with the shelf depth each category + is displayed at + managed: true + contract: enforced + columns: + - name: category_id + type: VARCHAR + nullable: false + audits: + - not_null + - unique + - name: parent_id + type: VARCHAR + - name: category_name + type: VARCHAR + - name: shelf_depth + type: NUMBER(38,0) + - name: _loaded_at + type: TIMESTAMP_NTZ + nullable: false + audits: + - not_null diff --git a/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_amount_checks.sql b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_amount_checks.sql new file mode 100644 index 0000000000..012a85d845 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_amount_checks.sql @@ -0,0 +1,27 @@ +TEST (name "stg_edge_amount_checks__keeps_key_widths_and_scales"); + +WITH __source__edge__amount_checks AS ( + SELECT + v.check_id, + v.group_code, + v.narrow_key::NUMBER(18, 0) AS narrow_key, + v.wide_key::NUMBER(38, 0) AS wide_key, + v.key_text, + v.amount::NUMBER(18, 2) AS amount, + v.fine_amount::NUMBER(20, 15) AS fine_amount, + v.ratio::FLOAT AS ratio, + v.json_text, + '2026-03-06 00:00:00'::TIMESTAMP_NTZ AS _loaded_at + FROM VALUES + ('AC-01', 'G-1', 7, '12345678901234567890123', '01', 10.00, 0.123456789012345, 0.29, '1.0'), + ('AC-02', 'G-2', NULL, NULL, NULL, NULL, NULL, NULL, NULL) AS v(check_id, group_code, narrow_key, wide_key, key_text, amount, fine_amount, ratio, json_text) +), + +__expected__stg_edge_amount_checks AS ( + SELECT + NULL AS check_id, + NULL AS narrow_key, + NULL AS wide_key, + NULL AS amount, + NULL AS fine_amount +) diff --git a/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_basket_lines.sql b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_basket_lines.sql new file mode 100644 index 0000000000..090d6b0f01 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_basket_lines.sql @@ -0,0 +1,23 @@ +TEST (name "stg_edge_basket_lines__lower_cases_and_trims_tags"); + +WITH __source__edge__basket_lines AS ( + SELECT + v.line_id, + v.basket_id, + v.line_no::NUMBER(38, 0) AS line_no, + v.sku, + v.tag, + '2026-03-06 00:00:00'::TIMESTAMP_NTZ AS _loaded_at + FROM VALUES + ('BL-01', 'B-1', 1, 'MALLARD-01', ' Bath '), + ('BL-02', 'B-1', 2, 'TEAL-02', 'GIFT'), + ('BL-03', 'B-2', 1, 'EIDER-03', NULL) AS v(line_id, basket_id, line_no, sku, tag) +), + +__expected__stg_edge_basket_lines AS ( + SELECT + NULL AS line_id, + NULL AS basket_id, + NULL AS line_no, + NULL AS tag +) diff --git a/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_category_links.sql b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_category_links.sql new file mode 100644 index 0000000000..1640722223 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_category_links.sql @@ -0,0 +1,21 @@ +TEST (name "stg_edge_category_links__passes_links_through"); + +WITH __source__edge__category_links AS ( + SELECT + v.category_id, + v.parent_id, + v.category_name, + v.shelf_depth::NUMBER(38, 0) AS shelf_depth, + '2026-03-06 00:00:00'::TIMESTAMP_NTZ AS _loaded_at + FROM VALUES + ('CAT-1', NULL, 'Ducks', 1), + ('CAT-2', 'CAT-1', 'Rubber ducks', 2) AS v(category_id, parent_id, category_name, shelf_depth) +), + +__expected__stg_edge_category_links AS ( + SELECT + NULL AS category_id, + NULL AS parent_id, + NULL AS category_name, + NULL AS shelf_depth +) diff --git a/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_date_texts.sql b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_date_texts.sql new file mode 100644 index 0000000000..8e25a4f580 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_date_texts.sql @@ -0,0 +1,27 @@ +TEST (name "stg_edge_date_texts__keeps_clock_precisions"); + +WITH __source__edge__date_texts AS ( + SELECT + v.text_id, + v.date_text, + v.zoned_text, + v.wall_clock::TIMESTAMP_NTZ(3) AS wall_clock, + v.wall_clock_text, + v.local_time::TIME(3) AS local_time, + v.fine_time::TIME(9) AS fine_time, + '2026-03-06 00:00:00'::TIMESTAMP_NTZ AS _loaded_at + FROM VALUES + ('DT-01', '5.3.2026', '2026-03-05T23:30:00Z', '2026-03-05 23:30:00.123', '2026-03-05 23:30:00.1239', '23:59:59.999', '23:59:59.9999999'), + ('DT-02', NULL, NULL, NULL, NULL, NULL, NULL) AS v(text_id, date_text, zoned_text, wall_clock, wall_clock_text, local_time, fine_time) +), + +__expected__stg_edge_date_texts AS ( + SELECT + NULL AS text_id, + NULL AS date_text, + NULL AS zoned_text, + NULL AS wall_clock, + NULL AS wall_clock_text, + NULL AS local_time, + NULL AS fine_time +) diff --git a/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_variant_values.sql b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_variant_values.sql new file mode 100644 index 0000000000..ba9d1abcdd --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_variant_values.sql @@ -0,0 +1,21 @@ +TEST (name "stg_edge_variant_values__turns_missing_values_into_json_null"); + +WITH __source__edge__variant_values AS ( + SELECT + v.value_id, + v.group_code, + v.sort_key::NUMBER(38, 0) AS sort_key, + PARSE_JSON(v.attribute_value) AS attribute_value, + v.value_text, + '2026-03-06 00:00:00'::TIMESTAMP_NTZ AS _loaded_at + FROM VALUES + ('VV-01', 'V-1', 1, '{"b": 2, "a": 1}', '{"b": 2, "a": 1}'), + ('VV-02', 'V-1', 2, 'null', 'null'), + ('VV-03', 'V-1', 3, NULL, NULL) AS v(value_id, group_code, sort_key, attribute_value, value_text) +), + +__expected__stg_edge_variant_values AS ( + SELECT + NULL AS value_id, + NULL AS attribute_value +) diff --git a/benchmarks/duckshop/tests/test_synthetic_data.py b/benchmarks/duckshop/tests/test_synthetic_data.py index 1d8ff54725..1dd7bf07b7 100644 --- a/benchmarks/duckshop/tests/test_synthetic_data.py +++ b/benchmarks/duckshop/tests/test_synthetic_data.py @@ -477,6 +477,11 @@ def test_given_any_data_scale_when_building_edge_rows_then_they_are_fixed(scale: "edge__ticket_notes": _edge_cases.ticket_notes(), "edge__partner_listings": _edge_cases.partner_listings(), "edge__delivery_updates": _edge_cases.delivery_updates(), + "edge__basket_lines": _edge_cases.basket_lines(), + "edge__date_texts": _edge_cases.date_texts(), + "edge__amount_checks": _edge_cases.amount_checks(), + "edge__variant_values": _edge_cases.variant_values(), + "edge__category_links": _edge_cases.category_links(), } assert {name: _digest(rows) for name, rows in rebuilt.items()} == { From 22387df7b78aa63e28e31a5a75e80316cad9ff5a Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:28:10 +0000 Subject: [PATCH 2/8] test(benchmarks): add DuckShop edge cases for array order, dates from text and numbers Unordered ARRAY_AGG ranked under LIMIT and read by order-free functions, SELECT DISTINCT over arrays, TRY_TO_DATE formats, zoned text read back at UTC, TIME arithmetic and formats, JOIN USING across key widths, NULL arithmetic and literal types, MEDIAN scales, DIV0 by zero literals, JSON zero fractions and aggregates inside windows. Expectations await capture on Snowflake. --- .../edge_cases/int_edge_amount_edges.sql | 64 +++++++ .../edge_cases/int_edge_amount_statistics.sql | 98 +++++++++++ .../edge_cases/int_edge_basket_tag_pairs.sql | 51 ++++++ .../edge_cases/int_edge_basket_tag_sets.sql | 73 ++++++++ .../edge_cases/int_edge_basket_top_tags.sql | 120 +++++++++++++ .../edge_cases/int_edge_clock_arithmetic.sql | 126 ++++++++++++++ .../edge_cases/int_edge_grouped_windows.sql | 78 +++++++++ .../edge_cases/int_edge_json_numbers.sql | 48 ++++++ .../edge_cases/int_edge_key_widths.sql | 144 ++++++++++++++++ .../edge_cases/int_edge_null_arithmetic.sql | 163 ++++++++++++++++++ .../edge_cases/int_edge_text_dates.sql | 43 +++++ .../edge_cases/int_edge_zoned_texts.sql | 86 +++++++++ .../edge_cases/test_int_edge_amount_edges.sql | 47 +++++ .../test_int_edge_amount_statistics.sql | 44 +++++ .../test_int_edge_basket_tag_pairs.sql | 26 +++ .../test_int_edge_basket_tag_sets.sql | 31 ++++ .../test_int_edge_basket_top_tags.sql | 26 +++ .../test_int_edge_clock_arithmetic.sql | 47 +++++ .../test_int_edge_grouped_windows.sql | 33 ++++ .../edge_cases/test_int_edge_json_numbers.sql | 35 ++++ .../edge_cases/test_int_edge_key_widths.sql | 33 ++++ .../test_int_edge_null_arithmetic.sql | 51 ++++++ .../edge_cases/test_int_edge_text_dates.sql | 48 ++++++ .../edge_cases/test_int_edge_zoned_texts.sql | 46 +++++ 24 files changed, 1561 insertions(+) create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_amount_edges.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_amount_statistics.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_basket_tag_pairs.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_basket_tag_sets.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_basket_top_tags.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_clock_arithmetic.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_grouped_windows.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_json_numbers.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_key_widths.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_null_arithmetic.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_text_dates.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_zoned_texts.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_amount_edges.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_amount_statistics.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_tag_pairs.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_tag_sets.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_top_tags.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_clock_arithmetic.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_grouped_windows.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_json_numbers.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_key_widths.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_null_arithmetic.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_text_dates.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_zoned_texts.sql diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_amount_edges.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_amount_edges.sql new file mode 100644 index 0000000000..30e1940320 --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_amount_edges.sql @@ -0,0 +1,64 @@ +MODEL ( + description """ + One row per check with numeric edges: DIV0 and DIV0NULL by literal zeros and NULL, TRUNC of + FLOAT ratios that sit just below a whole number of cents, FLOAT values as text (pi among them), + infinity and NaN words in other scripts read by TRY_TO_DOUBLE, and LAG with offset 0 and IGNORE + NULLS. A running COUNT_IF without ORDER BY is not compared row by row, since Snowflake may + number the rows in any order; the test checks only its total per group + """, + tags [edge_case], + columns ( + check_id (type VARCHAR, nullable false, audits [not_null, unique]), + group_code (type VARCHAR), + one_over_zero (type NUMBER(38, 6)), + amount_over_zero (type NUMBER(38, 6)), + amount_over_scaled_zero (type NUMBER(38, 6)), + amount_over_minus_zero (type NUMBER(38, 6)), + amount_over_null (type NUMBER(38, 6)), + null_over_zero (type NUMBER(38, 6)), + ratio_truncated_2 (type FLOAT), + ratio_truncated_3 (type FLOAT), + ratio_text (type VARCHAR), + pi_text (type VARCHAR), + key_text_as_double (type FLOAT), + same_amount_lag (type NUMBER(18, 2)), + running_paid_count (type NUMBER(38, 0)), + ), +); + +WITH checks AS ( + SELECT + c.check_id, + c.group_code, + c.key_text, + c.amount, + c.ratio + FROM __ref("stg_edge_amount_checks") AS c +), + +final AS ( + SELECT + c.check_id, + c.group_code, + DIV0(1, 0)::NUMBER(38, 6) AS one_over_zero, + DIV0(c.amount, 0)::NUMBER(38, 6) AS amount_over_zero, + DIV0(c.amount, 0.00)::NUMBER(38, 6) AS amount_over_scaled_zero, + DIV0(c.amount, -0)::NUMBER(38, 6) AS amount_over_minus_zero, + DIV0NULL(c.amount, NULL)::NUMBER(38, 6) AS amount_over_null, + DIV0(NULL, 0)::NUMBER(38, 6) AS null_over_zero, + TRUNC(c.ratio, 2)::FLOAT AS ratio_truncated_2, + TRUNCATE(c.ratio, 3)::FLOAT AS ratio_truncated_3, + c.ratio::VARCHAR AS ratio_text, + PI()::VARCHAR AS pi_text, + TRY_TO_DOUBLE(c.key_text)::FLOAT AS key_text_as_double, + LAG(c.amount, 0) IGNORE NULLS OVER (ORDER BY c.check_id)::NUMBER(18, 2) AS same_amount_lag, + -- No ORDER BY: each row's count depends on the order Snowflake reads the group in. + COUNT_IF(c.amount > 0) OVER ( + PARTITION BY c.group_code + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + )::NUMBER(38, 0) AS running_paid_count + FROM checks AS c +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_amount_statistics.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_amount_statistics.sql new file mode 100644 index 0000000000..888fc85e3a --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_amount_statistics.sql @@ -0,0 +1,98 @@ +MODEL ( + description """ + One row per check group with MEDIAN at scales 0 to 15, where the two middle values differ in + their last digits; MODE and COUNT_IF per group; and MODE, MEDIAN and COUNT_IF over no rows, + without GROUP BY and with HAVING but no GROUP BY, which Snowflake answers with one row. The tie + pick of a MAX_BY whose keys all tie, over FLOAT values of either sign of zero, is not compared: + either row is a valid answer + """, + tags [edge_case], + columns ( + group_code (type VARCHAR, nullable false, audits [not_null, unique]), + median_scale_0 (type NUMBER(38, 3)), + median_scale_2 (type NUMBER(38, 5)), + median_scale_4 (type NUMBER(38, 7)), + median_scale_10 (type NUMBER(38, 13)), + median_scale_11 (type NUMBER(38, 14)), + median_scale_12 (type NUMBER(38, 15)), + median_scale_15 (type NUMBER(38, 18)), + mode_amount (type NUMBER(18, 2)), + paid_count (type NUMBER(38, 0)), + signed_zero_pick (type FLOAT), + mode_over_no_rows (type NUMBER(18, 2)), + median_over_no_rows (type NUMBER(38, 5)), + paid_count_over_no_rows (type NUMBER(38, 0)), + mode_with_having_only (type NUMBER(18, 0)), + ), +); + +WITH checks AS ( + SELECT + c.check_id, + c.group_code, + c.narrow_key, + c.amount, + c.fine_amount, + c.ratio + FROM __ref("stg_edge_amount_checks") AS c +), + +per_group AS ( + SELECT + c.group_code, + MEDIAN(c.fine_amount::NUMBER(20, 0)) AS median_scale_0, + MEDIAN(c.fine_amount::NUMBER(20, 2)) AS median_scale_2, + MEDIAN(c.fine_amount::NUMBER(20, 4)) AS median_scale_4, + MEDIAN(c.fine_amount::NUMBER(20, 10)) AS median_scale_10, + MEDIAN(c.fine_amount::NUMBER(20, 11)) AS median_scale_11, + MEDIAN(c.fine_amount::NUMBER(20, 12)) AS median_scale_12, + MEDIAN(c.fine_amount) AS median_scale_15, + MODE(c.amount) AS mode_amount, + COUNT_IF(c.amount > 0) AS paid_count, + -- Every key ties, so either row's value is a valid pick; the test checks only that it is zero. + MAX_BY(c.ratio * 0, c.group_code) AS signed_zero_pick + FROM checks AS c + GROUP BY + c.group_code +), + +over_no_rows AS ( + SELECT + MODE(c.amount) AS mode_over_no_rows, + MEDIAN(c.amount) AS median_over_no_rows, + COUNT_IF(c.amount > 0) AS paid_count_over_no_rows + FROM checks AS c + WHERE c.check_id IS NULL +), + +having_only AS ( + SELECT MODE(c.narrow_key) AS mode_with_having_only + FROM checks AS c + WHERE c.check_id IS NULL + HAVING COUNT(*) >= 0 +), + +final AS ( + SELECT + g.group_code, + g.median_scale_0::NUMBER(38, 3) AS median_scale_0, + g.median_scale_2::NUMBER(38, 5) AS median_scale_2, + g.median_scale_4::NUMBER(38, 7) AS median_scale_4, + g.median_scale_10::NUMBER(38, 13) AS median_scale_10, + g.median_scale_11::NUMBER(38, 14) AS median_scale_11, + g.median_scale_12::NUMBER(38, 15) AS median_scale_12, + g.median_scale_15::NUMBER(38, 18) AS median_scale_15, + g.mode_amount::NUMBER(18, 2) AS mode_amount, + g.paid_count::NUMBER(38, 0) AS paid_count, + g.signed_zero_pick::FLOAT AS signed_zero_pick, + n.mode_over_no_rows::NUMBER(18, 2) AS mode_over_no_rows, + n.median_over_no_rows::NUMBER(38, 5) AS median_over_no_rows, + n.paid_count_over_no_rows::NUMBER(38, 0) AS paid_count_over_no_rows, + h.mode_with_having_only::NUMBER(18, 0) AS mode_with_having_only + FROM per_group AS g + CROSS JOIN over_no_rows AS n + CROSS JOIN having_only AS h +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_basket_tag_pairs.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_basket_tag_pairs.sql new file mode 100644 index 0000000000..4c829442fa --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_basket_tag_pairs.sql @@ -0,0 +1,51 @@ +MODEL ( + description """ + One row per distinct pair of tags seen together in a basket, written in both orders. Arrays keep + their element order, so SELECT DISTINCT keeps [bath, gift] and [gift, bath] apart and merges + only pairs repeated in the same order; a NULL tag stays an undefined element + """, + tags [edge_case], + columns ( + tag_pair_text (type VARCHAR, nullable false, audits [not_null, unique]), + tag_pair (type ARRAY), + pair_size (type NUMBER(38, 0)), + ), +); + +WITH lines AS ( + SELECT + l.basket_id, + l.line_no, + l.tag + FROM __ref("stg_edge_basket_lines") AS l +), + +pairs AS ( + SELECT ARRAY_CONSTRUCT(a.tag, b.tag) AS tag_pair + FROM lines AS a + INNER JOIN lines AS b + ON a.basket_id = b.basket_id + AND a.line_no < b.line_no + UNION ALL + SELECT ARRAY_CONSTRUCT(b.tag, a.tag) AS tag_pair + FROM lines AS a + INNER JOIN lines AS b + ON a.basket_id = b.basket_id + AND a.line_no < b.line_no +), + +distinct_pairs AS ( + SELECT DISTINCT p.tag_pair + FROM pairs AS p +), + +final AS ( + SELECT + ARRAY_TO_STRING(d.tag_pair, '|')::VARCHAR AS tag_pair_text, + d.tag_pair::ARRAY AS tag_pair, + ARRAY_SIZE(d.tag_pair)::NUMBER(38, 0) AS pair_size + FROM distinct_pairs AS d +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_basket_tag_sets.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_basket_tag_sets.sql new file mode 100644 index 0000000000..ecf87605cd --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_basket_tag_sets.sql @@ -0,0 +1,73 @@ +MODEL ( + description """ + One row per basket with order-free reads of an unordered ARRAY_AGG of its tags. Snowflake does + not define the element order of an ARRAY_AGG without WITHIN GROUP, so the model outputs only + facts that hold for every order: sizes, membership, overlaps, sorted distinct sets and the + values FLATTEN reads back through a CTE + """, + tags [edge_case], + columns ( + basket_id (type VARCHAR, nullable false, audits [not_null, unique]), + tag_count (type NUMBER(38, 0)), + has_gift (type BOOLEAN), + overlaps_pond_or_garden (type BOOLEAN), + distinct_tag_count (type NUMBER(38, 0)), + distinct_tags (type ARRAY), + flattened_count (type NUMBER(38, 0)), + flattened_tags (type VARCHAR), + ), +); + +WITH lines AS ( + SELECT + l.basket_id, + l.tag + FROM __ref("stg_edge_basket_lines") AS l +), + +baskets AS ( + -- Unordered on purpose: every read below gives the same answer for any element order. + SELECT + l.basket_id, + ARRAY_AGG(l.tag) AS tags + FROM lines AS l + GROUP BY + l.basket_id +), + +flattened AS ( + SELECT + b.basket_id, + f.value::VARCHAR AS tag + FROM baskets AS b, LATERAL FLATTEN(input => b.tags) AS f +), + +flattened_tags AS ( + SELECT + f.basket_id, + COUNT(*) AS flattened_count, + LISTAGG(DISTINCT f.tag, ',') WITHIN GROUP (ORDER BY f.tag) AS flattened_tags + FROM flattened AS f + GROUP BY + f.basket_id +), + +final AS ( + SELECT + b.basket_id, + ARRAY_SIZE(b.tags)::NUMBER(38, 0) AS tag_count, + ARRAY_CONTAINS('gift'::VARIANT, b.tags)::BOOLEAN AS has_gift, + ARRAYS_OVERLAP(b.tags, ARRAY_CONSTRUCT('pond', 'garden'))::BOOLEAN AS overlaps_pond_or_garden, + ARRAY_SIZE( + ARRAY_DISTINCT(ARRAY_CAT(b.tags, ARRAY_CONSTRUCT('gift'))) + )::NUMBER(38, 0) AS distinct_tag_count, + ARRAY_SORT(ARRAY_DISTINCT(ARRAY_CAT(b.tags, ARRAY_CONSTRUCT('gift'))))::ARRAY AS distinct_tags, + COALESCE(t.flattened_count, 0)::NUMBER(38, 0) AS flattened_count, + t.flattened_tags::VARCHAR AS flattened_tags + FROM baskets AS b + LEFT JOIN flattened_tags AS t + ON b.basket_id = t.basket_id +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_basket_top_tags.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_basket_top_tags.sql new file mode 100644 index 0000000000..215a2046c1 --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_basket_top_tags.sql @@ -0,0 +1,120 @@ +MODEL ( + description """ + The basket whose unordered tag array sorts last, picked by ORDER BY ... DESC LIMIT 1 written by + position, by name and through IFF, over a UNION ALL, and the first line by a positional ORDER BY + over a constructed array. How Snowflake ranks an ARRAY_AGG depends on its element order, which + it does not define; the fixtures give every basket a different set of first elements, so the + pick is the same for every element order + """, + tags [edge_case], + columns ( + pick_kind (type VARCHAR, nullable false, audits [not_null, unique]), + picked_id (type VARCHAR), + picked_size (type NUMBER(38, 0)), + ), +); + +WITH lines AS ( + SELECT + l.line_id, + l.basket_id, + l.sku, + l.tag + FROM __ref("stg_edge_basket_lines") AS l +), + +baskets AS ( + SELECT + l.basket_id, + ARRAY_AGG(l.tag) AS tags + FROM lines AS l + GROUP BY + l.basket_id +), + +by_position AS ( + SELECT + b.basket_id, + b.tags + FROM baskets AS b + ORDER BY + 2 DESC + LIMIT 1 +), + +by_name AS ( + SELECT b.basket_id + FROM baskets AS b + ORDER BY + b.tags DESC + LIMIT 1 +), + +through_iff AS ( + SELECT + b.basket_id, + IFF(TRUE, b.tags, b.tags) AS tags + FROM baskets AS b + ORDER BY + 2 DESC + LIMIT 1 +), + +over_union AS ( + SELECT + b.basket_id, + b.tags + FROM baskets AS b + UNION ALL + SELECT + 'B-0' AS basket_id, + ARRAY_CONSTRUCT('aaa') AS tags +ORDER BY 2 DESC +LIMIT 1 +), + +constructed AS ( + SELECT + l.line_id, + ARRAY_CONSTRUCT(l.sku) AS skus + FROM lines AS l + ORDER BY + 2, + 1 + LIMIT 1 +), + +final AS ( + SELECT + 'by_position' AS pick_kind, + p.basket_id AS picked_id, + ARRAY_SIZE(p.tags)::NUMBER(38, 0) AS picked_size + FROM by_position AS p + UNION ALL + SELECT + 'by_name' AS pick_kind, + n.basket_id AS picked_id, + NULL::NUMBER(38, 0) AS picked_size + FROM by_name AS n + UNION ALL + SELECT + 'through_iff' AS pick_kind, + i.basket_id AS picked_id, + ARRAY_SIZE(i.tags)::NUMBER(38, 0) AS picked_size + FROM through_iff AS i + UNION ALL + SELECT + 'over_union' AS pick_kind, + u.basket_id AS picked_id, + ARRAY_SIZE(u.tags)::NUMBER(38, 0) AS picked_size + FROM over_union AS u + UNION ALL + SELECT + 'constructed' AS pick_kind, + c.line_id AS picked_id, + ARRAY_SIZE(c.skus)::NUMBER(38, 0) AS picked_size + FROM constructed AS c +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_clock_arithmetic.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_clock_arithmetic.sql new file mode 100644 index 0000000000..8f6521bf7d --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_clock_arithmetic.sql @@ -0,0 +1,126 @@ +MODEL ( + description """ + One row per partner clock reading with time arithmetic at its edges: DATEADD and TIMEADD on a + TIME(3) and the type they return, DATEDIFF across units, TIME(9) values formatted with fewer + fraction digits (Snowflake truncates), FF3 and bare FF on timestamps of precision 3 and 9, a + TIMESTAMP_NTZ(3) and its NTZ(0) cast compared with text holding finer fractions (cast to + TIMESTAMP_NTZ, which Snowflake reads at precision 9), and the types of casts to TIME(3), + TO_TIMESTAMP of a NUMBER(13,3) and a bare TIMESTAMP cast + """, + tags [edge_case], + columns ( + text_id (type VARCHAR, nullable false, audits [not_null, unique]), + time_plus_minutes (type TIME), + time_minus_hours (type TIME), + time_plus_millisecond (type TIME), + time_add_type (type VARCHAR), + time_cast_type (type VARCHAR), + wall_clock_plus_day (type TIMESTAMP_NTZ), + wall_clock_add_type (type VARCHAR), + years_to_2027 (type NUMBER(38, 0)), + months_to_2027 (type NUMBER(38, 0)), + weeks_to_2027 (type NUMBER(38, 0)), + hours_to_2027 (type NUMBER(38, 0)), + milliseconds_to_2027 (type NUMBER(38, 0)), + minutes_to_midnight (type NUMBER(38, 0)), + fine_time_micro_text (type VARCHAR), + fine_time_second_text (type VARCHAR), + fine_time_milli_text (type VARCHAR), + text_clock_ff3 (type VARCHAR), + wall_clock_bare_ff (type VARCHAR), + wall_clock_equals_text (type BOOLEAN), + whole_second_equals_text (type BOOLEAN), + whole_second_before_text (type BOOLEAN), + epoch_millis_type (type VARCHAR), + bare_timestamp_type (type VARCHAR), + ), +); + +WITH clocks AS ( + SELECT + c.text_id, + c.wall_clock, + c.wall_clock_text, + c.local_time, + c.fine_time + FROM __ref("stg_edge_date_texts") AS c +), + +final AS ( + SELECT + c.text_id, + DATEADD('minute', 30, c.local_time)::TIME AS time_plus_minutes, + TIMEADD('hour', -10, c.local_time)::TIME AS time_minus_hours, + DATEADD('millisecond', 1, c.local_time)::TIME AS time_plus_millisecond, + REGEXP_REPLACE( + SYSTEM$TYPEOF(DATEADD('minute', 30, c.local_time)), + '\\[.*\\]$', + '' + )::VARCHAR AS time_add_type, + REGEXP_REPLACE( + SYSTEM$TYPEOF(c.wall_clock::TIME(3)), + '\\[.*\\]$', + '' + )::VARCHAR AS time_cast_type, + DATEADD('day', 1, c.wall_clock) AS wall_clock_plus_day, + REGEXP_REPLACE( + SYSTEM$TYPEOF(DATEADD('day', 1, c.wall_clock)), + '\\[.*\\]$', + '' + )::VARCHAR AS wall_clock_add_type, + DATEDIFF( + 'year', + c.wall_clock, + '2027-01-01 00:00:00'::TIMESTAMP_NTZ + )::NUMBER(38, 0) AS years_to_2027, + DATEDIFF( + 'month', + c.wall_clock, + '2027-01-01 00:00:00'::TIMESTAMP_NTZ + )::NUMBER(38, 0) AS months_to_2027, + DATEDIFF( + 'week', + c.wall_clock, + '2027-01-01 00:00:00'::TIMESTAMP_NTZ + )::NUMBER(38, 0) AS weeks_to_2027, + DATEDIFF( + 'hour', + c.wall_clock, + '2027-01-01 00:00:00'::TIMESTAMP_NTZ + )::NUMBER(38, 0) AS hours_to_2027, + DATEDIFF( + 'millisecond', + c.wall_clock, + '2027-01-01 00:00:00'::TIMESTAMP_NTZ + )::NUMBER(38, 0) AS milliseconds_to_2027, + DATEDIFF( + 'minute', + c.local_time, + '23:59:59'::TIME + )::NUMBER(38, 0) AS minutes_to_midnight, + TO_CHAR(c.fine_time, 'HH24:MI:SS.FF6')::VARCHAR AS fine_time_micro_text, + TO_CHAR(c.fine_time, 'HH24:MI:SS')::VARCHAR AS fine_time_second_text, + TO_CHAR(c.fine_time, 'HH24:MI:SS.FF3')::VARCHAR AS fine_time_milli_text, + TO_CHAR( + TRY_TO_TIMESTAMP_NTZ(c.wall_clock_text), + 'YYYY-MM-DD HH24:MI:SS.FF3' + )::VARCHAR AS text_clock_ff3, + TO_CHAR(c.wall_clock, 'HH24:MI:SS.FF')::VARCHAR AS wall_clock_bare_ff, + c.wall_clock = c.wall_clock_text::TIMESTAMP_NTZ AS wall_clock_equals_text, + c.wall_clock::TIMESTAMP_NTZ(0) = c.wall_clock_text::TIMESTAMP_NTZ AS whole_second_equals_text, + c.wall_clock::TIMESTAMP_NTZ(0) < c.wall_clock_text::TIMESTAMP_NTZ AS whole_second_before_text, + REGEXP_REPLACE( + SYSTEM$TYPEOF(TO_TIMESTAMP(1772359200.123::NUMBER(13, 3))), + '\\[.*\\]$', + '' + )::VARCHAR AS epoch_millis_type, + REGEXP_REPLACE( + SYSTEM$TYPEOF('2026-03-01 10:15:00'::TIMESTAMP), + '\\[.*\\]$', + '' + )::VARCHAR AS bare_timestamp_type + FROM clocks AS c +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_grouped_windows.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_grouped_windows.sql new file mode 100644 index 0000000000..7bda5d16bb --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_grouped_windows.sql @@ -0,0 +1,78 @@ +MODEL ( + description """ + One row per check group and one grand-total row from GROUP BY ROLLUP, whose total row holds a + NULL group code. Aggregates sit inside window functions in the grouped query: a grand total and + a share, a RANK ordered by COUNT(*), and a QUALIFY that keeps the group with the largest SUM + """, + tags [edge_case], + columns ( + group_label (type VARCHAR, nullable false, audits [not_null, unique]), + group_code (type VARCHAR), + group_amount (type NUMBER(38, 2)), + check_count (type NUMBER(38, 0)), + total_amount (type NUMBER(38, 2)), + amount_share (type NUMBER(38, 6)), + size_rank (type NUMBER(38, 0)), + is_largest_group (type BOOLEAN), + ), +); + +WITH checks AS ( + SELECT + c.group_code, + c.amount + FROM __ref("stg_edge_amount_checks") AS c +), + +grouped AS ( + SELECT + c.group_code, + SUM(SUM(c.amount)) OVER () AS total_amount, + DIV0(SUM(c.amount), SUM(SUM(c.amount)) OVER ()) AS amount_share, + RANK() OVER (ORDER BY COUNT(*) DESC, c.group_code) AS size_rank + FROM checks AS c + GROUP BY + c.group_code +), + +largest AS ( + SELECT c.group_code + FROM checks AS c + GROUP BY + c.group_code + QUALIFY ROW_NUMBER() OVER (ORDER BY SUM(c.amount) DESC NULLS LAST, c.group_code) = 1 +), + +rolled_up AS ( + SELECT + c.group_code, + SUM(c.amount) AS group_amount, + COUNT(*) AS check_count + FROM checks AS c + GROUP BY + ROLLUP ( + c.group_code + ) +), + +final AS ( + SELECT + COALESCE(r.group_code, 'ALL')::VARCHAR AS group_label, + r.group_code::VARCHAR AS group_code, + r.group_amount::NUMBER(38, 2) AS group_amount, + r.check_count::NUMBER(38, 0) AS check_count, + g.total_amount::NUMBER(38, 2) AS total_amount, + g.amount_share::NUMBER(38, 6) AS amount_share, + g.size_rank::NUMBER(38, 0) AS size_rank, + ( + l.group_code IS NOT NULL + )::BOOLEAN AS is_largest_group + FROM rolled_up AS r + LEFT JOIN grouped AS g + ON r.group_code = g.group_code + LEFT JOIN largest AS l + ON r.group_code = l.group_code +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_json_numbers.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_json_numbers.sql new file mode 100644 index 0000000000..6473251cac --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_json_numbers.sql @@ -0,0 +1,48 @@ +MODEL ( + description """ + One row per check whose JSON number text is parsed with TRY_PARSE_JSON: whole numbers written + with zero fractions, invalid numbers with two decimal points, a 38-digit number ending in .0, a + one followed by forty zero decimals, negative zero and exponents, with the parsed kind and the + JSON text Snowflake writes back + """, + tags [edge_case], + columns ( + check_id (type VARCHAR, nullable false, audits [not_null, unique]), + json_text (type VARCHAR), + parsed_kind (type VARCHAR), + parsed_text (type VARCHAR), + amount_kind (type VARCHAR), + amount_text (type VARCHAR), + is_valid_json (type BOOLEAN), + ), +); + +WITH checks AS ( + SELECT + c.check_id, + c.json_text + FROM __ref("stg_edge_amount_checks") AS c +), + +parsed AS ( + SELECT + c.check_id, + c.json_text, + TRY_PARSE_JSON(c.json_text) AS document + FROM checks AS c +), + +final AS ( + SELECT + p.check_id, + p.json_text, + TYPEOF(p.document)::VARCHAR AS parsed_kind, + TO_JSON(p.document)::VARCHAR AS parsed_text, + TYPEOF(p.document:amount)::VARCHAR AS amount_kind, + p.document:amount::VARCHAR AS amount_text, + CHECK_JSON(p.json_text) IS NULL AS is_valid_json + FROM parsed AS p +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_key_widths.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_key_widths.sql new file mode 100644 index 0000000000..71ba3b0458 --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_key_widths.sql @@ -0,0 +1,144 @@ +MODEL ( + description """ + One row per check pair matched by JOIN ... USING between a NUMBER(18,0) key and a NUMBER(38,0) + key, inner, left and full, including a 23-digit key, with the types Snowflake gives the merged + key and each side's qualified key; and a text key joined to a number key, where Snowflake + compares the text as a number + """, + tags [edge_case], + columns ( + pair_id (type VARCHAR, nullable false, audits [not_null, unique]), + join_kind (type VARCHAR), + check_key_text (type VARCHAR), + narrow_check_id (type VARCHAR), + wide_check_id (type VARCHAR), + merged_key_type (type VARCHAR), + narrow_side_type (type VARCHAR), + wide_side_type (type VARCHAR), + ), +); + +WITH checks AS ( + SELECT + c.check_id, + c.narrow_key, + c.wide_key, + c.key_text + FROM __ref("stg_edge_amount_checks") AS c +), + +narrow AS ( + SELECT + c.check_id AS narrow_check_id, + c.narrow_key AS check_key + FROM checks AS c + WHERE c.narrow_key IS NOT NULL +), + +wide AS ( + SELECT + c.check_id AS wide_check_id, + c.wide_key AS check_key + FROM checks AS c + WHERE c.wide_key IS NOT NULL +), + +text_keys AS ( + SELECT + c.check_id AS text_check_id, + c.key_text AS check_key + FROM checks AS c + WHERE TRY_TO_NUMBER(c.key_text) IS NOT NULL +), + +inner_pairs AS ( + SELECT + 'inner' AS join_kind, + check_key::VARCHAR AS check_key_text, + n.narrow_check_id, + w.wide_check_id, + SYSTEM$TYPEOF(check_key) AS merged_key_type, + SYSTEM$TYPEOF(n.check_key) AS narrow_side_type, + SYSTEM$TYPEOF(w.check_key) AS wide_side_type + FROM narrow AS n + INNER JOIN wide AS w + USING (check_key) +), + +left_pairs AS ( + SELECT + 'left' AS join_kind, + check_key::VARCHAR AS check_key_text, + n.narrow_check_id, + w.wide_check_id, + SYSTEM$TYPEOF(check_key) AS merged_key_type, + SYSTEM$TYPEOF(n.check_key) AS narrow_side_type, + SYSTEM$TYPEOF(w.check_key) AS wide_side_type + FROM narrow AS n + LEFT JOIN wide AS w + USING (check_key) +), + +full_pairs AS ( + SELECT + 'full' AS join_kind, + check_key::VARCHAR AS check_key_text, + n.narrow_check_id, + w.wide_check_id, + SYSTEM$TYPEOF(check_key) AS merged_key_type, + SYSTEM$TYPEOF(n.check_key) AS narrow_side_type, + SYSTEM$TYPEOF(w.check_key) AS wide_side_type + FROM narrow AS n + FULL JOIN wide AS w + USING (check_key) +), + +text_pairs AS ( + SELECT + 'text' AS join_kind, + check_key::VARCHAR AS check_key_text, + t.text_check_id AS narrow_check_id, + w.wide_check_id, + SYSTEM$TYPEOF(check_key) AS merged_key_type, + SYSTEM$TYPEOF(t.check_key) AS narrow_side_type, + SYSTEM$TYPEOF(w.check_key) AS wide_side_type + FROM text_keys AS t + INNER JOIN wide AS w + USING (check_key) +), + +all_pairs AS ( + SELECT * + FROM inner_pairs + UNION ALL BY NAME + SELECT * + FROM left_pairs + UNION ALL BY NAME + SELECT * + FROM full_pairs + UNION ALL BY NAME + SELECT * + FROM text_pairs +), + +final AS ( + SELECT + ( + a.join_kind + || ':' + || COALESCE(a.narrow_check_id, '-') + || ':' + || COALESCE(a.wide_check_id, '-') + )::VARCHAR AS pair_id, + a.join_kind::VARCHAR AS join_kind, + a.check_key_text::VARCHAR AS check_key_text, + a.narrow_check_id::VARCHAR AS narrow_check_id, + a.wide_check_id::VARCHAR AS wide_check_id, + REGEXP_REPLACE(a.merged_key_type, '\\[.*\\]$', '')::VARCHAR AS merged_key_type, + REGEXP_REPLACE(a.narrow_side_type, '\\[.*\\]$', '')::VARCHAR AS narrow_side_type, + REGEXP_REPLACE(a.wide_side_type, '\\[.*\\]$', '')::VARCHAR AS wide_side_type + FROM all_pairs AS a +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_null_arithmetic.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_null_arithmetic.sql new file mode 100644 index 0000000000..a30d9f7c25 --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_null_arithmetic.sql @@ -0,0 +1,163 @@ +MODEL ( + description """ + One row per check with NULL arithmetic and literal typing: NULL written first, FLOAT / NULL, + NUMBER(38,2) * NULL, a NULL column from a CTE, and the types Snowflake gives them, decimal and + exponent literals, COUNT, ROW_NUMBER, % and NULLIF, a column NULL in every branch of a UNION, + and a UNION with a decimal literal branch. Also CASE without ELSE and DECODE mixing text with + numbers, where no branch reads the text that is not a number + """, + tags [edge_case], + columns ( + check_id (type VARCHAR, nullable false, audits [not_null, unique]), + null_plus_amount (type NUMBER(38, 2)), + null_times_amount (type NUMBER(38, 2)), + null_over_amount (type NUMBER(38, 6)), + ratio_over_null (type FLOAT), + wide_amount_times_null (type NUMBER(38, 2)), + missing_times_amount (type NUMBER(38, 2)), + null_times_amount_or_zero (type VARCHAR), + null_over_amount_type (type VARCHAR), + ratio_over_null_type (type VARCHAR), + wide_amount_times_null_type (type VARCHAR), + missing_times_amount_type (type VARCHAR), + amount_or_decimal (type VARCHAR), + amount_or_decimal_type (type VARCHAR), + exponent_type (type VARCHAR), + small_exponent_product_type (type VARCHAR), + remainder_type (type VARCHAR), + key_remainder_type (type VARCHAR), + nullif_type (type VARCHAR), + ilike_null_type (type VARCHAR), + row_number_type (type VARCHAR), + count_type (type VARCHAR), + union_decimal_type (type VARCHAR), + all_null_union_type (type VARCHAR), + case_without_else (type VARCHAR), + decoded (type VARCHAR), + ), +); + +WITH checks AS ( + SELECT + c.check_id, + c.narrow_key, + c.key_text, + c.amount, + c.ratio + FROM __ref("stg_edge_amount_checks") AS c +), + +missing AS ( + SELECT NULL AS missing_amount +), + +counted AS ( + SELECT SYSTEM$TYPEOF(COUNT(c.check_id)) AS count_type + FROM checks AS c +), + +with_decimal AS ( + SELECT c.amount AS mixed_amount + FROM checks AS c + UNION ALL + SELECT 1.5 AS mixed_amount +), + +union_typed AS ( + SELECT MAX(SYSTEM$TYPEOF(w.mixed_amount)) AS union_decimal_type + FROM with_decimal AS w +), + +all_null AS ( + SELECT NULL AS note + UNION ALL + SELECT NULL AS note +), + +all_null_typed AS ( + SELECT MAX(SYSTEM$TYPEOF(a.note)) AS all_null_union_type + FROM all_null AS a +), + +computed AS ( + SELECT + c.check_id, + NULL + c.amount AS null_plus_amount, + NULL * c.amount AS null_times_amount, + NULL / c.amount AS null_over_amount, + c.ratio / NULL AS ratio_over_null, + c.amount::NUMBER(38, 2) * NULL AS wide_amount_times_null, + m.missing_amount * c.amount AS missing_times_amount, + COALESCE(NULL * c.amount, 0)::VARCHAR AS null_times_amount_or_zero, + SYSTEM$TYPEOF(NULL / c.amount) AS null_over_amount_type, + SYSTEM$TYPEOF(c.ratio / NULL) AS ratio_over_null_type, + SYSTEM$TYPEOF(c.amount::NUMBER(38, 2) * NULL) AS wide_amount_times_null_type, + SYSTEM$TYPEOF(m.missing_amount * c.amount) AS missing_times_amount_type, + COALESCE(c.amount, 1.5)::VARCHAR AS amount_or_decimal, + SYSTEM$TYPEOF(COALESCE(c.amount, 1.5)) AS amount_or_decimal_type, + SYSTEM$TYPEOF(1e5) AS exponent_type, + SYSTEM$TYPEOF(c.amount * 1e-12) AS small_exponent_product_type, + SYSTEM$TYPEOF(7 % 3) AS remainder_type, + SYSTEM$TYPEOF(c.narrow_key % 3) AS key_remainder_type, + SYSTEM$TYPEOF(NULLIF(c.amount, 1)) AS nullif_type, + SYSTEM$TYPEOF(c.key_text ILIKE NULL) AS ilike_null_type, + SYSTEM$TYPEOF(ROW_NUMBER() OVER (ORDER BY c.check_id)) AS row_number_type, + CASE + WHEN c.narrow_key = 7 + THEN c.narrow_key + WHEN c.narrow_key = 12 + THEN c.key_text + END AS case_without_else, + DECODE(c.narrow_key, 7, c.amount, 12, c.key_text) AS decoded + FROM checks AS c + CROSS JOIN missing AS m +), + +final AS ( + SELECT + c.check_id, + c.null_plus_amount::NUMBER(38, 2) AS null_plus_amount, + c.null_times_amount::NUMBER(38, 2) AS null_times_amount, + c.null_over_amount::NUMBER(38, 6) AS null_over_amount, + c.ratio_over_null::FLOAT AS ratio_over_null, + c.wide_amount_times_null::NUMBER(38, 2) AS wide_amount_times_null, + c.missing_times_amount::NUMBER(38, 2) AS missing_times_amount, + c.null_times_amount_or_zero, + REGEXP_REPLACE(c.null_over_amount_type, '\\[.*\\]$', '')::VARCHAR AS null_over_amount_type, + REGEXP_REPLACE(c.ratio_over_null_type, '\\[.*\\]$', '')::VARCHAR AS ratio_over_null_type, + REGEXP_REPLACE( + c.wide_amount_times_null_type, + '\\[.*\\]$', + '' + )::VARCHAR AS wide_amount_times_null_type, + REGEXP_REPLACE( + c.missing_times_amount_type, + '\\[.*\\]$', + '' + )::VARCHAR AS missing_times_amount_type, + c.amount_or_decimal, + REGEXP_REPLACE(c.amount_or_decimal_type, '\\[.*\\]$', '')::VARCHAR AS amount_or_decimal_type, + REGEXP_REPLACE(c.exponent_type, '\\[.*\\]$', '')::VARCHAR AS exponent_type, + REGEXP_REPLACE( + c.small_exponent_product_type, + '\\[.*\\]$', + '' + )::VARCHAR AS small_exponent_product_type, + REGEXP_REPLACE(c.remainder_type, '\\[.*\\]$', '')::VARCHAR AS remainder_type, + REGEXP_REPLACE(c.key_remainder_type, '\\[.*\\]$', '')::VARCHAR AS key_remainder_type, + REGEXP_REPLACE(c.nullif_type, '\\[.*\\]$', '')::VARCHAR AS nullif_type, + REGEXP_REPLACE(c.ilike_null_type, '\\[.*\\]$', '')::VARCHAR AS ilike_null_type, + REGEXP_REPLACE(c.row_number_type, '\\[.*\\]$', '')::VARCHAR AS row_number_type, + REGEXP_REPLACE(k.count_type, '\\[.*\\]$', '')::VARCHAR AS count_type, + REGEXP_REPLACE(u.union_decimal_type, '\\[.*\\]$', '')::VARCHAR AS union_decimal_type, + REGEXP_REPLACE(n.all_null_union_type, '\\[.*\\]$', '')::VARCHAR AS all_null_union_type, + c.case_without_else::VARCHAR AS case_without_else, + c.decoded::VARCHAR AS decoded + FROM computed AS c + CROSS JOIN counted AS k + CROSS JOIN union_typed AS u + CROSS JOIN all_null_typed AS n +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_text_dates.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_text_dates.sql new file mode 100644 index 0000000000..2a0a9b7c8f --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_text_dates.sql @@ -0,0 +1,43 @@ +MODEL ( + description """ + One row per partner date text read with TRY_TO_DATE under numeric and month-name formats: + unpadded days and months, two-digit years, ISO T and Z separators, trailing letters, a long s in + a month name, mixed separators and runs of spaces + """, + tags [edge_case], + columns ( + text_id (type VARCHAR, nullable false, audits [not_null, unique]), + date_text (type VARCHAR), + day_month_dots (type DATE), + iso_date (type DATE), + day_month_short_year (type DATE), + month_day_slashes (type DATE), + day_month_name (type DATE), + month_name_first (type DATE), + auto_date (type DATE), + ), +); + +WITH texts AS ( + SELECT + t.text_id, + t.date_text + FROM __ref("stg_edge_date_texts") AS t +), + +final AS ( + SELECT + t.text_id, + t.date_text, + TRY_TO_DATE(t.date_text, 'DD.MM.YYYY') AS day_month_dots, + TRY_TO_DATE(t.date_text, 'YYYY-MM-DD') AS iso_date, + TRY_TO_DATE(t.date_text, 'DD/MM/YY') AS day_month_short_year, + TRY_TO_DATE(t.date_text, 'MM/DD/YYYY') AS month_day_slashes, + TRY_TO_DATE(t.date_text, 'DD MON YYYY') AS day_month_name, + TRY_TO_DATE(t.date_text, 'MMMM DD YYYY') AS month_name_first, + TRY_TO_DATE(t.date_text) AS auto_date + FROM texts AS t +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_zoned_texts.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_zoned_texts.sql new file mode 100644 index 0000000000..d85e142056 --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_zoned_texts.sql @@ -0,0 +1,86 @@ +MODEL ( + description """ + One row per zoned partner timestamp text read as TIMESTAMP_TZ and TIMESTAMP_LTZ, with T, Z, + +00:00, +0000, +09:00 and -05:30 spellings, read back as the instant at UTC, its date, year and + day start, and compared with wall-clock text, a TIMESTAMP_NTZ and a DATE. Also the date of the + first zoned value by window, CONVERT_TIMEZONE with a lower-case 'utc', and the UTC hour of a + wall-clock time just before 1970. TIMESTAMP_LTZ results depend on the session time zone, which + the edge target pins to UTC + """, + tags [edge_case, known_divergence], + columns ( + text_id (type VARCHAR, nullable false, audits [not_null, unique]), + zoned_text (type VARCHAR), + zoned (type TIMESTAMP_TZ), + local_zoned (type TIMESTAMP_LTZ), + zoned_utc (type TIMESTAMP_NTZ), + local_zoned_utc (type TIMESTAMP_NTZ), + zoned_date (type DATE), + local_zoned_date (type DATE), + zoned_year (type NUMBER(38, 0)), + local_zoned_year (type NUMBER(38, 0)), + zoned_day_start (type TIMESTAMP_TZ), + local_zoned_day_start (type TIMESTAMP_LTZ), + zoned_equals_wall_text (type BOOLEAN), + local_zoned_equals_wall_text (type BOOLEAN), + zoned_after_wall_clock (type BOOLEAN), + local_zoned_after_wall_clock (type BOOLEAN), + zoned_from_next_day (type BOOLEAN), + local_zoned_from_next_day (type BOOLEAN), + first_zoned_date (type DATE), + zoned_lower_utc (type TIMESTAMP_NTZ), + wall_clock_utc_hour (type NUMBER(38, 0)), + ), +); + +WITH texts AS ( + SELECT + t.text_id, + t.zoned_text, + t.wall_clock, + t.wall_clock_text + FROM __ref("stg_edge_date_texts") AS t +), + +parsed AS ( + SELECT + t.text_id, + t.zoned_text, + t.wall_clock, + t.wall_clock_text, + TRY_TO_TIMESTAMP_TZ(t.zoned_text) AS zoned, + TRY_TO_TIMESTAMP_LTZ(t.zoned_text) AS local_zoned + FROM texts AS t +), + +final AS ( + SELECT + p.text_id, + p.zoned_text, + p.zoned, + p.local_zoned, + CONVERT_TIMEZONE('UTC', p.zoned)::TIMESTAMP_NTZ AS zoned_utc, + CONVERT_TIMEZONE('UTC', p.local_zoned)::TIMESTAMP_NTZ AS local_zoned_utc, + TO_DATE(p.zoned) AS zoned_date, + TO_DATE(p.local_zoned) AS local_zoned_date, + YEAR(p.zoned)::NUMBER(38, 0) AS zoned_year, + YEAR(p.local_zoned)::NUMBER(38, 0) AS local_zoned_year, + DATE_TRUNC('day', p.zoned) AS zoned_day_start, + DATE_TRUNC('day', p.local_zoned) AS local_zoned_day_start, + p.zoned = p.wall_clock_text::TIMESTAMP_TZ AS zoned_equals_wall_text, + p.local_zoned = p.wall_clock_text::TIMESTAMP_LTZ AS local_zoned_equals_wall_text, + p.zoned > p.wall_clock AS zoned_after_wall_clock, + p.local_zoned > p.wall_clock AS local_zoned_after_wall_clock, + p.zoned >= '2026-03-06'::DATE AS zoned_from_next_day, + p.local_zoned >= '2026-03-06'::DATE AS local_zoned_from_next_day, + FIRST_VALUE(p.zoned) OVER ( + ORDER BY p.text_id + ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING + )::DATE AS first_zoned_date, + CONVERT_TIMEZONE('utc', p.zoned)::TIMESTAMP_NTZ AS zoned_lower_utc, + DATE_PART(hour, CONVERT_TIMEZONE('UTC', p.wall_clock))::NUMBER(38, 0) AS wall_clock_utc_hour + FROM parsed AS p +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_amount_edges.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_amount_edges.sql new file mode 100644 index 0000000000..8b22eea798 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_amount_edges.sql @@ -0,0 +1,47 @@ +TEST (name "int_edge_amount_edges__divides_by_zeros_and_truncates_float_ratios"); + +WITH __ref__stg_edge_amount_checks AS ( + SELECT + v.check_id, + v.group_code, + v.narrow_key::NUMBER(18, 0) AS narrow_key, + v.wide_key::NUMBER(38, 0) AS wide_key, + v.key_text, + v.amount::NUMBER(18, 2) AS amount, + v.fine_amount::NUMBER(20, 15) AS fine_amount, + v.ratio::FLOAT AS ratio, + v.json_text + FROM VALUES + ('AC-01', 'G-1', NULL, NULL, '\u0130nf', 10.00, NULL, 0.29, NULL), + ('AC-02', 'G-1', NULL, NULL, 'inf', NULL, NULL, 1.005, NULL), + ('AC-03', 'G-1', NULL, NULL, 'NaN', 2.50, NULL, 2.675, NULL), + ('AC-04', 'G-2', NULL, NULL, '-Infinity', -0.50, NULL, -155.5, NULL), + ('AC-05', 'G-2', NULL, NULL, 'n/a', 0.00, NULL, 0.57, NULL), + ('AC-06', 'G-2', NULL, NULL, '1e3', 4.00, NULL, 3.0, NULL) AS v(check_id, group_code, narrow_key, wide_key, key_text, amount, fine_amount, ratio, json_text) +), + +__expected__int_edge_amount_edges AS ( + SELECT + NULL AS check_id, + NULL AS group_code, + NULL AS one_over_zero, + NULL AS amount_over_zero, + NULL AS amount_over_scaled_zero, + NULL AS amount_over_minus_zero, + NULL AS amount_over_null, + NULL AS null_over_zero, + NULL AS ratio_truncated_2, + NULL AS ratio_truncated_3, + NULL AS ratio_text, + NULL AS pi_text, + NULL AS key_text_as_double, + NULL AS same_amount_lag +), + +__assert__running_count_ends_at_the_group_total AS ( + SELECT e.group_code + FROM __ref("int_edge_amount_edges") AS e + GROUP BY + e.group_code + HAVING MAX(e.running_paid_count) <> IFF(e.group_code = 'G-1', 2, 1) +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_amount_statistics.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_amount_statistics.sql new file mode 100644 index 0000000000..c37192e5b5 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_amount_statistics.sql @@ -0,0 +1,44 @@ +TEST (name "int_edge_amount_statistics__takes_medians_at_every_scale_and_aggregates_over_no_rows"); + +WITH __ref__stg_edge_amount_checks AS ( + SELECT + v.check_id, + v.group_code, + v.narrow_key::NUMBER(18, 0) AS narrow_key, + v.wide_key::NUMBER(38, 0) AS wide_key, + v.key_text, + v.amount::NUMBER(18, 2) AS amount, + v.fine_amount::NUMBER(20, 15) AS fine_amount, + v.ratio::FLOAT AS ratio, + v.json_text + FROM VALUES + ('AC-01', 'G-1', 7, NULL, NULL, 10.00, 0.123456789012345, -155.5, NULL), + ('AC-02', 'G-1', 7, NULL, NULL, 10.00, 0.123456789012344, 0.29, NULL), + ('AC-03', 'G-2', 12, NULL, NULL, NULL, 1.000000000000001, 2.675, NULL), + ('AC-04', 'G-3', 5, NULL, NULL, 0.00, 0.000000000000001, 0.57, NULL), + ('AC-05', 'G-3', 5, NULL, NULL, -0.50, 0.000000000000002, -1.0, NULL) AS v(check_id, group_code, narrow_key, wide_key, key_text, amount, fine_amount, ratio, json_text) +), + +__expected__int_edge_amount_statistics AS ( + SELECT + NULL AS group_code, + NULL AS median_scale_0, + NULL AS median_scale_2, + NULL AS median_scale_4, + NULL AS median_scale_10, + NULL AS median_scale_11, + NULL AS median_scale_12, + NULL AS median_scale_15, + NULL AS mode_amount, + NULL AS paid_count, + NULL AS mode_over_no_rows, + NULL AS median_over_no_rows, + NULL AS paid_count_over_no_rows, + NULL AS mode_with_having_only +), + +__assert__tied_pick_is_a_zero AS ( + SELECT s.group_code + FROM __ref("int_edge_amount_statistics") AS s + WHERE s.signed_zero_pick <> 0 +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_tag_pairs.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_tag_pairs.sql new file mode 100644 index 0000000000..20d557930e --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_tag_pairs.sql @@ -0,0 +1,26 @@ +TEST (name "int_edge_basket_tag_pairs__keeps_pairs_in_different_orders_apart"); + +WITH __ref__stg_edge_basket_lines AS ( + SELECT + v.line_id, + v.basket_id, + v.line_no::NUMBER(38, 0) AS line_no, + v.sku, + v.tag + FROM VALUES + ('BL-01', 'B-1', 1, 'MALLARD-01', 'bath'), + ('BL-02', 'B-1', 2, 'TEAL-02', 'gift'), + ('BL-03', 'B-1', 3, 'EIDER-03', 'bath'), + ('BL-04', 'B-2', 1, 'PINTAIL-04', 'garden'), + ('BL-05', 'B-2', 2, 'MALLARD-01', 'bath'), + ('BL-06', 'B-3', 1, 'MERGANSER-05', 'pond'), + ('BL-07', 'B-4', 1, 'TEAL-02', NULL), + ('BL-08', 'B-4', 2, 'EIDER-03', 'gift') AS v(line_id, basket_id, line_no, sku, tag) +), + +__expected__int_edge_basket_tag_pairs AS ( + SELECT + NULL AS tag_pair_text, + NULL AS tag_pair, + NULL AS pair_size +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_tag_sets.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_tag_sets.sql new file mode 100644 index 0000000000..2e2fd84053 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_tag_sets.sql @@ -0,0 +1,31 @@ +TEST (name "int_edge_basket_tag_sets__reads_unordered_arrays_without_their_order"); + +WITH __ref__stg_edge_basket_lines AS ( + SELECT + v.line_id, + v.basket_id, + v.line_no::NUMBER(38, 0) AS line_no, + v.sku, + v.tag + FROM VALUES + ('BL-01', 'B-1', 1, 'MALLARD-01', 'bath'), + ('BL-02', 'B-1', 2, 'TEAL-02', 'gift'), + ('BL-03', 'B-1', 3, 'EIDER-03', 'bath'), + ('BL-04', 'B-2', 1, 'PINTAIL-04', 'garden'), + ('BL-05', 'B-2', 2, 'MALLARD-01', 'bath'), + ('BL-06', 'B-3', 1, 'MERGANSER-05', 'pond'), + ('BL-07', 'B-4', 1, 'TEAL-02', NULL), + ('BL-08', 'B-4', 2, 'EIDER-03', 'gift') AS v(line_id, basket_id, line_no, sku, tag) +), + +__expected__int_edge_basket_tag_sets AS ( + SELECT + NULL AS basket_id, + NULL AS tag_count, + NULL AS has_gift, + NULL AS overlaps_pond_or_garden, + NULL AS distinct_tag_count, + NULL AS distinct_tags, + NULL AS flattened_count, + NULL AS flattened_tags +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_top_tags.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_top_tags.sql new file mode 100644 index 0000000000..83592df8ca --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_top_tags.sql @@ -0,0 +1,26 @@ +TEST (name "int_edge_basket_top_tags__ranks_arrays_by_position_and_name"); + +WITH __ref__stg_edge_basket_lines AS ( + SELECT + v.line_id, + v.basket_id, + v.line_no::NUMBER(38, 0) AS line_no, + v.sku, + v.tag + FROM VALUES + ('BL-01', 'B-1', 1, 'MALLARD-01', 'bath'), + ('BL-02', 'B-1', 2, 'TEAL-02', 'gift'), + ('BL-03', 'B-1', 3, 'EIDER-03', 'bath'), + ('BL-04', 'B-2', 1, 'PINTAIL-04', 'garden'), + ('BL-05', 'B-2', 2, 'MALLARD-01', 'bath'), + ('BL-06', 'B-3', 1, 'MERGANSER-05', 'pond'), + ('BL-07', 'B-4', 1, 'TEAL-02', NULL), + ('BL-08', 'B-4', 2, 'EIDER-03', 'gift') AS v(line_id, basket_id, line_no, sku, tag) +), + +__expected__int_edge_basket_top_tags AS ( + SELECT + NULL AS pick_kind, + NULL AS picked_id, + NULL AS picked_size +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_clock_arithmetic.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_clock_arithmetic.sql new file mode 100644 index 0000000000..f89bb659ae --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_clock_arithmetic.sql @@ -0,0 +1,47 @@ +TEST (name "int_edge_clock_arithmetic__adds_diffs_and_formats_fine_clock_values"); + +WITH __ref__stg_edge_date_texts AS ( + SELECT + v.text_id, + NULL AS date_text, + NULL AS zoned_text, + v.wall_clock::TIMESTAMP_NTZ(3) AS wall_clock, + v.wall_clock_text, + v.local_time::TIME(3) AS local_time, + v.fine_time::TIME(9) AS fine_time + FROM VALUES + ('CK-01', '2026-03-05 23:30:00.123', '2026-03-05 23:30:00.1239', '23:59:59.999', '23:59:59.9999999'), + ('CK-02', '2026-03-05 23:30:00.000', '2026-03-05 23:30:00.9', '00:00:00', '12:00:00.0000005'), + ('CK-03', '2026-03-05 23:30:00.120', '2026-03-05 23:30:00.12', '12:30:15.250', '00:00:00.000000001'), + ('CK-04', '1969-12-31 23:59:59.999', '1969-12-31 23:59:59.999', '23:30:00', '23:59:59.999999999'), + ('CK-05', '2026-12-31 23:59:59.999', '2026-12-31 23:59:59.999000', '06:00:00.001', '06:00:00.0000001'), + ('CK-06', NULL, NULL, NULL, NULL) AS v(text_id, wall_clock, wall_clock_text, local_time, fine_time) +), + +__expected__int_edge_clock_arithmetic AS ( + SELECT + NULL AS text_id, + NULL AS time_plus_minutes, + NULL AS time_minus_hours, + NULL AS time_plus_millisecond, + NULL AS time_add_type, + NULL AS time_cast_type, + NULL AS wall_clock_plus_day, + NULL AS wall_clock_add_type, + NULL AS years_to_2027, + NULL AS months_to_2027, + NULL AS weeks_to_2027, + NULL AS hours_to_2027, + NULL AS milliseconds_to_2027, + NULL AS minutes_to_midnight, + NULL AS fine_time_micro_text, + NULL AS fine_time_second_text, + NULL AS fine_time_milli_text, + NULL AS text_clock_ff3, + NULL AS wall_clock_bare_ff, + NULL AS wall_clock_equals_text, + NULL AS whole_second_equals_text, + NULL AS whole_second_before_text, + NULL AS epoch_millis_type, + NULL AS bare_timestamp_type +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_grouped_windows.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_grouped_windows.sql new file mode 100644 index 0000000000..e2e5889e49 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_grouped_windows.sql @@ -0,0 +1,33 @@ +TEST (name "int_edge_grouped_windows__ranks_groups_by_aggregates_and_rolls_up_a_null_total"); + +WITH __ref__stg_edge_amount_checks AS ( + SELECT + v.check_id, + v.group_code, + v.narrow_key::NUMBER(18, 0) AS narrow_key, + v.wide_key::NUMBER(38, 0) AS wide_key, + v.key_text, + v.amount::NUMBER(18, 2) AS amount, + v.fine_amount::NUMBER(20, 15) AS fine_amount, + v.ratio::FLOAT AS ratio, + v.json_text + FROM VALUES + ('AC-01', 'G-1', NULL, NULL, NULL, 10.00, NULL, NULL, NULL), + ('AC-02', 'G-1', NULL, NULL, NULL, -0.50, NULL, NULL, NULL), + ('AC-03', 'G-1', NULL, NULL, NULL, 2.50, NULL, NULL, NULL), + ('AC-04', 'G-2', NULL, NULL, NULL, 0.00, NULL, NULL, NULL), + ('AC-05', 'G-2', NULL, NULL, NULL, NULL, NULL, NULL, NULL), + ('AC-06', 'G-3', NULL, NULL, NULL, 999999999999.99, NULL, NULL, NULL) AS v(check_id, group_code, narrow_key, wide_key, key_text, amount, fine_amount, ratio, json_text) +), + +__expected__int_edge_grouped_windows AS ( + SELECT + NULL AS group_label, + NULL AS group_code, + NULL AS group_amount, + NULL AS check_count, + NULL AS total_amount, + NULL AS amount_share, + NULL AS size_rank, + NULL AS is_largest_group +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_json_numbers.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_json_numbers.sql new file mode 100644 index 0000000000..4c2d54bb4f --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_json_numbers.sql @@ -0,0 +1,35 @@ +TEST (name "int_edge_json_numbers__parses_zero_fractions_and_rejects_two_decimal_points"); + +WITH __ref__stg_edge_amount_checks AS ( + SELECT + v.check_id, + NULL AS group_code, + NULL::NUMBER(18, 0) AS narrow_key, + NULL::NUMBER(38, 0) AS wide_key, + NULL AS key_text, + NULL::NUMBER(18, 2) AS amount, + NULL::NUMBER(20, 15) AS fine_amount, + NULL::FLOAT AS ratio, + IFF(v.json_text = '@forty_zeros', '1.' || REPEAT('0', 40), v.json_text) AS json_text + FROM VALUES + ('AC-01', '{"amount": 2.0, "qty": 1.00}'), + ('AC-02', '1.0.5'), + ('AC-03', '{"amount": 1.00.25}'), + ('AC-04', '[1.0.5, 2.0]'), + ('AC-05', '12345678901234567890123456789012345678.0'), + ('AC-06', '@forty_zeros'), + ('AC-07', '{"amount": -0.0}'), + ('AC-08', '{"amount": 2.0e5}'), + ('AC-09', '{"amount": 100.00}'), + ('AC-10', '123456789012345678901234567890123456789.0') AS v(check_id, json_text) +), + +__expected__int_edge_json_numbers AS ( + SELECT + NULL AS check_id, + NULL AS parsed_kind, + NULL AS parsed_text, + NULL AS amount_kind, + NULL AS amount_text, + NULL AS is_valid_json +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_key_widths.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_key_widths.sql new file mode 100644 index 0000000000..c2097f7a97 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_key_widths.sql @@ -0,0 +1,33 @@ +TEST (name "int_edge_key_widths__joins_number_widths_and_text_keys_with_using"); + +WITH __ref__stg_edge_amount_checks AS ( + SELECT + v.check_id, + v.group_code, + v.narrow_key::NUMBER(18, 0) AS narrow_key, + v.wide_key::NUMBER(38, 0) AS wide_key, + v.key_text, + v.amount::NUMBER(18, 2) AS amount, + v.fine_amount::NUMBER(20, 15) AS fine_amount, + v.ratio::FLOAT AS ratio, + v.json_text + FROM VALUES + ('AC-01', 'G-1', 7, '7', '01', 10.00, 0.123456789012345, 0.29, NULL), + ('AC-02', 'G-1', 12, '12345678901234567890123', '1', -0.50, 0.123456789012344, 1.005, NULL), + ('AC-03', 'G-1', NULL, '12', '12', 2.50, 0.5, 2.675, NULL), + ('AC-04', 'G-2', 123456789012345678, '123456789012345678', ' 7', 0.00, 1, -155.5, NULL), + ('AC-05', 'G-2', 99, NULL, NULL, NULL, NULL, NULL, NULL), + ('AC-06', 'G-3', 5, '5', 'n/a', 999999999999.99, 0.000000000000001, 0.57, NULL) AS v(check_id, group_code, narrow_key, wide_key, key_text, amount, fine_amount, ratio, json_text) +), + +__expected__int_edge_key_widths AS ( + SELECT + NULL AS pair_id, + NULL AS join_kind, + NULL AS check_key_text, + NULL AS narrow_check_id, + NULL AS wide_check_id, + NULL AS merged_key_type, + NULL AS narrow_side_type, + NULL AS wide_side_type +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_null_arithmetic.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_null_arithmetic.sql new file mode 100644 index 0000000000..d49f05ecb8 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_null_arithmetic.sql @@ -0,0 +1,51 @@ +TEST (name "int_edge_null_arithmetic__types_null_arithmetic_and_literals"); + +WITH __ref__stg_edge_amount_checks AS ( + SELECT + v.check_id, + v.group_code, + v.narrow_key::NUMBER(18, 0) AS narrow_key, + v.wide_key::NUMBER(38, 0) AS wide_key, + v.key_text, + v.amount::NUMBER(18, 2) AS amount, + v.fine_amount::NUMBER(20, 15) AS fine_amount, + v.ratio::FLOAT AS ratio, + v.json_text + FROM VALUES + ('AC-01', 'G-1', 7, '7', '01', 10.00, 0.123456789012345, 0.29, NULL), + ('AC-02', 'G-1', 12, '12345678901234567890123', '1', -0.50, 0.123456789012344, 1.005, NULL), + ('AC-03', 'G-1', NULL, '12', '12', 2.50, 0.5, 2.675, NULL), + ('AC-04', 'G-2', 123456789012345678, '123456789012345678', ' 7', 0.00, 1, -155.5, NULL), + ('AC-05', 'G-2', 99, NULL, NULL, NULL, NULL, NULL, NULL), + ('AC-06', 'G-3', 5, '5', 'n/a', 999999999999.99, 0.000000000000001, 0.57, NULL) AS v(check_id, group_code, narrow_key, wide_key, key_text, amount, fine_amount, ratio, json_text) +), + +__expected__int_edge_null_arithmetic AS ( + SELECT + NULL AS check_id, + NULL AS null_plus_amount, + NULL AS null_times_amount, + NULL AS null_over_amount, + NULL AS ratio_over_null, + NULL AS wide_amount_times_null, + NULL AS missing_times_amount, + NULL AS null_times_amount_or_zero, + NULL AS null_over_amount_type, + NULL AS ratio_over_null_type, + NULL AS wide_amount_times_null_type, + NULL AS missing_times_amount_type, + NULL AS amount_or_decimal, + NULL AS amount_or_decimal_type, + NULL AS exponent_type, + NULL AS small_exponent_product_type, + NULL AS remainder_type, + NULL AS key_remainder_type, + NULL AS nullif_type, + NULL AS ilike_null_type, + NULL AS row_number_type, + NULL AS count_type, + NULL AS union_decimal_type, + NULL AS all_null_union_type, + NULL AS case_without_else, + NULL AS decoded +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_text_dates.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_text_dates.sql new file mode 100644 index 0000000000..432c8f73f4 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_text_dates.sql @@ -0,0 +1,48 @@ +TEST (name "int_edge_text_dates__reads_numeric_and_month_name_formats"); + +WITH __ref__stg_edge_date_texts AS ( + SELECT + v.text_id, + v.date_text, + NULL AS zoned_text, + NULL::TIMESTAMP_NTZ(3) AS wall_clock, + NULL AS wall_clock_text, + NULL::TIME(3) AS local_time, + NULL::TIME(9) AS fine_time + FROM VALUES + ('DT-01', '5.3.2026'), + ('DT-02', '05.03.26'), + ('DT-03', '2026-3-5'), + ('DT-04', '05/03/26'), + ('DT-05', '5/3/26'), + ('DT-06', '03/05/2026'), + ('DT-07', '2026-10-05T10:00'), + ('DT-08', '2025-10-05Z'), + ('DT-09', '2025-10-05t'), + ('DT-10', '2025-10-05x'), + ('DT-11', '2025-10-05T10:00x'), + ('DT-12', '05 Augu\u017ft 2024'), + ('DT-13', '05 August 2024'), + ('DT-14', '05-Mar/2024'), + ('DT-15', '05Mar 2024'), + ('DT-16', '05 Mar2024'), + ('DT-17', ' 05 Mar 2024 '), + ('DT-18', 'March 05 2024'), + ('DT-19', '5 Mar 20245'), + ('DT-20', 'March 005 2024'), + ('DT-21', '15 oktober 2025'), + ('DT-22', '2026-02-30'), + ('DT-23', NULL) AS v(text_id, date_text) +), + +__expected__int_edge_text_dates AS ( + SELECT + NULL AS text_id, + NULL AS day_month_dots, + NULL AS iso_date, + NULL AS day_month_short_year, + NULL AS month_day_slashes, + NULL AS day_month_name, + NULL AS month_name_first, + NULL AS auto_date +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_zoned_texts.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_zoned_texts.sql new file mode 100644 index 0000000000..57c5d65e4b --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_zoned_texts.sql @@ -0,0 +1,46 @@ +TEST (name "int_edge_zoned_texts__reads_offsets_back_as_utc_instants_dates_and_years"); + +WITH __ref__stg_edge_date_texts AS ( + SELECT + v.text_id, + NULL AS date_text, + v.zoned_text, + v.wall_clock::TIMESTAMP_NTZ(3) AS wall_clock, + v.wall_clock_text, + NULL::TIME(3) AS local_time, + NULL::TIME(9) AS fine_time + FROM VALUES + ('ZT-01', '2026-03-06T08:30:00+09:00', '2026-03-05 23:30:00.000', '2026-03-05 23:30:00'), + ('ZT-02', '2026-03-05T23:30:00Z', '2026-03-05 23:00:00.000', '2026-03-05 23:30:00'), + ('ZT-03', '2026-03-05 23:30:00+00:00', '2026-03-06 00:00:00.000', '2026-03-05 23:30:00'), + ('ZT-04', '2026-03-05 23:30:00 +0000', '2026-03-05 23:30:00.000', '2026-03-05 23:30:00.000001'), + ('ZT-05', '2026-03-05T18:00:00-05:30', '2026-03-05 23:29:59.999', '2026-03-05 23:30:00'), + ('ZT-06', '2026-12-31T23:30:00-05:30', '2027-01-01 00:00:00.000', '2027-01-01 05:00:00'), + ('ZT-07', '2026-03-05T23:30:00', '2026-03-05 23:30:00.000', '2026-03-05 23:30:00'), + ('ZT-08', NULL, '1969-12-31 23:59:59.999', '1969-12-31 23:59:59.999'), + ('ZT-09', 'not a time', NULL, NULL) AS v(text_id, zoned_text, wall_clock, wall_clock_text) +), + +__expected__int_edge_zoned_texts AS ( + SELECT + NULL AS text_id, + NULL AS zoned, + NULL AS local_zoned, + NULL AS zoned_utc, + NULL AS local_zoned_utc, + NULL AS zoned_date, + NULL AS local_zoned_date, + NULL AS zoned_year, + NULL AS local_zoned_year, + NULL AS zoned_day_start, + NULL AS local_zoned_day_start, + NULL AS zoned_equals_wall_text, + NULL AS local_zoned_equals_wall_text, + NULL AS zoned_after_wall_clock, + NULL AS local_zoned_after_wall_clock, + NULL AS zoned_from_next_day, + NULL AS local_zoned_from_next_day, + NULL AS first_zoned_date, + NULL AS zoned_lower_utc, + NULL AS wall_clock_utc_hour +) From 45cfdca6e15bf0c871cceb2dbf79e68fd85d32ad Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:28:13 +0000 Subject: [PATCH 3/8] test(benchmarks): add DuckShop edge cases for VARIANT comparisons, text patterns and lineage ARRAY_MAX, ARRAY_MIN and ARRAY_REVERSE over mixed arrays, VARIANT equality across types and typed leaves, recursive FLATTEN paths, regular expression and case-folding edges, CONNECT BY beside a level column, recursive CTEs without RECURSIVE, row-deciding clauses, join keys from the right side and SQL that name checks have flagged. Expectations await capture on Snowflake. --- .../edge_cases/int_edge_analysis_patterns.sql | 102 +++++++++ .../edge_cases/int_edge_category_walk.sql | 57 +++++ .../edge_cases/int_edge_join_keys.sql | 125 +++++++++++ .../edge_cases/int_edge_note_patterns.sql | 72 +++++++ .../edge_cases/int_edge_payload_paths.sql | 52 +++++ .../edge_cases/int_edge_recursive_counts.sql | 57 +++++ .../edge_cases/int_edge_row_deciders.sql | 197 ++++++++++++++++++ .../edge_cases/int_edge_variant_extremes.sql | 55 +++++ .../edge_cases/int_edge_variant_matches.sql | 151 ++++++++++++++ .../duckshop/project/sqlbuild_project.toml | 36 ++++ .../test_int_edge_analysis_patterns.sql | 46 ++++ .../test_int_edge_category_walk.sql | 24 +++ .../edge_cases/test_int_edge_join_keys.sql | 30 +++ .../test_int_edge_note_patterns.sql | 49 +++++ .../test_int_edge_payload_paths.sql | 29 +++ .../test_int_edge_recursive_counts.sql | 23 ++ .../edge_cases/test_int_edge_row_deciders.sql | 27 +++ .../test_int_edge_variant_extremes.sql | 50 +++++ .../test_int_edge_variant_matches.sql | 41 ++++ 19 files changed, 1223 insertions(+) create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_analysis_patterns.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_category_walk.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_join_keys.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_note_patterns.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_payload_paths.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_recursive_counts.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_row_deciders.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_variant_extremes.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_variant_matches.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_analysis_patterns.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_category_walk.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_join_keys.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_note_patterns.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_payload_paths.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_recursive_counts.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_row_deciders.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_variant_extremes.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_variant_matches.sql diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_analysis_patterns.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_analysis_patterns.sql new file mode 100644 index 0000000000..0bed0a7ecf --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_analysis_patterns.sql @@ -0,0 +1,102 @@ +MODEL ( + description """ + One row per basket built from valid Snowflake SQL that name and type checks have reported as + errors: LAST_DAY with a date part as its second argument, an aggregate aliased as the column it + reads, positional columns, columns read by the names Snowflake gives unaliased expressions, and + contracts that declare TEXT(5) and a NUMBER(38,0) fed by COUNT + """, + tags [edge_case], + columns ( + basket_id (type VARCHAR, nullable false, audits [not_null, unique]), + month_end (type DATE), + line_no (type NUMBER(38, 0)), + positional_sku (type VARCHAR), + next_line_no (type NUMBER(38, 0)), + line_no_text (type VARCHAR), + sku_prefix (type TEXT(5)), + line_count (type NUMBER(38, 0)), + ), +); + +WITH lines AS ( + SELECT + l.basket_id, + l.sku, + l.line_no + FROM __ref("stg_edge_basket_lines") AS l +), + +dated AS ( + SELECT MIN(TRY_TO_DATE(d.date_text, 'YYYY-MM-DD')) AS first_date + FROM __ref("stg_edge_date_texts") AS d +), + +own_names AS ( + SELECT + l.basket_id, + MAX(l.line_no) AS line_no + FROM lines AS l + GROUP BY + l.basket_id +), + +positional AS ( + SELECT + $1 AS basket_id, + MIN($2) AS positional_sku + FROM lines + GROUP BY + 1 +), + +unnamed AS ( + -- No aliases: Snowflake names these columns LINE_NO + 1 and LINE_NO::VARCHAR. + SELECT + basket_id, + line_no + 1, + line_no::VARCHAR + FROM lines +), + +unnamed_per_basket AS ( + SELECT + u.basket_id, + MAX(u."LINE_NO + 1") AS next_line_no, + MIN(u."LINE_NO::VARCHAR") AS line_no_text + FROM unnamed AS u + GROUP BY + u.basket_id +), + +counted AS ( + SELECT + l.basket_id, + LEFT(MIN(l.sku), 5)::VARCHAR(5) AS sku_prefix, + COUNT(*) AS line_count + FROM lines AS l + GROUP BY + l.basket_id +), + +final AS ( + SELECT + c.basket_id, + LAST_DAY(d.first_date, month)::DATE AS month_end, + n.line_no, + p.positional_sku::VARCHAR AS positional_sku, + u.next_line_no::NUMBER(38, 0) AS next_line_no, + u.line_no_text::VARCHAR AS line_no_text, + c.sku_prefix, + c.line_count + FROM counted AS c + CROSS JOIN dated AS d + LEFT JOIN own_names AS n + ON c.basket_id = n.basket_id + LEFT JOIN positional AS p + ON c.basket_id = p.basket_id + LEFT JOIN unnamed_per_basket AS u + ON c.basket_id = u.basket_id +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_category_walk.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_category_walk.sql new file mode 100644 index 0000000000..8797cdc6ad --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_category_walk.sql @@ -0,0 +1,57 @@ +MODEL ( + description """ + One row per category reached by walking the category tree with START WITH ... CONNECT BY, with + its walk depth (the LEVEL pseudo-column), root name and path. Beside it, a scalar subquery reads + a column named level from another relation: inside the subquery that name is the column, not the + walk depth + """, + tags [edge_case], + columns ( + category_id (type VARCHAR, nullable false, audits [not_null, unique]), + walk_depth (type NUMBER(38, 0)), + root_name (type VARCHAR), + category_path (type VARCHAR), + deepest_shelf (type NUMBER(38, 0)), + ), +); + +WITH links AS ( + SELECT + l.category_id, + l.parent_id, + l.category_name + FROM __ref("stg_edge_category_links") AS l +), + +shelves AS ( + SELECT l.shelf_depth AS level + FROM __ref("stg_edge_category_links") AS l +), + +walk AS ( + SELECT + k.category_id, + LEVEL AS walk_depth, + CONNECT_BY_ROOT k.category_name AS root_name, + SYS_CONNECT_BY_PATH(k.category_name, ' > ') AS category_path, + ( + SELECT MAX(level) + FROM shelves + ) AS deepest_shelf + FROM links AS k +START WITH k.parent_id IS NULL +CONNECT BY k.parent_id = PRIOR k.category_id +), + +final AS ( + SELECT + w.category_id, + w.walk_depth::NUMBER(38, 0) AS walk_depth, + w.root_name::VARCHAR AS root_name, + w.category_path::VARCHAR AS category_path, + w.deepest_shelf::NUMBER(38, 0) AS deepest_shelf + FROM walk AS w +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_join_keys.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_join_keys.sql new file mode 100644 index 0000000000..ae423cb7cf --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_join_keys.sql @@ -0,0 +1,125 @@ +MODEL ( + description """ + One row per check group matched to a group name by join shapes whose merged keys and outer sides + are easy to trace wrongly: RIGHT JOIN ... USING (the key comes from the right), NATURAL JOIN, a + USING key followed by a later RIGHT JOIN that makes it NULL, and an ASOF JOIN whose unmatched + rows hold NULL on the right + """, + tags [edge_case], + columns ( + row_key (type VARCHAR, nullable false, audits [not_null, unique]), + join_shape (type VARCHAR), + group_code (type VARCHAR), + group_name (type VARCHAR), + threshold_name (type VARCHAR), + ), +); + +WITH checks AS ( + SELECT + c.check_id, + c.group_code, + c.narrow_key + FROM __ref("stg_edge_amount_checks") AS c +), + +check_groups AS ( + SELECT DISTINCT c.group_code + FROM checks AS c + WHERE c.group_code <> 'G-3' +), + +group_names AS ( + SELECT + v.group_code, + v.group_name + FROM VALUES + ('G-1', 'Mallard desk'), + ('G-2', 'Teal desk'), + ('G-9', 'Eider desk') AS v(group_code, group_name) +), + +desks AS ( + SELECT v.desk_code + FROM VALUES ('G-1'), ('G-7') AS v(desk_code) +), + +thresholds AS ( + SELECT + v.threshold_key, + v.threshold_name + FROM VALUES (10, 'ten or more'), (100, 'a hundred or more') AS v(threshold_key, threshold_name) +), + +right_using AS ( + SELECT + 'right_using' AS join_shape, + group_code, + n.group_name, + NULL AS threshold_name + FROM check_groups AS g + RIGHT JOIN group_names AS n + USING (group_code) +), + +natural_joined AS ( + SELECT + 'natural' AS join_shape, + n.group_code, + n.group_name, + NULL AS threshold_name + FROM check_groups AS g + NATURAL JOIN group_names AS n +), + +later_outer AS ( + SELECT + 'using_then_right' AS join_shape, + group_code, + n.group_name, + d.desk_code AS threshold_name + FROM check_groups AS g + INNER JOIN group_names AS n + USING (group_code) + RIGHT JOIN desks AS d + ON n.group_code = d.desk_code +), + +asof_matched AS ( + SELECT + 'asof:' || c.check_id AS join_shape, + c.group_code, + NULL AS group_name, + t.threshold_name + FROM checks AS c + ASOF JOIN thresholds AS t MATCH_CONDITION (c.narrow_key >= t.threshold_key) +), + +all_rows AS ( + SELECT * + FROM right_using + UNION ALL BY NAME + SELECT * + FROM natural_joined + UNION ALL BY NAME + SELECT * + FROM later_outer + UNION ALL BY NAME + SELECT * + FROM asof_matched +), + +final AS ( + SELECT + ( + a.join_shape || ':' || COALESCE(a.group_code, '-') || ':' || COALESCE(a.threshold_name, '-') + )::VARCHAR AS row_key, + a.join_shape::VARCHAR AS join_shape, + a.group_code::VARCHAR AS group_code, + a.group_name::VARCHAR AS group_name, + a.threshold_name::VARCHAR AS threshold_name + FROM all_rows AS a +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_note_patterns.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_note_patterns.sql new file mode 100644 index 0000000000..f51010d93d --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_note_patterns.sql @@ -0,0 +1,72 @@ +MODEL ( + description """ + One row per support ticket note searched with regular expressions at their edges: a lazy + quantifier followed by another ?, a bracket class that opens with ] or ^], lazy one-or-more in a + position search, positions of patterns that match empty text, . against a carriage return in + multi-line mode, and case-insensitive matches of the micro sign and final sigma. Also ILIKE with + a long s, alone and as the pattern beside an ESCAPE character, name similarity of accented names + in both cases, Base64 text with line feeds inside groups and padding, and the word at a position + numbered by a window ordered by a VARIANT + """, + tags [edge_case], + columns ( + note_id (type VARCHAR, nullable false, audits [not_null, unique]), + lazy_then_optional_count (type NUMBER(38, 0)), + lazy_then_optional_match (type VARCHAR), + optional_twice_match (type VARCHAR), + bracket_close_match (type VARCHAR), + bracket_negated_match (type VARCHAR), + lazy_plus_position (type NUMBER(38, 0)), + end_anchor_position (type NUMBER(38, 0)), + star_position (type NUMBER(38, 0)), + multiline_dot_match (type VARCHAR), + matches_micro_sign (type BOOLEAN), + matches_sigma (type BOOLEAN), + ilike_s_prefix (type BOOLEAN), + ilike_long_s_escaped (type BOOLEAN), + name_similarity (type NUMBER(38, 0)), + decoded_tags (type VARCHAR), + ranked_word (type VARCHAR), + ), +); + +WITH notes AS ( + SELECT + n.note_id, + n.body, + n.tags_text + FROM __ref("stg_edge_ticket_notes") AS n +), + +final AS ( + SELECT + n.note_id, + REGEXP_COUNT(n.body, '(xa)+??x')::NUMBER(38, 0) AS lazy_then_optional_count, + REGEXP_SUBSTR(n.body, '(xa)+??x')::VARCHAR AS lazy_then_optional_match, + REGEXP_SUBSTR(n.body, 'xa???')::VARCHAR AS optional_twice_match, + REGEXP_SUBSTR(n.body, '[]x+?]+')::VARCHAR AS bracket_close_match, + REGEXP_SUBSTR(n.body, '[^]+?]+')::VARCHAR AS bracket_negated_match, + REGEXP_INSTR(n.body, 'ba+?')::NUMBER(38, 0) AS lazy_plus_position, + REGEXP_INSTR(n.body, '$')::NUMBER(38, 0) AS end_anchor_position, + REGEXP_INSTR(n.body, 'a*')::NUMBER(38, 0) AS star_position, + REGEXP_SUBSTR(n.body, '^a.$', 1, 1, 'm')::VARCHAR AS multiline_dot_match, + REGEXP_LIKE(n.body, '.*\u00b5.*', 'i')::BOOLEAN AS matches_micro_sign, + REGEXP_LIKE(n.body, '.*\u03c3.*', 'i')::BOOLEAN AS matches_sigma, + ( + n.body ILIKE 's%' + )::BOOLEAN AS ilike_s_prefix, + ( + n.body ILIKE '\u017f%' ESCAPE 's' + )::BOOLEAN AS ilike_long_s_escaped, + JAROWINKLER_SIMILARITY(n.body, n.tags_text)::NUMBER(38, 0) AS name_similarity, + TRY_BASE64_DECODE_STRING(n.tags_text)::VARCHAR AS decoded_tags, + SPLIT_PART( + n.body, + ' ', + ROW_NUMBER() OVER (ORDER BY TO_VARIANT(n.note_id)) + )::VARCHAR AS ranked_word + FROM notes AS n +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_payload_paths.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_payload_paths.sql new file mode 100644 index 0000000000..1c5e1ff66c --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_payload_paths.sql @@ -0,0 +1,52 @@ +MODEL ( + description """ + One row per element of a partner attribute document flattened with RECURSIVE, with the PATH + Snowflake writes for keys holding $, -, @, [, a space, a non-ASCII letter or nothing unusual, + and for array indexes inside them + """, + tags [edge_case], + columns ( + element_id (type VARCHAR, nullable false, audits [not_null, unique]), + value_id (type VARCHAR), + element_path (type VARCHAR), + element_key (type VARCHAR), + element_index (type NUMBER(38, 0)), + element_kind (type VARCHAR), + ), +); + +WITH documents AS ( + SELECT + v.value_id, + v.attribute_value + FROM __ref("stg_edge_variant_values") AS v + WHERE + IS_OBJECT(v.attribute_value) + OR IS_ARRAY(v.attribute_value) +), + +elements AS ( + SELECT + d.value_id, + f.path, + f.key, + f.index, + f.value + FROM documents AS d, LATERAL FLATTEN(input => d.attribute_value, recursive => TRUE) AS f +), + +final AS ( + SELECT + ( + e.value_id || ':' || e.path + )::VARCHAR AS element_id, + e.value_id, + e.path::VARCHAR AS element_path, + e.key::VARCHAR AS element_key, + e.index::NUMBER(38, 0) AS element_index, + TYPEOF(e.value)::VARCHAR AS element_kind + FROM elements AS e +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_recursive_counts.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_recursive_counts.sql new file mode 100644 index 0000000000..fa81a85dc2 --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_recursive_counts.sql @@ -0,0 +1,57 @@ +MODEL ( + description """ + One row per category reached by a recursive CTE written without the RECURSIVE keyword, which + Snowflake does not require. Its columns shift by one step per iteration, so the grandparent's + name reaches a category only after two iterations + """, + tags [edge_case], + columns ( + category_id (type VARCHAR, nullable false, audits [not_null, unique]), + tree_depth (type NUMBER(38, 0)), + parent_name (type VARCHAR), + grandparent_name (type VARCHAR), + ), +); + +WITH links AS ( + SELECT + l.category_id, + l.parent_id, + l.category_name + FROM __ref("stg_edge_category_links") AS l +), + +tree(category_id, tree_depth, own_name, parent_name, grandparent_name) AS ( + SELECT + l.category_id, + 1::NUMBER(38, 0) AS tree_depth, + l.category_name::VARCHAR AS own_name, + NULL::VARCHAR AS parent_name, + NULL::VARCHAR AS grandparent_name + FROM links AS l + WHERE l.parent_id IS NULL + UNION ALL + SELECT + l.category_id, + ( + t.tree_depth + 1 + )::NUMBER(38, 0) AS tree_depth, + l.category_name::VARCHAR AS own_name, + t.own_name::VARCHAR AS parent_name, + t.parent_name::VARCHAR AS grandparent_name + FROM links AS l + INNER JOIN tree AS t + ON l.parent_id = t.category_id +), + +final AS ( + SELECT + t.category_id, + t.tree_depth::NUMBER(38, 0) AS tree_depth, + t.parent_name::VARCHAR AS parent_name, + t.grandparent_name::VARCHAR AS grandparent_name + FROM tree AS t +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_row_deciders.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_row_deciders.sql new file mode 100644 index 0000000000..1c69b61ef4 --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_row_deciders.sql @@ -0,0 +1,197 @@ +MODEL ( + description """ + One row per basket picked by a clause whose columns decide which rows come back but are never + output: WHERE, QUALIFY, GROUP BY 1 with HAVING, GROUP BY ALL, ORDER BY under LIMIT, EXISTS (as a + filter and as an output), a FLATTEN whose input decides how many rows come out, GROUP BY ROLLUP + by position, and EXCEPT ordered by position under LIMIT. Lineage that follows only the output + columns misses every one of these + """, + tags [edge_case], + columns ( + decider_row (type VARCHAR, nullable false, audits [not_null, unique]), + decider (type VARCHAR), + basket_id (type VARCHAR), + detail (type VARCHAR), + ), +); + +WITH lines AS ( + SELECT + l.line_id, + l.basket_id, + l.line_no, + l.sku, + l.tag + FROM __ref("stg_edge_basket_lines") AS l +), + +baskets AS ( + SELECT DISTINCT l.basket_id + FROM lines AS l +), + +filtered AS ( + SELECT + 'where' AS decider, + l.basket_id, + l.line_id AS detail + FROM lines AS l + WHERE l.tag = 'bath' +), + +first_lines AS ( + SELECT + 'qualify' AS decider, + l.basket_id, + l.line_id AS detail + FROM lines AS l + QUALIFY ROW_NUMBER() OVER (PARTITION BY l.basket_id ORDER BY l.line_no, l.line_id) = 1 +), + +busy AS ( + SELECT + 'group_by_position' AS decider, + l.basket_id, + NULL AS detail + FROM lines AS l + GROUP BY + 2 + HAVING COUNT(*) > 1 +), + +counted AS ( + SELECT + 'group_by_all' AS decider, + l.basket_id, + COUNT(*)::VARCHAR AS detail + FROM lines AS l + GROUP BY ALL +), + +lowest_sku AS ( + SELECT + 'order_by_limit' AS decider, + l.basket_id, + l.line_id AS detail + FROM lines AS l + ORDER BY + l.sku, + l.line_id + LIMIT 1 +), + +with_gift AS ( + SELECT + 'exists_filter' AS decider, + b.basket_id, + NULL AS detail + FROM baskets AS b + WHERE + EXISTS( + SELECT 1 + FROM lines AS g + WHERE + g.basket_id = b.basket_id + AND g.tag = 'gift' + ) +), + +gift_flags AS ( + SELECT + 'exists_output' AS decider, + b.basket_id, + EXISTS( + SELECT 1 + FROM lines AS g + WHERE + g.basket_id = b.basket_id + AND g.tag = 'gift' + )::VARCHAR AS detail + FROM baskets AS b +), + +sku_parts AS ( + SELECT + 'flatten_input' AS decider, + l.basket_id, + l.line_id || '#' || f.index::VARCHAR AS detail + FROM lines AS l, LATERAL FLATTEN(input => SPLIT(l.sku, '-')) AS f +), + +rolled AS ( + SELECT + 'rollup_position' AS decider, + l.basket_id, + GROUPING(l.tag)::VARCHAR || ':' || COALESCE(l.tag, '~') || '=' || COUNT(*)::VARCHAR AS detail + FROM lines AS l + GROUP BY + ROLLUP ( + 2, + l.tag + ) +), + +without_pond AS ( + SELECT l.basket_id + FROM lines AS l + EXCEPT + SELECT l.basket_id + FROM lines AS l + WHERE l.tag = 'pond' +ORDER BY 1 DESC +LIMIT 1 +), + +remaining AS ( + SELECT + 'except_limit' AS decider, + w.basket_id, + NULL AS detail + FROM without_pond AS w +), + +all_rows AS ( + SELECT * + FROM filtered + UNION ALL BY NAME + SELECT * + FROM first_lines + UNION ALL BY NAME + SELECT * + FROM busy + UNION ALL BY NAME + SELECT * + FROM counted + UNION ALL BY NAME + SELECT * + FROM lowest_sku + UNION ALL BY NAME + SELECT * + FROM with_gift + UNION ALL BY NAME + SELECT * + FROM gift_flags + UNION ALL BY NAME + SELECT * + FROM sku_parts + UNION ALL BY NAME + SELECT * + FROM rolled + UNION ALL BY NAME + SELECT * + FROM remaining +), + +final AS ( + SELECT + ( + a.decider || ':' || COALESCE(a.basket_id, '-') || ':' || COALESCE(a.detail, '-') + )::VARCHAR AS decider_row, + a.decider::VARCHAR AS decider, + a.basket_id::VARCHAR AS basket_id, + a.detail::VARCHAR AS detail + FROM all_rows AS a +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_variant_extremes.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_variant_extremes.sql new file mode 100644 index 0000000000..43b8b98a19 --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_variant_extremes.sql @@ -0,0 +1,55 @@ +MODEL ( + description """ + One row per partner attribute value with VARIANT orderings and array reads: ARRAY_MAX, ARRAY_MIN + and ARRAY_REVERSE over arrays mixing numbers, strings, booleans, JSON nulls and integers past + 2^53; the largest value of each group by window MAX, across kinds and non-ASCII strings; + ARRAY_REMOVE of a JSON null; JSON nulls in ARRAY_DISTINCT and ARRAYS_OVERLAP; and the value + looked up in an array of decimals written with trailing zeros + """, + tags [edge_case], + columns ( + value_id (type VARCHAR, nullable false, audits [not_null, unique]), + largest_element (type VARIANT), + smallest_element (type VARIANT), + reversed_elements (type ARRAY), + group_largest (type VARIANT), + without_json_nulls (type ARRAY), + distinct_element_count (type NUMBER(38, 0)), + overlaps_json_null (type BOOLEAN), + in_decimal_array (type BOOLEAN), + decimal_array_position (type NUMBER(38, 0)), + ), +); + +WITH attribute_values AS ( + SELECT + v.value_id, + v.group_code, + v.attribute_value, + AS_ARRAY(v.attribute_value) AS elements + FROM __ref("stg_edge_variant_values") AS v +), + +final AS ( + SELECT + a.value_id, + ARRAY_MAX(a.elements)::VARIANT AS largest_element, + ARRAY_MIN(a.elements)::VARIANT AS smallest_element, + ARRAY_REVERSE(a.elements)::ARRAY AS reversed_elements, + MAX(a.attribute_value) OVER (PARTITION BY a.group_code) AS group_largest, + ARRAY_REMOVE(a.elements, PARSE_JSON('null'))::ARRAY AS without_json_nulls, + ARRAY_SIZE(ARRAY_DISTINCT(a.elements))::NUMBER(38, 0) AS distinct_element_count, + ARRAYS_OVERLAP(a.elements, ARRAY_CONSTRUCT(PARSE_JSON('null')))::BOOLEAN AS overlaps_json_null, + ARRAY_CONTAINS( + a.attribute_value, + ARRAY_CONSTRUCT(-7.00, 1.00, 'a') + )::BOOLEAN AS in_decimal_array, + ARRAY_POSITION( + a.attribute_value, + ARRAY_CONSTRUCT(1.50, 1.00, 0.00) + )::NUMBER(38, 0) AS decimal_array_position + FROM attribute_values AS a +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_variant_matches.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_variant_matches.sql new file mode 100644 index 0000000000..83d6058e42 --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_variant_matches.sql @@ -0,0 +1,151 @@ +MODEL ( + description """ + One row per pair of partner attribute values in a group, compared with = and EQUAL_NULL: 1 + against "1" and 1.0, JSON null against SQL NULL, objects with reordered keys, and a DATE or + TIMESTAMP held in a VARIANT against the same text, which Snowflake keeps typed. Each value also + carries its group's MIN_BY and MAX_BY VARIANT picks, how many rows share its value in a window + partition, the group's distinct values by SELECT DISTINCT and ARRAY_AGG(DISTINCT), a scalar cast + of the value, and an OBJECT_CONSTRUCT star excluding a quoted lower-case column + """, + tags [edge_case], + columns ( + pair_id (type VARCHAR, nullable false, audits [not_null, unique]), + left_value (type VARIANT), + right_value (type VARIANT), + values_equal (type BOOLEAN), + values_equal_null (type BOOLEAN), + left_partition_size (type NUMBER(38, 0)), + group_first_value (type VARIANT), + group_last_value (type VARIANT), + group_distinct_rows (type NUMBER(38, 0)), + group_distinct_elements (type NUMBER(38, 0)), + left_scalar_text (type VARCHAR), + left_object_without_note (type OBJECT), + ), +); + +WITH loaded AS ( + SELECT + v.value_id, + v.group_code, + v.sort_key, + v.attribute_value, + v.value_text + FROM __ref("stg_edge_variant_values") AS v +), + +typed AS ( + -- A DATE and a TIMESTAMP stay typed inside a VARIANT, unlike the text they are written as. + SELECT + l.value_id || '-DATE' AS value_id, + l.group_code, + l.sort_key + 100 AS sort_key, + TO_VARIANT(TRY_TO_DATE(l.attribute_value::VARCHAR)) AS attribute_value, + l.value_text + FROM loaded AS l + WHERE + IS_VARCHAR(l.attribute_value) + AND TRY_TO_DATE(l.attribute_value::VARCHAR) IS NOT NULL + UNION ALL + SELECT + l.value_id || '-TIMESTAMP' AS value_id, + l.group_code, + l.sort_key + 200 AS sort_key, + TO_VARIANT(TRY_TO_TIMESTAMP_NTZ(l.attribute_value::VARCHAR)) AS attribute_value, + l.value_text + FROM loaded AS l + WHERE + IS_VARCHAR(l.attribute_value) + AND TRY_TO_DATE(l.attribute_value::VARCHAR) IS NOT NULL +), + +all_values AS ( + SELECT * + FROM loaded + UNION ALL BY NAME + SELECT * + FROM typed +), + +with_note AS ( + SELECT + v.value_id, + v.group_code, + v.sort_key, + v.attribute_value, + v.value_text AS "note" + FROM all_values AS v +), + +partitioned AS ( + SELECT + a.value_id, + a.group_code, + a.sort_key, + a.attribute_value, + COUNT(*) OVER (PARTITION BY a.group_code, a.attribute_value) AS partition_size, + OBJECT_CONSTRUCT(* EXCLUDE ("note")) AS object_without_note + FROM with_note AS a +), + +group_picks AS ( + SELECT + a.group_code, + MIN_BY(a.attribute_value, a.sort_key) AS group_first_value, + MAX_BY(a.attribute_value, a.sort_key) AS group_last_value, + ARRAY_SIZE(ARRAY_AGG(DISTINCT a.attribute_value)) AS group_distinct_elements + FROM all_values AS a + GROUP BY + a.group_code +), + +distinct_values AS ( + SELECT DISTINCT + a.group_code, + a.attribute_value + FROM all_values AS a +), + +distinct_counts AS ( + SELECT + d.group_code, + COUNT(d.attribute_value) AS group_distinct_rows + FROM distinct_values AS d + GROUP BY + d.group_code +), + +final AS ( + SELECT + ( + l.value_id || '~' || r.value_id + )::VARCHAR AS pair_id, + l.attribute_value AS left_value, + r.attribute_value AS right_value, + ( + l.attribute_value = r.attribute_value + )::BOOLEAN AS values_equal, + EQUAL_NULL(l.attribute_value, r.attribute_value)::BOOLEAN AS values_equal_null, + l.partition_size::NUMBER(38, 0) AS left_partition_size, + g.group_first_value, + g.group_last_value, + c.group_distinct_rows::NUMBER(38, 0) AS group_distinct_rows, + g.group_distinct_elements::NUMBER(38, 0) AS group_distinct_elements, + IFF( + IS_OBJECT(l.attribute_value) OR IS_ARRAY(l.attribute_value), + NULL, + l.attribute_value + )::VARCHAR AS left_scalar_text, + l.object_without_note::OBJECT AS left_object_without_note + FROM partitioned AS l + INNER JOIN partitioned AS r + ON l.group_code = r.group_code + AND l.sort_key < r.sort_key + INNER JOIN group_picks AS g + ON l.group_code = g.group_code + INNER JOIN distinct_counts AS c + ON l.group_code = c.group_code +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/sqlbuild_project.toml b/benchmarks/duckshop/project/sqlbuild_project.toml index fdcc6f7f58..477f8254b2 100644 --- a/benchmarks/duckshop/project/sqlbuild_project.toml +++ b/benchmarks/duckshop/project/sqlbuild_project.toml @@ -286,4 +286,40 @@ The edge case selects the key columns that FULL OUTER JOIN ... USING merges. The side, so qualifying them with either alias would drop the keys of rows found only on the other side. """ +[[rules.rule_ignores]] +rules = ["SQLCLEAN003", "SQLCLEAN023", "SQLCLEAN027"] +paths = ["models/intermediate/edge_cases/int_edge_join_keys.sql"] +reason = """ +The edge cases read the merged key of RIGHT JOIN ... USING and of a USING join followed by an outer +join unqualified, because it belongs to neither side; the side aliases are then unused. An ASOF +JOIN's condition is its MATCH_CONDITION, so it has no ON clause. +""" + +[[rules.rule_ignores]] +rules = ["SQLCLEAN018"] +paths = [ + "models/intermediate/edge_cases/int_edge_grouped_windows.sql", + "models/intermediate/edge_cases/int_edge_null_arithmetic.sql", +] +reason = """ +The ranking windows order by the group code of a grouped query and by the check id, both unique +where they are ranked, which Rules cannot prove through a GROUP BY or a CTE. +""" + +[[rules.rule_ignores]] +rules = ["SQLCLEAN022"] +paths = ["models/intermediate/edge_cases/int_edge_analysis_patterns.sql"] +reason = """ +The edge case leaves two calculated columns unaliased on purpose, and reads them by the names +Snowflake gives them. +""" + +[[rules.rule_ignores]] +rules = ["SQLCLEAN028"] +paths = ["models/intermediate/edge_cases/int_edge_category_walk.sql"] +reason = """ +LEVEL is a hierarchical-query pseudo-column that cannot be qualified, and the edge case reads a +column named level from another relation unqualified inside a scalar subquery on purpose. +""" + # <<< edge-case area diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_analysis_patterns.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_analysis_patterns.sql new file mode 100644 index 0000000000..436ab8df83 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_analysis_patterns.sql @@ -0,0 +1,46 @@ +TEST (name "int_edge_analysis_patterns__reads_positional_and_unaliased_columns"); + +WITH __ref__stg_edge_basket_lines AS ( + SELECT + v.line_id, + v.basket_id, + v.line_no::NUMBER(38, 0) AS line_no, + v.sku, + v.tag + FROM VALUES + ('BL-01', 'B-1', 1, 'MALLARD-01', 'bath'), + ('BL-02', 'B-1', 2, 'TEAL-02', 'gift'), + ('BL-03', 'B-1', 3, 'EIDER-03', 'bath'), + ('BL-04', 'B-2', 1, 'PINTAIL-04', 'garden'), + ('BL-05', 'B-2', 2, 'MALLARD-01', 'bath'), + ('BL-06', 'B-3', 1, 'MERGANSER-05', 'pond'), + ('BL-07', 'B-4', 1, 'TEAL-02', NULL), + ('BL-08', 'B-4', 2, 'EIDER-03', 'gift') AS v(line_id, basket_id, line_no, sku, tag) +), + +__ref__stg_edge_date_texts AS ( + SELECT + v.text_id, + v.date_text, + NULL AS zoned_text, + NULL::TIMESTAMP_NTZ(3) AS wall_clock, + NULL AS wall_clock_text, + NULL::TIME(3) AS local_time, + NULL::TIME(9) AS fine_time + FROM VALUES + ('DT-01', '2026-03-05'), + ('DT-02', '2026-02-14'), + ('DT-03', 'not a date') AS v(text_id, date_text) +), + +__expected__int_edge_analysis_patterns AS ( + SELECT + NULL AS basket_id, + NULL AS month_end, + NULL AS line_no, + NULL AS positional_sku, + NULL AS next_line_no, + NULL AS line_no_text, + NULL AS sku_prefix, + NULL AS line_count +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_category_walk.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_category_walk.sql new file mode 100644 index 0000000000..b171dc9d0c --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_category_walk.sql @@ -0,0 +1,24 @@ +TEST (name "int_edge_category_walk__reads_a_level_column_beside_the_walk_depth"); + +WITH __ref__stg_edge_category_links AS ( + SELECT + v.category_id, + v.parent_id, + v.category_name, + v.shelf_depth::NUMBER(38, 0) AS shelf_depth + FROM VALUES + ('CAT-1', NULL, 'Ducks', 1), + ('CAT-2', 'CAT-1', 'Rubber ducks', 4), + ('CAT-3', 'CAT-2', 'Collectible series', 7), + ('CAT-4', 'CAT-1', 'Decoys', 4), + ('CAT-5', NULL, 'Pond supplies', 1) AS v(category_id, parent_id, category_name, shelf_depth) +), + +__expected__int_edge_category_walk AS ( + SELECT + NULL AS category_id, + NULL AS walk_depth, + NULL AS root_name, + NULL AS category_path, + NULL AS deepest_shelf +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_join_keys.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_join_keys.sql new file mode 100644 index 0000000000..e368e0fa79 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_join_keys.sql @@ -0,0 +1,30 @@ +TEST (name "int_edge_join_keys__takes_merged_keys_and_outer_sides_from_the_right_relation"); + +WITH __ref__stg_edge_amount_checks AS ( + SELECT + v.check_id, + v.group_code, + v.narrow_key::NUMBER(18, 0) AS narrow_key, + v.wide_key::NUMBER(38, 0) AS wide_key, + v.key_text, + v.amount::NUMBER(18, 2) AS amount, + v.fine_amount::NUMBER(20, 15) AS fine_amount, + v.ratio::FLOAT AS ratio, + v.json_text + FROM VALUES + ('AC-01', 'G-1', 7, '7', '01', 10.00, 0.123456789012345, 0.29, NULL), + ('AC-02', 'G-1', 12, '12345678901234567890123', '1', -0.50, 0.123456789012344, 1.005, NULL), + ('AC-03', 'G-1', NULL, '12', '12', 2.50, 0.5, 2.675, NULL), + ('AC-04', 'G-2', 123456789012345678, '123456789012345678', ' 7', 0.00, 1, -155.5, NULL), + ('AC-05', 'G-2', 99, NULL, NULL, NULL, NULL, NULL, NULL), + ('AC-06', 'G-3', 5, '5', 'n/a', 999999999999.99, 0.000000000000001, 0.57, NULL) AS v(check_id, group_code, narrow_key, wide_key, key_text, amount, fine_amount, ratio, json_text) +), + +__expected__int_edge_join_keys AS ( + SELECT + NULL AS row_key, + NULL AS join_shape, + NULL AS group_code, + NULL AS group_name, + NULL AS threshold_name +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_note_patterns.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_note_patterns.sql new file mode 100644 index 0000000000..605438dc3d --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_note_patterns.sql @@ -0,0 +1,49 @@ +TEST (name "int_edge_note_patterns__searches_notes_with_edge_patterns"); + +WITH __ref__stg_edge_ticket_notes AS ( + SELECT + v.note_id, + IFF(v.body = '@long', REPEAT('pond duck ', 40), v.body) AS body, + v.tags_text + FROM VALUES + ('TN-01', 'xaxax', NULL), + ('TN-02', 'xab xa', NULL), + ('TN-03', 'x+', NULL), + ('TN-04', 'bxba', NULL), + ('TN-05', 'bxb', NULL), + ('TN-06', '', NULL), + ('TN-07', 'baa', NULL), + ('TN-08', 'a\r\nb', NULL), + ('TN-09', '\u03bc', NULL), + ('TN-10', '\u03c2', NULL), + ('TN-11', 'sabc', NULL), + ('TN-12', '\u017fabc', NULL), + ('TN-13', '\u00c9\u00e9', '\u00e9\u00c9'), + ('TN-14', '\u00c9mile \u00e9mile', '\u00e9mile \u00c9mile'), + ('TN-15', 'Pintail', 'b3J\nkZXI='), + ('TN-16', 'Teal', 'YQ=\n='), + ('TN-17', 'Eider', 'YQ\n=='), + ('TN-18', '@long', NULL), + ('TN-19', NULL, NULL) AS v(note_id, body, tags_text) +), + +__expected__int_edge_note_patterns AS ( + SELECT + NULL AS note_id, + NULL AS lazy_then_optional_count, + NULL AS lazy_then_optional_match, + NULL AS optional_twice_match, + NULL AS bracket_close_match, + NULL AS bracket_negated_match, + NULL AS lazy_plus_position, + NULL AS end_anchor_position, + NULL AS star_position, + NULL AS multiline_dot_match, + NULL AS matches_micro_sign, + NULL AS matches_sigma, + NULL AS ilike_s_prefix, + NULL AS ilike_long_s_escaped, + NULL AS name_similarity, + NULL AS decoded_tags, + NULL AS ranked_word +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_payload_paths.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_payload_paths.sql new file mode 100644 index 0000000000..0448d3c683 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_payload_paths.sql @@ -0,0 +1,29 @@ +TEST (name "int_edge_payload_paths__writes_recursive_paths_for_unusual_keys"); + +WITH __ref__stg_edge_variant_values AS ( + SELECT + v.value_id, + v.group_code, + v.sort_key::NUMBER(38, 0) AS sort_key, + IFF( + v.attribute_value = '@date', + TO_VARIANT('2026-03-01'::DATE), + PARSE_JSON(v.attribute_value) + ) AS attribute_value, + v.value_text + FROM VALUES + ('VV-01', 'V-1', 1, '{"a$b": 1, "a-b": {"@x": 2}, "a[b": [3, {"k": 4}]}', NULL), + ('VV-02', 'V-1', 2, '[{"x y": 1}, [2]]', NULL), + ('VV-03', 'V-1', 3, '5', NULL), + ('VV-04', 'V-1', 4, '{"\u00e9": 5, "plain key": 6, "": 7, "a.b": 8, "9z": 9}', NULL) AS v(value_id, group_code, sort_key, attribute_value, value_text) +), + +__expected__int_edge_payload_paths AS ( + SELECT + NULL AS element_id, + NULL AS value_id, + NULL AS element_path, + NULL AS element_key, + NULL AS element_index, + NULL AS element_kind +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_recursive_counts.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_recursive_counts.sql new file mode 100644 index 0000000000..d8ab89fb36 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_recursive_counts.sql @@ -0,0 +1,23 @@ +TEST (name "int_edge_recursive_counts__recurses_without_the_recursive_keyword"); + +WITH __ref__stg_edge_category_links AS ( + SELECT + v.category_id, + v.parent_id, + v.category_name, + v.shelf_depth::NUMBER(38, 0) AS shelf_depth + FROM VALUES + ('CAT-1', NULL, 'Ducks', 1), + ('CAT-2', 'CAT-1', 'Rubber ducks', 4), + ('CAT-3', 'CAT-2', 'Collectible series', 7), + ('CAT-4', 'CAT-1', 'Decoys', 4), + ('CAT-5', NULL, 'Pond supplies', 1) AS v(category_id, parent_id, category_name, shelf_depth) +), + +__expected__int_edge_recursive_counts AS ( + SELECT + NULL AS category_id, + NULL AS tree_depth, + NULL AS parent_name, + NULL AS grandparent_name +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_row_deciders.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_row_deciders.sql new file mode 100644 index 0000000000..c3b064cf2d --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_row_deciders.sql @@ -0,0 +1,27 @@ +TEST (name "int_edge_row_deciders__picks_baskets_by_columns_it_never_outputs"); + +WITH __ref__stg_edge_basket_lines AS ( + SELECT + v.line_id, + v.basket_id, + v.line_no::NUMBER(38, 0) AS line_no, + v.sku, + v.tag + FROM VALUES + ('BL-01', 'B-1', 1, 'MALLARD-01', 'bath'), + ('BL-02', 'B-1', 2, 'TEAL-02', 'gift'), + ('BL-03', 'B-1', 3, 'EIDER-03', 'bath'), + ('BL-04', 'B-2', 1, 'PINTAIL-04', 'garden'), + ('BL-05', 'B-2', 2, 'MALLARD-01', 'bath'), + ('BL-06', 'B-3', 1, 'MERGANSER-05', 'pond'), + ('BL-07', 'B-4', 1, 'TEAL-02', NULL), + ('BL-08', 'B-4', 2, 'EIDER-03', 'gift') AS v(line_id, basket_id, line_no, sku, tag) +), + +__expected__int_edge_row_deciders AS ( + SELECT + NULL AS decider_row, + NULL AS decider, + NULL AS basket_id, + NULL AS detail +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_variant_extremes.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_variant_extremes.sql new file mode 100644 index 0000000000..11125bd8d6 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_variant_extremes.sql @@ -0,0 +1,50 @@ +TEST (name "int_edge_variant_extremes__orders_mixed_variant_values"); + +WITH __ref__stg_edge_variant_values AS ( + SELECT + v.value_id, + v.group_code, + v.sort_key::NUMBER(38, 0) AS sort_key, + IFF( + v.attribute_value = '@date', + TO_VARIANT('2026-03-01'::DATE), + PARSE_JSON(v.attribute_value) + ) AS attribute_value, + v.value_text + FROM VALUES + ('VV-01', 'V-1', 1, '[1, "a", 2]', NULL), + ('VV-02', 'V-1', 2, '[1, null, 3]', NULL), + ('VV-03', 'V-1', 3, '[true, 2]', NULL), + ('VV-04', 'V-2', 1, '["a", "B", "b"]', NULL), + ('VV-05', 'V-2', 2, '[12345678901234567890, 1]', NULL), + ('VV-06', 'V-2', 3, '[9007199254740993, 9007199254740992]', NULL), + ('VV-07', 'V-3', 1, '[-0.0, 0]', NULL), + ('VV-08', 'V-3', 2, '[1e2, 99]', NULL), + ('VV-09', 'V-3', 3, '[]', NULL), + ('VV-10', 'V-4', 1, 'null', NULL), + ('VV-11', 'V-4', 2, '5', NULL), + ('VV-12', 'V-4', 3, '"x"', NULL), + ('VV-13', 'V-4', 4, '{"a": 1}', NULL), + ('VV-14', 'V-5', 1, '["\u00e9", "z", "b", "B"]', NULL), + ('VV-15', 'V-5', 2, '"\u00e9"', NULL), + ('VV-16', 'V-5', 3, '"z"', NULL), + ('VV-17', 'V-5', 4, '"B"', NULL), + ('VV-18', 'V-6', 1, '-7', NULL), + ('VV-19', 'V-6', 2, '1', NULL), + ('VV-20', 'V-6', 3, '"1"', NULL), + ('VV-21', 'V-6', 4, '0', NULL) AS v(value_id, group_code, sort_key, attribute_value, value_text) +), + +__expected__int_edge_variant_extremes AS ( + SELECT + NULL AS value_id, + NULL AS largest_element, + NULL AS smallest_element, + NULL AS reversed_elements, + NULL AS group_largest, + NULL AS without_json_nulls, + NULL AS distinct_element_count, + NULL AS overlaps_json_null, + NULL AS in_decimal_array, + NULL AS decimal_array_position +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_variant_matches.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_variant_matches.sql new file mode 100644 index 0000000000..7f3cc71011 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_variant_matches.sql @@ -0,0 +1,41 @@ +TEST (name "int_edge_variant_matches__compares_variants_by_type_and_value"); + +WITH __ref__stg_edge_variant_values AS ( + SELECT + v.value_id, + v.group_code, + v.sort_key::NUMBER(38, 0) AS sort_key, + IFF( + v.attribute_value = '@date', + TO_VARIANT('2026-03-01'::DATE), + PARSE_JSON(v.attribute_value) + ) AS attribute_value, + v.value_text + FROM VALUES + ('VV-01', 'V-1', 1, '1', 'one'), + ('VV-02', 'V-1', 2, '"1"', 'one as text'), + ('VV-03', 'V-1', 3, '1.0', 'one with a fraction'), + ('VV-04', 'V-1', 4, 'null', 'json null'), + ('VV-05', 'V-2', 1, '{"a": 1, "b": 2}', 'keys a, b'), + ('VV-06', 'V-2', 2, '{"b": 2, "a": 1}', 'keys b, a'), + ('VV-07', 'V-2', 3, '"2026-03-01"', 'date text'), + ('VV-08', 'V-2', 4, '"2026-03-01 10:15:00"', 'timestamp text'), + ('VV-09', 'V-3', 1, '[1, 2]', 'array'), + ('VV-10', 'V-3', 2, '[2, 1]', 'reversed array'), + ('VV-11', 'V-4', 1, '@date', 'stored date'), + ('VV-12', 'V-4', 2, '"2026-03-01"', 'date text') AS v(value_id, group_code, sort_key, attribute_value, value_text) +), + +__expected__int_edge_variant_matches AS ( + SELECT + NULL AS pair_id, + NULL AS values_equal, + NULL AS values_equal_null, + NULL AS left_partition_size, + NULL AS group_first_value, + NULL AS group_last_value, + NULL AS group_distinct_rows, + NULL AS group_distinct_elements, + NULL AS left_scalar_text, + NULL AS left_object_without_note +) From 6c9b0ed82f193a6cf9493fb4d83a49de0aaaf482 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:28:15 +0000 Subject: [PATCH 4/8] test(benchmarks): add DuckShop edge cases for project Python functions Volatile token and number functions checked by assertions that hold for any values, DIV0 of a volatile call beside look-alike names, a handler that answers NULL input, and Python values handed to VARIANT and FLOAT results. --- .../functions/python/edge_float_value.py | 12 +++++ .../functions/python/edge_next_ticket.py | 14 ++++++ .../functions/python/edge_payload_value.py | 29 +++++++++++ .../functions/python/edge_tag_label.py | 18 +++++++ .../functions/python/edge_ticket_token.py | 15 ++++++ .../edge_cases/int_edge_python_values.sql | 49 +++++++++++++++++++ .../edge_cases/int_edge_ticket_tokens.sql | 43 ++++++++++++++++ ...st_edge_tag_label__labels_missing_tags.sql | 25 ++++++++++ .../test_int_edge_python_values.sql | 31 ++++++++++++ .../test_int_edge_ticket_tokens.sql | 44 +++++++++++++++++ 10 files changed, 280 insertions(+) create mode 100644 benchmarks/duckshop/project/functions/python/edge_float_value.py create mode 100644 benchmarks/duckshop/project/functions/python/edge_next_ticket.py create mode 100644 benchmarks/duckshop/project/functions/python/edge_payload_value.py create mode 100644 benchmarks/duckshop/project/functions/python/edge_tag_label.py create mode 100644 benchmarks/duckshop/project/functions/python/edge_ticket_token.py create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_python_values.sql create mode 100644 benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_ticket_tokens.sql create mode 100644 benchmarks/duckshop/project/tests/unit/functions/python/test_edge_tag_label__labels_missing_tags.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_python_values.sql create mode 100644 benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_ticket_tokens.sql diff --git a/benchmarks/duckshop/project/functions/python/edge_float_value.py b/benchmarks/duckshop/project/functions/python/edge_float_value.py new file mode 100644 index 0000000000..cafb6cd1fc --- /dev/null +++ b/benchmarks/duckshop/project/functions/python/edge_float_value.py @@ -0,0 +1,12 @@ +from sqlbuild.functions import udf + + +@udf( + arguments={"amount": "NUMBER(38, 0)"}, + returns="FLOAT", + runtime_version="3.11", + tags=["edge_case"], +) +def main(amount: int | None) -> int | None: + """The amount handed back as a Python int to a FLOAT result; past 2^53 it loses digits.""" + return amount diff --git a/benchmarks/duckshop/project/functions/python/edge_next_ticket.py b/benchmarks/duckshop/project/functions/python/edge_next_ticket.py new file mode 100644 index 0000000000..42de94418c --- /dev/null +++ b/benchmarks/duckshop/project/functions/python/edge_next_ticket.py @@ -0,0 +1,14 @@ +import uuid + +from sqlbuild.functions import udf + + +@udf( + arguments={}, + returns="NUMBER(38, 0)", + runtime_version="3.11", + tags=["edge_case"], +) +def main() -> int: + """A fresh random positive ticket number on every call, from no arguments.""" + return uuid.uuid4().int % 10**15 + 1 diff --git a/benchmarks/duckshop/project/functions/python/edge_payload_value.py b/benchmarks/duckshop/project/functions/python/edge_payload_value.py new file mode 100644 index 0000000000..e6942a8a4e --- /dev/null +++ b/benchmarks/duckshop/project/functions/python/edge_payload_value.py @@ -0,0 +1,29 @@ +from datetime import UTC, datetime +from decimal import Decimal + +from sqlbuild.functions import udf + + +@udf( + arguments={"kind": "VARCHAR"}, + returns="VARIANT", + runtime_version="3.11", + tags=["edge_case"], +) +def main(kind: str | None) -> object: + """A partner payload value of the named kind, as Python hands it to a VARIANT. + + None becomes JSON null, not SQL NULL. Datetimes, Decimals and integers past 2^53 cross as + Snowflake converts them. + """ + values: dict[str, object] = { + "nothing": None, + "zoned_time": datetime(2026, 3, 1, 9, 30, tzinfo=UTC), + "naive_time": datetime(2026, 3, 1, 9, 30), + "naive_time_fraction": datetime(2026, 3, 1, 9, 30, 0, 250000), + "exponent_decimal": Decimal("1E+3"), + "plain_decimal": Decimal("1.50"), + "wide_integer": 2**60 + 1, + "nested": {"b": [1, None], "a": True}, + } + return values.get(kind or "", kind) diff --git a/benchmarks/duckshop/project/functions/python/edge_tag_label.py b/benchmarks/duckshop/project/functions/python/edge_tag_label.py new file mode 100644 index 0000000000..0ca6c90889 --- /dev/null +++ b/benchmarks/duckshop/project/functions/python/edge_tag_label.py @@ -0,0 +1,18 @@ +from sqlbuild.functions import udf + + +@udf( + arguments={"tag": "VARCHAR"}, + returns="VARCHAR", + runtime_version="3.11", + tags=["edge_case"], +) +def main(tag: str | None) -> str: + """A basket tag as a display label; 'Untagged' for a missing tag. + + The function is not declared STRICT, so Snowflake calls the handler for NULL input too and + returns its answer, not NULL. + """ + if tag is None or not tag.strip(): + return "Untagged" + return tag.strip().capitalize() diff --git a/benchmarks/duckshop/project/functions/python/edge_ticket_token.py b/benchmarks/duckshop/project/functions/python/edge_ticket_token.py new file mode 100644 index 0000000000..a0480d61fe --- /dev/null +++ b/benchmarks/duckshop/project/functions/python/edge_ticket_token.py @@ -0,0 +1,15 @@ +import uuid + +from sqlbuild.functions import udf + + +@udf( + arguments={"seed": "VARCHAR"}, + returns="VARCHAR", + runtime_version="3.11", + tags=["edge_case"], +) +def main(seed: str | None) -> str: + """A fresh random ticket token on every call, whatever the seed: Python UDFs are volatile.""" + del seed + return uuid.uuid4().hex diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_python_values.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_python_values.sql new file mode 100644 index 0000000000..cb97cda47e --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_python_values.sql @@ -0,0 +1,49 @@ +MODEL ( + description """ + One row per distinct basket tag, read as the kind of payload value a project Python function + hands to Snowflake: Python None in a VARIANT (JSON null, not SQL NULL), zoned and naive + datetimes, Decimals written with an exponent and with a trailing zero, an integer past 2^53, a + nested document, and an integer past 2^53 returned as FLOAT + """, + tags [edge_case], + columns ( + kind (type VARCHAR, nullable false, audits [not_null, unique]), + payload (type VARIANT), + payload_is_sql_null (type BOOLEAN), + payload_is_json_null (type BOOLEAN), + payload_kind (type VARCHAR), + payload_text (type VARCHAR), + amount_as_float (type FLOAT), + ), +); + +WITH kinds AS ( + SELECT DISTINCT LOWER(TRIM(l.tag)) AS kind + FROM __ref("stg_edge_basket_lines") AS l + WHERE l.tag IS NOT NULL +), + +values_out AS ( + SELECT + k.kind, + __udf("edge_payload_value")(k.kind) AS payload, + __udf("edge_float_value")(9007199254740993) AS amount_as_float + FROM kinds AS k +), + +final AS ( + SELECT + v.kind::VARCHAR AS kind, + v.payload, + ( + v.payload IS NULL + )::BOOLEAN AS payload_is_sql_null, + IS_NULL_VALUE(v.payload)::BOOLEAN AS payload_is_json_null, + TYPEOF(v.payload)::VARCHAR AS payload_kind, + TO_JSON(v.payload)::VARCHAR AS payload_text, + v.amount_as_float::FLOAT AS amount_as_float + FROM values_out AS v +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_ticket_tokens.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_ticket_tokens.sql new file mode 100644 index 0000000000..73c7972ba8 --- /dev/null +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_ticket_tokens.sql @@ -0,0 +1,43 @@ +MODEL ( + description """ + One row per basket line with values from volatile project Python functions: a ticket token from + the line id, from a constant and from no argument at all, and half a ticket number through DIV0, + beside a column and text that only look like the function's name. Snowflake runs a Python + function once per call and row, so the values change on every run; the test checks only facts + that hold for any values, such as every token being distinct + """, + tags [edge_case, known_divergence], + columns ( + line_id (type VARCHAR, nullable false, audits [not_null, unique]), + line_token (type VARCHAR), + constant_seed_token (type VARCHAR), + next_ticket (type NUMBER(38, 0)), + half_ticket (type NUMBER(38, 6)), + edge_next_ticket_source (type VARCHAR), + look_alike_text (type VARCHAR), + tag_label (type VARCHAR), + ), +); + +WITH lines AS ( + SELECT + l.line_id, + l.tag + FROM __ref("stg_edge_basket_lines") AS l +), + +final AS ( + SELECT + l.line_id, + __udf("edge_ticket_token")(l.line_id)::VARCHAR AS line_token, + __udf("edge_ticket_token")('x')::VARCHAR AS constant_seed_token, + __udf("edge_next_ticket")()::NUMBER(38, 0) AS next_ticket, + DIV0(__udf("edge_next_ticket")(), 2)::NUMBER(38, 6) AS half_ticket, + l.line_id AS edge_next_ticket_source, + 'edge_next_ticket edge_next_ticket'::VARCHAR AS look_alike_text, + __udf("edge_tag_label")(l.tag)::VARCHAR AS tag_label + FROM lines AS l +) + +SELECT * +FROM final diff --git a/benchmarks/duckshop/project/tests/unit/functions/python/test_edge_tag_label__labels_missing_tags.sql b/benchmarks/duckshop/project/tests/unit/functions/python/test_edge_tag_label__labels_missing_tags.sql new file mode 100644 index 0000000000..927b7c2226 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/functions/python/test_edge_tag_label__labels_missing_tags.sql @@ -0,0 +1,25 @@ +TEST (mode udf, name "edge_tag_label__labels_missing_tags"); + +WITH input_values AS ( + SELECT v.tag + FROM VALUES (' bath '), ('GIFT'), (''), (NULL) AS v(tag) +), + +__udf_actual__ AS ( + SELECT + i.tag, + __udf("edge_tag_label")(i.tag) AS tag_label + FROM input_values AS i +), + +-- Not declared STRICT, so Snowflake calls the handler for NULL and returns its label. +__udf_expected__ AS ( + SELECT + v.tag::VARCHAR AS tag, + v.tag_label::VARCHAR AS tag_label + FROM VALUES + (' bath ', 'Bath'), + ('GIFT', 'Gift'), + ('', 'Untagged'), + (NULL, 'Untagged') AS v(tag, tag_label) +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_python_values.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_python_values.sql new file mode 100644 index 0000000000..b8620b6f4c --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_python_values.sql @@ -0,0 +1,31 @@ +TEST (name "int_edge_python_values__hands_python_values_to_variants"); + +WITH __ref__stg_edge_basket_lines AS ( + SELECT + v.line_id, + v.basket_id, + v.line_no::NUMBER(38, 0) AS line_no, + v.sku, + v.tag + FROM VALUES + ('BL-01', 'B-1', 1, 'MALLARD-01', 'nothing'), + ('BL-02', 'B-1', 2, 'TEAL-02', 'zoned_time'), + ('BL-03', 'B-1', 3, 'EIDER-03', 'naive_time'), + ('BL-04', 'B-2', 1, 'PINTAIL-04', 'naive_time_fraction'), + ('BL-05', 'B-2', 2, 'MALLARD-01', 'exponent_decimal'), + ('BL-06', 'B-3', 1, 'MERGANSER-05', 'plain_decimal'), + ('BL-07', 'B-3', 2, 'TEAL-02', 'wide_integer'), + ('BL-08', 'B-4', 1, 'EIDER-03', 'nested'), + ('BL-09', 'B-4', 2, 'EIDER-03', 'some other kind'), + ('BL-10', 'B-4', 3, 'EIDER-03', NULL) AS v(line_id, basket_id, line_no, sku, tag) +), + +__expected__int_edge_python_values AS ( + SELECT + NULL AS kind, + NULL AS payload_is_sql_null, + NULL AS payload_is_json_null, + NULL AS payload_kind, + NULL AS payload_text, + NULL AS amount_as_float +) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_ticket_tokens.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_ticket_tokens.sql new file mode 100644 index 0000000000..67049642a1 --- /dev/null +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_ticket_tokens.sql @@ -0,0 +1,44 @@ +TEST (name "int_edge_ticket_tokens__calls_volatile_functions_once_per_row"); + +WITH __ref__stg_edge_basket_lines AS ( + SELECT + v.line_id, + v.basket_id, + v.line_no::NUMBER(38, 0) AS line_no, + v.sku, + v.tag + FROM VALUES + ('BL-01', 'B-1', 1, 'MALLARD-01', 'bath'), + ('BL-02', 'B-1', 2, 'TEAL-02', 'gift'), + ('BL-03', 'B-1', 3, 'EIDER-03', 'bath'), + ('BL-04', 'B-2', 1, 'PINTAIL-04', 'garden'), + ('BL-05', 'B-2', 2, 'MALLARD-01', 'bath'), + ('BL-06', 'B-3', 1, 'MERGANSER-05', 'pond'), + ('BL-07', 'B-4', 1, 'TEAL-02', NULL), + ('BL-08', 'B-4', 2, 'EIDER-03', 'gift') AS v(line_id, basket_id, line_no, sku, tag) +), + +__expected__int_edge_ticket_tokens AS ( + SELECT + NULL AS line_id, + NULL AS edge_next_ticket_source, + NULL AS look_alike_text, + NULL AS tag_label +), + +__assert__every_call_gives_a_new_value AS ( + SELECT COUNT(*) AS line_count + FROM __ref("int_edge_ticket_tokens") AS t + HAVING + COUNT(DISTINCT t.line_token) <> COUNT(*) + OR COUNT(DISTINCT t.constant_seed_token) <> COUNT(*) + OR COUNT(DISTINCT t.next_ticket) <> COUNT(*) +), + +__assert__half_tickets_are_positive AS ( + SELECT t.line_id + FROM __ref("int_edge_ticket_tokens") AS t + WHERE + t.half_ticket IS NULL + OR t.half_ticket <= 0 +) From 4bd810085ea55906d28a3570880c6a42db02c679 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:28:17 +0000 Subject: [PATCH 5/8] docs(benchmarks): describe the new DuckShop edge cases and their pending capture --- benchmarks/duckshop/README.md | 46 ++++++++++++++++++++++++++-------- benchmarks/duckshop/RUNBOOK.md | 17 ++++++++++--- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/benchmarks/duckshop/README.md b/benchmarks/duckshop/README.md index 298e8b8f62..05b4e865c5 100644 --- a/benchmarks/duckshop/README.md +++ b/benchmarks/duckshop/README.md @@ -117,23 +117,32 @@ project and does not change it: - unit tests in the matching `tests/unit//edge_cases/` folders and scenarios in `tests/scenarios/edge_cases/`; - raw `edge__*` sources in `sources/edge_cases.yml`, loaded by `python/loaders/edge_cases.py` from - fixed rows that `data_scale` does not change, so core data and core expectations stay the same. + fixed rows that `data_scale` does not change, so core data and core expectations stay the same; +- Python functions in `functions/python/edge_*.py`, tagged `edge_case`, and their tests in + `tests/unit/functions/python/test_edge_*.sql`. -It has 39 models (7 staging, 22 intermediate, 10 marts), 63 unit tests and 4 scenarios, in four -groups: +It has 67 models (12 staging, 45 intermediate, 10 marts), 5 Python functions, 92 unit tests and 4 +scenarios, in eight groups: | Group | Covers | |-------|--------| -| Semi-structured | JSON null against SQL NULL and missing keys; document text with sorted, case-differing and escaped keys; JSON number fidelity and casts out of `VARIANT`; path-like keys (`''`, `*`, `$`-prefixed, dotted) with `GET` and `GET_IGNORE_CASE`; `OBJECT_CONSTRUCT_KEEP_NULL`, `OBJECT_INSERT`, `OBJECT_DELETE`, `OBJECT_PICK`; array builders, `ARRAY_EXCEPT` and number spellings in `ARRAY_POSITION`; `FLATTEN` outer; lenient and malformed `TRY_PARSE_JSON`; typed lambdas | -| Dates and times | `TRY_TO_TIMESTAMP_NTZ` and its siblings over free text; epoch milliseconds as numbers and text; daylight-saving gaps and overlaps; fractional offsets; nanosecond truncation and epoch parts after 2242; pre-1970 text with sub-microsecond digits; date-part normalisation; month ends and leap days; week numbering across a year; sentinel timestamps | -| Ordering, numbers and text | NULL ordering under `DESC`; ties in ordered aggregates, `MAX_BY`, `MODE` and `ANY_VALUE`; default window frames; `UNION ALL BY NAME`; `ROUND` half-way values, decimal division, `MOD` and 38-digit amounts; integers past 2^53 against `FLOAT`; regular expressions and backslash escapes; `LIKE ... ESCAPE` and `ILIKE ANY`; `SPLIT_PART` and `SUBSTR` before the start; `NOT IN` with a NULL; names in many scripts | +| Semi-structured | JSON null against SQL NULL and missing keys; document text with sorted, case-differing and escaped keys; JSON number fidelity and casts out of `VARIANT`; path-like keys (`''`, `*`, `$`-prefixed, dotted) with `GET` and `GET_IGNORE_CASE`; `OBJECT_CONSTRUCT_KEEP_NULL`, `OBJECT_INSERT`, `OBJECT_DELETE`, `OBJECT_PICK`; array builders, `ARRAY_EXCEPT` and number spellings in `ARRAY_POSITION`; `FLATTEN` outer; lenient and malformed `TRY_PARSE_JSON`; typed lambdas; whole numbers written with zero fractions, numbers with two decimal points and numbers past 38 digits; `FLATTEN` `RECURSIVE` paths for keys holding `$`, `-`, `@`, `[`, spaces and non-ASCII letters | +| Ordering through arrays | an unordered `ARRAY_AGG` ranked under `LIMIT` by position, by name, through `IFF` and over `UNION ALL`, with fixtures whose pick does not depend on element order; order-free reads (`ARRAY_SIZE`, `ARRAY_CONTAINS`, `ARRAYS_OVERLAP`, `ARRAY_DISTINCT` of `ARRAY_CAT`, `FLATTEN` values through a CTE); `SELECT DISTINCT` over arrays repeated in different orders | +| VARIANT comparisons | `ARRAY_MAX`, `ARRAY_MIN` and `ARRAY_REVERSE` over arrays mixing numbers, strings, booleans, JSON nulls and integers past 2^53; `MAX` across kinds and non-ASCII strings; `=` and `EQUAL_NULL` for `1`, `"1"` and `1.0`, reordered keys, JSON null and DATE or TIMESTAMP values held in a `VARIANT` against their text; `MIN_BY` and `MAX_BY` of `VARIANT` values; `PARTITION BY`, `SELECT DISTINCT` and `ARRAY_AGG(DISTINCT)` over `VARIANT`; JSON nulls in `ARRAY_REMOVE`, `ARRAY_DISTINCT` and `ARRAYS_OVERLAP`; decimals with trailing zeros in `ARRAY_CONTAINS` and `ARRAY_POSITION`; `OBJECT_CONSTRUCT(* EXCLUDE ("note"))` | +| Dates and times | `TRY_TO_TIMESTAMP_NTZ` and its siblings over free text; epoch milliseconds as numbers and text; daylight-saving gaps and overlaps; fractional offsets; nanosecond truncation and epoch parts after 2242; pre-1970 text with sub-microsecond digits; date-part normalisation; month ends and leap days; week numbering across a year; sentinel timestamps; `TRY_TO_DATE` with numeric and month-name formats over unpadded fields, two-digit years, ISO `T` and `Z`, trailing letters, a long s, mixed separators and runs of spaces; text read as `TIMESTAMP_TZ` and `TIMESTAMP_LTZ` with `T`, `Z`, `+00:00`, `+0000`, `+09:00` and `-05:30`, back as the UTC instant, date, year and day start, and compared with text, `TIMESTAMP_NTZ` and `DATE`; `FIRST_VALUE` of a zoned value as a `DATE`; `DATEADD` and `TIMEADD` on `TIME(3)` and the type they return; `DATEDIFF` across units; `TIME(9)`, `FF3` and bare `FF` formats truncating | +| Ordering, numbers and text | NULL ordering under `DESC`; ties in ordered aggregates, `MAX_BY`, `MODE` and `ANY_VALUE`; default window frames; `UNION ALL BY NAME`; `ROUND` half-way values, decimal division, `MOD` and 38-digit amounts; integers past 2^53 against `FLOAT`; regular expressions and backslash escapes; `LIKE ... ESCAPE` and `ILIKE ANY`; `SPLIT_PART` and `SUBSTR` before the start; `NOT IN` with a NULL; names in many scripts; `JOIN ... USING` between `NUMBER(18,0)`, `NUMBER(38,0)` and text keys, with the types of the merged and qualified keys; NULL arithmetic and the types of NULL, decimal and exponent literals, `COUNT`, `ROW_NUMBER`, `%` and `NULLIF`; `MEDIAN` at scales 0 to 15; `DIV0` by literal zeros; `MODE`, `MEDIAN` and `COUNT_IF` over no rows; aggregates inside window functions and `QUALIFY`; `ROLLUP` totals; `TRUNC` of `FLOAT`; infinity words in other scripts; `CASE` without `ELSE` and `DECODE` over text; regular expressions with a `?` after a lazy quantifier, classes opening with `]`, empty matches and `.` against a carriage return; case-insensitive matches outside ASCII; `ILIKE` with a long s; `JAROWINKLER_SIMILARITY` of accented names in both cases; Base64 text with line feeds | +| Lineage and analysis | clauses whose columns decide rows without being output (`WHERE`, `QUALIFY`, `GROUP BY` by position and `ALL`, `ORDER BY` under `LIMIT`, `EXISTS`, a `FLATTEN` input, `ROLLUP` by position, `EXCEPT` under `LIMIT`); `RIGHT JOIN ... USING`, `NATURAL JOIN`, a `USING` key under a later outer join, and unmatched `ASOF JOIN` rows; `CONNECT BY` beside a column named `level`; a recursive CTE without `RECURSIVE` whose columns shift per iteration; `LAST_DAY` with a date part; positional columns and the names Snowflake gives unaliased expressions; `TEXT(5)` and widened `COUNT` contracts | +| Python functions | volatile tokens and numbers from a column, a constant and no argument, and `DIV0` of a volatile call beside look-alike names, checked by assertions that hold for any values; a handler answering NULL input without `STRICT`; Python `None` as JSON null, and datetimes, Decimals and integers past 2^53 handed to `VARIANT` and `FLOAT` results | | Scenarios and lifecycle | typed values across model boundaries; `VARIANT` expected output; a star under a lower-case contract and a quoted camelCase export; a snapshot keyed and compared on quoted camelCase columns; a cursor-window incremental model over far-apart fixture loads; a change log merged across two loads, checked against a full rebuild | -Unit-test expectations in the edge-case area were captured on Snowflake under the `edge` target's +Unit-test expectations in the edge-case area are captured on Snowflake under the `edge` target's pinned session, rather than derived by hand; scenario expectations, and the assertions in tests -about ties, are written by hand from those captured values. `fct_edge_delivery_events` is disabled: it is the -acceptance case for cursor `delete_insert` models that emit rows outside the window they replace. -See [RUNBOOK.md](RUNBOOK.md), step 7. +about ties and volatile values, are written by hand from those captured values. The 28 model tests +added with the ordering-through-arrays, `VARIANT` comparison, lineage and Python function cases, +and with the newer rows of the other groups, still hold placeholder expectations: run +`capture_edge_expectations.py --write` on them (RUNBOOK.md, step 7) before relying on them. +`fct_edge_delivery_events` is disabled: it is the acceptance case for cursor `delete_insert` models +that emit rows outside the window they replace. See [RUNBOOK.md](RUNBOOK.md), step 7. Benchmarks and core measurements leave the area out: @@ -210,6 +219,9 @@ them as expected differences or refusals instead of errors: | `TIMESTAMP_LTZ` | `stg_tickets`, `int_ticket_first_response`, `rpt_customer_order_lookup` | | `CURRENT_TIMESTAMP()` | `int_promotion_performance`, `int_ticket_sla_breaches`, `rpt_customer_order_lookup` | +In the edge-case area, `int_edge_zoned_texts` (`TIMESTAMP_LTZ`) and `int_edge_ticket_tokens` +(volatile Python functions, whose values change on every run) carry the tag too. + ## Known limitations These SQLBuild limitations shape parts of the project; each workaround is a reviewed exception or @@ -228,6 +240,20 @@ noted here: - `SQLCLEAN018` can only take proof from single-column keys. Append-only feeds redeliver rows, so their id is unique only together with `_loaded_at` (the `unique_per_delivery` audit), and the staging models that keep the latest delivery carry reviewed exceptions. +- SQLBuild 0.186.2 reports errors on some valid Snowflake SQL, so the edge-case area cannot hold + these shapes until compilation accepts them: a reference to an alias defined later in the same + `SELECT` or to an enclosing `SELECT`'s alias from a correlated subquery, a qualified positional + column (`o.$2`), a `MATCH_RECOGNIZE` alias used as a qualifier, `ARRAY_UNIQUE_AGG`, + `BITOR_AGG`, `BITXOR_AGG` and `MINHASH` under `GROUP BY`, the unqualified key of a + `NATURAL JOIN`, a timestamp compared with a text column, implicit text and number arguments to + `LPAD`, `REPEAT`, `REVERSE`, `LN` and `EXP`, a `DATE` plus a cast `NUMBER`, `::BYTEINT`, `GROUP BY` + expressions that differ only in case, an outer aggregate inside a subquery and a `WHERE` over a + number. `DATEADD` on a `TIME(3)` is typed `TIME(3)`, where Snowflake returns `TIME(9)`, so + `int_edge_clock_arithmetic` casts those results and records Snowflake's type as text. +- Some shapes need objects DuckShop does not define: fully qualified `schema.table.column` names + (model SQL has no intrinsic for its own schema, and unit tests replace references with CTEs), + `METADATA$` columns of a stream or `CHANGES` clause, and sequence `NEXTVAL`. SQLBuild's `@udf` + has no option for `STRICT` (`RETURNS NULL ON NULL INPUT`), so no function declares it. ## Compile diff --git a/benchmarks/duckshop/RUNBOOK.md b/benchmarks/duckshop/RUNBOOK.md index 93a836b905..2c78605335 100644 --- a/benchmarks/duckshop/RUNBOOK.md +++ b/benchmarks/duckshop/RUNBOOK.md @@ -177,9 +177,14 @@ sqb test --target edge --select tag:edge_case --concurrency 8 sqb scenario test --target edge --select edge_cases ``` -The build loads the `edge__*` sources from fixed rows that `data_scale` does not change, then -builds the edge models and runs their tests and audits. `fct_edge_delivery_events` is disabled and -reports `skip: disabled` (see [Pending acceptance cases](#pending-acceptance-cases)). +The build loads the `edge__*` sources from fixed rows that `data_scale` does not change, creates +the area's Python functions (`functions/python/edge_*.py`, tagged `edge_case`), then builds the edge +models and runs their tests and audits. `fct_edge_delivery_events` is disabled and reports +`skip: disabled` (see [Pending acceptance cases](#pending-acceptance-cases)). + +`int_edge_ticket_tokens` calls volatile Python functions, so its values change on every run; its +test compares only the deterministic columns and asserts facts that hold for any values, such as +every token being distinct. ### Replay the delivery change log as two loads @@ -222,6 +227,12 @@ which is the point, but a fixture mistake is captured as faithfully as a real ed expectations, and the assertions in tests about ties (where Snowflake may return any of the tied rows), are written by hand from the captured values. +Some edge models read values whose order Snowflake does not define, such as an `ARRAY_AGG` +without `WITHIN GROUP` or a `MAX_BY` whose keys tie. They output only order-free facts, and a +comment in the model or test says so; leave any column that can still differ between runs out of +the expected CTE and check it with an assertion instead. Run the capture twice on such tests: the +second run must leave the files unchanged. + Capture scenario snapshots for the edge scenarios with `sqb scenario capture --target edge --select edge_cases`. From eb668764430a88bc3f47f62f73be95c4457a422d Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sun, 1 Nov 2026 19:09:07 +0000 Subject: [PATCH 6/8] test(benchmarks): adjust DuckShop edge cases to what Snowflake accepts Drop a window frame without ORDER BY, CONVERT_TIMEZONE with a lower-case 'utc' and a scalar subquery inside CONNECT BY, which Snowflake rejects; read SYSTEM$TYPEOF from finished CTEs; check tied modes and scale-15 medians with assertions; output typed VARIANT picks as kind and text; cast date-text clock columns in staging so the source contract has no precision. Expectations captured on Snowflake. --- .../edge_cases/int_edge_amount_edges.sql | 11 +- .../edge_cases/int_edge_amount_statistics.sql | 7 +- .../edge_cases/int_edge_category_walk.sql | 41 +- .../edge_cases/int_edge_null_arithmetic.sql | 37 +- .../edge_cases/int_edge_row_deciders.sql | 14 +- .../edge_cases/int_edge_variant_matches.sql | 22 +- .../edge_cases/int_edge_zoned_texts.sql | 8 +- .../edge_cases/stg_edge_date_texts.sql | 11 +- .../duckshop/project/sources/edge_cases.yml | 6 +- .../duckshop/project/sqlbuild_project.toml | 7 +- .../edge_cases/test_int_edge_amount_edges.sql | 116 +++- .../test_int_edge_amount_statistics.sql | 81 ++- .../test_int_edge_category_walk.sql | 48 +- .../test_int_edge_variant_matches.sql | 609 +++++++++++++++++- .../edge_cases/test_int_edge_zoned_texts.sql | 207 +++++- .../edge_cases/test_stg_edge_date_texts.sql | 29 +- 16 files changed, 1114 insertions(+), 140 deletions(-) diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_amount_edges.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_amount_edges.sql index 30e1940320..edd8ed7fb1 100644 --- a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_amount_edges.sql +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_amount_edges.sql @@ -3,8 +3,7 @@ MODEL ( One row per check with numeric edges: DIV0 and DIV0NULL by literal zeros and NULL, TRUNC of FLOAT ratios that sit just below a whole number of cents, FLOAT values as text (pi among them), infinity and NaN words in other scripts read by TRY_TO_DOUBLE, and LAG with offset 0 and IGNORE - NULLS. A running COUNT_IF without ORDER BY is not compared row by row, since Snowflake may - number the rows in any order; the test checks only its total per group + NULLS. A running window frame without ORDER BY is left out: Snowflake rejects it """, tags [edge_case], columns ( @@ -22,7 +21,6 @@ MODEL ( pi_text (type VARCHAR), key_text_as_double (type FLOAT), same_amount_lag (type NUMBER(18, 2)), - running_paid_count (type NUMBER(38, 0)), ), ); @@ -51,12 +49,7 @@ final AS ( c.ratio::VARCHAR AS ratio_text, PI()::VARCHAR AS pi_text, TRY_TO_DOUBLE(c.key_text)::FLOAT AS key_text_as_double, - LAG(c.amount, 0) IGNORE NULLS OVER (ORDER BY c.check_id)::NUMBER(18, 2) AS same_amount_lag, - -- No ORDER BY: each row's count depends on the order Snowflake reads the group in. - COUNT_IF(c.amount > 0) OVER ( - PARTITION BY c.group_code - ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW - )::NUMBER(38, 0) AS running_paid_count + LAG(c.amount, 0) IGNORE NULLS OVER (ORDER BY c.check_id)::NUMBER(18, 2) AS same_amount_lag FROM checks AS c ) diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_amount_statistics.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_amount_statistics.sql index 888fc85e3a..f23b88903d 100644 --- a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_amount_statistics.sql +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_amount_statistics.sql @@ -2,9 +2,10 @@ MODEL ( description """ One row per check group with MEDIAN at scales 0 to 15, where the two middle values differ in their last digits; MODE and COUNT_IF per group; and MODE, MEDIAN and COUNT_IF over no rows, - without GROUP BY and with HAVING but no GROUP BY, which Snowflake answers with one row. The tie - pick of a MAX_BY whose keys all tie, over FLOAT values of either sign of zero, is not compared: - either row is a valid answer + without GROUP BY and with HAVING but no GROUP BY, which Snowflake answers with one row; MODE of + a number over no rows can come back as 0, not NULL. Tied modes, the pick of a MAX_BY whose keys + all tie and MEDIAN above scale 12, which Snowflake does not compute exactly, are checked by + assertions rather than compared """, tags [edge_case], columns ( diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_category_walk.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_category_walk.sql index 8797cdc6ad..b2091bbc42 100644 --- a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_category_walk.sql +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_category_walk.sql @@ -1,9 +1,11 @@ MODEL ( description """ One row per category reached by walking the category tree with START WITH ... CONNECT BY, with - its walk depth (the LEVEL pseudo-column), root name and path. Beside it, a scalar subquery reads - a column named level from another relation: inside the subquery that name is the column, not the - walk depth + its walk depth (the LEVEL pseudo-column), root name and path. A second walk joins in a relation + with a column named level: there Snowflake reads even the qualified d.level as the walk depth, + and CONNECT_BY_ROOT returns the row's own value instead of its root's. After the walk, a scalar + subquery reads the level column itself. Snowflake fails with an internal error on a scalar + subquery inside a CONNECT BY query, so none is written there """, tags [edge_case], columns ( @@ -11,6 +13,8 @@ MODEL ( walk_depth (type NUMBER(38, 0)), root_name (type VARCHAR), category_path (type VARCHAR), + joined_level (type NUMBER(38, 0)), + joined_root_name (type VARCHAR), deepest_shelf (type NUMBER(38, 0)), ), ); @@ -28,29 +32,48 @@ shelves AS ( FROM __ref("stg_edge_category_links") AS l ), +deepest AS ( + SELECT MAX(s.level) AS level + FROM shelves AS s +), + walk AS ( SELECT k.category_id, LEVEL AS walk_depth, CONNECT_BY_ROOT k.category_name AS root_name, - SYS_CONNECT_BY_PATH(k.category_name, ' > ') AS category_path, - ( - SELECT MAX(level) - FROM shelves - ) AS deepest_shelf + SYS_CONNECT_BY_PATH(k.category_name, ' > ') AS category_path FROM links AS k START WITH k.parent_id IS NULL CONNECT BY k.parent_id = PRIOR k.category_id ), +joined_walk AS ( + SELECT + k.category_id, + d.level AS joined_level, + CONNECT_BY_ROOT k.category_name AS joined_root_name + FROM links AS k + CROSS JOIN deepest AS d +START WITH k.parent_id IS NULL +CONNECT BY k.parent_id = PRIOR k.category_id +), + final AS ( SELECT w.category_id, w.walk_depth::NUMBER(38, 0) AS walk_depth, w.root_name::VARCHAR AS root_name, w.category_path::VARCHAR AS category_path, - w.deepest_shelf::NUMBER(38, 0) AS deepest_shelf + j.joined_level::NUMBER(38, 0) AS joined_level, + j.joined_root_name::VARCHAR AS joined_root_name, + ( + SELECT MAX(level) + FROM shelves + )::NUMBER(38, 0) AS deepest_shelf FROM walk AS w + INNER JOIN joined_walk AS j + ON w.category_id = j.category_id ) SELECT * diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_null_arithmetic.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_null_arithmetic.sql index a30d9f7c25..6b9e6d3453 100644 --- a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_null_arithmetic.sql +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_null_arithmetic.sql @@ -52,10 +52,17 @@ missing AS ( ), counted AS ( - SELECT SYSTEM$TYPEOF(COUNT(c.check_id)) AS count_type + SELECT COUNT(c.check_id) AS check_count FROM checks AS c ), +-- SYSTEM$TYPEOF of an aggregate does not make its query aggregate, so each type is read from a +-- column of a finished CTE. +count_typed AS ( + SELECT SYSTEM$TYPEOF(k.check_count) AS count_type + FROM counted AS k +), + with_decimal AS ( SELECT c.amount AS mixed_amount FROM checks AS c @@ -63,22 +70,40 @@ with_decimal AS ( SELECT 1.5 AS mixed_amount ), -union_typed AS ( - SELECT MAX(SYSTEM$TYPEOF(w.mixed_amount)) AS union_decimal_type +union_types AS ( + SELECT SYSTEM$TYPEOF(w.mixed_amount) AS mixed_type FROM with_decimal AS w ), +-- Every row has the same type. MAX over SYSTEM$TYPEOF fails in Snowflake with an internal error +-- once the result is joined on, so one row is taken instead. +union_typed AS ( + SELECT u.mixed_type AS union_decimal_type + FROM union_types AS u + ORDER BY + u.mixed_type + LIMIT 1 +), + all_null AS ( SELECT NULL AS note UNION ALL SELECT NULL AS note ), -all_null_typed AS ( - SELECT MAX(SYSTEM$TYPEOF(a.note)) AS all_null_union_type +all_null_types AS ( + SELECT SYSTEM$TYPEOF(a.note) AS note_type FROM all_null AS a ), +all_null_typed AS ( + SELECT n.note_type AS all_null_union_type + FROM all_null_types AS n + ORDER BY + n.note_type + LIMIT 1 +), + computed AS ( SELECT c.check_id, @@ -154,7 +179,7 @@ final AS ( c.case_without_else::VARCHAR AS case_without_else, c.decoded::VARCHAR AS decoded FROM computed AS c - CROSS JOIN counted AS k + CROSS JOIN count_typed AS k CROSS JOIN union_typed AS u CROSS JOIN all_null_typed AS n ) diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_row_deciders.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_row_deciders.sql index 1c69b61ef4..315b424bf9 100644 --- a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_row_deciders.sql +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_row_deciders.sql @@ -100,12 +100,14 @@ gift_flags AS ( SELECT 'exists_output' AS decider, b.basket_id, - EXISTS( - SELECT 1 - FROM lines AS g - WHERE - g.basket_id = b.basket_id - AND g.tag = 'gift' + ( + EXISTS( + SELECT 1 + FROM lines AS g + WHERE + g.basket_id = b.basket_id + AND g.tag = 'gift' + ) )::VARCHAR AS detail FROM baskets AS b ), diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_variant_matches.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_variant_matches.sql index 83d6058e42..53eecce497 100644 --- a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_variant_matches.sql +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_variant_matches.sql @@ -4,8 +4,10 @@ MODEL ( against "1" and 1.0, JSON null against SQL NULL, objects with reordered keys, and a DATE or TIMESTAMP held in a VARIANT against the same text, which Snowflake keeps typed. Each value also carries its group's MIN_BY and MAX_BY VARIANT picks, how many rows share its value in a window - partition, the group's distinct values by SELECT DISTINCT and ARRAY_AGG(DISTINCT), a scalar cast - of the value, and an OBJECT_CONSTRUCT star excluding a quoted lower-case column + partition, the group's distinct values by SELECT DISTINCT (counted with COUNT, which skips JSON + null) and ARRAY_AGG(DISTINCT), a scalar cast of the value, and an OBJECT_CONSTRUCT star + excluding a quoted lower-case column. Picks and objects that can hold typed values are output as + their kind and JSON text, since JSON text alone would lose the type """, tags [edge_case], columns ( @@ -15,12 +17,12 @@ MODEL ( values_equal (type BOOLEAN), values_equal_null (type BOOLEAN), left_partition_size (type NUMBER(38, 0)), - group_first_value (type VARIANT), - group_last_value (type VARIANT), + group_first_value (type VARCHAR), + group_last_value (type VARCHAR), group_distinct_rows (type NUMBER(38, 0)), group_distinct_elements (type NUMBER(38, 0)), left_scalar_text (type VARCHAR), - left_object_without_note (type OBJECT), + left_object_without_note (type VARCHAR), ), ); @@ -127,8 +129,12 @@ final AS ( )::BOOLEAN AS values_equal, EQUAL_NULL(l.attribute_value, r.attribute_value)::BOOLEAN AS values_equal_null, l.partition_size::NUMBER(38, 0) AS left_partition_size, - g.group_first_value, - g.group_last_value, + ( + TYPEOF(g.group_first_value) || ' ' || TO_JSON(g.group_first_value) + )::VARCHAR AS group_first_value, + ( + TYPEOF(g.group_last_value) || ' ' || TO_JSON(g.group_last_value) + )::VARCHAR AS group_last_value, c.group_distinct_rows::NUMBER(38, 0) AS group_distinct_rows, g.group_distinct_elements::NUMBER(38, 0) AS group_distinct_elements, IFF( @@ -136,7 +142,7 @@ final AS ( NULL, l.attribute_value )::VARCHAR AS left_scalar_text, - l.object_without_note::OBJECT AS left_object_without_note + TO_JSON(l.object_without_note)::VARCHAR AS left_object_without_note FROM partitioned AS l INNER JOIN partitioned AS r ON l.group_code = r.group_code diff --git a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_zoned_texts.sql b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_zoned_texts.sql index d85e142056..3367ace75b 100644 --- a/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_zoned_texts.sql +++ b/benchmarks/duckshop/project/models/intermediate/edge_cases/int_edge_zoned_texts.sql @@ -3,9 +3,9 @@ MODEL ( One row per zoned partner timestamp text read as TIMESTAMP_TZ and TIMESTAMP_LTZ, with T, Z, +00:00, +0000, +09:00 and -05:30 spellings, read back as the instant at UTC, its date, year and day start, and compared with wall-clock text, a TIMESTAMP_NTZ and a DATE. Also the date of the - first zoned value by window, CONVERT_TIMEZONE with a lower-case 'utc', and the UTC hour of a - wall-clock time just before 1970. TIMESTAMP_LTZ results depend on the session time zone, which - the edge target pins to UTC + first zoned value by window and the UTC hour of a wall-clock time just before 1970. + CONVERT_TIMEZONE with a lower-case 'utc' is left out: Snowflake rejects it as an unknown time + zone. TIMESTAMP_LTZ results depend on the session time zone, which the edge target pins to UTC """, tags [edge_case, known_divergence], columns ( @@ -28,7 +28,6 @@ MODEL ( zoned_from_next_day (type BOOLEAN), local_zoned_from_next_day (type BOOLEAN), first_zoned_date (type DATE), - zoned_lower_utc (type TIMESTAMP_NTZ), wall_clock_utc_hour (type NUMBER(38, 0)), ), ); @@ -77,7 +76,6 @@ final AS ( ORDER BY p.text_id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING )::DATE AS first_zoned_date, - CONVERT_TIMEZONE('utc', p.zoned)::TIMESTAMP_NTZ AS zoned_lower_utc, DATE_PART(hour, CONVERT_TIMEZONE('UTC', p.wall_clock))::NUMBER(38, 0) AS wall_clock_utc_hour FROM parsed AS p ) diff --git a/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_date_texts.sql b/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_date_texts.sql index ccd2f9a5ea..03fcc0100c 100644 --- a/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_date_texts.sql +++ b/benchmarks/duckshop/project/models/staging/edge_cases/stg_edge_date_texts.sql @@ -1,7 +1,8 @@ MODEL ( description """ - One row per partner date and time text, with the clock columns kept at their declared precisions - so later models can read them as text, zoned times and times of day + One row per partner date and time text, with the clock columns cast to fixed precisions + (TIMESTAMP_NTZ(3), TIME(3) and TIME(9)) so later models can read them as text, zoned times and + times of day """, tags [edge_case], columns ( @@ -25,10 +26,10 @@ final AS ( s.text_id, s.date_text, s.zoned_text, - s.wall_clock, + s.wall_clock::TIMESTAMP_NTZ(3) AS wall_clock, s.wall_clock_text, - s.local_time, - s.fine_time + s.local_time::TIME(3) AS local_time, + s.fine_time::TIME(9) AS fine_time FROM src AS s ) diff --git a/benchmarks/duckshop/project/sources/edge_cases.yml b/benchmarks/duckshop/project/sources/edge_cases.yml index 52da7225d8..738e9b0f2a 100644 --- a/benchmarks/duckshop/project/sources/edge_cases.yml +++ b/benchmarks/duckshop/project/sources/edge_cases.yml @@ -315,13 +315,13 @@ sources: - name: zoned_text type: VARCHAR - name: wall_clock - type: TIMESTAMP_NTZ(3) + type: TIMESTAMP_NTZ - name: wall_clock_text type: VARCHAR - name: local_time - type: TIME(3) + type: TIME - name: fine_time - type: TIME(9) + type: TIME - name: _loaded_at type: TIMESTAMP_NTZ nullable: false diff --git a/benchmarks/duckshop/project/sqlbuild_project.toml b/benchmarks/duckshop/project/sqlbuild_project.toml index 477f8254b2..396a2b12be 100644 --- a/benchmarks/duckshop/project/sqlbuild_project.toml +++ b/benchmarks/duckshop/project/sqlbuild_project.toml @@ -315,11 +315,12 @@ Snowflake gives them. """ [[rules.rule_ignores]] -rules = ["SQLCLEAN028"] +rules = ["SQLCLEAN027", "SQLCLEAN028"] paths = ["models/intermediate/edge_cases/int_edge_category_walk.sql"] reason = """ -LEVEL is a hierarchical-query pseudo-column that cannot be qualified, and the edge case reads a -column named level from another relation unqualified inside a scalar subquery on purpose. +LEVEL and CONNECT_BY_ROOT belong to the walk, not to a relation, so they cannot be qualified, and +the edge case reads a column named level from another relation unqualified inside a scalar +subquery on purpose. """ # <<< edge-case area diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_amount_edges.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_amount_edges.sql index 8b22eea798..feb7d5d802 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_amount_edges.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_amount_edges.sql @@ -22,26 +22,98 @@ WITH __ref__stg_edge_amount_checks AS ( __expected__int_edge_amount_edges AS ( SELECT - NULL AS check_id, - NULL AS group_code, - NULL AS one_over_zero, - NULL AS amount_over_zero, - NULL AS amount_over_scaled_zero, - NULL AS amount_over_minus_zero, - NULL AS amount_over_null, - NULL AS null_over_zero, - NULL AS ratio_truncated_2, - NULL AS ratio_truncated_3, - NULL AS ratio_text, - NULL AS pi_text, - NULL AS key_text_as_double, - NULL AS same_amount_lag -), - -__assert__running_count_ends_at_the_group_total AS ( - SELECT e.group_code - FROM __ref("int_edge_amount_edges") AS e - GROUP BY - e.group_code - HAVING MAX(e.running_paid_count) <> IFF(e.group_code = 'G-1', 2, 1) + 'AC-01' AS check_id, + 'G-1' AS group_code, + 0.000000::NUMBER(38, 6) AS one_over_zero, + 0.000000::NUMBER(38, 6) AS amount_over_zero, + 0.000000::NUMBER(38, 6) AS amount_over_scaled_zero, + 0.000000::NUMBER(38, 6) AS amount_over_minus_zero, + 0.000000::NUMBER(38, 6) AS amount_over_null, + CAST(NULL AS NUMBER(38, 6)) AS null_over_zero, + '0.28'::FLOAT AS ratio_truncated_2, + '0.29'::FLOAT AS ratio_truncated_3, + '0.29' AS ratio_text, + '3.141592654' AS pi_text, + CAST(NULL AS FLOAT) AS key_text_as_double, + 10.00::NUMBER(18, 2) AS same_amount_lag + UNION ALL + SELECT + 'AC-02' AS check_id, + 'G-1' AS group_code, + 0.000000::NUMBER(38, 6) AS one_over_zero, + CAST(NULL AS NUMBER(38, 6)) AS amount_over_zero, + CAST(NULL AS NUMBER(38, 6)) AS amount_over_scaled_zero, + CAST(NULL AS NUMBER(38, 6)) AS amount_over_minus_zero, + CAST(NULL AS NUMBER(38, 6)) AS amount_over_null, + CAST(NULL AS NUMBER(38, 6)) AS null_over_zero, + '1.0'::FLOAT AS ratio_truncated_2, + '1.004'::FLOAT AS ratio_truncated_3, + '1.005' AS ratio_text, + '3.141592654' AS pi_text, + 'inf'::FLOAT AS key_text_as_double, + CAST(NULL AS NUMBER(18, 2)) AS same_amount_lag + UNION ALL + SELECT + 'AC-03' AS check_id, + 'G-1' AS group_code, + 0.000000::NUMBER(38, 6) AS one_over_zero, + 0.000000::NUMBER(38, 6) AS amount_over_zero, + 0.000000::NUMBER(38, 6) AS amount_over_scaled_zero, + 0.000000::NUMBER(38, 6) AS amount_over_minus_zero, + 0.000000::NUMBER(38, 6) AS amount_over_null, + CAST(NULL AS NUMBER(38, 6)) AS null_over_zero, + '2.67'::FLOAT AS ratio_truncated_2, + '2.675'::FLOAT AS ratio_truncated_3, + '2.675' AS ratio_text, + '3.141592654' AS pi_text, + 'nan'::FLOAT AS key_text_as_double, + 2.50::NUMBER(18, 2) AS same_amount_lag + UNION ALL + SELECT + 'AC-04' AS check_id, + 'G-2' AS group_code, + 0.000000::NUMBER(38, 6) AS one_over_zero, + 0.000000::NUMBER(38, 6) AS amount_over_zero, + 0.000000::NUMBER(38, 6) AS amount_over_scaled_zero, + 0.000000::NUMBER(38, 6) AS amount_over_minus_zero, + 0.000000::NUMBER(38, 6) AS amount_over_null, + CAST(NULL AS NUMBER(38, 6)) AS null_over_zero, + '-155.5'::FLOAT AS ratio_truncated_2, + '-155.5'::FLOAT AS ratio_truncated_3, + '-155.5' AS ratio_text, + '3.141592654' AS pi_text, + '-inf'::FLOAT AS key_text_as_double, + -0.50::NUMBER(18, 2) AS same_amount_lag + UNION ALL + SELECT + 'AC-05' AS check_id, + 'G-2' AS group_code, + 0.000000::NUMBER(38, 6) AS one_over_zero, + 0.000000::NUMBER(38, 6) AS amount_over_zero, + 0.000000::NUMBER(38, 6) AS amount_over_scaled_zero, + 0.000000::NUMBER(38, 6) AS amount_over_minus_zero, + 0.000000::NUMBER(38, 6) AS amount_over_null, + CAST(NULL AS NUMBER(38, 6)) AS null_over_zero, + '0.56'::FLOAT AS ratio_truncated_2, + '0.57'::FLOAT AS ratio_truncated_3, + '0.57' AS ratio_text, + '3.141592654' AS pi_text, + CAST(NULL AS FLOAT) AS key_text_as_double, + 0.00::NUMBER(18, 2) AS same_amount_lag + UNION ALL + SELECT + 'AC-06' AS check_id, + 'G-2' AS group_code, + 0.000000::NUMBER(38, 6) AS one_over_zero, + 0.000000::NUMBER(38, 6) AS amount_over_zero, + 0.000000::NUMBER(38, 6) AS amount_over_scaled_zero, + 0.000000::NUMBER(38, 6) AS amount_over_minus_zero, + 0.000000::NUMBER(38, 6) AS amount_over_null, + CAST(NULL AS NUMBER(38, 6)) AS null_over_zero, + '3.0'::FLOAT AS ratio_truncated_2, + '3.0'::FLOAT AS ratio_truncated_3, + '3' AS ratio_text, + '3.141592654' AS pi_text, + '1000.0'::FLOAT AS key_text_as_double, + 4.00::NUMBER(18, 2) AS same_amount_lag ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_amount_statistics.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_amount_statistics.sql index c37192e5b5..5c980da579 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_amount_statistics.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_amount_statistics.sql @@ -21,24 +21,73 @@ WITH __ref__stg_edge_amount_checks AS ( __expected__int_edge_amount_statistics AS ( SELECT - NULL AS group_code, - NULL AS median_scale_0, - NULL AS median_scale_2, - NULL AS median_scale_4, - NULL AS median_scale_10, - NULL AS median_scale_11, - NULL AS median_scale_12, - NULL AS median_scale_15, - NULL AS mode_amount, - NULL AS paid_count, - NULL AS mode_over_no_rows, - NULL AS median_over_no_rows, - NULL AS paid_count_over_no_rows, - NULL AS mode_with_having_only + 'G-1' AS group_code, + 0.000::NUMBER(38, 3) AS median_scale_0, + 0.12000::NUMBER(38, 5) AS median_scale_2, + 0.1235000::NUMBER(38, 7) AS median_scale_4, + 0.1234567890000::NUMBER(38, 13) AS median_scale_10, + 0.12345678901000::NUMBER(38, 14) AS median_scale_11, + 0.123456789012000::NUMBER(38, 15) AS median_scale_12, + 2 AS paid_count, + CAST(NULL AS NUMBER(18, 2)) AS mode_over_no_rows, + CAST(NULL AS NUMBER(38, 5)) AS median_over_no_rows, + CAST(NULL AS NUMBER(38, 0)) AS paid_count_over_no_rows, + 0 AS mode_with_having_only + UNION ALL + SELECT + 'G-2' AS group_code, + 1.000::NUMBER(38, 3) AS median_scale_0, + 1.00000::NUMBER(38, 5) AS median_scale_2, + 1.0000000::NUMBER(38, 7) AS median_scale_4, + 1.0000000000000::NUMBER(38, 13) AS median_scale_10, + 1.00000000000000::NUMBER(38, 14) AS median_scale_11, + 1.000000000000000::NUMBER(38, 15) AS median_scale_12, + 0 AS paid_count, + CAST(NULL AS NUMBER(18, 2)) AS mode_over_no_rows, + CAST(NULL AS NUMBER(38, 5)) AS median_over_no_rows, + CAST(NULL AS NUMBER(38, 0)) AS paid_count_over_no_rows, + 0 AS mode_with_having_only + UNION ALL + SELECT + 'G-3' AS group_code, + 0.000::NUMBER(38, 3) AS median_scale_0, + 0.00000::NUMBER(38, 5) AS median_scale_2, + 0.0000000::NUMBER(38, 7) AS median_scale_4, + 0.0000000000000::NUMBER(38, 13) AS median_scale_10, + 0.00000000000000::NUMBER(38, 14) AS median_scale_11, + 0.000000000000000::NUMBER(38, 15) AS median_scale_12, + 0 AS paid_count, + CAST(NULL AS NUMBER(18, 2)) AS mode_over_no_rows, + CAST(NULL AS NUMBER(38, 5)) AS median_over_no_rows, + CAST(NULL AS NUMBER(38, 0)) AS paid_count_over_no_rows, + 0 AS mode_with_having_only ), -__assert__tied_pick_is_a_zero AS ( +-- Ties and scale-15 medians are not compared exactly: G-3's two amounts are each the mode, and +-- Snowflake's MEDIAN above scale 12 is not exact (0.123456789012344496 or ...498 for a midpoint of +-- 0.1234567890123445, depending on the plan). +__assert__picks_and_fine_medians_are_close AS ( SELECT s.group_code FROM __ref("int_edge_amount_statistics") AS s - WHERE s.signed_zero_pick <> 0 + WHERE + s.signed_zero_pick <> 0 + OR ( + s.group_code = 'G-1' + AND s.mode_amount <> 10.00 + ) + OR ( + s.group_code = 'G-2' + AND s.mode_amount IS NOT NULL + ) + OR ( + s.group_code = 'G-3' + AND s.mode_amount NOT IN (0.00, -0.50) + ) + OR ABS(s.median_scale_15 + - DECODE( + s.group_code, + 'G-1', 0.1234567890123445, + 'G-2', 1.000000000000001, + 'G-3', 0.0000000000000015 + )) > 0.00000000000000001 ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_category_walk.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_category_walk.sql index b171dc9d0c..354824034b 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_category_walk.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_category_walk.sql @@ -16,9 +16,47 @@ WITH __ref__stg_edge_category_links AS ( __expected__int_edge_category_walk AS ( SELECT - NULL AS category_id, - NULL AS walk_depth, - NULL AS root_name, - NULL AS category_path, - NULL AS deepest_shelf + 'CAT-1' AS category_id, + 1 AS walk_depth, + 'Ducks' AS root_name, + ' > Ducks' AS category_path, + 1 AS joined_level, + 'Ducks' AS joined_root_name, + 7 AS deepest_shelf + UNION ALL + SELECT + 'CAT-2' AS category_id, + 2 AS walk_depth, + 'Ducks' AS root_name, + ' > Ducks > Rubber ducks' AS category_path, + 2 AS joined_level, + 'Rubber ducks' AS joined_root_name, + 7 AS deepest_shelf + UNION ALL + SELECT + 'CAT-3' AS category_id, + 3 AS walk_depth, + 'Ducks' AS root_name, + ' > Ducks > Rubber ducks > Collectible series' AS category_path, + 3 AS joined_level, + 'Collectible series' AS joined_root_name, + 7 AS deepest_shelf + UNION ALL + SELECT + 'CAT-4' AS category_id, + 2 AS walk_depth, + 'Ducks' AS root_name, + ' > Ducks > Decoys' AS category_path, + 2 AS joined_level, + 'Decoys' AS joined_root_name, + 7 AS deepest_shelf + UNION ALL + SELECT + 'CAT-5' AS category_id, + 1 AS walk_depth, + 'Pond supplies' AS root_name, + ' > Pond supplies' AS category_path, + 1 AS joined_level, + 'Pond supplies' AS joined_root_name, + 7 AS deepest_shelf ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_variant_matches.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_variant_matches.sql index 7f3cc71011..1780b603ea 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_variant_matches.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_variant_matches.sql @@ -28,14 +28,603 @@ WITH __ref__stg_edge_variant_values AS ( __expected__int_edge_variant_matches AS ( SELECT - NULL AS pair_id, - NULL AS values_equal, - NULL AS values_equal_null, - NULL AS left_partition_size, - NULL AS group_first_value, - NULL AS group_last_value, - NULL AS group_distinct_rows, - NULL AS group_distinct_elements, - NULL AS left_scalar_text, - NULL AS left_object_without_note + 'VV-01~VV-02' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'INTEGER 1' AS group_first_value, + 'TIMESTAMP_NTZ "1970-01-01 00:00:01.000"' AS group_last_value, + 4 AS group_distinct_rows, + 5 AS group_distinct_elements, + '1' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":1,"GROUP_CODE":"V-1","SORT_KEY":1,"VALUE_ID":"VV-01"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-01~VV-02-DATE' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'INTEGER 1' AS group_first_value, + 'TIMESTAMP_NTZ "1970-01-01 00:00:01.000"' AS group_last_value, + 4 AS group_distinct_rows, + 5 AS group_distinct_elements, + '1' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":1,"GROUP_CODE":"V-1","SORT_KEY":1,"VALUE_ID":"VV-01"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-01~VV-02-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'INTEGER 1' AS group_first_value, + 'TIMESTAMP_NTZ "1970-01-01 00:00:01.000"' AS group_last_value, + 4 AS group_distinct_rows, + 5 AS group_distinct_elements, + '1' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":1,"GROUP_CODE":"V-1","SORT_KEY":1,"VALUE_ID":"VV-01"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-01~VV-03' AS pair_id, + TRUE AS values_equal, + TRUE AS values_equal_null, + 2 AS left_partition_size, + 'INTEGER 1' AS group_first_value, + 'TIMESTAMP_NTZ "1970-01-01 00:00:01.000"' AS group_last_value, + 4 AS group_distinct_rows, + 5 AS group_distinct_elements, + '1' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":1,"GROUP_CODE":"V-1","SORT_KEY":1,"VALUE_ID":"VV-01"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-01~VV-04' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'INTEGER 1' AS group_first_value, + 'TIMESTAMP_NTZ "1970-01-01 00:00:01.000"' AS group_last_value, + 4 AS group_distinct_rows, + 5 AS group_distinct_elements, + '1' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":1,"GROUP_CODE":"V-1","SORT_KEY":1,"VALUE_ID":"VV-01"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-02-DATE~VV-02-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'INTEGER 1' AS group_first_value, + 'TIMESTAMP_NTZ "1970-01-01 00:00:01.000"' AS group_last_value, + 4 AS group_distinct_rows, + 5 AS group_distinct_elements, + '1970-01-01' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"1970-01-01","GROUP_CODE":"V-1","SORT_KEY":102,"VALUE_ID":"VV-02-DATE"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-02~VV-02-DATE' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'INTEGER 1' AS group_first_value, + 'TIMESTAMP_NTZ "1970-01-01 00:00:01.000"' AS group_last_value, + 4 AS group_distinct_rows, + 5 AS group_distinct_elements, + '1' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"1","GROUP_CODE":"V-1","SORT_KEY":2,"VALUE_ID":"VV-02"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-02~VV-02-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'INTEGER 1' AS group_first_value, + 'TIMESTAMP_NTZ "1970-01-01 00:00:01.000"' AS group_last_value, + 4 AS group_distinct_rows, + 5 AS group_distinct_elements, + '1' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"1","GROUP_CODE":"V-1","SORT_KEY":2,"VALUE_ID":"VV-02"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-02~VV-03' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'INTEGER 1' AS group_first_value, + 'TIMESTAMP_NTZ "1970-01-01 00:00:01.000"' AS group_last_value, + 4 AS group_distinct_rows, + 5 AS group_distinct_elements, + '1' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"1","GROUP_CODE":"V-1","SORT_KEY":2,"VALUE_ID":"VV-02"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-02~VV-04' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'INTEGER 1' AS group_first_value, + 'TIMESTAMP_NTZ "1970-01-01 00:00:01.000"' AS group_last_value, + 4 AS group_distinct_rows, + 5 AS group_distinct_elements, + '1' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"1","GROUP_CODE":"V-1","SORT_KEY":2,"VALUE_ID":"VV-02"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-03~VV-02-DATE' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'INTEGER 1' AS group_first_value, + 'TIMESTAMP_NTZ "1970-01-01 00:00:01.000"' AS group_last_value, + 4 AS group_distinct_rows, + 5 AS group_distinct_elements, + '1' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":1,"GROUP_CODE":"V-1","SORT_KEY":3,"VALUE_ID":"VV-03"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-03~VV-02-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'INTEGER 1' AS group_first_value, + 'TIMESTAMP_NTZ "1970-01-01 00:00:01.000"' AS group_last_value, + 4 AS group_distinct_rows, + 5 AS group_distinct_elements, + '1' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":1,"GROUP_CODE":"V-1","SORT_KEY":3,"VALUE_ID":"VV-03"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-03~VV-04' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'INTEGER 1' AS group_first_value, + 'TIMESTAMP_NTZ "1970-01-01 00:00:01.000"' AS group_last_value, + 4 AS group_distinct_rows, + 5 AS group_distinct_elements, + '1' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":1,"GROUP_CODE":"V-1","SORT_KEY":3,"VALUE_ID":"VV-03"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-04~VV-02-DATE' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'INTEGER 1' AS group_first_value, + 'TIMESTAMP_NTZ "1970-01-01 00:00:01.000"' AS group_last_value, + 4 AS group_distinct_rows, + 5 AS group_distinct_elements, + CAST(NULL AS VARCHAR) AS left_scalar_text, + '{"ATTRIBUTE_VALUE":null,"GROUP_CODE":"V-1","SORT_KEY":4,"VALUE_ID":"VV-04"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-04~VV-02-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'INTEGER 1' AS group_first_value, + 'TIMESTAMP_NTZ "1970-01-01 00:00:01.000"' AS group_last_value, + 4 AS group_distinct_rows, + 5 AS group_distinct_elements, + CAST(NULL AS VARCHAR) AS left_scalar_text, + '{"ATTRIBUTE_VALUE":null,"GROUP_CODE":"V-1","SORT_KEY":4,"VALUE_ID":"VV-04"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-05~VV-06' AS pair_id, + TRUE AS values_equal, + TRUE AS values_equal_null, + 2 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + CAST(NULL AS VARCHAR) AS left_scalar_text, + '{"ATTRIBUTE_VALUE":{"a":1,"b":2},"GROUP_CODE":"V-2","SORT_KEY":1,"VALUE_ID":"VV-05"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-05~VV-07' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + CAST(NULL AS VARCHAR) AS left_scalar_text, + '{"ATTRIBUTE_VALUE":{"a":1,"b":2},"GROUP_CODE":"V-2","SORT_KEY":1,"VALUE_ID":"VV-05"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-05~VV-07-DATE' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + CAST(NULL AS VARCHAR) AS left_scalar_text, + '{"ATTRIBUTE_VALUE":{"a":1,"b":2},"GROUP_CODE":"V-2","SORT_KEY":1,"VALUE_ID":"VV-05"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-05~VV-07-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + CAST(NULL AS VARCHAR) AS left_scalar_text, + '{"ATTRIBUTE_VALUE":{"a":1,"b":2},"GROUP_CODE":"V-2","SORT_KEY":1,"VALUE_ID":"VV-05"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-05~VV-08' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + CAST(NULL AS VARCHAR) AS left_scalar_text, + '{"ATTRIBUTE_VALUE":{"a":1,"b":2},"GROUP_CODE":"V-2","SORT_KEY":1,"VALUE_ID":"VV-05"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-05~VV-08-DATE' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + CAST(NULL AS VARCHAR) AS left_scalar_text, + '{"ATTRIBUTE_VALUE":{"a":1,"b":2},"GROUP_CODE":"V-2","SORT_KEY":1,"VALUE_ID":"VV-05"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-05~VV-08-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + CAST(NULL AS VARCHAR) AS left_scalar_text, + '{"ATTRIBUTE_VALUE":{"a":1,"b":2},"GROUP_CODE":"V-2","SORT_KEY":1,"VALUE_ID":"VV-05"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-06~VV-07' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + CAST(NULL AS VARCHAR) AS left_scalar_text, + '{"ATTRIBUTE_VALUE":{"a":1,"b":2},"GROUP_CODE":"V-2","SORT_KEY":2,"VALUE_ID":"VV-06"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-06~VV-07-DATE' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + CAST(NULL AS VARCHAR) AS left_scalar_text, + '{"ATTRIBUTE_VALUE":{"a":1,"b":2},"GROUP_CODE":"V-2","SORT_KEY":2,"VALUE_ID":"VV-06"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-06~VV-07-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + CAST(NULL AS VARCHAR) AS left_scalar_text, + '{"ATTRIBUTE_VALUE":{"a":1,"b":2},"GROUP_CODE":"V-2","SORT_KEY":2,"VALUE_ID":"VV-06"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-06~VV-08' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + CAST(NULL AS VARCHAR) AS left_scalar_text, + '{"ATTRIBUTE_VALUE":{"a":1,"b":2},"GROUP_CODE":"V-2","SORT_KEY":2,"VALUE_ID":"VV-06"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-06~VV-08-DATE' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + CAST(NULL AS VARCHAR) AS left_scalar_text, + '{"ATTRIBUTE_VALUE":{"a":1,"b":2},"GROUP_CODE":"V-2","SORT_KEY":2,"VALUE_ID":"VV-06"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-06~VV-08-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + CAST(NULL AS VARCHAR) AS left_scalar_text, + '{"ATTRIBUTE_VALUE":{"a":1,"b":2},"GROUP_CODE":"V-2","SORT_KEY":2,"VALUE_ID":"VV-06"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-07-DATE~VV-07-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + '2026-03-01' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01","GROUP_CODE":"V-2","SORT_KEY":103,"VALUE_ID":"VV-07-DATE"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-07-DATE~VV-08-DATE' AS pair_id, + TRUE AS values_equal, + TRUE AS values_equal_null, + 2 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + '2026-03-01' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01","GROUP_CODE":"V-2","SORT_KEY":103,"VALUE_ID":"VV-07-DATE"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-07-DATE~VV-08-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + '2026-03-01' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01","GROUP_CODE":"V-2","SORT_KEY":103,"VALUE_ID":"VV-07-DATE"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-07-TIMESTAMP~VV-08-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + '2026-03-01 00:00:00.000' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01 00:00:00.000","GROUP_CODE":"V-2","SORT_KEY":203,' + || '"VALUE_ID":"VV-07-TIMESTAMP"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-07~VV-07-DATE' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + '2026-03-01' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01","GROUP_CODE":"V-2","SORT_KEY":3,"VALUE_ID":"VV-07"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-07~VV-07-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + '2026-03-01' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01","GROUP_CODE":"V-2","SORT_KEY":3,"VALUE_ID":"VV-07"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-07~VV-08' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + '2026-03-01' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01","GROUP_CODE":"V-2","SORT_KEY":3,"VALUE_ID":"VV-07"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-07~VV-08-DATE' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + '2026-03-01' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01","GROUP_CODE":"V-2","SORT_KEY":3,"VALUE_ID":"VV-07"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-07~VV-08-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + '2026-03-01' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01","GROUP_CODE":"V-2","SORT_KEY":3,"VALUE_ID":"VV-07"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-08-DATE~VV-07-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + '2026-03-01' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01","GROUP_CODE":"V-2","SORT_KEY":104,"VALUE_ID":"VV-08-DATE"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-08-DATE~VV-08-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + '2026-03-01' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01","GROUP_CODE":"V-2","SORT_KEY":104,"VALUE_ID":"VV-08-DATE"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-08~VV-07-DATE' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + '2026-03-01 10:15:00' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01 10:15:00","GROUP_CODE":"V-2","SORT_KEY":4,"VALUE_ID":"VV-08"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-08~VV-07-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + '2026-03-01 10:15:00' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01 10:15:00","GROUP_CODE":"V-2","SORT_KEY":4,"VALUE_ID":"VV-08"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-08~VV-08-DATE' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + '2026-03-01 10:15:00' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01 10:15:00","GROUP_CODE":"V-2","SORT_KEY":4,"VALUE_ID":"VV-08"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-08~VV-08-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'OBJECT {"a":1,"b":2}' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 10:15:00.000"' AS group_last_value, + 6 AS group_distinct_rows, + 6 AS group_distinct_elements, + '2026-03-01 10:15:00' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01 10:15:00","GROUP_CODE":"V-2","SORT_KEY":4,"VALUE_ID":"VV-08"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-09~VV-10' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'ARRAY [1,2]' AS group_first_value, + 'ARRAY [2,1]' AS group_last_value, + 2 AS group_distinct_rows, + 2 AS group_distinct_elements, + CAST(NULL AS VARCHAR) AS left_scalar_text, + '{"ATTRIBUTE_VALUE":[1,2],"GROUP_CODE":"V-3","SORT_KEY":1,"VALUE_ID":"VV-09"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-11~VV-12' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'DATE "2026-03-01"' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 00:00:00.000"' AS group_last_value, + 3 AS group_distinct_rows, + 3 AS group_distinct_elements, + '2026-03-01' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01","GROUP_CODE":"V-4","SORT_KEY":1,"VALUE_ID":"VV-11"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-11~VV-12-DATE' AS pair_id, + TRUE AS values_equal, + TRUE AS values_equal_null, + 2 AS left_partition_size, + 'DATE "2026-03-01"' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 00:00:00.000"' AS group_last_value, + 3 AS group_distinct_rows, + 3 AS group_distinct_elements, + '2026-03-01' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01","GROUP_CODE":"V-4","SORT_KEY":1,"VALUE_ID":"VV-11"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-11~VV-12-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'DATE "2026-03-01"' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 00:00:00.000"' AS group_last_value, + 3 AS group_distinct_rows, + 3 AS group_distinct_elements, + '2026-03-01' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01","GROUP_CODE":"V-4","SORT_KEY":1,"VALUE_ID":"VV-11"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-12-DATE~VV-12-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 2 AS left_partition_size, + 'DATE "2026-03-01"' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 00:00:00.000"' AS group_last_value, + 3 AS group_distinct_rows, + 3 AS group_distinct_elements, + '2026-03-01' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01","GROUP_CODE":"V-4","SORT_KEY":102,"VALUE_ID":"VV-12-DATE"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-12~VV-12-DATE' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'DATE "2026-03-01"' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 00:00:00.000"' AS group_last_value, + 3 AS group_distinct_rows, + 3 AS group_distinct_elements, + '2026-03-01' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01","GROUP_CODE":"V-4","SORT_KEY":2,"VALUE_ID":"VV-12"}' AS left_object_without_note + UNION ALL + SELECT + 'VV-12~VV-12-TIMESTAMP' AS pair_id, + FALSE AS values_equal, + FALSE AS values_equal_null, + 1 AS left_partition_size, + 'DATE "2026-03-01"' AS group_first_value, + 'TIMESTAMP_NTZ "2026-03-01 00:00:00.000"' AS group_last_value, + 3 AS group_distinct_rows, + 3 AS group_distinct_elements, + '2026-03-01' AS left_scalar_text, + '{"ATTRIBUTE_VALUE":"2026-03-01","GROUP_CODE":"V-4","SORT_KEY":2,"VALUE_ID":"VV-12"}' AS left_object_without_note ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_zoned_texts.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_zoned_texts.sql index 57c5d65e4b..df0676a7cd 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_zoned_texts.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_zoned_texts.sql @@ -23,24 +23,191 @@ WITH __ref__stg_edge_date_texts AS ( __expected__int_edge_zoned_texts AS ( SELECT - NULL AS text_id, - NULL AS zoned, - NULL AS local_zoned, - NULL AS zoned_utc, - NULL AS local_zoned_utc, - NULL AS zoned_date, - NULL AS local_zoned_date, - NULL AS zoned_year, - NULL AS local_zoned_year, - NULL AS zoned_day_start, - NULL AS local_zoned_day_start, - NULL AS zoned_equals_wall_text, - NULL AS local_zoned_equals_wall_text, - NULL AS zoned_after_wall_clock, - NULL AS local_zoned_after_wall_clock, - NULL AS zoned_from_next_day, - NULL AS local_zoned_from_next_day, - NULL AS first_zoned_date, - NULL AS zoned_lower_utc, - NULL AS wall_clock_utc_hour + 'ZT-01' AS text_id, + '2026-03-06 08:30:00 +09:00'::TIMESTAMP_TZ(9) AS zoned, + '2026-03-05 23:30:00 Z'::TIMESTAMP_LTZ(9) AS local_zoned, + '2026-03-05 23:30:00'::TIMESTAMP_NTZ(9) AS zoned_utc, + '2026-03-05 23:30:00'::TIMESTAMP_NTZ(9) AS local_zoned_utc, + '2026-03-06'::DATE AS zoned_date, + '2026-03-05'::DATE AS local_zoned_date, + 2026 AS zoned_year, + 2026 AS local_zoned_year, + '2026-03-06 00:00:00 +09:00'::TIMESTAMP_TZ(9) AS zoned_day_start, + '2026-03-05 00:00:00 Z'::TIMESTAMP_LTZ(9) AS local_zoned_day_start, + TRUE AS zoned_equals_wall_text, + TRUE AS local_zoned_equals_wall_text, + FALSE AS zoned_after_wall_clock, + FALSE AS local_zoned_after_wall_clock, + FALSE AS zoned_from_next_day, + FALSE AS local_zoned_from_next_day, + '2026-03-06'::DATE AS first_zoned_date, + 23 AS wall_clock_utc_hour + UNION ALL + SELECT + 'ZT-02' AS text_id, + '2026-03-05 23:30:00 Z'::TIMESTAMP_TZ(9) AS zoned, + '2026-03-05 23:30:00 Z'::TIMESTAMP_LTZ(9) AS local_zoned, + '2026-03-05 23:30:00'::TIMESTAMP_NTZ(9) AS zoned_utc, + '2026-03-05 23:30:00'::TIMESTAMP_NTZ(9) AS local_zoned_utc, + '2026-03-05'::DATE AS zoned_date, + '2026-03-05'::DATE AS local_zoned_date, + 2026 AS zoned_year, + 2026 AS local_zoned_year, + '2026-03-05 00:00:00 Z'::TIMESTAMP_TZ(9) AS zoned_day_start, + '2026-03-05 00:00:00 Z'::TIMESTAMP_LTZ(9) AS local_zoned_day_start, + TRUE AS zoned_equals_wall_text, + TRUE AS local_zoned_equals_wall_text, + TRUE AS zoned_after_wall_clock, + TRUE AS local_zoned_after_wall_clock, + FALSE AS zoned_from_next_day, + FALSE AS local_zoned_from_next_day, + '2026-03-06'::DATE AS first_zoned_date, + 23 AS wall_clock_utc_hour + UNION ALL + SELECT + 'ZT-03' AS text_id, + '2026-03-05 23:30:00 Z'::TIMESTAMP_TZ(9) AS zoned, + '2026-03-05 23:30:00 Z'::TIMESTAMP_LTZ(9) AS local_zoned, + '2026-03-05 23:30:00'::TIMESTAMP_NTZ(9) AS zoned_utc, + '2026-03-05 23:30:00'::TIMESTAMP_NTZ(9) AS local_zoned_utc, + '2026-03-05'::DATE AS zoned_date, + '2026-03-05'::DATE AS local_zoned_date, + 2026 AS zoned_year, + 2026 AS local_zoned_year, + '2026-03-05 00:00:00 Z'::TIMESTAMP_TZ(9) AS zoned_day_start, + '2026-03-05 00:00:00 Z'::TIMESTAMP_LTZ(9) AS local_zoned_day_start, + TRUE AS zoned_equals_wall_text, + TRUE AS local_zoned_equals_wall_text, + FALSE AS zoned_after_wall_clock, + FALSE AS local_zoned_after_wall_clock, + FALSE AS zoned_from_next_day, + FALSE AS local_zoned_from_next_day, + '2026-03-06'::DATE AS first_zoned_date, + 0 AS wall_clock_utc_hour + UNION ALL + SELECT + 'ZT-04' AS text_id, + '2026-03-05 23:30:00 Z'::TIMESTAMP_TZ(9) AS zoned, + '2026-03-05 23:30:00 Z'::TIMESTAMP_LTZ(9) AS local_zoned, + '2026-03-05 23:30:00'::TIMESTAMP_NTZ(9) AS zoned_utc, + '2026-03-05 23:30:00'::TIMESTAMP_NTZ(9) AS local_zoned_utc, + '2026-03-05'::DATE AS zoned_date, + '2026-03-05'::DATE AS local_zoned_date, + 2026 AS zoned_year, + 2026 AS local_zoned_year, + '2026-03-05 00:00:00 Z'::TIMESTAMP_TZ(9) AS zoned_day_start, + '2026-03-05 00:00:00 Z'::TIMESTAMP_LTZ(9) AS local_zoned_day_start, + FALSE AS zoned_equals_wall_text, + FALSE AS local_zoned_equals_wall_text, + FALSE AS zoned_after_wall_clock, + FALSE AS local_zoned_after_wall_clock, + FALSE AS zoned_from_next_day, + FALSE AS local_zoned_from_next_day, + '2026-03-06'::DATE AS first_zoned_date, + 23 AS wall_clock_utc_hour + UNION ALL + SELECT + 'ZT-05' AS text_id, + '2026-03-05 18:00:00 -05:30'::TIMESTAMP_TZ(9) AS zoned, + '2026-03-05 23:30:00 Z'::TIMESTAMP_LTZ(9) AS local_zoned, + '2026-03-05 23:30:00'::TIMESTAMP_NTZ(9) AS zoned_utc, + '2026-03-05 23:30:00'::TIMESTAMP_NTZ(9) AS local_zoned_utc, + '2026-03-05'::DATE AS zoned_date, + '2026-03-05'::DATE AS local_zoned_date, + 2026 AS zoned_year, + 2026 AS local_zoned_year, + '2026-03-05 00:00:00 -05:30'::TIMESTAMP_TZ(9) AS zoned_day_start, + '2026-03-05 00:00:00 Z'::TIMESTAMP_LTZ(9) AS local_zoned_day_start, + TRUE AS zoned_equals_wall_text, + TRUE AS local_zoned_equals_wall_text, + TRUE AS zoned_after_wall_clock, + TRUE AS local_zoned_after_wall_clock, + FALSE AS zoned_from_next_day, + FALSE AS local_zoned_from_next_day, + '2026-03-06'::DATE AS first_zoned_date, + 23 AS wall_clock_utc_hour + UNION ALL + SELECT + 'ZT-06' AS text_id, + '2026-12-31 23:30:00 -05:30'::TIMESTAMP_TZ(9) AS zoned, + '2027-01-01 05:00:00 Z'::TIMESTAMP_LTZ(9) AS local_zoned, + '2027-01-01 05:00:00'::TIMESTAMP_NTZ(9) AS zoned_utc, + '2027-01-01 05:00:00'::TIMESTAMP_NTZ(9) AS local_zoned_utc, + '2026-12-31'::DATE AS zoned_date, + '2027-01-01'::DATE AS local_zoned_date, + 2026 AS zoned_year, + 2027 AS local_zoned_year, + '2026-12-31 00:00:00 -05:30'::TIMESTAMP_TZ(9) AS zoned_day_start, + '2027-01-01 00:00:00 Z'::TIMESTAMP_LTZ(9) AS local_zoned_day_start, + TRUE AS zoned_equals_wall_text, + TRUE AS local_zoned_equals_wall_text, + TRUE AS zoned_after_wall_clock, + TRUE AS local_zoned_after_wall_clock, + TRUE AS zoned_from_next_day, + TRUE AS local_zoned_from_next_day, + '2026-03-06'::DATE AS first_zoned_date, + 0 AS wall_clock_utc_hour + UNION ALL + SELECT + 'ZT-07' AS text_id, + '2026-03-05 23:30:00 Z'::TIMESTAMP_TZ(9) AS zoned, + '2026-03-05 23:30:00 Z'::TIMESTAMP_LTZ(9) AS local_zoned, + '2026-03-05 23:30:00'::TIMESTAMP_NTZ(9) AS zoned_utc, + '2026-03-05 23:30:00'::TIMESTAMP_NTZ(9) AS local_zoned_utc, + '2026-03-05'::DATE AS zoned_date, + '2026-03-05'::DATE AS local_zoned_date, + 2026 AS zoned_year, + 2026 AS local_zoned_year, + '2026-03-05 00:00:00 Z'::TIMESTAMP_TZ(9) AS zoned_day_start, + '2026-03-05 00:00:00 Z'::TIMESTAMP_LTZ(9) AS local_zoned_day_start, + TRUE AS zoned_equals_wall_text, + TRUE AS local_zoned_equals_wall_text, + FALSE AS zoned_after_wall_clock, + FALSE AS local_zoned_after_wall_clock, + FALSE AS zoned_from_next_day, + FALSE AS local_zoned_from_next_day, + '2026-03-06'::DATE AS first_zoned_date, + 23 AS wall_clock_utc_hour + UNION ALL + SELECT + 'ZT-08' AS text_id, + CAST(NULL AS TIMESTAMP_TZ(9)) AS zoned, + CAST(NULL AS TIMESTAMP_LTZ(9)) AS local_zoned, + CAST(NULL AS TIMESTAMP_NTZ(9)) AS zoned_utc, + CAST(NULL AS TIMESTAMP_NTZ(9)) AS local_zoned_utc, + CAST(NULL AS DATE) AS zoned_date, + CAST(NULL AS DATE) AS local_zoned_date, + CAST(NULL AS NUMBER(38, 0)) AS zoned_year, + CAST(NULL AS NUMBER(38, 0)) AS local_zoned_year, + CAST(NULL AS TIMESTAMP_TZ(9)) AS zoned_day_start, + CAST(NULL AS TIMESTAMP_LTZ(9)) AS local_zoned_day_start, + CAST(NULL AS BOOLEAN) AS zoned_equals_wall_text, + CAST(NULL AS BOOLEAN) AS local_zoned_equals_wall_text, + CAST(NULL AS BOOLEAN) AS zoned_after_wall_clock, + CAST(NULL AS BOOLEAN) AS local_zoned_after_wall_clock, + CAST(NULL AS BOOLEAN) AS zoned_from_next_day, + CAST(NULL AS BOOLEAN) AS local_zoned_from_next_day, + '2026-03-06'::DATE AS first_zoned_date, + 23 AS wall_clock_utc_hour + UNION ALL + SELECT + 'ZT-09' AS text_id, + CAST(NULL AS TIMESTAMP_TZ(9)) AS zoned, + CAST(NULL AS TIMESTAMP_LTZ(9)) AS local_zoned, + CAST(NULL AS TIMESTAMP_NTZ(9)) AS zoned_utc, + CAST(NULL AS TIMESTAMP_NTZ(9)) AS local_zoned_utc, + CAST(NULL AS DATE) AS zoned_date, + CAST(NULL AS DATE) AS local_zoned_date, + CAST(NULL AS NUMBER(38, 0)) AS zoned_year, + CAST(NULL AS NUMBER(38, 0)) AS local_zoned_year, + CAST(NULL AS TIMESTAMP_TZ(9)) AS zoned_day_start, + CAST(NULL AS TIMESTAMP_LTZ(9)) AS local_zoned_day_start, + CAST(NULL AS BOOLEAN) AS zoned_equals_wall_text, + CAST(NULL AS BOOLEAN) AS local_zoned_equals_wall_text, + CAST(NULL AS BOOLEAN) AS zoned_after_wall_clock, + CAST(NULL AS BOOLEAN) AS local_zoned_after_wall_clock, + CAST(NULL AS BOOLEAN) AS zoned_from_next_day, + CAST(NULL AS BOOLEAN) AS local_zoned_from_next_day, + '2026-03-06'::DATE AS first_zoned_date, + CAST(NULL AS NUMBER(38, 0)) AS wall_clock_utc_hour ) diff --git a/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_date_texts.sql b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_date_texts.sql index 8e25a4f580..f68b146607 100644 --- a/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_date_texts.sql +++ b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_date_texts.sql @@ -5,10 +5,10 @@ WITH __source__edge__date_texts AS ( v.text_id, v.date_text, v.zoned_text, - v.wall_clock::TIMESTAMP_NTZ(3) AS wall_clock, + v.wall_clock::TIMESTAMP_NTZ AS wall_clock, v.wall_clock_text, - v.local_time::TIME(3) AS local_time, - v.fine_time::TIME(9) AS fine_time, + v.local_time::TIME AS local_time, + v.fine_time::TIME AS fine_time, '2026-03-06 00:00:00'::TIMESTAMP_NTZ AS _loaded_at FROM VALUES ('DT-01', '5.3.2026', '2026-03-05T23:30:00Z', '2026-03-05 23:30:00.123', '2026-03-05 23:30:00.1239', '23:59:59.999', '23:59:59.9999999'), @@ -17,11 +17,20 @@ WITH __source__edge__date_texts AS ( __expected__stg_edge_date_texts AS ( SELECT - NULL AS text_id, - NULL AS date_text, - NULL AS zoned_text, - NULL AS wall_clock, - NULL AS wall_clock_text, - NULL AS local_time, - NULL AS fine_time + 'DT-01' AS text_id, + '5.3.2026' AS date_text, + '2026-03-05T23:30:00Z' AS zoned_text, + '2026-03-05 23:30:00.123'::TIMESTAMP_NTZ(3) AS wall_clock, + '2026-03-05 23:30:00.1239' AS wall_clock_text, + '23:59:59.999'::TIME(3) AS local_time, + '23:59:59.9999999'::TIME(9) AS fine_time + UNION ALL + SELECT + 'DT-02' AS text_id, + CAST(NULL AS VARCHAR) AS date_text, + CAST(NULL AS VARCHAR) AS zoned_text, + CAST(NULL AS TIMESTAMP_NTZ(3)) AS wall_clock, + CAST(NULL AS VARCHAR) AS wall_clock_text, + CAST(NULL AS TIME(3)) AS local_time, + CAST(NULL AS TIME(9)) AS fine_time ) From 7035eda1b8991945a6377db2c0651d8df5248aa4 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sun, 1 Nov 2026 19:09:09 +0000 Subject: [PATCH 7/8] test(benchmarks): capture DuckShop edge expectations on Snowflake Fill the new edge tests' expected rows from two identical captures under the edge target, and list in the README the shapes Snowflake rejects and the answers worth knowing. --- benchmarks/duckshop/README.md | 21 +- .../test_int_edge_analysis_patterns.sql | 46 ++- .../test_int_edge_basket_tag_pairs.sql | 36 +- .../test_int_edge_basket_tag_sets.sql | 46 ++- .../test_int_edge_basket_top_tags.sql | 26 +- .../test_int_edge_clock_arithmetic.sql | 178 ++++++-- .../test_int_edge_grouped_windows.sql | 46 ++- .../edge_cases/test_int_edge_join_keys.sql | 94 ++++- .../edge_cases/test_int_edge_json_numbers.sql | 84 +++- .../edge_cases/test_int_edge_key_widths.sql | 176 +++++++- .../test_int_edge_note_patterns.sql | 380 +++++++++++++++++- .../test_int_edge_null_arithmetic.sql | 192 +++++++-- .../test_int_edge_payload_paths.sql | 132 +++++- .../test_int_edge_python_values.sql | 76 +++- .../test_int_edge_recursive_counts.sql | 32 +- .../edge_cases/test_int_edge_row_deciders.sql | 302 +++++++++++++- .../edge_cases/test_int_edge_text_dates.sql | 236 ++++++++++- .../test_int_edge_ticket_tokens.sql | 50 ++- .../test_int_edge_variant_extremes.sql | 260 +++++++++++- .../test_stg_edge_amount_checks.sql | 17 +- .../edge_cases/test_stg_edge_basket_lines.sql | 20 +- .../test_stg_edge_category_links.sql | 14 +- .../test_stg_edge_variant_values.sql | 12 +- 23 files changed, 2298 insertions(+), 178 deletions(-) diff --git a/benchmarks/duckshop/README.md b/benchmarks/duckshop/README.md index 05b4e865c5..04cc4d03a0 100644 --- a/benchmarks/duckshop/README.md +++ b/benchmarks/duckshop/README.md @@ -137,11 +137,8 @@ scenarios, in eight groups: Unit-test expectations in the edge-case area are captured on Snowflake under the `edge` target's pinned session, rather than derived by hand; scenario expectations, and the assertions in tests -about ties and volatile values, are written by hand from those captured values. The 28 model tests -added with the ordering-through-arrays, `VARIANT` comparison, lineage and Python function cases, -and with the newer rows of the other groups, still hold placeholder expectations: run -`capture_edge_expectations.py --write` on them (RUNBOOK.md, step 7) before relying on them. -`fct_edge_delivery_events` is disabled: it is the acceptance case for cursor `delete_insert` models +about ties, volatile values and values Snowflake does not compute exactly, are written by hand +from those captured values. `fct_edge_delivery_events` is disabled: it is the acceptance case for cursor `delete_insert` models that emit rows outside the window they replace. See [RUNBOOK.md](RUNBOOK.md), step 7. Benchmarks and core measurements leave the area out: @@ -254,6 +251,20 @@ noted here: (model SQL has no intrinsic for its own schema, and unit tests replace references with CTEs), `METADATA$` columns of a stream or `CHANGES` clause, and sequence `NEXTVAL`. SQLBuild's `@udf` has no option for `STRICT` (`RETURNS NULL ON NULL INPUT`), so no function declares it. +- Snowflake rejects some shapes, so the edge-case area leaves them out: a window frame without + `ORDER BY` (such as a running `COUNT_IF`), `CONVERT_TIMEZONE` with a lower-case `'utc'`, and a + scalar subquery inside a `CONNECT BY` query (an internal error). `MAX` over `SYSTEM$TYPEOF` + values that are then joined on also fails with an internal error, so `int_edge_null_arithmetic` + reads one row's type instead. +- Snowflake answers some shapes in ways worth knowing, and the captured expectations record them: + `SYSTEM$TYPEOF(COUNT(x))` does not make a query aggregate; in a `CONNECT BY` query that joins + another relation, a column named `level` reads as the walk depth even when qualified, and + `CONNECT_BY_ROOT` returns the row's own value; `COUNT` of a `VARIANT` skips JSON null; `MODE` of + a number over no rows can return 0; `MEDIAN` above scale 12 is not exact and differs between + plans; `JOIN ... USING` types the qualified keys of an inner join as the narrower side's type. +- SQLBuild 0.186.2 reads the type of a Snowflake source column declared with a precision, such as + `TIME(3)` or `TIMESTAMP_NTZ(3)`, without it, and stops the build with a contract mismatch. The + `edge__date_texts` columns are declared without a precision and cast in `stg_edge_date_texts`. ## Compile diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_analysis_patterns.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_analysis_patterns.sql index 436ab8df83..dac9d2c2b1 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_analysis_patterns.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_analysis_patterns.sql @@ -35,12 +35,42 @@ __ref__stg_edge_date_texts AS ( __expected__int_edge_analysis_patterns AS ( SELECT - NULL AS basket_id, - NULL AS month_end, - NULL AS line_no, - NULL AS positional_sku, - NULL AS next_line_no, - NULL AS line_no_text, - NULL AS sku_prefix, - NULL AS line_count + 'B-1' AS basket_id, + '2026-02-28'::DATE AS month_end, + 3 AS line_no, + 'EIDER-03' AS positional_sku, + 4 AS next_line_no, + '1' AS line_no_text, + 'EIDER' AS sku_prefix, + 3 AS line_count + UNION ALL + SELECT + 'B-2' AS basket_id, + '2026-02-28'::DATE AS month_end, + 2 AS line_no, + 'MALLARD-01' AS positional_sku, + 3 AS next_line_no, + '1' AS line_no_text, + 'MALLA' AS sku_prefix, + 2 AS line_count + UNION ALL + SELECT + 'B-3' AS basket_id, + '2026-02-28'::DATE AS month_end, + 1 AS line_no, + 'MERGANSER-05' AS positional_sku, + 2 AS next_line_no, + '1' AS line_no_text, + 'MERGA' AS sku_prefix, + 1 AS line_count + UNION ALL + SELECT + 'B-4' AS basket_id, + '2026-02-28'::DATE AS month_end, + 2 AS line_no, + 'EIDER-03' AS positional_sku, + 3 AS next_line_no, + '1' AS line_no_text, + 'EIDER' AS sku_prefix, + 2 AS line_count ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_tag_pairs.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_tag_pairs.sql index 20d557930e..b000f7c84a 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_tag_pairs.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_tag_pairs.sql @@ -20,7 +20,37 @@ WITH __ref__stg_edge_basket_lines AS ( __expected__int_edge_basket_tag_pairs AS ( SELECT - NULL AS tag_pair_text, - NULL AS tag_pair, - NULL AS pair_size + 'bath|bath' AS tag_pair_text, + PARSE_JSON('["bath","bath"]')::ARRAY AS tag_pair, + 2 AS pair_size + UNION ALL + SELECT + 'bath|garden' AS tag_pair_text, + PARSE_JSON('["bath","garden"]')::ARRAY AS tag_pair, + 2 AS pair_size + UNION ALL + SELECT + 'bath|gift' AS tag_pair_text, + PARSE_JSON('["bath","gift"]')::ARRAY AS tag_pair, + 2 AS pair_size + UNION ALL + SELECT + 'garden|bath' AS tag_pair_text, + PARSE_JSON('["garden","bath"]')::ARRAY AS tag_pair, + 2 AS pair_size + UNION ALL + SELECT + 'gift|' AS tag_pair_text, + PARSE_JSON('["gift",undefined]')::ARRAY AS tag_pair, + 2 AS pair_size + UNION ALL + SELECT + 'gift|bath' AS tag_pair_text, + PARSE_JSON('["gift","bath"]')::ARRAY AS tag_pair, + 2 AS pair_size + UNION ALL + SELECT + '|gift' AS tag_pair_text, + PARSE_JSON('[undefined,"gift"]')::ARRAY AS tag_pair, + 2 AS pair_size ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_tag_sets.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_tag_sets.sql index 2e2fd84053..a49ed62932 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_tag_sets.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_tag_sets.sql @@ -20,12 +20,42 @@ WITH __ref__stg_edge_basket_lines AS ( __expected__int_edge_basket_tag_sets AS ( SELECT - NULL AS basket_id, - NULL AS tag_count, - NULL AS has_gift, - NULL AS overlaps_pond_or_garden, - NULL AS distinct_tag_count, - NULL AS distinct_tags, - NULL AS flattened_count, - NULL AS flattened_tags + 'B-1' AS basket_id, + 3 AS tag_count, + TRUE AS has_gift, + FALSE AS overlaps_pond_or_garden, + 2 AS distinct_tag_count, + PARSE_JSON('["bath","gift"]')::ARRAY AS distinct_tags, + 3 AS flattened_count, + 'bath,gift' AS flattened_tags + UNION ALL + SELECT + 'B-2' AS basket_id, + 2 AS tag_count, + FALSE AS has_gift, + TRUE AS overlaps_pond_or_garden, + 3 AS distinct_tag_count, + PARSE_JSON('["bath","garden","gift"]')::ARRAY AS distinct_tags, + 2 AS flattened_count, + 'bath,garden' AS flattened_tags + UNION ALL + SELECT + 'B-3' AS basket_id, + 1 AS tag_count, + FALSE AS has_gift, + TRUE AS overlaps_pond_or_garden, + 2 AS distinct_tag_count, + PARSE_JSON('["gift","pond"]')::ARRAY AS distinct_tags, + 1 AS flattened_count, + 'pond' AS flattened_tags + UNION ALL + SELECT + 'B-4' AS basket_id, + 1 AS tag_count, + TRUE AS has_gift, + FALSE AS overlaps_pond_or_garden, + 1 AS distinct_tag_count, + PARSE_JSON('["gift"]')::ARRAY AS distinct_tags, + 1 AS flattened_count, + 'gift' AS flattened_tags ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_top_tags.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_top_tags.sql index 83592df8ca..0c975296da 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_top_tags.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_basket_top_tags.sql @@ -20,7 +20,27 @@ WITH __ref__stg_edge_basket_lines AS ( __expected__int_edge_basket_top_tags AS ( SELECT - NULL AS pick_kind, - NULL AS picked_id, - NULL AS picked_size + 'by_name' AS pick_kind, + 'B-3' AS picked_id, + CAST(NULL AS NUMBER(38, 0)) AS picked_size + UNION ALL + SELECT + 'by_position' AS pick_kind, + 'B-3' AS picked_id, + 1 AS picked_size + UNION ALL + SELECT + 'constructed' AS pick_kind, + 'BL-03' AS picked_id, + 1 AS picked_size + UNION ALL + SELECT + 'over_union' AS pick_kind, + 'B-3' AS picked_id, + 1 AS picked_size + UNION ALL + SELECT + 'through_iff' AS pick_kind, + 'B-3' AS picked_id, + 1 AS picked_size ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_clock_arithmetic.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_clock_arithmetic.sql index f89bb659ae..b9599190ee 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_clock_arithmetic.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_clock_arithmetic.sql @@ -20,28 +20,158 @@ WITH __ref__stg_edge_date_texts AS ( __expected__int_edge_clock_arithmetic AS ( SELECT - NULL AS text_id, - NULL AS time_plus_minutes, - NULL AS time_minus_hours, - NULL AS time_plus_millisecond, - NULL AS time_add_type, - NULL AS time_cast_type, - NULL AS wall_clock_plus_day, - NULL AS wall_clock_add_type, - NULL AS years_to_2027, - NULL AS months_to_2027, - NULL AS weeks_to_2027, - NULL AS hours_to_2027, - NULL AS milliseconds_to_2027, - NULL AS minutes_to_midnight, - NULL AS fine_time_micro_text, - NULL AS fine_time_second_text, - NULL AS fine_time_milli_text, - NULL AS text_clock_ff3, - NULL AS wall_clock_bare_ff, - NULL AS wall_clock_equals_text, - NULL AS whole_second_equals_text, - NULL AS whole_second_before_text, - NULL AS epoch_millis_type, - NULL AS bare_timestamp_type + 'CK-01' AS text_id, + '00:29:59.999'::TIME(9) AS time_plus_minutes, + '13:59:59.999'::TIME(9) AS time_minus_hours, + '00:00:00'::TIME(9) AS time_plus_millisecond, + 'TIME(9)' AS time_add_type, + 'TIME(3)' AS time_cast_type, + '2026-03-06 23:30:00.123'::TIMESTAMP_NTZ(9) AS wall_clock_plus_day, + 'TIMESTAMP_NTZ(9)' AS wall_clock_add_type, + 1 AS years_to_2027, + 10 AS months_to_2027, + 43 AS weeks_to_2027, + 7225 AS hours_to_2027, + 26008199877 AS milliseconds_to_2027, + 0 AS minutes_to_midnight, + '23:59:59.999999' AS fine_time_micro_text, + '23:59:59' AS fine_time_second_text, + '23:59:59.999' AS fine_time_milli_text, + '2026-03-05 23:30:00.123' AS text_clock_ff3, + '23:30:00.123' AS wall_clock_bare_ff, + FALSE AS wall_clock_equals_text, + FALSE AS whole_second_equals_text, + TRUE AS whole_second_before_text, + 'TIMESTAMP_NTZ(3)' AS epoch_millis_type, + 'TIMESTAMP_NTZ(9)' AS bare_timestamp_type + UNION ALL + SELECT + 'CK-02' AS text_id, + '00:30:00'::TIME(9) AS time_plus_minutes, + '14:00:00'::TIME(9) AS time_minus_hours, + '00:00:00.001'::TIME(9) AS time_plus_millisecond, + 'TIME(9)' AS time_add_type, + 'TIME(3)' AS time_cast_type, + '2026-03-06 23:30:00'::TIMESTAMP_NTZ(9) AS wall_clock_plus_day, + 'TIMESTAMP_NTZ(9)' AS wall_clock_add_type, + 1 AS years_to_2027, + 10 AS months_to_2027, + 43 AS weeks_to_2027, + 7225 AS hours_to_2027, + 26008200000 AS milliseconds_to_2027, + 1439 AS minutes_to_midnight, + '12:00:00.000000' AS fine_time_micro_text, + '12:00:00' AS fine_time_second_text, + '12:00:00.000' AS fine_time_milli_text, + '2026-03-05 23:30:00.900' AS text_clock_ff3, + '23:30:00.000' AS wall_clock_bare_ff, + FALSE AS wall_clock_equals_text, + FALSE AS whole_second_equals_text, + TRUE AS whole_second_before_text, + 'TIMESTAMP_NTZ(3)' AS epoch_millis_type, + 'TIMESTAMP_NTZ(9)' AS bare_timestamp_type + UNION ALL + SELECT + 'CK-03' AS text_id, + '13:00:15.25'::TIME(9) AS time_plus_minutes, + '02:30:15.25'::TIME(9) AS time_minus_hours, + '12:30:15.251'::TIME(9) AS time_plus_millisecond, + 'TIME(9)' AS time_add_type, + 'TIME(3)' AS time_cast_type, + '2026-03-06 23:30:00.12'::TIMESTAMP_NTZ(9) AS wall_clock_plus_day, + 'TIMESTAMP_NTZ(9)' AS wall_clock_add_type, + 1 AS years_to_2027, + 10 AS months_to_2027, + 43 AS weeks_to_2027, + 7225 AS hours_to_2027, + 26008199880 AS milliseconds_to_2027, + 689 AS minutes_to_midnight, + '00:00:00.000000' AS fine_time_micro_text, + '00:00:00' AS fine_time_second_text, + '00:00:00.000' AS fine_time_milli_text, + '2026-03-05 23:30:00.120' AS text_clock_ff3, + '23:30:00.120' AS wall_clock_bare_ff, + TRUE AS wall_clock_equals_text, + FALSE AS whole_second_equals_text, + TRUE AS whole_second_before_text, + 'TIMESTAMP_NTZ(3)' AS epoch_millis_type, + 'TIMESTAMP_NTZ(9)' AS bare_timestamp_type + UNION ALL + SELECT + 'CK-04' AS text_id, + '00:00:00'::TIME(9) AS time_plus_minutes, + '13:30:00'::TIME(9) AS time_minus_hours, + '23:30:00.001'::TIME(9) AS time_plus_millisecond, + 'TIME(9)' AS time_add_type, + 'TIME(3)' AS time_cast_type, + '1970-01-01 23:59:59.999'::TIMESTAMP_NTZ(9) AS wall_clock_plus_day, + 'TIMESTAMP_NTZ(9)' AS wall_clock_add_type, + 58 AS years_to_2027, + 685 AS months_to_2027, + 2974 AS weeks_to_2027, + 499657 AS hours_to_2027, + 1798761600001 AS milliseconds_to_2027, + 29 AS minutes_to_midnight, + '23:59:59.999999' AS fine_time_micro_text, + '23:59:59' AS fine_time_second_text, + '23:59:59.999' AS fine_time_milli_text, + '1969-12-31 23:59:59.999' AS text_clock_ff3, + '23:59:59.999' AS wall_clock_bare_ff, + TRUE AS wall_clock_equals_text, + FALSE AS whole_second_equals_text, + TRUE AS whole_second_before_text, + 'TIMESTAMP_NTZ(3)' AS epoch_millis_type, + 'TIMESTAMP_NTZ(9)' AS bare_timestamp_type + UNION ALL + SELECT + 'CK-05' AS text_id, + '06:30:00.001'::TIME(9) AS time_plus_minutes, + '20:00:00.001'::TIME(9) AS time_minus_hours, + '06:00:00.002'::TIME(9) AS time_plus_millisecond, + 'TIME(9)' AS time_add_type, + 'TIME(3)' AS time_cast_type, + '2027-01-01 23:59:59.999'::TIMESTAMP_NTZ(9) AS wall_clock_plus_day, + 'TIMESTAMP_NTZ(9)' AS wall_clock_add_type, + 1 AS years_to_2027, + 1 AS months_to_2027, + 0 AS weeks_to_2027, + 1 AS hours_to_2027, + 1 AS milliseconds_to_2027, + 1079 AS minutes_to_midnight, + '06:00:00.000000' AS fine_time_micro_text, + '06:00:00' AS fine_time_second_text, + '06:00:00.000' AS fine_time_milli_text, + '2026-12-31 23:59:59.999' AS text_clock_ff3, + '23:59:59.999' AS wall_clock_bare_ff, + TRUE AS wall_clock_equals_text, + FALSE AS whole_second_equals_text, + TRUE AS whole_second_before_text, + 'TIMESTAMP_NTZ(3)' AS epoch_millis_type, + 'TIMESTAMP_NTZ(9)' AS bare_timestamp_type + UNION ALL + SELECT + 'CK-06' AS text_id, + CAST(NULL AS TIME(9)) AS time_plus_minutes, + CAST(NULL AS TIME(9)) AS time_minus_hours, + CAST(NULL AS TIME(9)) AS time_plus_millisecond, + 'TIME(9)' AS time_add_type, + 'TIME(3)' AS time_cast_type, + CAST(NULL AS TIMESTAMP_NTZ(9)) AS wall_clock_plus_day, + 'TIMESTAMP_NTZ(9)' AS wall_clock_add_type, + CAST(NULL AS NUMBER(38, 0)) AS years_to_2027, + CAST(NULL AS NUMBER(38, 0)) AS months_to_2027, + CAST(NULL AS NUMBER(38, 0)) AS weeks_to_2027, + CAST(NULL AS NUMBER(38, 0)) AS hours_to_2027, + CAST(NULL AS NUMBER(38, 0)) AS milliseconds_to_2027, + CAST(NULL AS NUMBER(38, 0)) AS minutes_to_midnight, + CAST(NULL AS VARCHAR) AS fine_time_micro_text, + CAST(NULL AS VARCHAR) AS fine_time_second_text, + CAST(NULL AS VARCHAR) AS fine_time_milli_text, + CAST(NULL AS VARCHAR) AS text_clock_ff3, + CAST(NULL AS VARCHAR) AS wall_clock_bare_ff, + CAST(NULL AS BOOLEAN) AS wall_clock_equals_text, + CAST(NULL AS BOOLEAN) AS whole_second_equals_text, + CAST(NULL AS BOOLEAN) AS whole_second_before_text, + 'TIMESTAMP_NTZ(3)' AS epoch_millis_type, + 'TIMESTAMP_NTZ(9)' AS bare_timestamp_type ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_grouped_windows.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_grouped_windows.sql index e2e5889e49..7a0b137c41 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_grouped_windows.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_grouped_windows.sql @@ -22,12 +22,42 @@ WITH __ref__stg_edge_amount_checks AS ( __expected__int_edge_grouped_windows AS ( SELECT - NULL AS group_label, - NULL AS group_code, - NULL AS group_amount, - NULL AS check_count, - NULL AS total_amount, - NULL AS amount_share, - NULL AS size_rank, - NULL AS is_largest_group + 'ALL' AS group_label, + CAST(NULL AS VARCHAR) AS group_code, + 1000000000011.99::NUMBER(38, 2) AS group_amount, + 6 AS check_count, + CAST(NULL AS NUMBER(38, 2)) AS total_amount, + CAST(NULL AS NUMBER(38, 6)) AS amount_share, + CAST(NULL AS NUMBER(38, 0)) AS size_rank, + FALSE AS is_largest_group + UNION ALL + SELECT + 'G-1' AS group_label, + 'G-1' AS group_code, + 12.00::NUMBER(38, 2) AS group_amount, + 3 AS check_count, + 1000000000011.99::NUMBER(38, 2) AS total_amount, + 0.000000::NUMBER(38, 6) AS amount_share, + 1 AS size_rank, + FALSE AS is_largest_group + UNION ALL + SELECT + 'G-2' AS group_label, + 'G-2' AS group_code, + 0.00::NUMBER(38, 2) AS group_amount, + 2 AS check_count, + 1000000000011.99::NUMBER(38, 2) AS total_amount, + 0.000000::NUMBER(38, 6) AS amount_share, + 2 AS size_rank, + FALSE AS is_largest_group + UNION ALL + SELECT + 'G-3' AS group_label, + 'G-3' AS group_code, + 999999999999.99::NUMBER(38, 2) AS group_amount, + 1 AS check_count, + 1000000000011.99::NUMBER(38, 2) AS total_amount, + 1.000000::NUMBER(38, 6) AS amount_share, + 3 AS size_rank, + TRUE AS is_largest_group ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_join_keys.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_join_keys.sql index e368e0fa79..9fe0e74795 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_join_keys.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_join_keys.sql @@ -22,9 +22,93 @@ WITH __ref__stg_edge_amount_checks AS ( __expected__int_edge_join_keys AS ( SELECT - NULL AS row_key, - NULL AS join_shape, - NULL AS group_code, - NULL AS group_name, - NULL AS threshold_name + 'asof:AC-01:G-1:-' AS row_key, + 'asof:AC-01' AS join_shape, + 'G-1' AS group_code, + CAST(NULL AS VARCHAR) AS group_name, + CAST(NULL AS VARCHAR) AS threshold_name + UNION ALL + SELECT + 'asof:AC-02:G-1:ten or more' AS row_key, + 'asof:AC-02' AS join_shape, + 'G-1' AS group_code, + CAST(NULL AS VARCHAR) AS group_name, + 'ten or more' AS threshold_name + UNION ALL + SELECT + 'asof:AC-03:G-1:-' AS row_key, + 'asof:AC-03' AS join_shape, + 'G-1' AS group_code, + CAST(NULL AS VARCHAR) AS group_name, + CAST(NULL AS VARCHAR) AS threshold_name + UNION ALL + SELECT + 'asof:AC-04:G-2:a hundred or more' AS row_key, + 'asof:AC-04' AS join_shape, + 'G-2' AS group_code, + CAST(NULL AS VARCHAR) AS group_name, + 'a hundred or more' AS threshold_name + UNION ALL + SELECT + 'asof:AC-05:G-2:ten or more' AS row_key, + 'asof:AC-05' AS join_shape, + 'G-2' AS group_code, + CAST(NULL AS VARCHAR) AS group_name, + 'ten or more' AS threshold_name + UNION ALL + SELECT + 'asof:AC-06:G-3:-' AS row_key, + 'asof:AC-06' AS join_shape, + 'G-3' AS group_code, + CAST(NULL AS VARCHAR) AS group_name, + CAST(NULL AS VARCHAR) AS threshold_name + UNION ALL + SELECT + 'natural:G-1:-' AS row_key, + 'natural' AS join_shape, + 'G-1' AS group_code, + 'Mallard desk' AS group_name, + CAST(NULL AS VARCHAR) AS threshold_name + UNION ALL + SELECT + 'natural:G-2:-' AS row_key, + 'natural' AS join_shape, + 'G-2' AS group_code, + 'Teal desk' AS group_name, + CAST(NULL AS VARCHAR) AS threshold_name + UNION ALL + SELECT + 'right_using:G-1:-' AS row_key, + 'right_using' AS join_shape, + 'G-1' AS group_code, + 'Mallard desk' AS group_name, + CAST(NULL AS VARCHAR) AS threshold_name + UNION ALL + SELECT + 'right_using:G-2:-' AS row_key, + 'right_using' AS join_shape, + 'G-2' AS group_code, + 'Teal desk' AS group_name, + CAST(NULL AS VARCHAR) AS threshold_name + UNION ALL + SELECT + 'right_using:G-9:-' AS row_key, + 'right_using' AS join_shape, + 'G-9' AS group_code, + 'Eider desk' AS group_name, + CAST(NULL AS VARCHAR) AS threshold_name + UNION ALL + SELECT + 'using_then_right:-:G-7' AS row_key, + 'using_then_right' AS join_shape, + CAST(NULL AS VARCHAR) AS group_code, + CAST(NULL AS VARCHAR) AS group_name, + 'G-7' AS threshold_name + UNION ALL + SELECT + 'using_then_right:G-1:G-1' AS row_key, + 'using_then_right' AS join_shape, + 'G-1' AS group_code, + 'Mallard desk' AS group_name, + 'G-1' AS threshold_name ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_json_numbers.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_json_numbers.sql index 4c2d54bb4f..4d80a422ca 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_json_numbers.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_json_numbers.sql @@ -26,10 +26,82 @@ WITH __ref__stg_edge_amount_checks AS ( __expected__int_edge_json_numbers AS ( SELECT - NULL AS check_id, - NULL AS parsed_kind, - NULL AS parsed_text, - NULL AS amount_kind, - NULL AS amount_text, - NULL AS is_valid_json + 'AC-01' AS check_id, + 'OBJECT' AS parsed_kind, + '{"amount":2,"qty":1}' AS parsed_text, + 'INTEGER' AS amount_kind, + '2' AS amount_text, + TRUE AS is_valid_json + UNION ALL + SELECT + 'AC-02' AS check_id, + CAST(NULL AS VARCHAR) AS parsed_kind, + CAST(NULL AS VARCHAR) AS parsed_text, + CAST(NULL AS VARCHAR) AS amount_kind, + CAST(NULL AS VARCHAR) AS amount_text, + FALSE AS is_valid_json + UNION ALL + SELECT + 'AC-03' AS check_id, + CAST(NULL AS VARCHAR) AS parsed_kind, + CAST(NULL AS VARCHAR) AS parsed_text, + CAST(NULL AS VARCHAR) AS amount_kind, + CAST(NULL AS VARCHAR) AS amount_text, + FALSE AS is_valid_json + UNION ALL + SELECT + 'AC-04' AS check_id, + CAST(NULL AS VARCHAR) AS parsed_kind, + CAST(NULL AS VARCHAR) AS parsed_text, + CAST(NULL AS VARCHAR) AS amount_kind, + CAST(NULL AS VARCHAR) AS amount_text, + FALSE AS is_valid_json + UNION ALL + SELECT + 'AC-05' AS check_id, + 'INTEGER' AS parsed_kind, + '12345678901234567890123456789012345678' AS parsed_text, + CAST(NULL AS VARCHAR) AS amount_kind, + CAST(NULL AS VARCHAR) AS amount_text, + TRUE AS is_valid_json + UNION ALL + SELECT + 'AC-06' AS check_id, + 'INTEGER' AS parsed_kind, + '1' AS parsed_text, + CAST(NULL AS VARCHAR) AS amount_kind, + CAST(NULL AS VARCHAR) AS amount_text, + TRUE AS is_valid_json + UNION ALL + SELECT + 'AC-07' AS check_id, + 'OBJECT' AS parsed_kind, + '{"amount":0}' AS parsed_text, + 'INTEGER' AS amount_kind, + '0' AS amount_text, + TRUE AS is_valid_json + UNION ALL + SELECT + 'AC-08' AS check_id, + 'OBJECT' AS parsed_kind, + '{"amount":2.000000000000000e+05}' AS parsed_text, + 'DOUBLE' AS amount_kind, + '200000' AS amount_text, + TRUE AS is_valid_json + UNION ALL + SELECT + 'AC-09' AS check_id, + 'OBJECT' AS parsed_kind, + '{"amount":100}' AS parsed_text, + 'INTEGER' AS amount_kind, + '100' AS amount_text, + TRUE AS is_valid_json + UNION ALL + SELECT + 'AC-10' AS check_id, + 'INTEGER' AS parsed_kind, + '123456789012345678901234567890123456789' AS parsed_text, + CAST(NULL AS VARCHAR) AS amount_kind, + CAST(NULL AS VARCHAR) AS amount_text, + TRUE AS is_valid_json ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_key_widths.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_key_widths.sql index c2097f7a97..da6e1500b2 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_key_widths.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_key_widths.sql @@ -22,12 +22,172 @@ WITH __ref__stg_edge_amount_checks AS ( __expected__int_edge_key_widths AS ( SELECT - NULL AS pair_id, - NULL AS join_kind, - NULL AS check_key_text, - NULL AS narrow_check_id, - NULL AS wide_check_id, - NULL AS merged_key_type, - NULL AS narrow_side_type, - NULL AS wide_side_type + 'full:-:AC-02' AS pair_id, + 'full' AS join_kind, + '12345678901234567890123' AS check_key_text, + CAST(NULL AS VARCHAR) AS narrow_check_id, + 'AC-02' AS wide_check_id, + 'NUMBER(38,0)' AS merged_key_type, + 'NUMBER(18,0)' AS narrow_side_type, + 'NUMBER(38,0)' AS wide_side_type + UNION ALL + SELECT + 'full:AC-01:AC-01' AS pair_id, + 'full' AS join_kind, + '7' AS check_key_text, + 'AC-01' AS narrow_check_id, + 'AC-01' AS wide_check_id, + 'NUMBER(38,0)' AS merged_key_type, + 'NUMBER(18,0)' AS narrow_side_type, + 'NUMBER(38,0)' AS wide_side_type + UNION ALL + SELECT + 'full:AC-02:AC-03' AS pair_id, + 'full' AS join_kind, + '12' AS check_key_text, + 'AC-02' AS narrow_check_id, + 'AC-03' AS wide_check_id, + 'NUMBER(38,0)' AS merged_key_type, + 'NUMBER(18,0)' AS narrow_side_type, + 'NUMBER(38,0)' AS wide_side_type + UNION ALL + SELECT + 'full:AC-04:AC-04' AS pair_id, + 'full' AS join_kind, + '123456789012345678' AS check_key_text, + 'AC-04' AS narrow_check_id, + 'AC-04' AS wide_check_id, + 'NUMBER(38,0)' AS merged_key_type, + 'NUMBER(18,0)' AS narrow_side_type, + 'NUMBER(38,0)' AS wide_side_type + UNION ALL + SELECT + 'full:AC-05:-' AS pair_id, + 'full' AS join_kind, + '99' AS check_key_text, + 'AC-05' AS narrow_check_id, + CAST(NULL AS VARCHAR) AS wide_check_id, + 'NUMBER(38,0)' AS merged_key_type, + 'NUMBER(18,0)' AS narrow_side_type, + 'NUMBER(38,0)' AS wide_side_type + UNION ALL + SELECT + 'full:AC-06:AC-06' AS pair_id, + 'full' AS join_kind, + '5' AS check_key_text, + 'AC-06' AS narrow_check_id, + 'AC-06' AS wide_check_id, + 'NUMBER(38,0)' AS merged_key_type, + 'NUMBER(18,0)' AS narrow_side_type, + 'NUMBER(38,0)' AS wide_side_type + UNION ALL + SELECT + 'inner:AC-01:AC-01' AS pair_id, + 'inner' AS join_kind, + '7' AS check_key_text, + 'AC-01' AS narrow_check_id, + 'AC-01' AS wide_check_id, + 'NUMBER(18,0)' AS merged_key_type, + 'NUMBER(18,0)' AS narrow_side_type, + 'NUMBER(18,0)' AS wide_side_type + UNION ALL + SELECT + 'inner:AC-02:AC-03' AS pair_id, + 'inner' AS join_kind, + '12' AS check_key_text, + 'AC-02' AS narrow_check_id, + 'AC-03' AS wide_check_id, + 'NUMBER(18,0)' AS merged_key_type, + 'NUMBER(18,0)' AS narrow_side_type, + 'NUMBER(18,0)' AS wide_side_type + UNION ALL + SELECT + 'inner:AC-04:AC-04' AS pair_id, + 'inner' AS join_kind, + '123456789012345678' AS check_key_text, + 'AC-04' AS narrow_check_id, + 'AC-04' AS wide_check_id, + 'NUMBER(18,0)' AS merged_key_type, + 'NUMBER(18,0)' AS narrow_side_type, + 'NUMBER(18,0)' AS wide_side_type + UNION ALL + SELECT + 'inner:AC-06:AC-06' AS pair_id, + 'inner' AS join_kind, + '5' AS check_key_text, + 'AC-06' AS narrow_check_id, + 'AC-06' AS wide_check_id, + 'NUMBER(18,0)' AS merged_key_type, + 'NUMBER(18,0)' AS narrow_side_type, + 'NUMBER(18,0)' AS wide_side_type + UNION ALL + SELECT + 'left:AC-01:AC-01' AS pair_id, + 'left' AS join_kind, + '7' AS check_key_text, + 'AC-01' AS narrow_check_id, + 'AC-01' AS wide_check_id, + 'NUMBER(18,0)' AS merged_key_type, + 'NUMBER(18,0)' AS narrow_side_type, + 'NUMBER(38,0)' AS wide_side_type + UNION ALL + SELECT + 'left:AC-02:AC-03' AS pair_id, + 'left' AS join_kind, + '12' AS check_key_text, + 'AC-02' AS narrow_check_id, + 'AC-03' AS wide_check_id, + 'NUMBER(18,0)' AS merged_key_type, + 'NUMBER(18,0)' AS narrow_side_type, + 'NUMBER(38,0)' AS wide_side_type + UNION ALL + SELECT + 'left:AC-04:AC-04' AS pair_id, + 'left' AS join_kind, + '123456789012345678' AS check_key_text, + 'AC-04' AS narrow_check_id, + 'AC-04' AS wide_check_id, + 'NUMBER(18,0)' AS merged_key_type, + 'NUMBER(18,0)' AS narrow_side_type, + 'NUMBER(38,0)' AS wide_side_type + UNION ALL + SELECT + 'left:AC-05:-' AS pair_id, + 'left' AS join_kind, + '99' AS check_key_text, + 'AC-05' AS narrow_check_id, + CAST(NULL AS VARCHAR) AS wide_check_id, + 'NUMBER(18,0)' AS merged_key_type, + 'NUMBER(18,0)' AS narrow_side_type, + 'NUMBER(38,0)' AS wide_side_type + UNION ALL + SELECT + 'left:AC-06:AC-06' AS pair_id, + 'left' AS join_kind, + '5' AS check_key_text, + 'AC-06' AS narrow_check_id, + 'AC-06' AS wide_check_id, + 'NUMBER(18,0)' AS merged_key_type, + 'NUMBER(18,0)' AS narrow_side_type, + 'NUMBER(38,0)' AS wide_side_type + UNION ALL + SELECT + 'text:AC-03:AC-03' AS pair_id, + 'text' AS join_kind, + '12' AS check_key_text, + 'AC-03' AS narrow_check_id, + 'AC-03' AS wide_check_id, + 'VARCHAR(3)' AS merged_key_type, + 'VARCHAR(3)' AS narrow_side_type, + 'VARCHAR(3)' AS wide_side_type + UNION ALL + SELECT + 'text:AC-04:AC-01' AS pair_id, + 'text' AS join_kind, + ' 7' AS check_key_text, + 'AC-04' AS narrow_check_id, + 'AC-01' AS wide_check_id, + 'VARCHAR(3)' AS merged_key_type, + 'VARCHAR(3)' AS narrow_side_type, + 'VARCHAR(3)' AS wide_side_type ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_note_patterns.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_note_patterns.sql index 605438dc3d..be945f0afc 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_note_patterns.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_note_patterns.sql @@ -29,21 +29,367 @@ WITH __ref__stg_edge_ticket_notes AS ( __expected__int_edge_note_patterns AS ( SELECT - NULL AS note_id, - NULL AS lazy_then_optional_count, - NULL AS lazy_then_optional_match, - NULL AS optional_twice_match, - NULL AS bracket_close_match, - NULL AS bracket_negated_match, - NULL AS lazy_plus_position, - NULL AS end_anchor_position, - NULL AS star_position, - NULL AS multiline_dot_match, - NULL AS matches_micro_sign, - NULL AS matches_sigma, - NULL AS ilike_s_prefix, - NULL AS ilike_long_s_escaped, - NULL AS name_similarity, - NULL AS decoded_tags, - NULL AS ranked_word + 'TN-01' AS note_id, + 1 AS lazy_then_optional_count, + 'xaxax' AS lazy_then_optional_match, + 'xa' AS optional_twice_match, + 'x' AS bracket_close_match, + 'xaxax' AS bracket_negated_match, + 0 AS lazy_plus_position, + 6 AS end_anchor_position, + 1 AS star_position, + CAST(NULL AS VARCHAR) AS multiline_dot_match, + FALSE AS matches_micro_sign, + FALSE AS matches_sigma, + FALSE AS ilike_s_prefix, + FALSE AS ilike_long_s_escaped, + CAST(NULL AS NUMBER(38, 0)) AS name_similarity, + CAST(NULL AS VARCHAR) AS decoded_tags, + 'xaxax' AS ranked_word + UNION ALL + SELECT + 'TN-02' AS note_id, + 2 AS lazy_then_optional_count, + 'x' AS lazy_then_optional_match, + 'xa' AS optional_twice_match, + 'x' AS bracket_close_match, + 'xab xa' AS bracket_negated_match, + 3 AS lazy_plus_position, + 7 AS end_anchor_position, + 1 AS star_position, + CAST(NULL AS VARCHAR) AS multiline_dot_match, + FALSE AS matches_micro_sign, + FALSE AS matches_sigma, + FALSE AS ilike_s_prefix, + FALSE AS ilike_long_s_escaped, + CAST(NULL AS NUMBER(38, 0)) AS name_similarity, + CAST(NULL AS VARCHAR) AS decoded_tags, + 'xa' AS ranked_word + UNION ALL + SELECT + 'TN-03' AS note_id, + 1 AS lazy_then_optional_count, + 'x' AS lazy_then_optional_match, + 'x' AS optional_twice_match, + 'x+' AS bracket_close_match, + 'x' AS bracket_negated_match, + 0 AS lazy_plus_position, + 3 AS end_anchor_position, + 1 AS star_position, + CAST(NULL AS VARCHAR) AS multiline_dot_match, + FALSE AS matches_micro_sign, + FALSE AS matches_sigma, + FALSE AS ilike_s_prefix, + FALSE AS ilike_long_s_escaped, + CAST(NULL AS NUMBER(38, 0)) AS name_similarity, + CAST(NULL AS VARCHAR) AS decoded_tags, + '' AS ranked_word + UNION ALL + SELECT + 'TN-04' AS note_id, + 1 AS lazy_then_optional_count, + 'x' AS lazy_then_optional_match, + 'x' AS optional_twice_match, + 'x' AS bracket_close_match, + 'bxba' AS bracket_negated_match, + 1 AS lazy_plus_position, + 5 AS end_anchor_position, + 1 AS star_position, + CAST(NULL AS VARCHAR) AS multiline_dot_match, + FALSE AS matches_micro_sign, + FALSE AS matches_sigma, + FALSE AS ilike_s_prefix, + FALSE AS ilike_long_s_escaped, + CAST(NULL AS NUMBER(38, 0)) AS name_similarity, + CAST(NULL AS VARCHAR) AS decoded_tags, + '' AS ranked_word + UNION ALL + SELECT + 'TN-05' AS note_id, + 1 AS lazy_then_optional_count, + 'x' AS lazy_then_optional_match, + 'x' AS optional_twice_match, + 'x' AS bracket_close_match, + 'bxb' AS bracket_negated_match, + 1 AS lazy_plus_position, + 4 AS end_anchor_position, + 1 AS star_position, + CAST(NULL AS VARCHAR) AS multiline_dot_match, + FALSE AS matches_micro_sign, + FALSE AS matches_sigma, + FALSE AS ilike_s_prefix, + FALSE AS ilike_long_s_escaped, + CAST(NULL AS NUMBER(38, 0)) AS name_similarity, + CAST(NULL AS VARCHAR) AS decoded_tags, + '' AS ranked_word + UNION ALL + SELECT + 'TN-06' AS note_id, + 0 AS lazy_then_optional_count, + CAST(NULL AS VARCHAR) AS lazy_then_optional_match, + CAST(NULL AS VARCHAR) AS optional_twice_match, + CAST(NULL AS VARCHAR) AS bracket_close_match, + CAST(NULL AS VARCHAR) AS bracket_negated_match, + 0 AS lazy_plus_position, + 0 AS end_anchor_position, + 0 AS star_position, + CAST(NULL AS VARCHAR) AS multiline_dot_match, + FALSE AS matches_micro_sign, + FALSE AS matches_sigma, + FALSE AS ilike_s_prefix, + FALSE AS ilike_long_s_escaped, + CAST(NULL AS NUMBER(38, 0)) AS name_similarity, + CAST(NULL AS VARCHAR) AS decoded_tags, + '' AS ranked_word + UNION ALL + SELECT + 'TN-07' AS note_id, + 0 AS lazy_then_optional_count, + CAST(NULL AS VARCHAR) AS lazy_then_optional_match, + CAST(NULL AS VARCHAR) AS optional_twice_match, + CAST(NULL AS VARCHAR) AS bracket_close_match, + 'baa' AS bracket_negated_match, + 1 AS lazy_plus_position, + 4 AS end_anchor_position, + 1 AS star_position, + CAST(NULL AS VARCHAR) AS multiline_dot_match, + FALSE AS matches_micro_sign, + FALSE AS matches_sigma, + FALSE AS ilike_s_prefix, + FALSE AS ilike_long_s_escaped, + CAST(NULL AS NUMBER(38, 0)) AS name_similarity, + CAST(NULL AS VARCHAR) AS decoded_tags, + '' AS ranked_word + UNION ALL + SELECT + 'TN-08' AS note_id, + 0 AS lazy_then_optional_count, + CAST(NULL AS VARCHAR) AS lazy_then_optional_match, + CAST(NULL AS VARCHAR) AS optional_twice_match, + CAST(NULL AS VARCHAR) AS bracket_close_match, + 'a\u000d\u000ab' AS bracket_negated_match, + 4 AS lazy_plus_position, + 5 AS end_anchor_position, + 1 AS star_position, + 'a\u000d' AS multiline_dot_match, + FALSE AS matches_micro_sign, + FALSE AS matches_sigma, + FALSE AS ilike_s_prefix, + FALSE AS ilike_long_s_escaped, + CAST(NULL AS NUMBER(38, 0)) AS name_similarity, + CAST(NULL AS VARCHAR) AS decoded_tags, + '' AS ranked_word + UNION ALL + SELECT + 'TN-09' AS note_id, + 0 AS lazy_then_optional_count, + CAST(NULL AS VARCHAR) AS lazy_then_optional_match, + CAST(NULL AS VARCHAR) AS optional_twice_match, + CAST(NULL AS VARCHAR) AS bracket_close_match, + 'μ' AS bracket_negated_match, + 0 AS lazy_plus_position, + 2 AS end_anchor_position, + 1 AS star_position, + CAST(NULL AS VARCHAR) AS multiline_dot_match, + TRUE AS matches_micro_sign, + FALSE AS matches_sigma, + FALSE AS ilike_s_prefix, + FALSE AS ilike_long_s_escaped, + CAST(NULL AS NUMBER(38, 0)) AS name_similarity, + CAST(NULL AS VARCHAR) AS decoded_tags, + '' AS ranked_word + UNION ALL + SELECT + 'TN-10' AS note_id, + 0 AS lazy_then_optional_count, + CAST(NULL AS VARCHAR) AS lazy_then_optional_match, + CAST(NULL AS VARCHAR) AS optional_twice_match, + CAST(NULL AS VARCHAR) AS bracket_close_match, + 'ς' AS bracket_negated_match, + 0 AS lazy_plus_position, + 2 AS end_anchor_position, + 1 AS star_position, + CAST(NULL AS VARCHAR) AS multiline_dot_match, + FALSE AS matches_micro_sign, + TRUE AS matches_sigma, + FALSE AS ilike_s_prefix, + FALSE AS ilike_long_s_escaped, + CAST(NULL AS NUMBER(38, 0)) AS name_similarity, + CAST(NULL AS VARCHAR) AS decoded_tags, + '' AS ranked_word + UNION ALL + SELECT + 'TN-11' AS note_id, + 0 AS lazy_then_optional_count, + CAST(NULL AS VARCHAR) AS lazy_then_optional_match, + CAST(NULL AS VARCHAR) AS optional_twice_match, + CAST(NULL AS VARCHAR) AS bracket_close_match, + 'sabc' AS bracket_negated_match, + 3 AS lazy_plus_position, + 5 AS end_anchor_position, + 1 AS star_position, + CAST(NULL AS VARCHAR) AS multiline_dot_match, + FALSE AS matches_micro_sign, + FALSE AS matches_sigma, + TRUE AS ilike_s_prefix, + TRUE AS ilike_long_s_escaped, + CAST(NULL AS NUMBER(38, 0)) AS name_similarity, + CAST(NULL AS VARCHAR) AS decoded_tags, + '' AS ranked_word + UNION ALL + SELECT + 'TN-12' AS note_id, + 0 AS lazy_then_optional_count, + CAST(NULL AS VARCHAR) AS lazy_then_optional_match, + CAST(NULL AS VARCHAR) AS optional_twice_match, + CAST(NULL AS VARCHAR) AS bracket_close_match, + 'ſabc' AS bracket_negated_match, + 3 AS lazy_plus_position, + 5 AS end_anchor_position, + 1 AS star_position, + CAST(NULL AS VARCHAR) AS multiline_dot_match, + FALSE AS matches_micro_sign, + FALSE AS matches_sigma, + TRUE AS ilike_s_prefix, + TRUE AS ilike_long_s_escaped, + CAST(NULL AS NUMBER(38, 0)) AS name_similarity, + CAST(NULL AS VARCHAR) AS decoded_tags, + '' AS ranked_word + UNION ALL + SELECT + 'TN-13' AS note_id, + 0 AS lazy_then_optional_count, + CAST(NULL AS VARCHAR) AS lazy_then_optional_match, + CAST(NULL AS VARCHAR) AS optional_twice_match, + CAST(NULL AS VARCHAR) AS bracket_close_match, + 'Éé' AS bracket_negated_match, + 0 AS lazy_plus_position, + 3 AS end_anchor_position, + 1 AS star_position, + CAST(NULL AS VARCHAR) AS multiline_dot_match, + FALSE AS matches_micro_sign, + FALSE AS matches_sigma, + FALSE AS ilike_s_prefix, + FALSE AS ilike_long_s_escaped, + 100 AS name_similarity, + CAST(NULL AS VARCHAR) AS decoded_tags, + '' AS ranked_word + UNION ALL + SELECT + 'TN-14' AS note_id, + 0 AS lazy_then_optional_count, + CAST(NULL AS VARCHAR) AS lazy_then_optional_match, + CAST(NULL AS VARCHAR) AS optional_twice_match, + CAST(NULL AS VARCHAR) AS bracket_close_match, + 'Émile émile' AS bracket_negated_match, + 0 AS lazy_plus_position, + 12 AS end_anchor_position, + 1 AS star_position, + CAST(NULL AS VARCHAR) AS multiline_dot_match, + FALSE AS matches_micro_sign, + FALSE AS matches_sigma, + FALSE AS ilike_s_prefix, + FALSE AS ilike_long_s_escaped, + 100 AS name_similarity, + CAST(NULL AS VARCHAR) AS decoded_tags, + '' AS ranked_word + UNION ALL + SELECT + 'TN-15' AS note_id, + 0 AS lazy_then_optional_count, + CAST(NULL AS VARCHAR) AS lazy_then_optional_match, + CAST(NULL AS VARCHAR) AS optional_twice_match, + CAST(NULL AS VARCHAR) AS bracket_close_match, + 'Pintail' AS bracket_negated_match, + 0 AS lazy_plus_position, + 8 AS end_anchor_position, + 1 AS star_position, + CAST(NULL AS VARCHAR) AS multiline_dot_match, + FALSE AS matches_micro_sign, + FALSE AS matches_sigma, + FALSE AS ilike_s_prefix, + FALSE AS ilike_long_s_escaped, + 41 AS name_similarity, + 'order' AS decoded_tags, + '' AS ranked_word + UNION ALL + SELECT + 'TN-16' AS note_id, + 0 AS lazy_then_optional_count, + CAST(NULL AS VARCHAR) AS lazy_then_optional_match, + CAST(NULL AS VARCHAR) AS optional_twice_match, + CAST(NULL AS VARCHAR) AS bracket_close_match, + 'Teal' AS bracket_negated_match, + 0 AS lazy_plus_position, + 5 AS end_anchor_position, + 1 AS star_position, + CAST(NULL AS VARCHAR) AS multiline_dot_match, + FALSE AS matches_micro_sign, + FALSE AS matches_sigma, + FALSE AS ilike_s_prefix, + FALSE AS ilike_long_s_escaped, + 0 AS name_similarity, + 'a' AS decoded_tags, + '' AS ranked_word + UNION ALL + SELECT + 'TN-17' AS note_id, + 0 AS lazy_then_optional_count, + CAST(NULL AS VARCHAR) AS lazy_then_optional_match, + CAST(NULL AS VARCHAR) AS optional_twice_match, + CAST(NULL AS VARCHAR) AS bracket_close_match, + 'Eider' AS bracket_negated_match, + 0 AS lazy_plus_position, + 6 AS end_anchor_position, + 1 AS star_position, + CAST(NULL AS VARCHAR) AS multiline_dot_match, + FALSE AS matches_micro_sign, + FALSE AS matches_sigma, + FALSE AS ilike_s_prefix, + FALSE AS ilike_long_s_escaped, + 0 AS name_similarity, + 'a' AS decoded_tags, + '' AS ranked_word + UNION ALL + SELECT + 'TN-18' AS note_id, + 0 AS lazy_then_optional_count, + CAST(NULL AS VARCHAR) AS lazy_then_optional_match, + CAST(NULL AS VARCHAR) AS optional_twice_match, + CAST(NULL AS VARCHAR) AS bracket_close_match, + 'pond duck pond duck pond duck pond duck pond duck pond duck pond duck pond duck pond duck ' + || 'pond duck pond duck pond duck pond duck pond duck pond duck pond duck pond duck pond duck ' + || 'pond duck pond duck pond duck pond duck pond duck pond duck pond duck pond duck pond duck ' + || 'pond duck pond duck pond duck pond duck pond duck pond duck pond duck pond duck pond duck ' + || 'pond duck pond duck pond duck pond duck ' AS bracket_negated_match, + 0 AS lazy_plus_position, + 401 AS end_anchor_position, + 1 AS star_position, + CAST(NULL AS VARCHAR) AS multiline_dot_match, + FALSE AS matches_micro_sign, + FALSE AS matches_sigma, + FALSE AS ilike_s_prefix, + FALSE AS ilike_long_s_escaped, + CAST(NULL AS NUMBER(38, 0)) AS name_similarity, + CAST(NULL AS VARCHAR) AS decoded_tags, + 'duck' AS ranked_word + UNION ALL + SELECT + 'TN-19' AS note_id, + CAST(NULL AS NUMBER(38, 0)) AS lazy_then_optional_count, + CAST(NULL AS VARCHAR) AS lazy_then_optional_match, + CAST(NULL AS VARCHAR) AS optional_twice_match, + CAST(NULL AS VARCHAR) AS bracket_close_match, + CAST(NULL AS VARCHAR) AS bracket_negated_match, + CAST(NULL AS NUMBER(38, 0)) AS lazy_plus_position, + CAST(NULL AS NUMBER(38, 0)) AS end_anchor_position, + CAST(NULL AS NUMBER(38, 0)) AS star_position, + CAST(NULL AS VARCHAR) AS multiline_dot_match, + CAST(NULL AS BOOLEAN) AS matches_micro_sign, + CAST(NULL AS BOOLEAN) AS matches_sigma, + CAST(NULL AS BOOLEAN) AS ilike_s_prefix, + CAST(NULL AS BOOLEAN) AS ilike_long_s_escaped, + CAST(NULL AS NUMBER(38, 0)) AS name_similarity, + CAST(NULL AS VARCHAR) AS decoded_tags, + CAST(NULL AS VARCHAR) AS ranked_word ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_null_arithmetic.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_null_arithmetic.sql index d49f05ecb8..b4aa0744c5 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_null_arithmetic.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_null_arithmetic.sql @@ -22,30 +22,170 @@ WITH __ref__stg_edge_amount_checks AS ( __expected__int_edge_null_arithmetic AS ( SELECT - NULL AS check_id, - NULL AS null_plus_amount, - NULL AS null_times_amount, - NULL AS null_over_amount, - NULL AS ratio_over_null, - NULL AS wide_amount_times_null, - NULL AS missing_times_amount, - NULL AS null_times_amount_or_zero, - NULL AS null_over_amount_type, - NULL AS ratio_over_null_type, - NULL AS wide_amount_times_null_type, - NULL AS missing_times_amount_type, - NULL AS amount_or_decimal, - NULL AS amount_or_decimal_type, - NULL AS exponent_type, - NULL AS small_exponent_product_type, - NULL AS remainder_type, - NULL AS key_remainder_type, - NULL AS nullif_type, - NULL AS ilike_null_type, - NULL AS row_number_type, - NULL AS count_type, - NULL AS union_decimal_type, - NULL AS all_null_union_type, - NULL AS case_without_else, - NULL AS decoded + 'AC-01' AS check_id, + CAST(NULL AS NUMBER(38, 2)) AS null_plus_amount, + CAST(NULL AS NUMBER(38, 2)) AS null_times_amount, + CAST(NULL AS NUMBER(38, 6)) AS null_over_amount, + CAST(NULL AS FLOAT) AS ratio_over_null, + CAST(NULL AS NUMBER(38, 2)) AS wide_amount_times_null, + CAST(NULL AS NUMBER(38, 2)) AS missing_times_amount, + '0.00' AS null_times_amount_or_zero, + 'NUMBER(26,6)' AS null_over_amount_type, + 'FLOAT' AS ratio_over_null_type, + 'NUMBER(38,2)' AS wide_amount_times_null_type, + 'NUMBER(36,2)' AS missing_times_amount_type, + '10.00' AS amount_or_decimal, + 'NUMBER(18,2)' AS amount_or_decimal_type, + 'NUMBER(6,0)' AS exponent_type, + 'NUMBER(29,12)' AS small_exponent_product_type, + 'NUMBER(2,0)' AS remainder_type, + 'NUMBER(18,0)' AS key_remainder_type, + 'NUMBER(18,2)' AS nullif_type, + 'NULL' AS ilike_null_type, + 'NUMBER(18,0)' AS row_number_type, + 'NUMBER(18,0)' AS count_type, + 'NUMBER(18,2)' AS union_decimal_type, + 'NULL' AS all_null_union_type, + '7.00000' AS case_without_else, + '10.00000' AS decoded + UNION ALL + SELECT + 'AC-02' AS check_id, + CAST(NULL AS NUMBER(38, 2)) AS null_plus_amount, + CAST(NULL AS NUMBER(38, 2)) AS null_times_amount, + CAST(NULL AS NUMBER(38, 6)) AS null_over_amount, + CAST(NULL AS FLOAT) AS ratio_over_null, + CAST(NULL AS NUMBER(38, 2)) AS wide_amount_times_null, + CAST(NULL AS NUMBER(38, 2)) AS missing_times_amount, + '0.00' AS null_times_amount_or_zero, + 'NUMBER(26,6)' AS null_over_amount_type, + 'FLOAT' AS ratio_over_null_type, + 'NUMBER(38,2)' AS wide_amount_times_null_type, + 'NUMBER(36,2)' AS missing_times_amount_type, + '-0.50' AS amount_or_decimal, + 'NUMBER(18,2)' AS amount_or_decimal_type, + 'NUMBER(6,0)' AS exponent_type, + 'NUMBER(29,12)' AS small_exponent_product_type, + 'NUMBER(2,0)' AS remainder_type, + 'NUMBER(18,0)' AS key_remainder_type, + 'NUMBER(18,2)' AS nullif_type, + 'NULL' AS ilike_null_type, + 'NUMBER(18,0)' AS row_number_type, + 'NUMBER(18,0)' AS count_type, + 'NUMBER(18,2)' AS union_decimal_type, + 'NULL' AS all_null_union_type, + '1.00000' AS case_without_else, + '1.00000' AS decoded + UNION ALL + SELECT + 'AC-03' AS check_id, + CAST(NULL AS NUMBER(38, 2)) AS null_plus_amount, + CAST(NULL AS NUMBER(38, 2)) AS null_times_amount, + CAST(NULL AS NUMBER(38, 6)) AS null_over_amount, + CAST(NULL AS FLOAT) AS ratio_over_null, + CAST(NULL AS NUMBER(38, 2)) AS wide_amount_times_null, + CAST(NULL AS NUMBER(38, 2)) AS missing_times_amount, + '0.00' AS null_times_amount_or_zero, + 'NUMBER(26,6)' AS null_over_amount_type, + 'FLOAT' AS ratio_over_null_type, + 'NUMBER(38,2)' AS wide_amount_times_null_type, + 'NUMBER(36,2)' AS missing_times_amount_type, + '2.50' AS amount_or_decimal, + 'NUMBER(18,2)' AS amount_or_decimal_type, + 'NUMBER(6,0)' AS exponent_type, + 'NUMBER(29,12)' AS small_exponent_product_type, + 'NUMBER(2,0)' AS remainder_type, + 'NUMBER(18,0)' AS key_remainder_type, + 'NUMBER(18,2)' AS nullif_type, + 'NULL' AS ilike_null_type, + 'NUMBER(18,0)' AS row_number_type, + 'NUMBER(18,0)' AS count_type, + 'NUMBER(18,2)' AS union_decimal_type, + 'NULL' AS all_null_union_type, + CAST(NULL AS VARCHAR) AS case_without_else, + CAST(NULL AS VARCHAR) AS decoded + UNION ALL + SELECT + 'AC-04' AS check_id, + CAST(NULL AS NUMBER(38, 2)) AS null_plus_amount, + CAST(NULL AS NUMBER(38, 2)) AS null_times_amount, + CAST(NULL AS NUMBER(38, 6)) AS null_over_amount, + CAST(NULL AS FLOAT) AS ratio_over_null, + CAST(NULL AS NUMBER(38, 2)) AS wide_amount_times_null, + CAST(NULL AS NUMBER(38, 2)) AS missing_times_amount, + '0.00' AS null_times_amount_or_zero, + 'NUMBER(26,6)' AS null_over_amount_type, + 'FLOAT' AS ratio_over_null_type, + 'NUMBER(38,2)' AS wide_amount_times_null_type, + 'NUMBER(36,2)' AS missing_times_amount_type, + '0.00' AS amount_or_decimal, + 'NUMBER(18,2)' AS amount_or_decimal_type, + 'NUMBER(6,0)' AS exponent_type, + 'NUMBER(29,12)' AS small_exponent_product_type, + 'NUMBER(2,0)' AS remainder_type, + 'NUMBER(18,0)' AS key_remainder_type, + 'NUMBER(18,2)' AS nullif_type, + 'NULL' AS ilike_null_type, + 'NUMBER(18,0)' AS row_number_type, + 'NUMBER(18,0)' AS count_type, + 'NUMBER(18,2)' AS union_decimal_type, + 'NULL' AS all_null_union_type, + CAST(NULL AS VARCHAR) AS case_without_else, + CAST(NULL AS VARCHAR) AS decoded + UNION ALL + SELECT + 'AC-05' AS check_id, + CAST(NULL AS NUMBER(38, 2)) AS null_plus_amount, + CAST(NULL AS NUMBER(38, 2)) AS null_times_amount, + CAST(NULL AS NUMBER(38, 6)) AS null_over_amount, + CAST(NULL AS FLOAT) AS ratio_over_null, + CAST(NULL AS NUMBER(38, 2)) AS wide_amount_times_null, + CAST(NULL AS NUMBER(38, 2)) AS missing_times_amount, + '0.00' AS null_times_amount_or_zero, + 'NUMBER(26,6)' AS null_over_amount_type, + 'FLOAT' AS ratio_over_null_type, + 'NUMBER(38,2)' AS wide_amount_times_null_type, + 'NUMBER(36,2)' AS missing_times_amount_type, + '1.50' AS amount_or_decimal, + 'NUMBER(18,2)' AS amount_or_decimal_type, + 'NUMBER(6,0)' AS exponent_type, + 'NUMBER(29,12)' AS small_exponent_product_type, + 'NUMBER(2,0)' AS remainder_type, + 'NUMBER(18,0)' AS key_remainder_type, + 'NUMBER(18,2)' AS nullif_type, + 'NULL' AS ilike_null_type, + 'NUMBER(18,0)' AS row_number_type, + 'NUMBER(18,0)' AS count_type, + 'NUMBER(18,2)' AS union_decimal_type, + 'NULL' AS all_null_union_type, + CAST(NULL AS VARCHAR) AS case_without_else, + CAST(NULL AS VARCHAR) AS decoded + UNION ALL + SELECT + 'AC-06' AS check_id, + CAST(NULL AS NUMBER(38, 2)) AS null_plus_amount, + CAST(NULL AS NUMBER(38, 2)) AS null_times_amount, + CAST(NULL AS NUMBER(38, 6)) AS null_over_amount, + CAST(NULL AS FLOAT) AS ratio_over_null, + CAST(NULL AS NUMBER(38, 2)) AS wide_amount_times_null, + CAST(NULL AS NUMBER(38, 2)) AS missing_times_amount, + '0.00' AS null_times_amount_or_zero, + 'NUMBER(26,6)' AS null_over_amount_type, + 'FLOAT' AS ratio_over_null_type, + 'NUMBER(38,2)' AS wide_amount_times_null_type, + 'NUMBER(36,2)' AS missing_times_amount_type, + '999999999999.99' AS amount_or_decimal, + 'NUMBER(18,2)' AS amount_or_decimal_type, + 'NUMBER(6,0)' AS exponent_type, + 'NUMBER(29,12)' AS small_exponent_product_type, + 'NUMBER(2,0)' AS remainder_type, + 'NUMBER(18,0)' AS key_remainder_type, + 'NUMBER(18,2)' AS nullif_type, + 'NULL' AS ilike_null_type, + 'NUMBER(18,0)' AS row_number_type, + 'NUMBER(18,0)' AS count_type, + 'NUMBER(18,2)' AS union_decimal_type, + 'NULL' AS all_null_union_type, + CAST(NULL AS VARCHAR) AS case_without_else, + CAST(NULL AS VARCHAR) AS decoded ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_payload_paths.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_payload_paths.sql index 0448d3c683..29c778bce6 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_payload_paths.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_payload_paths.sql @@ -20,10 +20,130 @@ WITH __ref__stg_edge_variant_values AS ( __expected__int_edge_payload_paths AS ( SELECT - NULL AS element_id, - NULL AS value_id, - NULL AS element_path, - NULL AS element_key, - NULL AS element_index, - NULL AS element_kind + 'VV-01:[''a$b'']' AS element_id, + 'VV-01' AS value_id, + '[''a$b'']' AS element_path, + 'a$b' AS element_key, + CAST(NULL AS NUMBER(38, 0)) AS element_index, + 'INTEGER' AS element_kind + UNION ALL + SELECT + 'VV-01:[''a-b'']' AS element_id, + 'VV-01' AS value_id, + '[''a-b'']' AS element_path, + 'a-b' AS element_key, + CAST(NULL AS NUMBER(38, 0)) AS element_index, + 'OBJECT' AS element_kind + UNION ALL + SELECT + 'VV-01:[''a-b''][''@x'']' AS element_id, + 'VV-01' AS value_id, + '[''a-b''][''@x'']' AS element_path, + '@x' AS element_key, + CAST(NULL AS NUMBER(38, 0)) AS element_index, + 'INTEGER' AS element_kind + UNION ALL + SELECT + 'VV-01:[''a[b'']' AS element_id, + 'VV-01' AS value_id, + '[''a[b'']' AS element_path, + 'a[b' AS element_key, + CAST(NULL AS NUMBER(38, 0)) AS element_index, + 'ARRAY' AS element_kind + UNION ALL + SELECT + 'VV-01:[''a[b''][0]' AS element_id, + 'VV-01' AS value_id, + '[''a[b''][0]' AS element_path, + CAST(NULL AS VARCHAR) AS element_key, + 0 AS element_index, + 'INTEGER' AS element_kind + UNION ALL + SELECT + 'VV-01:[''a[b''][1]' AS element_id, + 'VV-01' AS value_id, + '[''a[b''][1]' AS element_path, + CAST(NULL AS VARCHAR) AS element_key, + 1 AS element_index, + 'OBJECT' AS element_kind + UNION ALL + SELECT + 'VV-01:[''a[b''][1].k' AS element_id, + 'VV-01' AS value_id, + '[''a[b''][1].k' AS element_path, + 'k' AS element_key, + CAST(NULL AS NUMBER(38, 0)) AS element_index, + 'INTEGER' AS element_kind + UNION ALL + SELECT + 'VV-02:[0]' AS element_id, + 'VV-02' AS value_id, + '[0]' AS element_path, + CAST(NULL AS VARCHAR) AS element_key, + 0 AS element_index, + 'OBJECT' AS element_kind + UNION ALL + SELECT + 'VV-02:[0][''x y'']' AS element_id, + 'VV-02' AS value_id, + '[0][''x y'']' AS element_path, + 'x y' AS element_key, + CAST(NULL AS NUMBER(38, 0)) AS element_index, + 'INTEGER' AS element_kind + UNION ALL + SELECT + 'VV-02:[1]' AS element_id, + 'VV-02' AS value_id, + '[1]' AS element_path, + CAST(NULL AS VARCHAR) AS element_key, + 1 AS element_index, + 'ARRAY' AS element_kind + UNION ALL + SELECT + 'VV-02:[1][0]' AS element_id, + 'VV-02' AS value_id, + '[1][0]' AS element_path, + CAST(NULL AS VARCHAR) AS element_key, + 0 AS element_index, + 'INTEGER' AS element_kind + UNION ALL + SELECT + 'VV-04:['''']' AS element_id, + 'VV-04' AS value_id, + '['''']' AS element_path, + '' AS element_key, + CAST(NULL AS NUMBER(38, 0)) AS element_index, + 'INTEGER' AS element_kind + UNION ALL + SELECT + 'VV-04:[''9z'']' AS element_id, + 'VV-04' AS value_id, + '[''9z'']' AS element_path, + '9z' AS element_key, + CAST(NULL AS NUMBER(38, 0)) AS element_index, + 'INTEGER' AS element_kind + UNION ALL + SELECT + 'VV-04:[''a.b'']' AS element_id, + 'VV-04' AS value_id, + '[''a.b'']' AS element_path, + 'a.b' AS element_key, + CAST(NULL AS NUMBER(38, 0)) AS element_index, + 'INTEGER' AS element_kind + UNION ALL + SELECT + 'VV-04:[''plain key'']' AS element_id, + 'VV-04' AS value_id, + '[''plain key'']' AS element_path, + 'plain key' AS element_key, + CAST(NULL AS NUMBER(38, 0)) AS element_index, + 'INTEGER' AS element_kind + UNION ALL + SELECT + 'VV-04:é' AS element_id, + 'VV-04' AS value_id, + 'é' AS element_path, + 'é' AS element_key, + CAST(NULL AS NUMBER(38, 0)) AS element_index, + 'INTEGER' AS element_kind ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_python_values.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_python_values.sql index b8620b6f4c..ae793a84e4 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_python_values.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_python_values.sql @@ -22,10 +22,74 @@ WITH __ref__stg_edge_basket_lines AS ( __expected__int_edge_python_values AS ( SELECT - NULL AS kind, - NULL AS payload_is_sql_null, - NULL AS payload_is_json_null, - NULL AS payload_kind, - NULL AS payload_text, - NULL AS amount_as_float + 'exponent_decimal' AS kind, + FALSE AS payload_is_sql_null, + FALSE AS payload_is_json_null, + 'VARCHAR' AS payload_kind, + '"1E+3"' AS payload_text, + '9007199254740992.0'::FLOAT AS amount_as_float + UNION ALL + SELECT + 'naive_time' AS kind, + FALSE AS payload_is_sql_null, + FALSE AS payload_is_json_null, + 'VARCHAR' AS payload_kind, + '"2026-03-01 09:30:00"' AS payload_text, + '9007199254740992.0'::FLOAT AS amount_as_float + UNION ALL + SELECT + 'naive_time_fraction' AS kind, + FALSE AS payload_is_sql_null, + FALSE AS payload_is_json_null, + 'VARCHAR' AS payload_kind, + '"2026-03-01 09:30:00.250000"' AS payload_text, + '9007199254740992.0'::FLOAT AS amount_as_float + UNION ALL + SELECT + 'nested' AS kind, + FALSE AS payload_is_sql_null, + FALSE AS payload_is_json_null, + 'OBJECT' AS payload_kind, + '{"a":true,"b":[1,null]}' AS payload_text, + '9007199254740992.0'::FLOAT AS amount_as_float + UNION ALL + SELECT + 'nothing' AS kind, + FALSE AS payload_is_sql_null, + TRUE AS payload_is_json_null, + 'NULL_VALUE' AS payload_kind, + 'null' AS payload_text, + '9007199254740992.0'::FLOAT AS amount_as_float + UNION ALL + SELECT + 'plain_decimal' AS kind, + FALSE AS payload_is_sql_null, + FALSE AS payload_is_json_null, + 'VARCHAR' AS payload_kind, + '"1.50"' AS payload_text, + '9007199254740992.0'::FLOAT AS amount_as_float + UNION ALL + SELECT + 'some other kind' AS kind, + FALSE AS payload_is_sql_null, + FALSE AS payload_is_json_null, + 'VARCHAR' AS payload_kind, + '"some other kind"' AS payload_text, + '9007199254740992.0'::FLOAT AS amount_as_float + UNION ALL + SELECT + 'wide_integer' AS kind, + FALSE AS payload_is_sql_null, + FALSE AS payload_is_json_null, + 'INTEGER' AS payload_kind, + '1152921504606846977' AS payload_text, + '9007199254740992.0'::FLOAT AS amount_as_float + UNION ALL + SELECT + 'zoned_time' AS kind, + FALSE AS payload_is_sql_null, + FALSE AS payload_is_json_null, + 'VARCHAR' AS payload_kind, + '"2026-03-01 09:30:00+00:00"' AS payload_text, + '9007199254740992.0'::FLOAT AS amount_as_float ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_recursive_counts.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_recursive_counts.sql index d8ab89fb36..a11e8e0bd6 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_recursive_counts.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_recursive_counts.sql @@ -16,8 +16,32 @@ WITH __ref__stg_edge_category_links AS ( __expected__int_edge_recursive_counts AS ( SELECT - NULL AS category_id, - NULL AS tree_depth, - NULL AS parent_name, - NULL AS grandparent_name + 'CAT-1' AS category_id, + 1 AS tree_depth, + CAST(NULL AS VARCHAR) AS parent_name, + CAST(NULL AS VARCHAR) AS grandparent_name + UNION ALL + SELECT + 'CAT-2' AS category_id, + 2 AS tree_depth, + 'Ducks' AS parent_name, + CAST(NULL AS VARCHAR) AS grandparent_name + UNION ALL + SELECT + 'CAT-3' AS category_id, + 3 AS tree_depth, + 'Rubber ducks' AS parent_name, + 'Ducks' AS grandparent_name + UNION ALL + SELECT + 'CAT-4' AS category_id, + 2 AS tree_depth, + 'Ducks' AS parent_name, + CAST(NULL AS VARCHAR) AS grandparent_name + UNION ALL + SELECT + 'CAT-5' AS category_id, + 1 AS tree_depth, + CAST(NULL AS VARCHAR) AS parent_name, + CAST(NULL AS VARCHAR) AS grandparent_name ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_row_deciders.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_row_deciders.sql index c3b064cf2d..7069767ef4 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_row_deciders.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_row_deciders.sql @@ -20,8 +20,302 @@ WITH __ref__stg_edge_basket_lines AS ( __expected__int_edge_row_deciders AS ( SELECT - NULL AS decider_row, - NULL AS decider, - NULL AS basket_id, - NULL AS detail + 'except_limit:B-4:-' AS decider_row, + 'except_limit' AS decider, + 'B-4' AS basket_id, + CAST(NULL AS VARCHAR) AS detail + UNION ALL + SELECT + 'exists_filter:B-1:-' AS decider_row, + 'exists_filter' AS decider, + 'B-1' AS basket_id, + CAST(NULL AS VARCHAR) AS detail + UNION ALL + SELECT + 'exists_filter:B-4:-' AS decider_row, + 'exists_filter' AS decider, + 'B-4' AS basket_id, + CAST(NULL AS VARCHAR) AS detail + UNION ALL + SELECT + 'exists_output:B-1:true' AS decider_row, + 'exists_output' AS decider, + 'B-1' AS basket_id, + 'true' AS detail + UNION ALL + SELECT + 'exists_output:B-2:false' AS decider_row, + 'exists_output' AS decider, + 'B-2' AS basket_id, + 'false' AS detail + UNION ALL + SELECT + 'exists_output:B-3:false' AS decider_row, + 'exists_output' AS decider, + 'B-3' AS basket_id, + 'false' AS detail + UNION ALL + SELECT + 'exists_output:B-4:true' AS decider_row, + 'exists_output' AS decider, + 'B-4' AS basket_id, + 'true' AS detail + UNION ALL + SELECT + 'flatten_input:B-1:BL-01#0' AS decider_row, + 'flatten_input' AS decider, + 'B-1' AS basket_id, + 'BL-01#0' AS detail + UNION ALL + SELECT + 'flatten_input:B-1:BL-01#1' AS decider_row, + 'flatten_input' AS decider, + 'B-1' AS basket_id, + 'BL-01#1' AS detail + UNION ALL + SELECT + 'flatten_input:B-1:BL-02#0' AS decider_row, + 'flatten_input' AS decider, + 'B-1' AS basket_id, + 'BL-02#0' AS detail + UNION ALL + SELECT + 'flatten_input:B-1:BL-02#1' AS decider_row, + 'flatten_input' AS decider, + 'B-1' AS basket_id, + 'BL-02#1' AS detail + UNION ALL + SELECT + 'flatten_input:B-1:BL-03#0' AS decider_row, + 'flatten_input' AS decider, + 'B-1' AS basket_id, + 'BL-03#0' AS detail + UNION ALL + SELECT + 'flatten_input:B-1:BL-03#1' AS decider_row, + 'flatten_input' AS decider, + 'B-1' AS basket_id, + 'BL-03#1' AS detail + UNION ALL + SELECT + 'flatten_input:B-2:BL-04#0' AS decider_row, + 'flatten_input' AS decider, + 'B-2' AS basket_id, + 'BL-04#0' AS detail + UNION ALL + SELECT + 'flatten_input:B-2:BL-04#1' AS decider_row, + 'flatten_input' AS decider, + 'B-2' AS basket_id, + 'BL-04#1' AS detail + UNION ALL + SELECT + 'flatten_input:B-2:BL-05#0' AS decider_row, + 'flatten_input' AS decider, + 'B-2' AS basket_id, + 'BL-05#0' AS detail + UNION ALL + SELECT + 'flatten_input:B-2:BL-05#1' AS decider_row, + 'flatten_input' AS decider, + 'B-2' AS basket_id, + 'BL-05#1' AS detail + UNION ALL + SELECT + 'flatten_input:B-3:BL-06#0' AS decider_row, + 'flatten_input' AS decider, + 'B-3' AS basket_id, + 'BL-06#0' AS detail + UNION ALL + SELECT + 'flatten_input:B-3:BL-06#1' AS decider_row, + 'flatten_input' AS decider, + 'B-3' AS basket_id, + 'BL-06#1' AS detail + UNION ALL + SELECT + 'flatten_input:B-4:BL-07#0' AS decider_row, + 'flatten_input' AS decider, + 'B-4' AS basket_id, + 'BL-07#0' AS detail + UNION ALL + SELECT + 'flatten_input:B-4:BL-07#1' AS decider_row, + 'flatten_input' AS decider, + 'B-4' AS basket_id, + 'BL-07#1' AS detail + UNION ALL + SELECT + 'flatten_input:B-4:BL-08#0' AS decider_row, + 'flatten_input' AS decider, + 'B-4' AS basket_id, + 'BL-08#0' AS detail + UNION ALL + SELECT + 'flatten_input:B-4:BL-08#1' AS decider_row, + 'flatten_input' AS decider, + 'B-4' AS basket_id, + 'BL-08#1' AS detail + UNION ALL + SELECT + 'group_by_all:B-1:3' AS decider_row, + 'group_by_all' AS decider, + 'B-1' AS basket_id, + '3' AS detail + UNION ALL + SELECT + 'group_by_all:B-2:2' AS decider_row, + 'group_by_all' AS decider, + 'B-2' AS basket_id, + '2' AS detail + UNION ALL + SELECT + 'group_by_all:B-3:1' AS decider_row, + 'group_by_all' AS decider, + 'B-3' AS basket_id, + '1' AS detail + UNION ALL + SELECT + 'group_by_all:B-4:2' AS decider_row, + 'group_by_all' AS decider, + 'B-4' AS basket_id, + '2' AS detail + UNION ALL + SELECT + 'group_by_position:B-1:-' AS decider_row, + 'group_by_position' AS decider, + 'B-1' AS basket_id, + CAST(NULL AS VARCHAR) AS detail + UNION ALL + SELECT + 'group_by_position:B-2:-' AS decider_row, + 'group_by_position' AS decider, + 'B-2' AS basket_id, + CAST(NULL AS VARCHAR) AS detail + UNION ALL + SELECT + 'group_by_position:B-4:-' AS decider_row, + 'group_by_position' AS decider, + 'B-4' AS basket_id, + CAST(NULL AS VARCHAR) AS detail + UNION ALL + SELECT + 'order_by_limit:B-1:BL-03' AS decider_row, + 'order_by_limit' AS decider, + 'B-1' AS basket_id, + 'BL-03' AS detail + UNION ALL + SELECT + 'qualify:B-1:BL-01' AS decider_row, + 'qualify' AS decider, + 'B-1' AS basket_id, + 'BL-01' AS detail + UNION ALL + SELECT + 'qualify:B-2:BL-04' AS decider_row, + 'qualify' AS decider, + 'B-2' AS basket_id, + 'BL-04' AS detail + UNION ALL + SELECT + 'qualify:B-3:BL-06' AS decider_row, + 'qualify' AS decider, + 'B-3' AS basket_id, + 'BL-06' AS detail + UNION ALL + SELECT + 'qualify:B-4:BL-07' AS decider_row, + 'qualify' AS decider, + 'B-4' AS basket_id, + 'BL-07' AS detail + UNION ALL + SELECT + 'rollup_position:-:1:~=8' AS decider_row, + 'rollup_position' AS decider, + CAST(NULL AS VARCHAR) AS basket_id, + '1:~=8' AS detail + UNION ALL + SELECT + 'rollup_position:B-1:0:bath=2' AS decider_row, + 'rollup_position' AS decider, + 'B-1' AS basket_id, + '0:bath=2' AS detail + UNION ALL + SELECT + 'rollup_position:B-1:0:gift=1' AS decider_row, + 'rollup_position' AS decider, + 'B-1' AS basket_id, + '0:gift=1' AS detail + UNION ALL + SELECT + 'rollup_position:B-1:1:~=3' AS decider_row, + 'rollup_position' AS decider, + 'B-1' AS basket_id, + '1:~=3' AS detail + UNION ALL + SELECT + 'rollup_position:B-2:0:bath=1' AS decider_row, + 'rollup_position' AS decider, + 'B-2' AS basket_id, + '0:bath=1' AS detail + UNION ALL + SELECT + 'rollup_position:B-2:0:garden=1' AS decider_row, + 'rollup_position' AS decider, + 'B-2' AS basket_id, + '0:garden=1' AS detail + UNION ALL + SELECT + 'rollup_position:B-2:1:~=2' AS decider_row, + 'rollup_position' AS decider, + 'B-2' AS basket_id, + '1:~=2' AS detail + UNION ALL + SELECT + 'rollup_position:B-3:0:pond=1' AS decider_row, + 'rollup_position' AS decider, + 'B-3' AS basket_id, + '0:pond=1' AS detail + UNION ALL + SELECT + 'rollup_position:B-3:1:~=1' AS decider_row, + 'rollup_position' AS decider, + 'B-3' AS basket_id, + '1:~=1' AS detail + UNION ALL + SELECT + 'rollup_position:B-4:0:gift=1' AS decider_row, + 'rollup_position' AS decider, + 'B-4' AS basket_id, + '0:gift=1' AS detail + UNION ALL + SELECT + 'rollup_position:B-4:0:~=1' AS decider_row, + 'rollup_position' AS decider, + 'B-4' AS basket_id, + '0:~=1' AS detail + UNION ALL + SELECT + 'rollup_position:B-4:1:~=2' AS decider_row, + 'rollup_position' AS decider, + 'B-4' AS basket_id, + '1:~=2' AS detail + UNION ALL + SELECT + 'where:B-1:BL-01' AS decider_row, + 'where' AS decider, + 'B-1' AS basket_id, + 'BL-01' AS detail + UNION ALL + SELECT + 'where:B-1:BL-03' AS decider_row, + 'where' AS decider, + 'B-1' AS basket_id, + 'BL-03' AS detail + UNION ALL + SELECT + 'where:B-2:BL-05' AS decider_row, + 'where' AS decider, + 'B-2' AS basket_id, + 'BL-05' AS detail ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_text_dates.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_text_dates.sql index 432c8f73f4..bb3bddbcf2 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_text_dates.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_text_dates.sql @@ -37,12 +37,232 @@ WITH __ref__stg_edge_date_texts AS ( __expected__int_edge_text_dates AS ( SELECT - NULL AS text_id, - NULL AS day_month_dots, - NULL AS iso_date, - NULL AS day_month_short_year, - NULL AS month_day_slashes, - NULL AS day_month_name, - NULL AS month_name_first, - NULL AS auto_date + 'DT-01' AS text_id, + '2026-03-05'::DATE AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + CAST(NULL AS DATE) AS auto_date + UNION ALL + SELECT + 'DT-02' AS text_id, + '0026-03-05'::DATE AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + CAST(NULL AS DATE) AS auto_date + UNION ALL + SELECT + 'DT-03' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + '2026-03-05'::DATE AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + '2026-03-05'::DATE AS auto_date + UNION ALL + SELECT + 'DT-04' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + '2026-03-05'::DATE AS day_month_short_year, + '0026-05-03'::DATE AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + '0026-05-03'::DATE AS auto_date + UNION ALL + SELECT + 'DT-05' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + '2026-03-05'::DATE AS day_month_short_year, + '0026-05-03'::DATE AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + '0026-05-03'::DATE AS auto_date + UNION ALL + SELECT + 'DT-06' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + '2026-03-05'::DATE AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + '2026-03-05'::DATE AS auto_date + UNION ALL + SELECT + 'DT-07' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + '2026-10-05'::DATE AS auto_date + UNION ALL + SELECT + 'DT-08' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + CAST(NULL AS DATE) AS auto_date + UNION ALL + SELECT + 'DT-09' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + CAST(NULL AS DATE) AS auto_date + UNION ALL + SELECT + 'DT-10' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + CAST(NULL AS DATE) AS auto_date + UNION ALL + SELECT + 'DT-11' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + CAST(NULL AS DATE) AS auto_date + UNION ALL + SELECT + 'DT-12' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + CAST(NULL AS DATE) AS auto_date + UNION ALL + SELECT + 'DT-13' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + '2024-08-05'::DATE AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + CAST(NULL AS DATE) AS auto_date + UNION ALL + SELECT + 'DT-14' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + CAST(NULL AS DATE) AS auto_date + UNION ALL + SELECT + 'DT-15' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + '2024-03-05'::DATE AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + CAST(NULL AS DATE) AS auto_date + UNION ALL + SELECT + 'DT-16' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + '2024-03-05'::DATE AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + CAST(NULL AS DATE) AS auto_date + UNION ALL + SELECT + 'DT-17' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + '2024-03-05'::DATE AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + CAST(NULL AS DATE) AS auto_date + UNION ALL + SELECT + 'DT-18' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + '2024-03-05'::DATE AS month_name_first, + CAST(NULL AS DATE) AS auto_date + UNION ALL + SELECT + 'DT-19' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + CAST(NULL AS DATE) AS auto_date + UNION ALL + SELECT + 'DT-20' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + CAST(NULL AS DATE) AS auto_date + UNION ALL + SELECT + 'DT-21' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + CAST(NULL AS DATE) AS auto_date + UNION ALL + SELECT + 'DT-22' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + CAST(NULL AS DATE) AS auto_date + UNION ALL + SELECT + 'DT-23' AS text_id, + CAST(NULL AS DATE) AS day_month_dots, + CAST(NULL AS DATE) AS iso_date, + CAST(NULL AS DATE) AS day_month_short_year, + CAST(NULL AS DATE) AS month_day_slashes, + CAST(NULL AS DATE) AS day_month_name, + CAST(NULL AS DATE) AS month_name_first, + CAST(NULL AS DATE) AS auto_date ) diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_ticket_tokens.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_ticket_tokens.sql index 67049642a1..0764ffa03f 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_ticket_tokens.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_ticket_tokens.sql @@ -20,10 +20,52 @@ WITH __ref__stg_edge_basket_lines AS ( __expected__int_edge_ticket_tokens AS ( SELECT - NULL AS line_id, - NULL AS edge_next_ticket_source, - NULL AS look_alike_text, - NULL AS tag_label + 'BL-01' AS line_id, + 'BL-01' AS edge_next_ticket_source, + 'edge_next_ticket edge_next_ticket' AS look_alike_text, + 'Bath' AS tag_label + UNION ALL + SELECT + 'BL-02' AS line_id, + 'BL-02' AS edge_next_ticket_source, + 'edge_next_ticket edge_next_ticket' AS look_alike_text, + 'Gift' AS tag_label + UNION ALL + SELECT + 'BL-03' AS line_id, + 'BL-03' AS edge_next_ticket_source, + 'edge_next_ticket edge_next_ticket' AS look_alike_text, + 'Bath' AS tag_label + UNION ALL + SELECT + 'BL-04' AS line_id, + 'BL-04' AS edge_next_ticket_source, + 'edge_next_ticket edge_next_ticket' AS look_alike_text, + 'Garden' AS tag_label + UNION ALL + SELECT + 'BL-05' AS line_id, + 'BL-05' AS edge_next_ticket_source, + 'edge_next_ticket edge_next_ticket' AS look_alike_text, + 'Bath' AS tag_label + UNION ALL + SELECT + 'BL-06' AS line_id, + 'BL-06' AS edge_next_ticket_source, + 'edge_next_ticket edge_next_ticket' AS look_alike_text, + 'Pond' AS tag_label + UNION ALL + SELECT + 'BL-07' AS line_id, + 'BL-07' AS edge_next_ticket_source, + 'edge_next_ticket edge_next_ticket' AS look_alike_text, + 'Untagged' AS tag_label + UNION ALL + SELECT + 'BL-08' AS line_id, + 'BL-08' AS edge_next_ticket_source, + 'edge_next_ticket edge_next_ticket' AS look_alike_text, + 'Gift' AS tag_label ), __assert__every_call_gives_a_new_value AS ( diff --git a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_variant_extremes.sql b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_variant_extremes.sql index 11125bd8d6..ac8b111d7c 100644 --- a/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_variant_extremes.sql +++ b/benchmarks/duckshop/project/tests/unit/intermediate/edge_cases/test_int_edge_variant_extremes.sql @@ -37,14 +37,254 @@ WITH __ref__stg_edge_variant_values AS ( __expected__int_edge_variant_extremes AS ( SELECT - NULL AS value_id, - NULL AS largest_element, - NULL AS smallest_element, - NULL AS reversed_elements, - NULL AS group_largest, - NULL AS without_json_nulls, - NULL AS distinct_element_count, - NULL AS overlaps_json_null, - NULL AS in_decimal_array, - NULL AS decimal_array_position + 'VV-01' AS value_id, + PARSE_JSON('"a"') AS largest_element, + PARSE_JSON('1') AS smallest_element, + PARSE_JSON('[2,"a",1]')::ARRAY AS reversed_elements, + PARSE_JSON('[1,null,3]') AS group_largest, + PARSE_JSON('[1,"a",2]')::ARRAY AS without_json_nulls, + 3 AS distinct_element_count, + FALSE AS overlaps_json_null, + FALSE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-02' AS value_id, + PARSE_JSON('null') AS largest_element, + PARSE_JSON('1') AS smallest_element, + PARSE_JSON('[3,null,1]')::ARRAY AS reversed_elements, + PARSE_JSON('[1,null,3]') AS group_largest, + PARSE_JSON('[1,3]')::ARRAY AS without_json_nulls, + 3 AS distinct_element_count, + TRUE AS overlaps_json_null, + FALSE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-03' AS value_id, + PARSE_JSON('2') AS largest_element, + PARSE_JSON('true') AS smallest_element, + PARSE_JSON('[2,true]')::ARRAY AS reversed_elements, + PARSE_JSON('[1,null,3]') AS group_largest, + PARSE_JSON('[true,2]')::ARRAY AS without_json_nulls, + 2 AS distinct_element_count, + FALSE AS overlaps_json_null, + FALSE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-04' AS value_id, + PARSE_JSON('"b"') AS largest_element, + PARSE_JSON('"B"') AS smallest_element, + PARSE_JSON('["b","B","a"]')::ARRAY AS reversed_elements, + PARSE_JSON('["a","B","b"]') AS group_largest, + PARSE_JSON('["a","B","b"]')::ARRAY AS without_json_nulls, + 3 AS distinct_element_count, + FALSE AS overlaps_json_null, + FALSE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-05' AS value_id, + PARSE_JSON('12345678901234567890') AS largest_element, + PARSE_JSON('1') AS smallest_element, + PARSE_JSON('[1,12345678901234567890]')::ARRAY AS reversed_elements, + PARSE_JSON('["a","B","b"]') AS group_largest, + PARSE_JSON('[12345678901234567890,1]')::ARRAY AS without_json_nulls, + 2 AS distinct_element_count, + FALSE AS overlaps_json_null, + FALSE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-06' AS value_id, + PARSE_JSON('9007199254740993') AS largest_element, + PARSE_JSON('9007199254740992') AS smallest_element, + PARSE_JSON('[9007199254740992,9007199254740993]')::ARRAY AS reversed_elements, + PARSE_JSON('["a","B","b"]') AS group_largest, + PARSE_JSON('[9007199254740993,9007199254740992]')::ARRAY AS without_json_nulls, + 2 AS distinct_element_count, + FALSE AS overlaps_json_null, + FALSE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-07' AS value_id, + PARSE_JSON('0') AS largest_element, + PARSE_JSON('0') AS smallest_element, + PARSE_JSON('[0,0]')::ARRAY AS reversed_elements, + PARSE_JSON('[1.000000000000000e+02,99]') AS group_largest, + PARSE_JSON('[0,0]')::ARRAY AS without_json_nulls, + 1 AS distinct_element_count, + FALSE AS overlaps_json_null, + FALSE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-08' AS value_id, + PARSE_JSON('1.000000000000000e+02') AS largest_element, + PARSE_JSON('99') AS smallest_element, + PARSE_JSON('[99,1.000000000000000e+02]')::ARRAY AS reversed_elements, + PARSE_JSON('[1.000000000000000e+02,99]') AS group_largest, + PARSE_JSON('[1.000000000000000e+02,99]')::ARRAY AS without_json_nulls, + 2 AS distinct_element_count, + FALSE AS overlaps_json_null, + FALSE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-09' AS value_id, + CAST(NULL AS VARIANT) AS largest_element, + CAST(NULL AS VARIANT) AS smallest_element, + PARSE_JSON('[]')::ARRAY AS reversed_elements, + PARSE_JSON('[1.000000000000000e+02,99]') AS group_largest, + PARSE_JSON('[]')::ARRAY AS without_json_nulls, + 0 AS distinct_element_count, + FALSE AS overlaps_json_null, + FALSE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-10' AS value_id, + CAST(NULL AS VARIANT) AS largest_element, + CAST(NULL AS VARIANT) AS smallest_element, + CAST(NULL AS ARRAY) AS reversed_elements, + PARSE_JSON('null') AS group_largest, + CAST(NULL AS ARRAY) AS without_json_nulls, + CAST(NULL AS NUMBER(38, 0)) AS distinct_element_count, + CAST(NULL AS BOOLEAN) AS overlaps_json_null, + FALSE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-11' AS value_id, + CAST(NULL AS VARIANT) AS largest_element, + CAST(NULL AS VARIANT) AS smallest_element, + CAST(NULL AS ARRAY) AS reversed_elements, + PARSE_JSON('null') AS group_largest, + CAST(NULL AS ARRAY) AS without_json_nulls, + CAST(NULL AS NUMBER(38, 0)) AS distinct_element_count, + CAST(NULL AS BOOLEAN) AS overlaps_json_null, + FALSE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-12' AS value_id, + CAST(NULL AS VARIANT) AS largest_element, + CAST(NULL AS VARIANT) AS smallest_element, + CAST(NULL AS ARRAY) AS reversed_elements, + PARSE_JSON('null') AS group_largest, + CAST(NULL AS ARRAY) AS without_json_nulls, + CAST(NULL AS NUMBER(38, 0)) AS distinct_element_count, + CAST(NULL AS BOOLEAN) AS overlaps_json_null, + FALSE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-13' AS value_id, + CAST(NULL AS VARIANT) AS largest_element, + CAST(NULL AS VARIANT) AS smallest_element, + CAST(NULL AS ARRAY) AS reversed_elements, + PARSE_JSON('null') AS group_largest, + CAST(NULL AS ARRAY) AS without_json_nulls, + CAST(NULL AS NUMBER(38, 0)) AS distinct_element_count, + CAST(NULL AS BOOLEAN) AS overlaps_json_null, + FALSE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-14' AS value_id, + PARSE_JSON('"é"') AS largest_element, + PARSE_JSON('"B"') AS smallest_element, + PARSE_JSON('["B","b","z","é"]')::ARRAY AS reversed_elements, + PARSE_JSON('["é","z","b","B"]') AS group_largest, + PARSE_JSON('["é","z","b","B"]')::ARRAY AS without_json_nulls, + 4 AS distinct_element_count, + FALSE AS overlaps_json_null, + FALSE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-15' AS value_id, + CAST(NULL AS VARIANT) AS largest_element, + CAST(NULL AS VARIANT) AS smallest_element, + CAST(NULL AS ARRAY) AS reversed_elements, + PARSE_JSON('["é","z","b","B"]') AS group_largest, + CAST(NULL AS ARRAY) AS without_json_nulls, + CAST(NULL AS NUMBER(38, 0)) AS distinct_element_count, + CAST(NULL AS BOOLEAN) AS overlaps_json_null, + FALSE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-16' AS value_id, + CAST(NULL AS VARIANT) AS largest_element, + CAST(NULL AS VARIANT) AS smallest_element, + CAST(NULL AS ARRAY) AS reversed_elements, + PARSE_JSON('["é","z","b","B"]') AS group_largest, + CAST(NULL AS ARRAY) AS without_json_nulls, + CAST(NULL AS NUMBER(38, 0)) AS distinct_element_count, + CAST(NULL AS BOOLEAN) AS overlaps_json_null, + FALSE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-17' AS value_id, + CAST(NULL AS VARIANT) AS largest_element, + CAST(NULL AS VARIANT) AS smallest_element, + CAST(NULL AS ARRAY) AS reversed_elements, + PARSE_JSON('["é","z","b","B"]') AS group_largest, + CAST(NULL AS ARRAY) AS without_json_nulls, + CAST(NULL AS NUMBER(38, 0)) AS distinct_element_count, + CAST(NULL AS BOOLEAN) AS overlaps_json_null, + FALSE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-18' AS value_id, + CAST(NULL AS VARIANT) AS largest_element, + CAST(NULL AS VARIANT) AS smallest_element, + CAST(NULL AS ARRAY) AS reversed_elements, + PARSE_JSON('"1"') AS group_largest, + CAST(NULL AS ARRAY) AS without_json_nulls, + CAST(NULL AS NUMBER(38, 0)) AS distinct_element_count, + CAST(NULL AS BOOLEAN) AS overlaps_json_null, + TRUE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-19' AS value_id, + CAST(NULL AS VARIANT) AS largest_element, + CAST(NULL AS VARIANT) AS smallest_element, + CAST(NULL AS ARRAY) AS reversed_elements, + PARSE_JSON('"1"') AS group_largest, + CAST(NULL AS ARRAY) AS without_json_nulls, + CAST(NULL AS NUMBER(38, 0)) AS distinct_element_count, + CAST(NULL AS BOOLEAN) AS overlaps_json_null, + TRUE AS in_decimal_array, + 1 AS decimal_array_position + UNION ALL + SELECT + 'VV-20' AS value_id, + CAST(NULL AS VARIANT) AS largest_element, + CAST(NULL AS VARIANT) AS smallest_element, + CAST(NULL AS ARRAY) AS reversed_elements, + PARSE_JSON('"1"') AS group_largest, + CAST(NULL AS ARRAY) AS without_json_nulls, + CAST(NULL AS NUMBER(38, 0)) AS distinct_element_count, + CAST(NULL AS BOOLEAN) AS overlaps_json_null, + FALSE AS in_decimal_array, + CAST(NULL AS NUMBER(38, 0)) AS decimal_array_position + UNION ALL + SELECT + 'VV-21' AS value_id, + CAST(NULL AS VARIANT) AS largest_element, + CAST(NULL AS VARIANT) AS smallest_element, + CAST(NULL AS ARRAY) AS reversed_elements, + PARSE_JSON('"1"') AS group_largest, + CAST(NULL AS ARRAY) AS without_json_nulls, + CAST(NULL AS NUMBER(38, 0)) AS distinct_element_count, + CAST(NULL AS BOOLEAN) AS overlaps_json_null, + FALSE AS in_decimal_array, + 2 AS decimal_array_position ) diff --git a/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_amount_checks.sql b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_amount_checks.sql index 012a85d845..4c5572471d 100644 --- a/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_amount_checks.sql +++ b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_amount_checks.sql @@ -19,9 +19,16 @@ WITH __source__edge__amount_checks AS ( __expected__stg_edge_amount_checks AS ( SELECT - NULL AS check_id, - NULL AS narrow_key, - NULL AS wide_key, - NULL AS amount, - NULL AS fine_amount + 'AC-01' AS check_id, + 7 AS narrow_key, + 12345678901234567890123 AS wide_key, + 10.00::NUMBER(18, 2) AS amount, + 0.123456789012345::NUMBER(20, 15) AS fine_amount + UNION ALL + SELECT + 'AC-02' AS check_id, + CAST(NULL AS NUMBER(18, 0)) AS narrow_key, + CAST(NULL AS NUMBER(38, 0)) AS wide_key, + CAST(NULL AS NUMBER(18, 2)) AS amount, + CAST(NULL AS NUMBER(20, 15)) AS fine_amount ) diff --git a/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_basket_lines.sql b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_basket_lines.sql index 090d6b0f01..5e6717a96a 100644 --- a/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_basket_lines.sql +++ b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_basket_lines.sql @@ -16,8 +16,20 @@ WITH __source__edge__basket_lines AS ( __expected__stg_edge_basket_lines AS ( SELECT - NULL AS line_id, - NULL AS basket_id, - NULL AS line_no, - NULL AS tag + 'BL-01' AS line_id, + 'B-1' AS basket_id, + 1 AS line_no, + 'bath' AS tag + UNION ALL + SELECT + 'BL-02' AS line_id, + 'B-1' AS basket_id, + 2 AS line_no, + 'gift' AS tag + UNION ALL + SELECT + 'BL-03' AS line_id, + 'B-2' AS basket_id, + 1 AS line_no, + CAST(NULL AS VARCHAR) AS tag ) diff --git a/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_category_links.sql b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_category_links.sql index 1640722223..f5e45a2ddb 100644 --- a/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_category_links.sql +++ b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_category_links.sql @@ -14,8 +14,14 @@ WITH __source__edge__category_links AS ( __expected__stg_edge_category_links AS ( SELECT - NULL AS category_id, - NULL AS parent_id, - NULL AS category_name, - NULL AS shelf_depth + 'CAT-1' AS category_id, + CAST(NULL AS VARCHAR) AS parent_id, + 'Ducks' AS category_name, + 1 AS shelf_depth + UNION ALL + SELECT + 'CAT-2' AS category_id, + 'CAT-1' AS parent_id, + 'Rubber ducks' AS category_name, + 2 AS shelf_depth ) diff --git a/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_variant_values.sql b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_variant_values.sql index ba9d1abcdd..b94bf09cca 100644 --- a/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_variant_values.sql +++ b/benchmarks/duckshop/project/tests/unit/staging/edge_cases/test_stg_edge_variant_values.sql @@ -16,6 +16,14 @@ WITH __source__edge__variant_values AS ( __expected__stg_edge_variant_values AS ( SELECT - NULL AS value_id, - NULL AS attribute_value + 'VV-01' AS value_id, + PARSE_JSON('{"a":1,"b":2}') AS attribute_value + UNION ALL + SELECT + 'VV-02' AS value_id, + PARSE_JSON('null') AS attribute_value + UNION ALL + SELECT + 'VV-03' AS value_id, + PARSE_JSON('null') AS attribute_value ) From 23d3759ef7c6d315002f2599e9fb1284d559322b Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Mon, 2 Nov 2026 01:20:36 +0000 Subject: [PATCH 8/8] test(benchmarks): accept the DuckShop NATURAL JOIN edge case under SQLClean rules --- benchmarks/duckshop/project/sqlbuild_project.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/benchmarks/duckshop/project/sqlbuild_project.toml b/benchmarks/duckshop/project/sqlbuild_project.toml index 396a2b12be..103f00c940 100644 --- a/benchmarks/duckshop/project/sqlbuild_project.toml +++ b/benchmarks/duckshop/project/sqlbuild_project.toml @@ -287,12 +287,13 @@ side, so qualifying them with either alias would drop the keys of rows found onl """ [[rules.rule_ignores]] -rules = ["SQLCLEAN003", "SQLCLEAN023", "SQLCLEAN027"] +rules = ["SQLCLEAN003", "SQLCLEAN023", "SQLCLEAN027", "SQLCLEAN051"] paths = ["models/intermediate/edge_cases/int_edge_join_keys.sql"] reason = """ The edge cases read the merged key of RIGHT JOIN ... USING and of a USING join followed by an outer join unqualified, because it belongs to neither side; the side aliases are then unused. An ASOF -JOIN's condition is its MATCH_CONDITION, so it has no ON clause. +JOIN's condition is its MATCH_CONDITION, so it has no ON clause. The NATURAL JOIN is the case +under test. """ [[rules.rule_ignores]]