')), 'text/html')::VARCHAR AS m, ARRAY_SIZE(OBJECT_KEYS(PARSE_JSON(SNOWFLAKE.NOTIFICATION.TEXT_HTML('
A message
')))) AS keys FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_quotes"
+description = "A message with a double quote, which the JSON must escape so that it parses back. Catches: the quote left unescaped"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT GET(PARSE_JSON(SNOWFLAKE.NOTIFICATION.TEXT_HTML('say \"hi\"')), 'text/html')::VARCHAR AS m FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_message"
+description = "A NULL message; the page does not say; the recording decides. Catches: an error, or the text null"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT SNOWFLAKE.NOTIFICATION.TEXT_HTML(NULL::VARCHAR) IS NULL AS n FROM input"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/text-plain.toml b/cases/translation/functions/text-plain.toml
new file mode 100644
index 000000000..a77002201
--- /dev/null
+++ b/cases/translation/functions/text-plain.toml
@@ -0,0 +1,36 @@
+category = "translation"
+construct = "sf.functions.text-plain"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_message"
+description = "The page's example message, measured by its key and value once parsed. Catches: another key used, or the message returned bare"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT GET(PARSE_JSON(SNOWFLAKE.NOTIFICATION.TEXT_PLAIN('A message')), 'text/plain')::VARCHAR AS m, ARRAY_SIZE(OBJECT_KEYS(PARSE_JSON(SNOWFLAKE.NOTIFICATION.TEXT_PLAIN('A message')))) AS keys FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_quotes"
+description = "A message with a double quote, which the JSON must escape so that it parses back. Catches: the quote left unescaped"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT GET(PARSE_JSON(SNOWFLAKE.NOTIFICATION.TEXT_PLAIN('say \"hi\"')), 'text/plain')::VARCHAR AS m FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_message"
+description = "A NULL message; the page does not say; the recording decides. Catches: an error, or the text null"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT SNOWFLAKE.NOTIFICATION.TEXT_PLAIN(NULL::VARCHAR) IS NULL AS n FROM input"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/time-from-parts.toml b/cases/translation/functions/time-from-parts.toml
new file mode 100644
index 000000000..984e8cfe2
--- /dev/null
+++ b/cases/translation/functions/time-from-parts.toml
@@ -0,0 +1,88 @@
+category = "translation"
+construct = "sf.functions.time-from-parts"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_number"
+description = "Normal times. Catches: hour and second swapped, or a 12-hour clock"
+columns = [{ name = "h", type = "NUMBER(38,0)" }, { name = "m", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["13", "4", "59"],
+ ["0", "0", "1"],
+]
+query = "SELECT TIME_FROM_PARTS(h, m, x) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_nanoseconds"
+description = "Nanoseconds that need all nine digits. The result is formatted with TO_VARCHAR(..., 'HH24:MI:SS.FF9') because the connector returns TIME in microseconds and the session TIME_OUTPUT_FORMAT has no fraction. Catches: nanoseconds read as milliseconds or truncated to microseconds"
+columns = [{ name = "n", type = "NUMBER(38,0)" }]
+rows = [
+ ["987654321"],
+ ["5"],
+]
+query = "SELECT TO_VARCHAR(TIME_FROM_PARTS(1, 2, 3, n), 'HH24:MI:SS.FF9') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_carry"
+description = "Values beyond their ranges, which carry and wrap the day. Catches: an error instead of carrying (25:70:70 is 02:11:10)"
+columns = [{ name = "h", type = "NUMBER(38,0)" }, { name = "m", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["25", "70", "70"],
+ ["0", "0", "3661"],
+ ["12", "-1", "0"],
+]
+query = "SELECT TIME_FROM_PARTS(h, m, x) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_hour"
+description = "NULL hour. Catches: NULL read as 0"
+columns = [{ name = "h", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["1"],
+]
+query = "SELECT TIME_FROM_PARTS(h, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_null_minute"
+description = "NULL minute. Catches: NULL read as 0"
+columns = [{ name = "m", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["1"],
+]
+query = "SELECT TIME_FROM_PARTS(0, m, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_null_second"
+description = "NULL second. Catches: NULL read as 0"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["1"],
+]
+query = "SELECT TIME_FROM_PARTS(0, 0, x) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_null_nanoseconds"
+description = "NULL nanoseconds. The result is formatted with TO_VARCHAR(..., 'HH24:MI:SS.FF9') because the connector returns TIME in microseconds and the session TIME_OUTPUT_FORMAT has no fraction. Catches: NULL read as 0"
+columns = [{ name = "n", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["1"],
+]
+query = "SELECT TO_VARCHAR(TIME_FROM_PARTS(0, 0, 0, n), 'HH24:MI:SS.FF9') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/time-slice.toml b/cases/translation/functions/time-slice.toml
new file mode 100644
index 000000000..736c7fdc1
--- /dev/null
+++ b/cases/translation/functions/time-slice.toml
@@ -0,0 +1,87 @@
+category = "translation"
+construct = "sf.functions.time-slice"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "The page's 4-month slice, and a 3-day slice. Catches: slices aligned to the year start or to the date itself, or END returning the last day inside the slice"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2019-02-28'"],
+ ["'2024-02-29'"],
+]
+query = "SELECT TIME_SLICE(d, 4, 'MONTH', 'START') AS ms, TIME_SLICE(d, 4, 'MONTH', 'END') AS me, TIME_SLICE(d, 3, 'DAY') AS d3 FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_timestamp"
+description = "Slices of 11 hours and 11 minutes; 2019-02-28 (epoch day 17955) is not a multiple of either slice, so epoch and day-start alignment differ. Catches: slices aligned to the day start for minutes, or the fraction kept"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2019-02-28 01:23:45.678'"],
+ ["'2019-02-28 12:34:56.789'"],
+]
+query = "SELECT TIME_SLICE(t, 11, 'HOUR') AS h11, TIME_SLICE(t, 11, 'minute') AS m11 FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_week"
+description = "Two-week slices, aligned to the week that holds 1970-01-01. Catches: week slices aligned to 1970-01-01 itself (a Thursday)"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-03-06'"],
+ ["'2024-03-13'"],
+]
+query = "SELECT TIME_SLICE(d, 2, 'WEEK') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_date_or_time_expr"
+description = "NULL date. Catches: NULL read as 1970-01-01"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-01-01'"],
+]
+query = "SELECT TIME_SLICE(d, 1, 'MONTH') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_null_slice_length"
+description = "NULL length. Catches: NULL read as 1"
+columns = [{ name = "n", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIME_SLICE('2024-05-05'::DATE, n, 'MONTH') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_pre_1970"
+description = "Dates before 1970, which the page says are valid. Catches: truncation toward 1970 instead of floor"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'1969-12-31'"],
+ ["'1955-06-01'"],
+]
+query = "SELECT TIME_SLICE(d, 15, 'YEAR') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t007_zero_length"
+compile_error = true
+description = "A slice length of 0, below the documented minimum of 1. Catches: a division by zero yielding NULL instead of an error"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-01-01'"],
+]
+query = "SELECT TIME_SLICE(d, 0, 'DAY') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
diff --git a/cases/translation/functions/timeadd.toml b/cases/translation/functions/timeadd.toml
new file mode 100644
index 000000000..6a038606e
--- /dev/null
+++ b/cases/translation/functions/timeadd.toml
@@ -0,0 +1,132 @@
+category = "translation"
+construct = "sf.functions.timeadd"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "Days, months and years added to dates, including negatives. Catches: value and expression swapped, or months added as 30 days"
+columns = [{ name = "n", type = "NUMBER(38,0)" }, { name = "d", type = "DATE" }]
+rows = [
+ ["45", "'2024-01-20'"],
+ ["-3", "'2024-05-31'"],
+]
+query = "SELECT TIMEADD(day, n, d) AS dd, TIMEADD(month, n, d) AS mm, TIMEADD(year, n, d) AS yy FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_date_hour"
+description = "Hours added to a DATE, which the page says gives a TIMESTAMP. Catches: a DATE result (the hours dropped) or an error"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-02-28'"],
+]
+query = "SELECT TIMEADD(hour, 30, d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp_ntz"
+description = "The part aliases ms, mi and mm on a timestamp. Catches: mm read as minutes (Snowflake: months), mi as months, or ms as microseconds"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-12-31 23:59:59.999'"],
+]
+query = "SELECT TIMEADD(ms, 2, t) AS ms, TIMEADD(mi, 1, t) AS mi, TIMEADD(mm, 1, t) AS mm FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_time"
+description = "Hours added to a TIME past midnight. Catches: an error or a timestamp instead of wrapping to 01:30"
+columns = [{ name = "t", type = "TIME" }]
+rows = [
+ ["'23:30:00'"],
+]
+query = "SELECT TIMEADD(hour, 2, t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_timestamp_tz"
+description = "A day added to a TIMESTAMP_TZ. Catches: the offset dropped or converted to UTC (the wall column shows the offset, which the instant comparison ignores)"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-31 23:30:00.250 -0800'"],
+]
+query = "SELECT TIMEADD(day, 1, t) AS r, CAST(TIMEADD(day, 1, t) AS TIMESTAMP_NTZ) AS wall FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t006_null_value"
+description = "NULL value. Catches: NULL read as 0 (the date unchanged)"
+columns = [{ name = "n", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["1"],
+]
+query = "SELECT TIMEADD(day, n, '2024-01-01'::DATE) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_null_date_or_time_expr"
+description = "NULL date. Catches: NULL read as a default date"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-01-01'"],
+]
+query = "SELECT TIMEADD(day, 1, d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t008_month_end"
+description = "Month and year steps from month ends and 29 February. Catches: overflow into the next month (2024-03-02) instead of clamping to 2024-02-29, or 2025-03-01 from 2024-02-29"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-01-31'"],
+ ["'2024-02-29'"],
+ ["'2023-01-31'"],
+]
+query = "SELECT TIMEADD(month, 1, d) AS m, TIMEADD(year, 1, d) AS y FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t009_fractional_value"
+description = "A fractional value: records how Snowflake rounds it. Catches: truncation (1 day) where Snowflake rounds 1.7 to 2, or the reverse"
+columns = [{ name = "n", type = "NUMBER(10,2)" }]
+rows = [
+ ["1.70"],
+ ["-1.50"],
+]
+query = "SELECT TIMEADD(day, n, '2024-01-01'::DATE) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t010_dst"
+description = "An hour added at 01:30 New York time on both DST change days, through the two-argument CONVERT_TIMEZONE of a UTC timestamp. Catches: wall-clock arithmetic in the zone (07:30Z instead of the elapsed 06:30Z on the autumn row) (the wall column shows the offset, which the instant comparison ignores)"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-03-10 06:30:00'"],
+ ["'2024-11-03 05:30:00'"],
+]
+query = "SELECT TIMEADD(hour, 1, CONVERT_TIMEZONE('America/New_York', t)) AS r, CAST(TIMEADD(hour, 1, CONVERT_TIMEZONE('America/New_York', t)) AS TIMESTAMP_NTZ) AS wall FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t011_time_day"
+compile_error = true
+description = "Days added to a TIME, which the page says is an error. Catches: a wrapped TIME instead of an error"
+columns = [{ name = "t", type = "TIME" }]
+rows = [
+ ["'10:00:00'"],
+]
+query = "SELECT TIMEADD(day, 1, t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
diff --git a/cases/translation/functions/timediff.toml b/cases/translation/functions/timediff.toml
new file mode 100644
index 000000000..305bab4e7
--- /dev/null
+++ b/cases/translation/functions/timediff.toml
@@ -0,0 +1,108 @@
+category = "translation"
+construct = "sf.functions.timediff"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "Year, month, day and week boundaries crossed by one day. Catches: elapsed whole units (0 years for 31 Dec to 1 Jan) instead of crossed boundaries, or the sign reversed"
+columns = [{ name = "a", type = "DATE" }, { name = "b", type = "DATE" }]
+rows = [
+ ["'2023-12-31'", "'2024-01-01'"],
+ ["'2024-01-31'", "'2024-02-01'"],
+ ["'2024-03-10'", "'2024-03-04'"],
+]
+query = "SELECT TIMEDIFF(year, a, b) AS y, TIMEDIFF(month, a, b) AS m, TIMEDIFF(day, a, b) AS d, TIMEDIFF(week, a, b) AS w FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_timestamp_ntz"
+description = "Hour, minute and millisecond counts. Catches: elapsed time (0 hours for 10:59 to 11:00) instead of crossed boundaries, or seconds counted in milliseconds"
+columns = [{ name = "a", type = "TIMESTAMP_NTZ" }, { name = "b", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-01-01 10:59:59.999'", "'2024-01-01 11:00:00.001'"],
+]
+query = "SELECT TIMEDIFF(hour, a, b) AS h, TIMEDIFF(second, a, b) AS s, TIMEDIFF(millisecond, a, b) AS ms FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_time"
+description = "Differences between TIMEs. Catches: an error for TIME arguments"
+columns = [{ name = "a", type = "TIME" }, { name = "b", type = "TIME" }]
+rows = [
+ ["'23:00:00'", "'01:30:00'"],
+]
+query = "SELECT TIMEDIFF(minute, a, b) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_mixed"
+description = "A DATE against a TIMESTAMP. Catches: an error, or the date read as end of day"
+columns = [{ name = "a", type = "DATE" }, { name = "b", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-01-01'", "'2024-01-01 05:00:00'"],
+]
+query = "SELECT TIMEDIFF(hour, a, b) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_null_date_or_time_expr1"
+description = "NULL first expression. Catches: NULL read as a default date"
+columns = [{ name = "a", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-01-01'"],
+]
+query = "SELECT TIMEDIFF(day, a, '2024-01-10'::DATE) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_null_date_or_time_expr2"
+description = "NULL second expression. Catches: NULL read as a default date"
+columns = [{ name = "b", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-01-01'"],
+]
+query = "SELECT TIMEDIFF(day, '2024-01-10'::DATE, b) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_leap"
+description = "Day counts across 29 February. Catches: a 365-day year"
+columns = [{ name = "a", type = "DATE" }, { name = "b", type = "DATE" }]
+rows = [
+ ["'2024-02-28'", "'2024-03-01'"],
+ ["'2023-02-28'", "'2024-02-28'"],
+]
+query = "SELECT TIMEDIFF(day, a, b) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t008_dst"
+description = "Hours between TIMESTAMP_TZ values across a DST change. Catches: wall-clock difference (2) instead of elapsed hours (1)"
+columns = [{ name = "a", type = "TIMESTAMP_TZ" }, { name = "b", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-03-10 01:00:00 -0500'", "'2024-03-10 03:00:00 -0400'"],
+ ["'2024-11-03 01:00:00 -0400'", "'2024-11-03 01:00:00 -0500'"],
+]
+query = "SELECT TIMEDIFF(hour, a, b) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t009_negative"
+description = "A later first argument. Catches: an absolute value"
+columns = [{ name = "a", type = "DATE" }, { name = "b", type = "DATE" }]
+rows = [
+ ["'2024-06-01'", "'2023-01-15'"],
+]
+query = "SELECT TIMEDIFF(month, a, b) AS m, TIMEDIFF(day, a, b) AS d FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
diff --git a/cases/translation/functions/timestamp-from-parts.timestamp-from-parts.toml b/cases/translation/functions/timestamp-from-parts.timestamp-from-parts.toml
new file mode 100644
index 000000000..c2766899b
--- /dev/null
+++ b/cases/translation/functions/timestamp-from-parts.timestamp-from-parts.toml
@@ -0,0 +1,185 @@
+category = "translation"
+construct = "sf.functions.timestamp-from-parts.timestamp-from-parts"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_number"
+description = "TIMESTAMP_FROM_PARTS of a normal timestamp. Catches: parts in the wrong order, or a DATE/other timestamp type returned"
+columns = [{ name = "y", type = "NUMBER(38,0)" }, { name = "mo", type = "NUMBER(38,0)" }, { name = "d", type = "NUMBER(38,0)" }, { name = "h", type = "NUMBER(38,0)" }, { name = "mi", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["2024", "2", "29", "13", "5", "9"],
+]
+query = "SELECT TIMESTAMP_FROM_PARTS(y, mo, d, h, mi, x) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_nanosecond"
+description = "A nanosecond part. Catches: nanoseconds read as milliseconds"
+columns = [{ name = "n", type = "NUMBER(38,0)" }]
+rows = [
+ ["123456789"],
+ ["1000"],
+]
+query = "SELECT TIMESTAMP_FROM_PARTS(2024, 1, 1, 0, 0, 0, n) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_carry"
+description = "Parts beyond their ranges, which carry. Catches: an error instead of carrying across a year end, or a negative part clamped instead of carrying backwards"
+columns = [{ name = "h", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["24", "0"],
+ ["23", "3600"],
+ ["0", "-1"],
+]
+query = "SELECT TIMESTAMP_FROM_PARTS(2024, 12, 31, h, 0, x) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_year"
+description = "NULL year. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_FROM_PARTS(v, 1, 1, 0, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_null_month"
+description = "NULL month. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_FROM_PARTS(2024, v, 1, 0, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_null_day"
+description = "NULL day. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_FROM_PARTS(2024, 1, v, 0, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_null_hour"
+description = "NULL hour. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_FROM_PARTS(2024, 1, 1, v, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t008_null_minute"
+description = "NULL minute. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_FROM_PARTS(2024, 1, 1, 0, v, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t009_null_second"
+description = "NULL second. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_FROM_PARTS(2024, 1, 1, 0, 0, v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t010_null_nanosecond"
+description = "NULL nanosecond. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_FROM_PARTS(2024, 1, 1, 0, 0, 0, v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t011_leap"
+description = "29 February in a common year, which carries. Catches: an error instead of 1 March"
+columns = [{ name = "y", type = "NUMBER(38,0)" }]
+rows = [
+ ["2023"],
+ ["2024"],
+]
+query = "SELECT TIMESTAMP_FROM_PARTS(y, 2, 29, 12, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t012_time_zone"
+compile_error = true
+description = "An explicit time zone. Under the utc-v1 TIMESTAMP_TYPE_MAPPING (TIMESTAMP_NTZ) TIMESTAMP_FROM_PARTS resolves to the NTZ variant, which takes no time zone: Snowflake refuses the call (1044). Catches: the zone honoured (a TIMESTAMP_TZ value returned) where Snowflake refuses the call under the session mapping"
+columns = [{ name = "z", type = "VARCHAR" }]
+rows = [
+ ["'America/Los_Angeles'"],
+ ["'Asia/Kolkata'"],
+]
+query = "SELECT TIMESTAMP_FROM_PARTS(2024, 7, 1, 9, 30, 0, 0, z) AS r, CAST(TIMESTAMP_FROM_PARTS(2024, 7, 1, 9, 30, 0, 0, z) AS TIMESTAMP_NTZ) AS wall FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t013_null_time_zone"
+compile_error = true
+description = "A NULL time zone. Under the utc-v1 TIMESTAMP_TYPE_MAPPING (TIMESTAMP_NTZ) TIMESTAMP_FROM_PARTS resolves to the NTZ variant, which takes no time zone: Snowflake refuses the call (1044). Catches: the zone honoured (a TIMESTAMP_TZ value returned) where Snowflake refuses the call under the session mapping"
+columns = [{ name = "z", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'UTC'"],
+]
+query = "SELECT TIMESTAMP_FROM_PARTS(2024, 1, 1, 0, 0, 0, 0, z) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t014_dst"
+compile_error = true
+description = "A DST zone. Under the utc-v1 TIMESTAMP_TYPE_MAPPING (TIMESTAMP_NTZ) TIMESTAMP_FROM_PARTS resolves to the NTZ variant, which takes no time zone: Snowflake refuses the call (1044). Catches: the zone honoured (a TIMESTAMP_TZ value returned) where Snowflake refuses the call under the session mapping"
+columns = [{ name = "d", type = "NUMBER(38,0)" }, { name = "h", type = "NUMBER(38,0)" }, { name = "mo", type = "NUMBER(38,0)" }]
+rows = [
+ ["10", "2", "3"],
+ ["3", "1", "11"],
+]
+query = "SELECT TIMESTAMP_FROM_PARTS(2024, mo, d, h, 30, 0, 0, 'America/New_York') AS r, CAST(TIMESTAMP_FROM_PARTS(2024, mo, d, h, 30, 0, 0, 'America/New_York') AS TIMESTAMP_NTZ) AS wall FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t015_date_time"
+description = "The date_expr, time_expr form, which the page allows under the NTZ mapping. Catches: an error, or the time's fraction dropped"
+columns = [{ name = "d", type = "DATE" }, { name = "t", type = "TIME" }]
+rows = [
+ ["'2024-02-29'", "'23:59:59.123'"],
+]
+query = "SELECT TIMESTAMP_FROM_PARTS(d, t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
diff --git a/cases/translation/functions/timestamp-from-parts.timestamp-ltz-from-parts.toml b/cases/translation/functions/timestamp-from-parts.timestamp-ltz-from-parts.toml
new file mode 100644
index 000000000..750fe9311
--- /dev/null
+++ b/cases/translation/functions/timestamp-from-parts.timestamp-ltz-from-parts.toml
@@ -0,0 +1,135 @@
+category = "translation"
+construct = "sf.functions.timestamp-from-parts.timestamp-ltz-from-parts"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_number"
+description = "TIMESTAMP_LTZ_FROM_PARTS of a normal timestamp. Catches: parts in the wrong order, or a DATE/other timestamp type returned"
+columns = [{ name = "y", type = "NUMBER(38,0)" }, { name = "mo", type = "NUMBER(38,0)" }, { name = "d", type = "NUMBER(38,0)" }, { name = "h", type = "NUMBER(38,0)" }, { name = "mi", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["2024", "2", "29", "13", "5", "9"],
+]
+query = "SELECT TIMESTAMP_LTZ_FROM_PARTS(y, mo, d, h, mi, x) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_nanosecond"
+description = "A nanosecond part. Catches: nanoseconds read as milliseconds"
+columns = [{ name = "n", type = "NUMBER(38,0)" }]
+rows = [
+ ["123456789"],
+ ["1000"],
+]
+query = "SELECT TIMESTAMP_LTZ_FROM_PARTS(2024, 1, 1, 0, 0, 0, n) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_carry"
+description = "Parts beyond their ranges, which carry. Catches: an error instead of carrying across a year end, or a negative part clamped instead of carrying backwards"
+columns = [{ name = "h", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["24", "0"],
+ ["23", "3600"],
+ ["0", "-1"],
+]
+query = "SELECT TIMESTAMP_LTZ_FROM_PARTS(2024, 12, 31, h, 0, x) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_year"
+description = "NULL year. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_LTZ_FROM_PARTS(v, 1, 1, 0, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_null_month"
+description = "NULL month. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_LTZ_FROM_PARTS(2024, v, 1, 0, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_null_day"
+description = "NULL day. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_LTZ_FROM_PARTS(2024, 1, v, 0, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_null_hour"
+description = "NULL hour. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_LTZ_FROM_PARTS(2024, 1, 1, v, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t008_null_minute"
+description = "NULL minute. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_LTZ_FROM_PARTS(2024, 1, 1, 0, v, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t009_null_second"
+description = "NULL second. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_LTZ_FROM_PARTS(2024, 1, 1, 0, 0, v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t010_null_nanosecond"
+description = "NULL nanosecond. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_LTZ_FROM_PARTS(2024, 1, 1, 0, 0, 0, v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t011_leap"
+description = "29 February in a common year, which carries. Catches: an error instead of 1 March"
+columns = [{ name = "y", type = "NUMBER(38,0)" }]
+rows = [
+ ["2023"],
+ ["2024"],
+]
+query = "SELECT TIMESTAMP_LTZ_FROM_PARTS(y, 2, 29, 12, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
diff --git a/cases/translation/functions/timestamp-from-parts.timestamp-ntz-from-parts.toml b/cases/translation/functions/timestamp-from-parts.timestamp-ntz-from-parts.toml
new file mode 100644
index 000000000..e67ee9f21
--- /dev/null
+++ b/cases/translation/functions/timestamp-from-parts.timestamp-ntz-from-parts.toml
@@ -0,0 +1,147 @@
+category = "translation"
+construct = "sf.functions.timestamp-from-parts.timestamp-ntz-from-parts"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_number"
+description = "TIMESTAMP_NTZ_FROM_PARTS of a normal timestamp. Catches: parts in the wrong order, or a DATE/other timestamp type returned"
+columns = [{ name = "y", type = "NUMBER(38,0)" }, { name = "mo", type = "NUMBER(38,0)" }, { name = "d", type = "NUMBER(38,0)" }, { name = "h", type = "NUMBER(38,0)" }, { name = "mi", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["2024", "2", "29", "13", "5", "9"],
+]
+query = "SELECT TIMESTAMP_NTZ_FROM_PARTS(y, mo, d, h, mi, x) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_nanosecond"
+description = "A nanosecond part. Catches: nanoseconds read as milliseconds"
+columns = [{ name = "n", type = "NUMBER(38,0)" }]
+rows = [
+ ["123456789"],
+ ["1000"],
+]
+query = "SELECT TIMESTAMP_NTZ_FROM_PARTS(2024, 1, 1, 0, 0, 0, n) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_carry"
+description = "Parts beyond their ranges, which carry. Catches: an error instead of carrying across a year end, or a negative part clamped instead of carrying backwards"
+columns = [{ name = "h", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["24", "0"],
+ ["23", "3600"],
+ ["0", "-1"],
+]
+query = "SELECT TIMESTAMP_NTZ_FROM_PARTS(2024, 12, 31, h, 0, x) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_year"
+description = "NULL year. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_NTZ_FROM_PARTS(v, 1, 1, 0, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+expected_divergence = "timestamp_range"
+tags = ["null"]
+
+[[case]]
+id = "t005_null_month"
+description = "NULL month. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_NTZ_FROM_PARTS(2024, v, 1, 0, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_null_day"
+description = "NULL day. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_NTZ_FROM_PARTS(2024, 1, v, 0, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_null_hour"
+description = "NULL hour. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_NTZ_FROM_PARTS(2024, 1, 1, v, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t008_null_minute"
+description = "NULL minute. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_NTZ_FROM_PARTS(2024, 1, 1, 0, v, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t009_null_second"
+description = "NULL second. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_NTZ_FROM_PARTS(2024, 1, 1, 0, 0, v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t010_null_nanosecond"
+description = "NULL nanosecond. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_NTZ_FROM_PARTS(2024, 1, 1, 0, 0, 0, v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t011_leap"
+description = "29 February in a common year, which carries. Catches: an error instead of 1 March"
+columns = [{ name = "y", type = "NUMBER(38,0)" }]
+rows = [
+ ["2023"],
+ ["2024"],
+]
+query = "SELECT TIMESTAMP_NTZ_FROM_PARTS(y, 2, 29, 12, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t012_date_time"
+description = "The date_expr, time_expr form, which the page allows for TIMESTAMP_NTZ_FROM_PARTS. Catches: an error, or the time's fraction dropped"
+columns = [{ name = "d", type = "DATE" }, { name = "t", type = "TIME" }]
+rows = [
+ ["'2024-02-29'", "'23:59:59.123'"],
+]
+query = "SELECT TIMESTAMP_NTZ_FROM_PARTS(d, t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
diff --git a/cases/translation/functions/timestamp-from-parts.timestamp-tz-from-parts.toml b/cases/translation/functions/timestamp-from-parts.timestamp-tz-from-parts.toml
new file mode 100644
index 000000000..cb1b21554
--- /dev/null
+++ b/cases/translation/functions/timestamp-from-parts.timestamp-tz-from-parts.toml
@@ -0,0 +1,171 @@
+category = "translation"
+construct = "sf.functions.timestamp-from-parts.timestamp-tz-from-parts"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_number"
+description = "TIMESTAMP_TZ_FROM_PARTS of a normal timestamp. Catches: parts in the wrong order, or a DATE/other timestamp type returned"
+columns = [{ name = "y", type = "NUMBER(38,0)" }, { name = "mo", type = "NUMBER(38,0)" }, { name = "d", type = "NUMBER(38,0)" }, { name = "h", type = "NUMBER(38,0)" }, { name = "mi", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["2024", "2", "29", "13", "5", "9"],
+]
+query = "SELECT TIMESTAMP_TZ_FROM_PARTS(y, mo, d, h, mi, x) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_nanosecond"
+description = "A nanosecond part. Catches: nanoseconds read as milliseconds"
+columns = [{ name = "n", type = "NUMBER(38,0)" }]
+rows = [
+ ["123456789"],
+ ["1000"],
+]
+query = "SELECT TIMESTAMP_TZ_FROM_PARTS(2024, 1, 1, 0, 0, 0, n) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_carry"
+description = "Parts beyond their ranges, which carry. Catches: an error instead of carrying across a year end, or a negative part clamped instead of carrying backwards"
+columns = [{ name = "h", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["24", "0"],
+ ["23", "3600"],
+ ["0", "-1"],
+]
+query = "SELECT TIMESTAMP_TZ_FROM_PARTS(2024, 12, 31, h, 0, x) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_year"
+description = "NULL year. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_TZ_FROM_PARTS(v, 1, 1, 0, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_null_month"
+description = "NULL month. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_TZ_FROM_PARTS(2024, v, 1, 0, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_null_day"
+description = "NULL day. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_TZ_FROM_PARTS(2024, 1, v, 0, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_null_hour"
+description = "NULL hour. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_TZ_FROM_PARTS(2024, 1, 1, v, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t008_null_minute"
+description = "NULL minute. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_TZ_FROM_PARTS(2024, 1, 1, 0, v, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t009_null_second"
+description = "NULL second. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_TZ_FROM_PARTS(2024, 1, 1, 0, 0, v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t010_null_nanosecond"
+description = "NULL nanosecond. Catches: NULL read as 0 or 1"
+columns = [{ name = "v", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["2"],
+]
+query = "SELECT TIMESTAMP_TZ_FROM_PARTS(2024, 1, 1, 0, 0, 0, v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t011_leap"
+description = "29 February in a common year, which carries. Catches: an error instead of 1 March"
+columns = [{ name = "y", type = "NUMBER(38,0)" }]
+rows = [
+ ["2023"],
+ ["2024"],
+]
+query = "SELECT TIMESTAMP_TZ_FROM_PARTS(y, 2, 29, 12, 0, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t012_time_zone"
+description = "An explicit time zone. Catches: the zone ignored (UTC offset), or the wall clock shifted instead of the offset set (the wall column shows the offset, which the instant comparison ignores)"
+columns = [{ name = "z", type = "VARCHAR" }]
+rows = [
+ ["'America/Los_Angeles'"],
+ ["'Asia/Kolkata'"],
+]
+query = "SELECT TIMESTAMP_TZ_FROM_PARTS(2024, 7, 1, 9, 30, 0, 0, z) AS r, CAST(TIMESTAMP_TZ_FROM_PARTS(2024, 7, 1, 9, 30, 0, 0, z) AS TIMESTAMP_NTZ) AS wall FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t013_null_time_zone"
+description = "NULL time zone. Catches: NULL read as the session zone"
+columns = [{ name = "z", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'UTC'"],
+]
+query = "SELECT TIMESTAMP_TZ_FROM_PARTS(2024, 1, 1, 0, 0, 0, 0, z) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t014_dst"
+description = "Wall clocks inside the spring gap and the autumn overlap. Catches: a gap time kept as is, or the overlap resolved to the other offset (the wall column shows the offset, which the instant comparison ignores)"
+columns = [{ name = "d", type = "NUMBER(38,0)" }, { name = "h", type = "NUMBER(38,0)" }, { name = "mo", type = "NUMBER(38,0)" }]
+rows = [
+ ["10", "2", "3"],
+ ["3", "1", "11"],
+]
+query = "SELECT TIMESTAMP_TZ_FROM_PARTS(2024, mo, d, h, 30, 0, 0, 'America/New_York') AS r, CAST(TIMESTAMP_TZ_FROM_PARTS(2024, mo, d, h, 30, 0, 0, 'America/New_York') AS TIMESTAMP_NTZ) AS wall FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
diff --git a/cases/translation/functions/timestampadd.toml b/cases/translation/functions/timestampadd.toml
new file mode 100644
index 000000000..928bc9313
--- /dev/null
+++ b/cases/translation/functions/timestampadd.toml
@@ -0,0 +1,132 @@
+category = "translation"
+construct = "sf.functions.timestampadd"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "Days, months and years added to dates, including negatives. Catches: value and expression swapped, or months added as 30 days"
+columns = [{ name = "n", type = "NUMBER(38,0)" }, { name = "d", type = "DATE" }]
+rows = [
+ ["45", "'2024-01-20'"],
+ ["-3", "'2024-05-31'"],
+]
+query = "SELECT TIMESTAMPADD(day, n, d) AS dd, TIMESTAMPADD(month, n, d) AS mm, TIMESTAMPADD(year, n, d) AS yy FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_date_hour"
+description = "Hours added to a DATE, which the page says gives a TIMESTAMP. Catches: a DATE result (the hours dropped) or an error"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-02-28'"],
+]
+query = "SELECT TIMESTAMPADD(hour, 30, d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp_ntz"
+description = "The part aliases ms, mi and mm on a timestamp. Catches: mm read as minutes (Snowflake: months), mi as months, or ms as microseconds"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-12-31 23:59:59.999'"],
+]
+query = "SELECT TIMESTAMPADD(ms, 2, t) AS ms, TIMESTAMPADD(mi, 1, t) AS mi, TIMESTAMPADD(mm, 1, t) AS mm FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_time"
+description = "Hours added to a TIME past midnight. Catches: an error or a timestamp instead of wrapping to 01:30"
+columns = [{ name = "t", type = "TIME" }]
+rows = [
+ ["'23:30:00'"],
+]
+query = "SELECT TIMESTAMPADD(hour, 2, t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_timestamp_tz"
+description = "A day added to a TIMESTAMP_TZ. Catches: the offset dropped or converted to UTC (the wall column shows the offset, which the instant comparison ignores)"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-31 23:30:00.250 -0800'"],
+]
+query = "SELECT TIMESTAMPADD(day, 1, t) AS r, CAST(TIMESTAMPADD(day, 1, t) AS TIMESTAMP_NTZ) AS wall FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t006_null_value"
+description = "NULL value. Catches: NULL read as 0 (the date unchanged)"
+columns = [{ name = "n", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["1"],
+]
+query = "SELECT TIMESTAMPADD(day, n, '2024-01-01'::DATE) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_null_date_or_time_expr"
+description = "NULL date. Catches: NULL read as a default date"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-01-01'"],
+]
+query = "SELECT TIMESTAMPADD(day, 1, d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t008_month_end"
+description = "Month and year steps from month ends and 29 February. Catches: overflow into the next month (2024-03-02) instead of clamping to 2024-02-29, or 2025-03-01 from 2024-02-29"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-01-31'"],
+ ["'2024-02-29'"],
+ ["'2023-01-31'"],
+]
+query = "SELECT TIMESTAMPADD(month, 1, d) AS m, TIMESTAMPADD(year, 1, d) AS y FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t009_fractional_value"
+description = "A fractional value: records how Snowflake rounds it. Catches: truncation (1 day) where Snowflake rounds 1.7 to 2, or the reverse"
+columns = [{ name = "n", type = "NUMBER(10,2)" }]
+rows = [
+ ["1.70"],
+ ["-1.50"],
+]
+query = "SELECT TIMESTAMPADD(day, n, '2024-01-01'::DATE) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t010_dst"
+description = "An hour added at 01:30 New York time on both DST change days, through the two-argument CONVERT_TIMEZONE of a UTC timestamp. Catches: wall-clock arithmetic in the zone (07:30Z instead of the elapsed 06:30Z on the autumn row) (the wall column shows the offset, which the instant comparison ignores)"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-03-10 06:30:00'"],
+ ["'2024-11-03 05:30:00'"],
+]
+query = "SELECT TIMESTAMPADD(hour, 1, CONVERT_TIMEZONE('America/New_York', t)) AS r, CAST(TIMESTAMPADD(hour, 1, CONVERT_TIMEZONE('America/New_York', t)) AS TIMESTAMP_NTZ) AS wall FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t011_time_day"
+compile_error = true
+description = "Days added to a TIME, which the page says is an error. Catches: a wrapped TIME instead of an error"
+columns = [{ name = "t", type = "TIME" }]
+rows = [
+ ["'10:00:00'"],
+]
+query = "SELECT TIMESTAMPADD(day, 1, t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
diff --git a/cases/translation/functions/timestampdiff.toml b/cases/translation/functions/timestampdiff.toml
new file mode 100644
index 000000000..52315ce99
--- /dev/null
+++ b/cases/translation/functions/timestampdiff.toml
@@ -0,0 +1,108 @@
+category = "translation"
+construct = "sf.functions.timestampdiff"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "Year, month, day and week boundaries crossed by one day. Catches: elapsed whole units (0 years for 31 Dec to 1 Jan) instead of crossed boundaries, or the sign reversed"
+columns = [{ name = "a", type = "DATE" }, { name = "b", type = "DATE" }]
+rows = [
+ ["'2023-12-31'", "'2024-01-01'"],
+ ["'2024-01-31'", "'2024-02-01'"],
+ ["'2024-03-10'", "'2024-03-04'"],
+]
+query = "SELECT TIMESTAMPDIFF(year, a, b) AS y, TIMESTAMPDIFF(month, a, b) AS m, TIMESTAMPDIFF(day, a, b) AS d, TIMESTAMPDIFF(week, a, b) AS w FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_timestamp_ntz"
+description = "Hour, minute and millisecond counts. Catches: elapsed time (0 hours for 10:59 to 11:00) instead of crossed boundaries, or seconds counted in milliseconds"
+columns = [{ name = "a", type = "TIMESTAMP_NTZ" }, { name = "b", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-01-01 10:59:59.999'", "'2024-01-01 11:00:00.001'"],
+]
+query = "SELECT TIMESTAMPDIFF(hour, a, b) AS h, TIMESTAMPDIFF(second, a, b) AS s, TIMESTAMPDIFF(millisecond, a, b) AS ms FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_time"
+description = "Differences between TIMEs. Catches: an error for TIME arguments"
+columns = [{ name = "a", type = "TIME" }, { name = "b", type = "TIME" }]
+rows = [
+ ["'23:00:00'", "'01:30:00'"],
+]
+query = "SELECT TIMESTAMPDIFF(minute, a, b) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_mixed"
+description = "A DATE against a TIMESTAMP. Catches: an error, or the date read as end of day"
+columns = [{ name = "a", type = "DATE" }, { name = "b", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-01-01'", "'2024-01-01 05:00:00'"],
+]
+query = "SELECT TIMESTAMPDIFF(hour, a, b) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_null_date_or_time_expr1"
+description = "NULL first expression. Catches: NULL read as a default date"
+columns = [{ name = "a", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-01-01'"],
+]
+query = "SELECT TIMESTAMPDIFF(day, a, '2024-01-10'::DATE) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_null_date_or_time_expr2"
+description = "NULL second expression. Catches: NULL read as a default date"
+columns = [{ name = "b", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-01-01'"],
+]
+query = "SELECT TIMESTAMPDIFF(day, '2024-01-10'::DATE, b) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_leap"
+description = "Day counts across 29 February. Catches: a 365-day year"
+columns = [{ name = "a", type = "DATE" }, { name = "b", type = "DATE" }]
+rows = [
+ ["'2024-02-28'", "'2024-03-01'"],
+ ["'2023-02-28'", "'2024-02-28'"],
+]
+query = "SELECT TIMESTAMPDIFF(day, a, b) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t008_dst"
+description = "Hours between TIMESTAMP_TZ values across a DST change. Catches: wall-clock difference (2) instead of elapsed hours (1)"
+columns = [{ name = "a", type = "TIMESTAMP_TZ" }, { name = "b", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-03-10 01:00:00 -0500'", "'2024-03-10 03:00:00 -0400'"],
+ ["'2024-11-03 01:00:00 -0400'", "'2024-11-03 01:00:00 -0500'"],
+]
+query = "SELECT TIMESTAMPDIFF(hour, a, b) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t009_negative"
+description = "A later first argument. Catches: an absolute value"
+columns = [{ name = "a", type = "DATE" }, { name = "b", type = "DATE" }]
+rows = [
+ ["'2024-06-01'", "'2023-01-15'"],
+]
+query = "SELECT TIMESTAMPDIFF(month, a, b) AS m, TIMESTAMPDIFF(day, a, b) AS d FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
diff --git a/cases/translation/functions/to-array.toml b/cases/translation/functions/to-array.toml
new file mode 100644
index 000000000..9f29f30da
--- /dev/null
+++ b/cases/translation/functions/to-array.toml
@@ -0,0 +1,51 @@
+category = "translation"
+construct = "sf.functions.to-array"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_array"
+description = "Arrays, including an empty one. Catches: an array wrapped in another array"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'[1,2]'"],
+ ["'[]'"],
+]
+query = "SELECT TO_ARRAY(PARSE_JSON(a)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_scalar"
+description = "A string with a comma, a number and an object. Catches: the string split on the comma, or the object turned into its values"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'\"a,b\"'"],
+ ["'1.5'"],
+ ["'{\"a\":1}'"],
+]
+query = "SELECT TO_ARRAY(PARSE_JSON(a)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_typed"
+description = "Typed values that are not VARIANT. Catches: the text parsed as JSON, or the decimal scale lost"
+columns = [{ name = "k", type = "NUMBER(10,2)" }, { name = "v", type = "VARCHAR" }]
+rows = [
+ ["2.50", "'[1]'"],
+]
+query = "SELECT TO_ARRAY(k) AS k, TO_ARRAY(v) AS v FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_expr"
+description = "A SQL NULL and a JSON null, both of which the page says give NULL. Catches: a one-element array holding the null"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'null'"],
+]
+query = "SELECT TO_ARRAY(PARSE_JSON(a)) AS r, TO_ARRAY(PARSE_JSON(a)) IS NULL AS n FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/to-binary.toml b/cases/translation/functions/to-binary.toml
new file mode 100644
index 000000000..b9e686d48
--- /dev/null
+++ b/cases/translation/functions/to-binary.toml
@@ -0,0 +1,47 @@
+category = "translation"
+construct = "sf.functions.to-binary"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_formats"
+description = "The same bytes from HEX in lower case, from BASE64 and from UTF-8 text. Catches: the HEX digits read case-sensitively, or the text bytes taken as HEX"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'4a4B'"],
+]
+query = "SELECT TO_BINARY(v) AS hex, TO_BINARY('Sks=', 'BASE64') AS b64, TO_BINARY('JK', 'UTF-8') AS utf8 FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_empty"
+description = "An empty string; the page does not say; the recording decides whether it gives empty bytes or NULL. Catches: empty bytes where Snowflake returns NULL, or the reverse"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["''"],
+]
+query = "SELECT TO_BINARY(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary", "empty"]
+
+[[case]]
+id = "t003_invalid_hex"
+description = "Text that is not hexadecimal under the default format. Catches: NULL returned, or the text read as UTF-8"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'xyz'"],
+]
+query = "SELECT TO_BINARY(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t004_null_string_expr"
+description = "A NULL string. Catches: empty bytes returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_BINARY(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/to-boolean.toml b/cases/translation/functions/to-boolean.toml
new file mode 100644
index 000000000..9be997902
--- /dev/null
+++ b/cases/translation/functions/to-boolean.toml
@@ -0,0 +1,66 @@
+category = "translation"
+construct = "sf.functions.to-boolean"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_strings"
+description = "Accepted spellings in mixed case. Catches: only true and false accepted, or the comparison case-sensitive"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'YES'"],
+ ["'Off'"],
+ ["'t'"],
+ ["'n'"],
+ ["'1'"],
+ ["'0'"],
+]
+query = "SELECT TO_BOOLEAN(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_numbers"
+description = "Zero, a negative fraction and a large number. Catches: only 1 read as TRUE, or a fraction rounded to 0 first"
+columns = [{ name = "x", type = "NUMBER(10,3)" }]
+rows = [
+ ["0"],
+ ["-0.500"],
+ ["2"],
+]
+query = "SELECT TO_BOOLEAN(x) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_invalid_string"
+description = "A string not in either list, which the page says is an error. Catches: NULL or FALSE returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'maybe'"],
+]
+query = "SELECT TO_BOOLEAN(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t004_nan"
+compile_error = true
+description = "A FLOAT NaN, which the page says is an error; Snowflake refuses the FLOAT argument when compiling. Catches: the FLOAT accepted and NaN read as TRUE, as for any non-zero number"
+columns = [{ name = "f", type = "DOUBLE" }]
+rows = [
+ ["'NaN'"],
+]
+query = "SELECT TO_BOOLEAN(f) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t005_null_string_or_numeric_expr"
+description = "A NULL, which the page says gives NULL without an error. Catches: FALSE returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_BOOLEAN(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/to-char.to-char.toml b/cases/translation/functions/to-char.to-char.toml
new file mode 100644
index 000000000..15f2ba8b0
--- /dev/null
+++ b/cases/translation/functions/to-char.to-char.toml
@@ -0,0 +1,60 @@
+category = "translation"
+construct = "sf.functions.to-char.to-char"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_number_format"
+description = "Numbers with a group separator and two fixed decimals, including a negative and a value that rounds. Catches: the format ignored, or the third decimal truncated"
+columns = [{ name = "x", type = "NUMBER(10,3)" }]
+rows = [
+ ["1234.567"],
+ ["-5.005"],
+]
+query = "SELECT TO_CHAR(x, '9,999.99') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_date_format"
+description = "A date with a month-name format, and without a format (the session DATE_OUTPUT_FORMAT). Catches: the format ignored, or a numeric month"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-02-29'"],
+]
+query = "SELECT TO_CHAR(d, 'DD-MON-YYYY') AS fmt, TO_CHAR(d) AS dflt FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_binary_format"
+description = "Bytes written as BASE64 and as HEX. Catches: the format ignored (HEX for both)"
+columns = [{ name = "b", type = "BINARY" }]
+rows = [
+ ["'4A4B'"],
+]
+query = "SELECT TO_CHAR(b, 'BASE64') AS b64, TO_CHAR(b, 'HEX') AS hex FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_variant"
+description = "A VARIANT string, which the page keeps as is, and a JSON null, which becomes the text null. Catches: the string written with JSON quotes, or the JSON null returned as SQL NULL"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'\"a b\"'"],
+ ["'null'"],
+]
+query = "SELECT TO_CHAR(PARSE_JSON(v)) AS r, TO_CHAR(PARSE_JSON(v)) IS NULL AS n FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_null_expr"
+description = "A NULL value. Catches: the text null returned"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_CHAR(x) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/to-char.to-varchar.toml b/cases/translation/functions/to-char.to-varchar.toml
new file mode 100644
index 000000000..83e9db015
--- /dev/null
+++ b/cases/translation/functions/to-char.to-varchar.toml
@@ -0,0 +1,60 @@
+category = "translation"
+construct = "sf.functions.to-char.to-varchar"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_number_format"
+description = "Numbers with a group separator and two fixed decimals, including a negative and a value that rounds. Catches: the format ignored, or the third decimal truncated"
+columns = [{ name = "x", type = "NUMBER(10,3)" }]
+rows = [
+ ["1234.567"],
+ ["-5.005"],
+]
+query = "SELECT TO_VARCHAR(x, '9,999.99') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_date_format"
+description = "A date with a month-name format, and without a format (the session DATE_OUTPUT_FORMAT). Catches: the format ignored, or a numeric month"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-02-29'"],
+]
+query = "SELECT TO_VARCHAR(d, 'DD-MON-YYYY') AS fmt, TO_VARCHAR(d) AS dflt FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_binary_format"
+description = "Bytes written as BASE64 and as HEX. Catches: the format ignored (HEX for both)"
+columns = [{ name = "b", type = "BINARY" }]
+rows = [
+ ["'4A4B'"],
+]
+query = "SELECT TO_VARCHAR(b, 'BASE64') AS b64, TO_VARCHAR(b, 'HEX') AS hex FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_variant"
+description = "A VARIANT string, which the page keeps as is, and a JSON null, which becomes the text null. Catches: the string written with JSON quotes, or the JSON null returned as SQL NULL"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'\"a b\"'"],
+ ["'null'"],
+]
+query = "SELECT TO_VARCHAR(PARSE_JSON(v)) AS r, TO_VARCHAR(PARSE_JSON(v)) IS NULL AS n FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_null_expr"
+description = "A NULL value. Catches: the text null returned"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_VARCHAR(x) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/to-date.date.toml b/cases/translation/functions/to-date.date.toml
new file mode 100644
index 000000000..66f638453
--- /dev/null
+++ b/cases/translation/functions/to-date.date.toml
@@ -0,0 +1,109 @@
+category = "translation"
+construct = "sf.functions.to-date.date"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_varchar"
+description = "DATE of ISO strings. Catches: a timestamp result, or month and day swapped"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'2024-01-31'"],
+ ["'1999-12-01'"],
+]
+query = "SELECT DATE(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_format"
+description = "An explicit day-first format. Catches: the format ignored (2024-01-12, or an error on 31/01/2024)"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'01/12/2024'"],
+ ["'31/01/2024'"],
+]
+query = "SELECT DATE(a, 'DD/MM/YYYY') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp"
+description = "A timestamp late in the day. Catches: the date rounded to the next day"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-02-29 23:59:59.999'"],
+]
+query = "SELECT DATE(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_timestamp_tz"
+description = "A TIMESTAMP_TZ whose UTC date is the next day. Catches: the date of the UTC instant (2025-01-01)"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-31 23:30:00.250 -0800'"],
+]
+query = "SELECT DATE(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_integer_string"
+description = "Integer strings of seconds and milliseconds. Catches: every integer read as seconds, or as days since the epoch"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'1700000000'"],
+ ["'1700000000123'"],
+ ["'86399'"],
+]
+query = "SELECT DATE(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t006_null_string_expr"
+description = "NULL string. Catches: NULL read as an empty string (an error)"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'2024-01-01'"],
+]
+query = "SELECT DATE(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_null_format"
+description = "NULL format. Catches: NULL read as AUTO"
+columns = [{ name = "f", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'YYYY-MM-DD'"],
+]
+query = "SELECT DATE('2024-02-03', f) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t008_leap"
+description = "29 February in a leap year. Catches: a rejected leap day"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'2024-02-29'"],
+ ["'2000-02-29'"],
+]
+query = "SELECT DATE(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t009_not_a_date"
+description = "29 February in a common year. Catches: a carry to 1 March or NULL instead of an error"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'2023-02-29'"],
+]
+query = "SELECT DATE(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
diff --git a/cases/translation/functions/to-date.to-date.toml b/cases/translation/functions/to-date.to-date.toml
new file mode 100644
index 000000000..974172b9d
--- /dev/null
+++ b/cases/translation/functions/to-date.to-date.toml
@@ -0,0 +1,109 @@
+category = "translation"
+construct = "sf.functions.to-date.to-date"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_varchar"
+description = "TO_DATE of ISO strings. Catches: a timestamp result, or month and day swapped"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'2024-01-31'"],
+ ["'1999-12-01'"],
+]
+query = "SELECT TO_DATE(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_format"
+description = "An explicit day-first format. Catches: the format ignored (2024-01-12, or an error on 31/01/2024)"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'01/12/2024'"],
+ ["'31/01/2024'"],
+]
+query = "SELECT TO_DATE(a, 'DD/MM/YYYY') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp"
+description = "A timestamp late in the day. Catches: the date rounded to the next day"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-02-29 23:59:59.999'"],
+]
+query = "SELECT TO_DATE(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_timestamp_tz"
+description = "A TIMESTAMP_TZ whose UTC date is the next day. Catches: the date of the UTC instant (2025-01-01)"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-31 23:30:00.250 -0800'"],
+]
+query = "SELECT TO_DATE(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_integer_string"
+description = "Integer strings of seconds and milliseconds. Catches: every integer read as seconds, or as days since the epoch"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'1700000000'"],
+ ["'1700000000123'"],
+ ["'86399'"],
+]
+query = "SELECT TO_DATE(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t006_null_string_expr"
+description = "NULL string. Catches: NULL read as an empty string (an error)"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'2024-01-01'"],
+]
+query = "SELECT TO_DATE(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_null_format"
+description = "NULL format. Catches: NULL read as AUTO"
+columns = [{ name = "f", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'YYYY-MM-DD'"],
+]
+query = "SELECT TO_DATE('2024-02-03', f) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t008_leap"
+description = "29 February in a leap year. Catches: a rejected leap day"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'2024-02-29'"],
+ ["'2000-02-29'"],
+]
+query = "SELECT TO_DATE(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t009_not_a_date"
+description = "29 February in a common year. Catches: a carry to 1 March or NULL instead of an error"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'2023-02-29'"],
+]
+query = "SELECT TO_DATE(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
diff --git a/cases/translation/functions/to-decfloat.toml b/cases/translation/functions/to-decfloat.toml
new file mode 100644
index 000000000..799ddc77d
--- /dev/null
+++ b/cases/translation/functions/to-decfloat.toml
@@ -0,0 +1,49 @@
+category = "translation"
+construct = "sf.functions.to-decfloat"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_strings"
+description = "Decimal strings with trailing zeros and an exponent, compared as text; whether the trailing zero is kept the page does not say, so the recording decides. Catches: the exponent rejected, or trailing zeros handled differently from Snowflake"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'1.10'"],
+ ["'-2.5e3'"],
+ ["'0.000000000000000000000000000000000001'"],
+]
+query = "SELECT TO_DECFLOAT(v)::VARCHAR AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_format"
+description = "A string with a group separator parsed with a format model. Catches: the format ignored (an error or NULL)"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'1,234.5'"],
+]
+query = "SELECT TO_DECFLOAT(v, '9,999.9')::VARCHAR AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_nan"
+description = "The string NaN, which the page says is not supported. Catches: a NaN value returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'NaN'"],
+]
+query = "SELECT TO_DECFLOAT(v)::VARCHAR AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t004_null_expr"
+description = "A NULL, which the page says gives NULL. Catches: zero returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_DECFLOAT(v)::VARCHAR AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/to-decimal.to-decimal.toml b/cases/translation/functions/to-decimal.to-decimal.toml
new file mode 100644
index 000000000..119cf5b69
--- /dev/null
+++ b/cases/translation/functions/to-decimal.to-decimal.toml
@@ -0,0 +1,63 @@
+category = "translation"
+construct = "sf.functions.to-decimal.to-decimal"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_scale"
+description = "Strings with three fractional digits at scale 2, and the default scale 0 on a half. Catches: truncation (12.34 for 12.345), or half-even rounding (12 for 12.5)"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'12.345'"],
+ ["'-12.345'"],
+ ["'12.5'"],
+]
+query = "SELECT TO_DECIMAL(v, 10, 2) AS s2, TO_DECIMAL(v) AS s0 FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t002_format"
+description = "A string with a group separator parsed with a format model. Catches: the format ignored (an error), or the scale dropped"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'1,234.5'"],
+]
+query = "SELECT TO_DECIMAL(v, '9,999.9', 6, 1) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_variant"
+description = "A VARIANT holding true, false, a string and a JSON null. Catches: an error for the booleans, or the JSON null as 0"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'true'"],
+ ["'false'"],
+ ["'\"7\"'"],
+ ["'null'"],
+]
+query = "SELECT TO_DECIMAL(PARSE_JSON(v)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_precision"
+description = "A value with more digits than the precision allows. Catches: the value truncated or returned as NULL"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'12345'"],
+]
+query = "SELECT TO_DECIMAL(v, 4, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t005_null_expr"
+description = "A NULL, which the page says gives NULL. Catches: zero returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_DECIMAL(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/to-decimal.to-number.toml b/cases/translation/functions/to-decimal.to-number.toml
new file mode 100644
index 000000000..69bda6299
--- /dev/null
+++ b/cases/translation/functions/to-decimal.to-number.toml
@@ -0,0 +1,63 @@
+category = "translation"
+construct = "sf.functions.to-decimal.to-number"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_scale"
+description = "Strings with three fractional digits at scale 2, and the default scale 0 on a half. Catches: truncation (12.34 for 12.345), or half-even rounding (12 for 12.5)"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'12.345'"],
+ ["'-12.345'"],
+ ["'12.5'"],
+]
+query = "SELECT TO_NUMBER(v, 10, 2) AS s2, TO_NUMBER(v) AS s0 FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t002_format"
+description = "A string with a group separator parsed with a format model. Catches: the format ignored (an error), or the scale dropped"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'1,234.5'"],
+]
+query = "SELECT TO_NUMBER(v, '9,999.9', 6, 1) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_variant"
+description = "A VARIANT holding true, false, a string and a JSON null. Catches: an error for the booleans, or the JSON null as 0"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'true'"],
+ ["'false'"],
+ ["'\"7\"'"],
+ ["'null'"],
+]
+query = "SELECT TO_NUMBER(PARSE_JSON(v)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_precision"
+description = "A value with more digits than the precision allows. Catches: the value truncated or returned as NULL"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'12345'"],
+]
+query = "SELECT TO_NUMBER(v, 4, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t005_null_expr"
+description = "A NULL, which the page says gives NULL. Catches: zero returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_NUMBER(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/to-decimal.to-numeric.toml b/cases/translation/functions/to-decimal.to-numeric.toml
new file mode 100644
index 000000000..e56e80667
--- /dev/null
+++ b/cases/translation/functions/to-decimal.to-numeric.toml
@@ -0,0 +1,63 @@
+category = "translation"
+construct = "sf.functions.to-decimal.to-numeric"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_scale"
+description = "Strings with three fractional digits at scale 2, and the default scale 0 on a half. Catches: truncation (12.34 for 12.345), or half-even rounding (12 for 12.5)"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'12.345'"],
+ ["'-12.345'"],
+ ["'12.5'"],
+]
+query = "SELECT TO_NUMERIC(v, 10, 2) AS s2, TO_NUMERIC(v) AS s0 FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t002_format"
+description = "A string with a group separator parsed with a format model. Catches: the format ignored (an error), or the scale dropped"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'1,234.5'"],
+]
+query = "SELECT TO_NUMERIC(v, '9,999.9', 6, 1) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_variant"
+description = "A VARIANT holding true, false, a string and a JSON null. Catches: an error for the booleans, or the JSON null as 0"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'true'"],
+ ["'false'"],
+ ["'\"7\"'"],
+ ["'null'"],
+]
+query = "SELECT TO_NUMERIC(PARSE_JSON(v)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_precision"
+description = "A value with more digits than the precision allows. Catches: the value truncated or returned as NULL"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'12345'"],
+]
+query = "SELECT TO_NUMERIC(v, 4, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t005_null_expr"
+description = "A NULL, which the page says gives NULL. Catches: zero returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_NUMERIC(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/to-double.toml b/cases/translation/functions/to-double.toml
new file mode 100644
index 000000000..96b8ca5a6
--- /dev/null
+++ b/cases/translation/functions/to-double.toml
@@ -0,0 +1,52 @@
+category = "translation"
+construct = "sf.functions.to-double"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_strings"
+description = "Scientific notation and the special values the page accepts. Catches: the special values rejected"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'1.5e2'"],
+ ["'-inf'"],
+ ["'Infinity'"],
+ ["'nan'"],
+]
+query = "SELECT TO_DOUBLE(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_variant"
+description = "A VARIANT holding true, a string and a JSON null. Catches: an error for the boolean, or the JSON null as 0"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'true'"],
+ ["'\"2.5\"'"],
+ ["'null'"],
+]
+query = "SELECT TO_DOUBLE(PARSE_JSON(v)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_invalid"
+description = "Text that is not a number. Catches: NULL or 0 returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'abc'"],
+]
+query = "SELECT TO_DOUBLE(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t004_null_expr"
+description = "A NULL, which the page says gives NULL. Catches: zero returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_DOUBLE(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/to-geography.toml b/cases/translation/functions/to-geography.toml
new file mode 100644
index 000000000..80e66464b
--- /dev/null
+++ b/cases/translation/functions/to-geography.toml
@@ -0,0 +1,95 @@
+category = "translation"
+construct = "sf.functions.to-geography"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_formats"
+description = "The same point as WKT, WKB hex, EWKT and EWKB with SRID 4326, and GeoJSON. Catches: a format rejected or misread, or longitude and latitude swapped in one of them"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'POINT(10 20)'"],
+ ["'010100000000000000000024400000000000003440'"],
+ ["'SRID=4326;POINT(10 20)'"],
+ ["'0101000020E610000000000000000024400000000000003440'"],
+ ["'{\"type\":\"Point\",\"coordinates\":[10,20]}'"],
+]
+query = "SELECT ordinal, ST_ASWKT(TO_GEOGRAPHY(v)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_binary"
+description = "WKB as BINARY. Catches: BINARY input rejected, or read as hex text"
+columns = [{ name = "b", type = "BINARY" }]
+rows = [
+ ["'010100000000000000000024400000000000003440'"],
+]
+query = "SELECT ST_ASWKT(TO_GEOGRAPHY(b)) AS r FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_variant"
+description = "A GeoJSON OBJECT in a VARIANT. Catches: the OBJECT rejected, or its text parsed as WKT"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'{\"type\":\"Point\",\"coordinates\":[10,20]}'"],
+]
+query = "SELECT ST_ASWKT(TO_GEOGRAPHY(PARSE_JSON(v))) AS r FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_geometry"
+description = "A GEOMETRY with SRID 4326, which the page accepts. Catches: the GEOMETRY rejected, or its coordinates swapped"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'POINT(10 20)'"],
+]
+query = "SELECT ST_ASWKT(TO_GEOGRAPHY(TO_GEOMETRY(v, 4326))) AS r FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_srid"
+description = "EWKT with SRID 3857, which the page says is an error. Catches: the point accepted in another SRID"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'SRID=3857;POINT(10 20)'"],
+]
+query = "SELECT ST_ASWKT(TO_GEOGRAPHY(v)) AS r FROM input"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t006_unparsable"
+description = "Text in none of the formats, which the page says is an error. Catches: NULL returned, or the text read as an empty shape"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'not a shape'"],
+]
+query = "SELECT ST_ASWKT(TO_GEOGRAPHY(v)) AS r FROM input"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t007_allow_invalid"
+description = "A self-intersecting polygon with allow_invalid TRUE. Catches: the bow tie repaired or rejected despite allow_invalid"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'POLYGON((0 0,10 10,10 0,0 10,0 0))'"],
+]
+query = "SELECT ST_ISVALID(TO_GEOGRAPHY(v, TRUE)) AS valid FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t008_null_varchar_expression"
+description = "A NULL text; the page does not say; the recording decides. Catches: an error, or an empty shape returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_GEOGRAPHY(v) IS NULL AS n FROM input"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/to-geometry.toml b/cases/translation/functions/to-geometry.toml
new file mode 100644
index 000000000..1839794b4
--- /dev/null
+++ b/cases/translation/functions/to-geometry.toml
@@ -0,0 +1,83 @@
+category = "translation"
+construct = "sf.functions.to-geometry"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_formats"
+description = "The same point as WKT, WKB hex and GeoJSON, which the page says get SRID 0. Catches: a format rejected, SRID 4326 assumed, or longitude and latitude swapped"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'POINT(10 20)'"],
+ ["'010100000000000000000024400000000000003440'"],
+ ["'{\"type\":\"Point\",\"coordinates\":[10,20]}'"],
+]
+query = "SELECT ordinal, ST_ASWKT(TO_GEOMETRY(v)) AS r, ST_SRID(TO_GEOMETRY(v)) AS srid FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_srid"
+description = "WKT with an srid argument. Catches: the srid argument ignored (SRID 0)"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'POINT(10 20)'"],
+]
+query = "SELECT ST_SRID(TO_GEOMETRY(v, 3857)) AS srid FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_ewkt"
+description = "EWKT with SRID 3857, which the page says specifies the SRID to use, and EWKB with SRID 3857 (the page does not say; the recording decides on its SRID). Catches: the EWKT SRID dropped to 0, or the prefix rejected"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'SRID=3857;POINT(10 20)'"],
+ ["'0101000020110F000000000000000024400000000000003440'"],
+]
+query = "SELECT ordinal, ST_ASWKT(TO_GEOMETRY(v)) AS r, ST_SRID(TO_GEOMETRY(v)) AS srid FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_geography"
+description = "A GEOGRAPHY, which the page accepts; its example converts a GEOGRAPHY to a GEOMETRY with SRID 4326. Catches: the GEOGRAPHY rejected, SRID 0 returned, or its coordinates swapped"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'POINT(10 20)'"],
+]
+query = "SELECT ST_ASWKT(TO_GEOMETRY(TO_GEOGRAPHY(v))) AS r, ST_SRID(TO_GEOMETRY(TO_GEOGRAPHY(v))) AS srid FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_unparsable"
+description = "Text in none of the formats, which the page says is an error. Catches: NULL returned, or the text read as an empty shape"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'not a shape'"],
+]
+query = "SELECT ST_ASWKT(TO_GEOMETRY(v)) AS r FROM input"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t006_allow_invalid"
+description = "A self-intersecting polygon with allow_invalid TRUE, alone and after an srid. Catches: the bow tie repaired or rejected despite allow_invalid, or TRUE read as the srid"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'POLYGON((0 0,10 10,10 0,0 10,0 0))'"],
+]
+query = "SELECT ST_ISVALID(TO_GEOMETRY(v, TRUE)) AS valid, ST_ISVALID(TO_GEOMETRY(v, 3857, TRUE)) AS valid_srid, ST_SRID(TO_GEOMETRY(v, 3857, TRUE)) AS srid FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t007_null_varchar_expression"
+description = "A NULL text; the page does not say; the recording decides. Catches: an error, or an empty shape returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_GEOMETRY(v) IS NULL AS n FROM input"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/to-ipaddress.toml b/cases/translation/functions/to-ipaddress.toml
new file mode 100644
index 000000000..7c4a72df5
--- /dev/null
+++ b/cases/translation/functions/to-ipaddress.toml
@@ -0,0 +1,37 @@
+category = "translation"
+construct = "sf.functions.to-ipaddress"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_canonical"
+description = "An IPv4 address and an IPv6 address written with upper-case hex and zero runs, compared as text. Catches: the input text returned unchanged instead of the canonical form"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'192.168.0.1'"],
+ ["'2001:0DB8:0000:0000:0000:0000:0000:0001'"],
+]
+query = "SELECT TO_VARCHAR(TO_IPADDRESS(v)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_invalid"
+description = "An IPv4 address with an octet above 255, which the page says raises an error. Catches: NULL returned, or the octet wrapped"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'256.1.1.1'"],
+]
+query = "SELECT TO_VARCHAR(TO_IPADDRESS(v)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t003_null_string_expr"
+description = "A NULL, which the page says gives NULL. Catches: an error"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_VARCHAR(TO_IPADDRESS(v)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/to-json.toml b/cases/translation/functions/to-json.toml
new file mode 100644
index 000000000..bfcb93cc2
--- /dev/null
+++ b/cases/translation/functions/to-json.toml
@@ -0,0 +1,37 @@
+category = "translation"
+construct = "sf.functions.to-json"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_compact"
+description = "A document with spaces, a decimal with a trailing zero and an escaped quote, on single-key objects so key order cannot vary. Catches: the original spacing kept, or the quote unescaped"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'{ \"a\" : [1, 2.50] }'"],
+ ["'[\"x\\\\\"y\"]'"],
+]
+query = "SELECT TO_JSON(PARSE_JSON(a)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_json_null"
+description = "A JSON null, which the page says gives the text null. Catches: a SQL NULL returned"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'null'"],
+]
+query = "SELECT TO_JSON(PARSE_JSON(a)) AS r, TO_JSON(PARSE_JSON(a)) IS NULL AS n FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_expr"
+description = "A SQL NULL, which the page says gives NULL. Catches: the text null returned"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_JSON(PARSE_JSON(a)) AS r, TO_JSON(PARSE_JSON(a)) IS NULL AS n FROM input"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/to-object.toml b/cases/translation/functions/to-object.toml
new file mode 100644
index 000000000..98e49ca8b
--- /dev/null
+++ b/cases/translation/functions/to-object.toml
@@ -0,0 +1,48 @@
+category = "translation"
+construct = "sf.functions.to-object"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_object"
+description = "Objects, nested and empty. Catches: an empty object returned as NULL, or nested objects flattened"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'{\"a\":{\"b\":[1]}}'"],
+ ["'{}'"],
+]
+query = "SELECT TO_OBJECT(PARSE_JSON(a)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_array"
+description = "An array, for which the page says any input other than an OBJECT reports an error. Catches: the array converted to an object, instead of the error"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'[1]'"],
+]
+query = "SELECT TO_OBJECT(PARSE_JSON(a)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t003_null_expr"
+description = "A SQL NULL. Catches: an empty object returned"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_OBJECT(PARSE_JSON(a)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t004_string"
+description = "A VARIANT holding the text {}. The page says any input other than an OBJECT reports an error, but the recording shows Snowflake parsing the string into the empty object. Catches: the error the page describes raised, or the string returned unchanged"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'\"{}\"'"],
+]
+query = "SELECT TO_OBJECT(PARSE_JSON(a)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
diff --git a/cases/translation/functions/to-query.toml b/cases/translation/functions/to-query.toml
new file mode 100644
index 000000000..991428a8e
--- /dev/null
+++ b/cases/translation/functions/to-query.toml
@@ -0,0 +1,37 @@
+category = "translation"
+construct = "sf.functions.to-query"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_query"
+description = "A query text with two columns, read back from the FROM clause. Catches: the text returned as a string"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT a, b FROM TABLE(TO_QUERY('SELECT 1 AS a, ''x'' AS b'))"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_bind"
+description = "A bind variable converted with TO_NUMBER, as in the page example. Catches: the bind variable left unsubstituted"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT a FROM TABLE(TO_QUERY('SELECT TO_NUMBER(:n) + 1 AS a', n => '41'))"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_arg"
+compile_error = true
+description = "A NULL bind argument typed as VARCHAR. The page says \"If any argument is NULL, the function returns NULL without reporting any error\", but Snowflake refuses the NULL bind when compiling (1015, the argument needs to be constant), as it refused a bare NULL (1045). Catches: the NULL bind accepted, or an empty result set returned"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT COUNT(*) AS n FROM TABLE(TO_QUERY('SELECT TO_NUMBER(:n) AS a', n => NULL::VARCHAR))"
+order = "ordered"
+tags = ["null", "error"]
diff --git a/cases/translation/functions/to-time.time.toml b/cases/translation/functions/to-time.time.toml
new file mode 100644
index 000000000..afc2e533f
--- /dev/null
+++ b/cases/translation/functions/to-time.time.toml
@@ -0,0 +1,73 @@
+category = "translation"
+construct = "sf.functions.to-time.time"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_varchar"
+description = "TIME of strings with fractional seconds. The result is formatted with TO_VARCHAR(..., 'HH24:MI:SS.FF9') because the connector returns TIME in microseconds and the session TIME_OUTPUT_FORMAT has no fraction. Catches: fractional seconds dropped or rounded"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'13:14:15.123'"],
+ ["'00:00:00'"],
+ ["'23:59:59.999999999'"],
+]
+query = "SELECT TO_VARCHAR(TIME(a), 'HH24:MI:SS.FF9') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_timestamp"
+description = "A timestamp's time part. Catches: the date leaking in, or an error"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-02-29 17:01:02.5'"],
+]
+query = "SELECT TIME(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp_tz"
+description = "A TIMESTAMP_TZ's wall-clock time. Catches: the UTC time (07:30) instead of the wall clock (23:30)"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-31 23:30:00.250 -0800'"],
+]
+query = "SELECT TIME(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_integer_string"
+description = "Integer strings of seconds and milliseconds. Catches: the integer not taken modulo a day, or always read as seconds"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'86461'"],
+ ["'31536002789'"],
+]
+query = "SELECT TIME(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_null_string_expr"
+description = "NULL string. Catches: NULL read as midnight"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'01:00:00'"],
+]
+query = "SELECT TIME(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_out_of_range"
+description = "An hour of 25. Catches: a wrap to 01:00 or NULL instead of an error"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'25:00:00'"],
+]
+query = "SELECT TIME(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
diff --git a/cases/translation/functions/to-time.to-time.toml b/cases/translation/functions/to-time.to-time.toml
new file mode 100644
index 000000000..7d46ddce5
--- /dev/null
+++ b/cases/translation/functions/to-time.to-time.toml
@@ -0,0 +1,98 @@
+category = "translation"
+construct = "sf.functions.to-time.to-time"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_varchar"
+description = "TO_TIME of strings with fractional seconds. The result is formatted with TO_VARCHAR(..., 'HH24:MI:SS.FF9') because the connector returns TIME in microseconds and the session TIME_OUTPUT_FORMAT has no fraction. Catches: fractional seconds dropped or rounded"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'13:14:15.123'"],
+ ["'00:00:00'"],
+ ["'23:59:59.999999999'"],
+]
+query = "SELECT TO_VARCHAR(TO_TIME(a), 'HH24:MI:SS.FF9') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_format"
+description = "A 12-hour format with AM/PM. Catches: the format ignored, or 12 AM read as noon"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'12:05 AM'"],
+ ["'01:30 PM'"],
+]
+query = "SELECT TO_TIME(a, 'HH12:MI AM') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp"
+description = "A timestamp's time part. Catches: the date leaking in, or an error"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-02-29 17:01:02.5'"],
+]
+query = "SELECT TO_TIME(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_timestamp_tz"
+description = "A TIMESTAMP_TZ's wall-clock time. Catches: the UTC time (07:30) instead of the wall clock (23:30)"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-31 23:30:00.250 -0800'"],
+]
+query = "SELECT TO_TIME(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_integer_string"
+description = "Integer strings of seconds and milliseconds. Catches: the integer not taken modulo a day, or always read as seconds"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'86461'"],
+ ["'31536002789'"],
+]
+query = "SELECT TO_TIME(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+expected_divergence = "date_text_spelling"
+tags = ["type-combination"]
+
+[[case]]
+id = "t006_null_string_expr"
+description = "NULL string. Catches: NULL read as midnight"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'01:00:00'"],
+]
+query = "SELECT TO_TIME(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_out_of_range"
+description = "An hour of 25. Catches: a wrap to 01:00 or NULL instead of an error"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'25:00:00'"],
+]
+query = "SELECT TO_TIME(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t008_null_format"
+description = "NULL format. Catches: NULL read as AUTO"
+columns = [{ name = "f", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'HH24:MI'"],
+]
+query = "SELECT TO_TIME('13:30', f) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/to-timestamp.toml b/cases/translation/functions/to-timestamp.toml
new file mode 100644
index 000000000..7b3b39a28
--- /dev/null
+++ b/cases/translation/functions/to-timestamp.toml
@@ -0,0 +1,109 @@
+category = "translation"
+construct = "sf.functions.to-timestamp"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_number"
+description = "Whole seconds since the epoch. Catches: milliseconds assumed (1970-01-20 for 1700000000)"
+columns = [{ name = "n", type = "NUMBER(38,0)" }]
+rows = [
+ ["1700000000"],
+ ["0"],
+ ["951782400"],
+]
+query = "SELECT TO_TIMESTAMP(n) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_scale"
+description = "Scales 3, 6 and 9. The constant argument is a literal per call, as Snowflake requires. Catches: the scale ignored (seconds) or applied as a power of the wrong base"
+columns = [{ name = "n1", type = "NUMBER(38,0)" }, { name = "n2", type = "NUMBER(38,0)" }, { name = "n3", type = "NUMBER(38,0)" }]
+rows = [
+ ["1700000000123", "1700000000123456", "1700000000123456789"],
+]
+query = "SELECT TO_TIMESTAMP(n1, 3) AS r1, TO_TIMESTAMP(n2, 6) AS r2, TO_TIMESTAMP(n3, 9) AS r3 FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_decimal"
+description = "A fractional number of seconds, whose scale is kept. Catches: the fraction truncated"
+columns = [{ name = "n", type = "NUMBER(10,2)" }]
+rows = [
+ ["1.50"],
+ ["86400.25"],
+]
+query = "SELECT TO_TIMESTAMP(n) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_variants"
+description = "TO_TIMESTAMP_NTZ, _LTZ and _TZ of the same number. Catches: the wrong result type among NTZ, LTZ and TZ"
+columns = [{ name = "n", type = "NUMBER(38,0)" }]
+rows = [
+ ["1700000000"],
+]
+query = "SELECT TO_TIMESTAMP_NTZ(n) AS ntz, TO_TIMESTAMP_LTZ(n) AS ltz, TO_TIMESTAMP_TZ(n) AS tz FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_null_numeric_expr"
+description = "NULL number. Catches: NULL read as the epoch"
+columns = [{ name = "n", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["1"],
+]
+query = "SELECT TO_TIMESTAMP(n) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_null_scale"
+compile_error = true
+description = "A literal NULL scale. Snowflake needs an integer constant there and refuses NULL. Catches: NULL read as 0 instead of the error"
+columns = [{ name = "k", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT TO_TIMESTAMP(1700000000123, NULL) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_pre_epoch"
+description = "Negative values, before 1970. Catches: truncation toward zero for a negative fraction"
+columns = [{ name = "n", type = "NUMBER(10,2)" }]
+rows = [
+ ["-1.00"],
+ ["-0.50"],
+]
+query = "SELECT TO_TIMESTAMP(n) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t008_leap"
+description = "The first second of 29 February 2024. Catches: a 365-day year in the conversion"
+columns = [{ name = "n", type = "NUMBER(38,0)" }]
+rows = [
+ ["1709164800"],
+ ["1709251199"],
+]
+query = "SELECT TO_TIMESTAMP(n) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t009_varchar"
+description = "Strings with fractional seconds, an explicit format and an integer string. Catches: the fraction dropped, the format ignored, or an integer string read as a date"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'2024-02-29 13:05:09.123'"],
+]
+query = "SELECT TO_TIMESTAMP(a) AS r, TO_TIMESTAMP('29/02/2024 13:05', 'DD/MM/YYYY HH24:MI') AS f, TO_TIMESTAMP('1700000000') AS i FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
diff --git a/cases/translation/functions/to-uuid.toml b/cases/translation/functions/to-uuid.toml
new file mode 100644
index 000000000..57501104a
--- /dev/null
+++ b/cases/translation/functions/to-uuid.toml
@@ -0,0 +1,37 @@
+category = "translation"
+construct = "sf.functions.to-uuid"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_case"
+description = "The same UUID in upper and lower case, compared as text; the UUID data type page says UUID values are case-insensitive. Catches: the case kept as given, so the two differ"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'6BA7B810-9DAD-11D1-80B4-00C04FD430C8'"],
+ ["'6ba7b810-9dad-11d1-80b4-00c04fd430c8'"],
+]
+query = "SELECT TO_UUID(v)::VARCHAR AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_invalid"
+description = "A string one digit short of a UUID, which Snowflake accepted and returned as text. Catches: an error raised, NULL returned, or the string padded"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'6BA7B810-9DAD-11D1-80B4-00C04FD430C'"],
+]
+query = "SELECT TO_UUID(v)::VARCHAR AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_string_expr"
+description = "A NULL, which the page says gives NULL. Catches: a nil UUID returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_UUID(v)::VARCHAR AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/to-variant.toml b/cases/translation/functions/to-variant.toml
new file mode 100644
index 000000000..70e9edb74
--- /dev/null
+++ b/cases/translation/functions/to-variant.toml
@@ -0,0 +1,25 @@
+category = "translation"
+construct = "sf.functions.to-variant"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_types"
+description = "Values of four types, each with the type TYPEOF reports. Catches: every value stored as text, or the date stored as a string"
+columns = [{ name = "x", type = "NUMBER(10,3)" }, { name = "v", type = "VARCHAR" }, { name = "d", type = "DATE" }, { name = "b", type = "BOOLEAN" }]
+rows = [
+ ["1.500", "'a'", "'2024-02-29'", "TRUE"],
+]
+query = "SELECT TO_VARIANT(x) AS x, TYPEOF(TO_VARIANT(x)) AS tx, TO_VARIANT(v) AS v, TYPEOF(TO_VARIANT(v)) AS tv, TO_VARIANT(d) AS d, TYPEOF(TO_VARIANT(d)) AS td, TYPEOF(TO_VARIANT(b)) AS tb FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_null_expr"
+description = "A NULL; the page does not say; the recording decides whether the result is a SQL NULL or a JSON null. Catches: the other kind of null than Snowflake returns"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_VARIANT(v) AS r, TO_VARIANT(v) IS NULL AS n FROM input"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/to-xml.toml b/cases/translation/functions/to-xml.toml
new file mode 100644
index 000000000..d411dce31
--- /dev/null
+++ b/cases/translation/functions/to-xml.toml
@@ -0,0 +1,37 @@
+category = "translation"
+construct = "sf.functions.to-xml"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_round_trip"
+description = "An XML document parsed and written back. Catches: attributes or empty elements lost"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'1.50x'"],
+]
+query = "SELECT TO_XML(PARSE_XML(a)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_generated"
+description = "A single-key object and a number, which get generated tags. Catches: the JSON text returned unchanged"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'{\"k\":[1,\"x\"]}'"],
+ ["'5'"],
+]
+query = "SELECT TO_XML(PARSE_JSON(a)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_expression"
+description = "A SQL NULL, which the page says gives NULL. Catches: an empty element returned"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_XML(PARSE_JSON(a)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/transform.toml b/cases/translation/functions/transform.toml
new file mode 100644
index 000000000..53579e9d3
--- /dev/null
+++ b/cases/translation/functions/transform.toml
@@ -0,0 +1,36 @@
+category = "translation"
+construct = "sf.functions.transform"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_elements"
+description = "Integers and a decimal doubled. Catches: the decimal truncated, or the order changed"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'[1,2.5,-3]'"],
+]
+query = "SELECT TRANSFORM(PARSE_JSON(a), x -> x * 2) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_declared_type"
+description = "A lambda argument declared VARCHAR, applied to a number and text. Catches: the number written with JSON quotes, or the declared type ignored (an error)"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'[1,\"a\"]'"],
+]
+query = "SELECT TRANSFORM(PARSE_JSON(a), x VARCHAR -> x || '!') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_array"
+description = "A NULL array. Catches: an empty array returned"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TRANSFORM(PARSE_JSON(a), x -> x * 2) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/translate.toml b/cases/translation/functions/translate.toml
new file mode 100644
index 000000000..4396d210f
--- /dev/null
+++ b/cases/translation/functions/translate.toml
@@ -0,0 +1,74 @@
+category = "translation"
+construct = "sf.functions.translate"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_varchar"
+description = "A shorter target that removes a character, and multi-byte characters. Catches: unmatched source characters kept (Oracle-style keep instead of remove), or byte positions (\u00e9)"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "f", type = "VARCHAR" }, { name = "t", type = "VARCHAR" }]
+rows = [
+ ["'abcabc'", "'abc'", "'xy'"],
+ ["'\u00e9te'", "'\u00e9'", "'e'"],
+ ["'ba'", "'ab'", "'ba'"],
+]
+query = "SELECT TRANSLATE(a, f, t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_null_subject"
+description = "NULL subject. Catches: NULL read as empty"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'a'"],
+]
+query = "SELECT TRANSLATE(a, 'a', 'b') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t003_null_sourcealphabet"
+description = "NULL source alphabet. Catches: NULL read as empty (subject unchanged)"
+columns = [{ name = "f", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'a'"],
+]
+query = "SELECT TRANSLATE('ab', f, 'x') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t004_null_targetalphabet"
+description = "NULL target alphabet. Catches: NULL read as empty (characters removed)"
+columns = [{ name = "t", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["''"],
+]
+query = "SELECT TRANSLATE('ab', 'a', t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_empty_target"
+description = "An empty target, which removes every source character. Catches: the source characters kept"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'banana'"],
+]
+query = "SELECT TRANSLATE(a, 'an', '') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary", "empty"]
+
+[[case]]
+id = "t006_target_longer"
+description = "A target longer than the source, which the page says is an error. Catches: the extra target characters ignored"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'abc'"],
+]
+query = "SELECT TRANSLATE(a, 'a', 'xy') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
diff --git a/cases/translation/functions/trim.toml b/cases/translation/functions/trim.toml
new file mode 100644
index 000000000..182b1554a
--- /dev/null
+++ b/cases/translation/functions/trim.toml
@@ -0,0 +1,64 @@
+category = "translation"
+construct = "sf.functions.trim"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_characters"
+description = "TRIM with a character set given out of order. Catches: the set read as a substring, or characters removed from one side only"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "c", type = "VARCHAR" }]
+rows = [
+ ["'xyyxabcxy'", "'yx'"],
+ ["'$.$a.b$.'", "'.$'"],
+ ["'\u00e9a\u00e9'", "'\u00e9'"],
+]
+query = "SELECT TRIM(a, c) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_default"
+description = "TRIM with the default blank, beside tabs and newlines. Catches: all whitespace removed (the tab and newline kept by Snowflake)"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["' ab '"],
+ ["CHR(9) || ' ab ' || CHR(10)"],
+]
+query = "SELECT TRIM(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_expr"
+description = "NULL string. Catches: NULL read as empty"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["' a '"],
+]
+query = "SELECT TRIM(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t004_null_characters"
+description = "NULL character set. Catches: NULL read as the default blank"
+columns = [{ name = "c", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'x'"],
+]
+query = "SELECT TRIM(' xax ', c) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_empty"
+description = "An empty character set, and a string made only of trimmed characters. Catches: an empty set read as the default blank, or NULL for a fully trimmed string"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "c", type = "VARCHAR" }]
+rows = [
+ ["' a '", "''"],
+ ["'xxx'", "'x'"],
+]
+query = "SELECT TRIM(a, c) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary", "empty"]
diff --git a/cases/translation/functions/trunc.trunc.toml b/cases/translation/functions/trunc.trunc.toml
new file mode 100644
index 000000000..b4208a51d
--- /dev/null
+++ b/cases/translation/functions/trunc.trunc.toml
@@ -0,0 +1,104 @@
+category = "translation"
+construct = "sf.functions.trunc.trunc"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_int"
+description = "TRUNC of integers with a negative scale. Catches: FLOOR used for negatives (TRUNC(-2.7) is -2), or the date TRUNC chosen; or a negative scale ignored"
+columns = [{ name = "x", type = "NUMBER(38,0)" }, { name = "s", type = "NUMBER(38,0)" }]
+rows = [
+ ["1567", "-2"],
+ ["-1567", "-2"],
+ ["5", "0"],
+]
+query = "SELECT TRUNC(x, s) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_dec"
+description = "TRUNC of NUMBER(12,4) with and without a scale. Catches: FLOOR used for negatives (TRUNC(-2.7) is -2), or the date TRUNC chosen"
+columns = [{ name = "x", type = "NUMBER(12,4)" }]
+rows = [
+ ["2.1000"],
+ ["-2.1000"],
+ ["-2.5000"],
+ ["2.7000"],
+]
+query = "SELECT TRUNC(x) AS r, TRUNC(x, 2) AS s FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_float"
+description = "TRUNC of FLOAT returns FLOAT. Catches: FLOOR used for negatives (TRUNC(-2.7) is -2), or the date TRUNC chosen; or a NUMBER returned"
+columns = [{ name = "x", type = "FLOAT" }]
+rows = [
+ ["3.14159"],
+ ["-3.14159"],
+ ["-0.4"],
+]
+query = "SELECT TRUNC(x) AS r, TRUNC(x, 1) AS s FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_decfloat"
+description = "TRUNC of DECFLOAT returns DECFLOAT. The result is cast to VARCHAR because the recorder cannot decode a DECFLOAT column. Catches: computed in FLOAT: the fraction of a 20-digit value lost, or 0.29 at scale 2 off by one ulp"
+columns = [{ name = "x", type = "DECFLOAT" }]
+rows = [
+ ["2.25"],
+ ["-2.25"],
+ ["0.29"],
+ ["12345678901234567890.25"],
+]
+query = "SELECT CAST(TRUNC(x, 1) AS VARCHAR) AS r, CAST(TRUNC(x, 2) AS VARCHAR) AS s FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_null_x"
+description = "TRUNC of NULL. Catches: NULL turned into 0"
+columns = [{ name = "x", type = "NUMBER(12,4)" }, { name = "s", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL", "1"],
+]
+query = "SELECT TRUNC(x, s) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_null_scale"
+description = "TRUNC with a NULL scale. Catches: the NULL scale read as 0"
+columns = [{ name = "x", type = "NUMBER(12,4)" }, { name = "s", type = "NUMBER(38,0)" }]
+rows = [
+ ["2.5555", "NULL"],
+]
+query = "SELECT TRUNC(x, s) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_scale_past"
+description = "Scales past the input scale and far negative. Catches: scale capped or wrapping"
+columns = [{ name = "x", type = "NUMBER(12,4)" }, { name = "s", type = "NUMBER(38,0)" }]
+rows = [
+ ["12.3456", "6"],
+ ["12.3456", "-5"],
+ ["-987.6543", "3"],
+]
+query = "SELECT TRUNC(x, s) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t008_negatives"
+description = "Negative values at the rounding step. Catches: FLOOR used for negatives (TRUNC(-2.7) is -2), or the date TRUNC chosen"
+columns = [{ name = "x", type = "NUMBER(10,2)" }]
+rows = [
+ ["-2.19"],
+ ["-0.09"],
+]
+query = "SELECT TRUNC(x, 1) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
diff --git a/cases/translation/functions/trunc.truncate.toml b/cases/translation/functions/trunc.truncate.toml
new file mode 100644
index 000000000..e271728b2
--- /dev/null
+++ b/cases/translation/functions/trunc.truncate.toml
@@ -0,0 +1,104 @@
+category = "translation"
+construct = "sf.functions.trunc.truncate"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_int"
+description = "TRUNCATE of integers with a negative scale. Catches: FLOOR used for negatives (TRUNCATE(-2.7) is -2); or a negative scale ignored"
+columns = [{ name = "x", type = "NUMBER(38,0)" }, { name = "s", type = "NUMBER(38,0)" }]
+rows = [
+ ["1567", "-2"],
+ ["-1567", "-2"],
+ ["5", "0"],
+]
+query = "SELECT TRUNCATE(x, s) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_dec"
+description = "TRUNCATE of NUMBER(12,4) with and without a scale. Catches: FLOOR used for negatives (TRUNCATE(-2.7) is -2)"
+columns = [{ name = "x", type = "NUMBER(12,4)" }]
+rows = [
+ ["2.1000"],
+ ["-2.1000"],
+ ["-2.5000"],
+ ["2.7000"],
+]
+query = "SELECT TRUNCATE(x) AS r, TRUNCATE(x, 2) AS s FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_float"
+description = "TRUNCATE of FLOAT returns FLOAT. Catches: FLOOR used for negatives (TRUNCATE(-2.7) is -2); or a NUMBER returned"
+columns = [{ name = "x", type = "FLOAT" }]
+rows = [
+ ["3.14159"],
+ ["-3.14159"],
+ ["-0.4"],
+]
+query = "SELECT TRUNCATE(x) AS r, TRUNCATE(x, 1) AS s FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_decfloat"
+description = "TRUNCATE of DECFLOAT returns DECFLOAT. The result is cast to VARCHAR because the recorder cannot decode a DECFLOAT column. Catches: computed in FLOAT: the fraction of a 20-digit value lost, or 0.29 at scale 2 off by one ulp"
+columns = [{ name = "x", type = "DECFLOAT" }]
+rows = [
+ ["2.25"],
+ ["-2.25"],
+ ["0.29"],
+ ["12345678901234567890.25"],
+]
+query = "SELECT CAST(TRUNCATE(x, 1) AS VARCHAR) AS r, CAST(TRUNCATE(x, 2) AS VARCHAR) AS s FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_null_x"
+description = "TRUNCATE of NULL. Catches: NULL turned into 0"
+columns = [{ name = "x", type = "NUMBER(12,4)" }, { name = "s", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL", "1"],
+]
+query = "SELECT TRUNCATE(x, s) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_null_scale"
+description = "TRUNCATE with a NULL scale. Catches: the NULL scale read as 0"
+columns = [{ name = "x", type = "NUMBER(12,4)" }, { name = "s", type = "NUMBER(38,0)" }]
+rows = [
+ ["2.5555", "NULL"],
+]
+query = "SELECT TRUNCATE(x, s) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_scale_past"
+description = "Scales past the input scale and far negative. Catches: a negative scale beyond the integer digits not giving 0, or a scale above the input scale changing the value"
+columns = [{ name = "x", type = "NUMBER(12,4)" }, { name = "s", type = "NUMBER(38,0)" }]
+rows = [
+ ["12.3456", "6"],
+ ["12.3456", "-5"],
+ ["-987.6543", "3"],
+]
+query = "SELECT TRUNCATE(x, s) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t008_negatives"
+description = "Negative values at the rounding step. Catches: FLOOR used for negatives (TRUNCATE(-2.7) is -2)"
+columns = [{ name = "x", type = "NUMBER(10,2)" }]
+rows = [
+ ["-2.19"],
+ ["-0.09"],
+]
+query = "SELECT TRUNCATE(x, 1) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
diff --git a/cases/translation/functions/trunc2.toml b/cases/translation/functions/trunc2.toml
new file mode 100644
index 000000000..b84378d37
--- /dev/null
+++ b/cases/translation/functions/trunc2.toml
@@ -0,0 +1,84 @@
+category = "translation"
+construct = "sf.functions.trunc2"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "Calendar parts of a Sunday in a leap year. Catches: week truncated to Sunday, quarter to the wrong month, or a TIMESTAMP returned for a DATE"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-03-31'"],
+ ["'2024-01-01'"],
+]
+query = "SELECT TRUNC(d, 'year') AS year, TRUNC(d, 'quarter') AS quarter, TRUNC(d, 'month') AS month, TRUNC(d, 'week') AS week, TRUNC(d, 'day') AS day FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_timestamp_ntz"
+description = "Time parts with fractional seconds. Catches: rounding instead of truncation (12:00 for 11:59:59.999)"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-02-29 11:59:59.999'"],
+]
+query = "SELECT TRUNC(t, 'day') AS day, TRUNC(t, 'hour') AS hour, TRUNC(t, 'minute') AS minute, TRUNC(t, 'second') AS second, TRUNC(t, 'millisecond') AS millisecond FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp_tz"
+description = "A TIMESTAMP_TZ truncated in its own offset. Catches: truncation in UTC (2025-01-01) instead of the wall clock (2024-12-31 00:00 -0800), or the offset dropped (the wall columns show the offset, which the instant comparison ignores)"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-31 23:30:00.250 -0800'"],
+]
+query = "SELECT TRUNC(t, 'day') AS day, CAST(TRUNC(t, 'day') AS TIMESTAMP_NTZ) AS day_wall, TRUNC(t, 'month') AS month, CAST(TRUNC(t, 'month') AS TIMESTAMP_NTZ) AS month_wall, TRUNC(t, 'year') AS year, CAST(TRUNC(t, 'year') AS TIMESTAMP_NTZ) AS year_wall FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_time"
+description = "A TIME truncated to hour and minute. Catches: an error for a TIME, or a timestamp returned"
+columns = [{ name = "t", type = "TIME" }]
+rows = [
+ ["'17:59:59.999'"],
+]
+query = "SELECT TRUNC(t, 'hour') AS hour, TRUNC(t, 'minute') AS minute FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_null_date_or_time_expr"
+description = "NULL next to a set date. Catches: NULL read as a default date"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-05-05'"],
+]
+query = "SELECT TRUNC(d, 'month') AS month FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_dst"
+description = "TIMESTAMP_TZ values on both sides of a DST change. Catches: the offset re-derived from a zone rule (or UTC) instead of kept (the wall columns show the offset, which the instant comparison ignores)"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-03-10 03:30:00 -0400'"],
+ ["'2024-11-03 01:30:00 -0500'"],
+]
+query = "SELECT TRUNC(t, 'hour') AS hour, CAST(TRUNC(t, 'hour') AS TIMESTAMP_NTZ) AS hour_wall, TRUNC(t, 'day') AS day, CAST(TRUNC(t, 'day') AS TIMESTAMP_NTZ) AS day_wall FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t007_time_day"
+compile_error = true
+description = "A TIME truncated to day, which the page says is an error. Catches: midnight returned instead of an error"
+columns = [{ name = "t", type = "TIME" }]
+rows = [
+ ["'10:00:00'"],
+]
+query = "SELECT TRUNC(t, 'day') AS day FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
diff --git a/cases/translation/functions/try-base64-decode-binary.toml b/cases/translation/functions/try-base64-decode-binary.toml
new file mode 100644
index 000000000..671fbed9c
--- /dev/null
+++ b/cases/translation/functions/try-base64-decode-binary.toml
@@ -0,0 +1,73 @@
+category = "translation"
+construct = "sf.functions.try-base64-decode-binary"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_varchar"
+description = "TRY_BASE64_DECODE_BINARY of valid Base64. Catches: wrong bytes, the URL-safe alphabet, or padding mishandled"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'+/8='"],
+ ["'YWJj'"],
+]
+query = "SELECT TRY_BASE64_DECODE_BINARY(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_invalid"
+description = "Invalid Base64 next to valid Base64. Catches: an error for the invalid row (the non-TRY behaviour) or NULL for the valid row too"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'ab$c'"],
+ ["'YQ'"],
+ ["'YQ=='"],
+]
+query = "SELECT TRY_BASE64_DECODE_BINARY(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_alphabet"
+description = "A custom alphabet. The constant argument is a literal per call, as Snowflake requires. Catches: the alphabet ignored (NULL for -_ input)"
+columns = [{ name = "a1", type = "VARCHAR" }]
+rows = [
+ ["'-_8~'"],
+]
+query = "SELECT TRY_BASE64_DECODE_BINARY(a1, '-_~') AS r1 FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_input"
+description = "NULL input next to a set one. Catches: NULL decoded as empty"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'YQ=='"],
+]
+query = "SELECT TRY_BASE64_DECODE_BINARY(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_null_alphabet"
+description = "NULL alphabet next to a set one. The constant argument is a literal per call, as Snowflake requires. Catches: NULL read as the default alphabet"
+columns = [{ name = "a1", type = "VARCHAR" }, { name = "a2", type = "VARCHAR" }]
+rows = [
+ ["'YQ=='", "'YQ=='"],
+]
+query = "SELECT TRY_BASE64_DECODE_BINARY(a1, NULL) AS r1, TRY_BASE64_DECODE_BINARY(a2, '+/=') AS r2 FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_empty"
+description = "The empty string. Catches: NULL for empty input instead of an empty value"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["''"],
+]
+query = "SELECT TRY_BASE64_DECODE_BINARY(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary", "empty"]
diff --git a/cases/translation/functions/try-base64-decode-string.toml b/cases/translation/functions/try-base64-decode-string.toml
new file mode 100644
index 000000000..e8ca82c62
--- /dev/null
+++ b/cases/translation/functions/try-base64-decode-string.toml
@@ -0,0 +1,73 @@
+category = "translation"
+construct = "sf.functions.try-base64-decode-string"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_varchar"
+description = "TRY_BASE64_DECODE_STRING of valid Base64. Catches: wrong bytes or Latin-1 text for multi-byte input"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'w7zigqw='"],
+ ["'YWJj'"],
+]
+query = "SELECT TRY_BASE64_DECODE_STRING(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_invalid"
+description = "Invalid Base64 next to valid Base64. Catches: an error for the invalid row (the non-TRY behaviour) or NULL for the valid row too"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'ab$c'"],
+ ["'YQ'"],
+ ["'YQ=='"],
+]
+query = "SELECT TRY_BASE64_DECODE_STRING(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_alphabet"
+description = "A custom alphabet. The constant argument is a literal per call, as Snowflake requires. Catches: the alphabet ignored (NULL for -_ input)"
+columns = [{ name = "a1", type = "VARCHAR" }]
+rows = [
+ ["'fn5-Pw~~'"],
+]
+query = "SELECT TRY_BASE64_DECODE_STRING(a1, '-_~') AS r1 FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_input"
+description = "NULL input next to a set one. Catches: NULL decoded as empty"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'YQ=='"],
+]
+query = "SELECT TRY_BASE64_DECODE_STRING(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_null_alphabet"
+description = "NULL alphabet next to a set one. The constant argument is a literal per call, as Snowflake requires. Catches: NULL read as the default alphabet"
+columns = [{ name = "a1", type = "VARCHAR" }, { name = "a2", type = "VARCHAR" }]
+rows = [
+ ["'YQ=='", "'YQ=='"],
+]
+query = "SELECT TRY_BASE64_DECODE_STRING(a1, NULL) AS r1, TRY_BASE64_DECODE_STRING(a2, '+/=') AS r2 FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_empty"
+description = "The empty string. Catches: NULL for empty input instead of an empty value"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["''"],
+]
+query = "SELECT TRY_BASE64_DECODE_STRING(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary", "empty"]
diff --git a/cases/translation/functions/try-cast.toml b/cases/translation/functions/try-cast.toml
new file mode 100644
index 000000000..439d6c2ca
--- /dev/null
+++ b/cases/translation/functions/try-cast.toml
@@ -0,0 +1,60 @@
+category = "translation"
+construct = "sf.functions.try-cast"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_valid"
+description = "Strings that convert. Catches: NULL returned for a valid string"
+columns = [{ name = "n", type = "VARCHAR" }, { name = "d", type = "VARCHAR" }, { name = "b", type = "VARCHAR" }]
+rows = [
+ ["'42'", "'2024-02-29'", "'off'"],
+]
+query = "SELECT TRY_CAST(n AS NUMBER) AS n, TRY_CAST(d AS DATE) AS d, TRY_CAST(b AS BOOLEAN) AS b FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_invalid"
+description = "Strings that do not convert, including 30 February. Catches: an error raised, or the date rolled over to March"
+columns = [{ name = "n", type = "VARCHAR" }, { name = "d", type = "VARCHAR" }, { name = "b", type = "VARCHAR" }]
+rows = [
+ ["'4x'", "'2023-02-30'", "'maybe'"],
+]
+query = "SELECT TRY_CAST(n AS NUMBER) AS n, TRY_CAST(d AS DATE) AS d, TRY_CAST(b AS BOOLEAN) AS b FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_precision"
+description = "A string with more digits than the target precision. Catches: an error raised, or the value truncated"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'1234'"],
+ ["'123'"],
+]
+query = "SELECT TRY_CAST(v AS NUMBER(3,0)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t004_number_input"
+compile_error = true
+description = "A NUMBER argument; the page says TRY_CAST only works for string expressions. Catches: the number converted as CAST would"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT TRY_CAST(x AS VARCHAR) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t005_null_source_string_expr"
+description = "A NULL string. Catches: an error"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TRY_CAST(v AS NUMBER) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/try-decrypt-raw.toml b/cases/translation/functions/try-decrypt-raw.toml
new file mode 100644
index 000000000..a7c97a147
--- /dev/null
+++ b/cases/translation/functions/try-decrypt-raw.toml
@@ -0,0 +1,36 @@
+category = "translation"
+construct = "sf.functions.try-decrypt-raw"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_round_trip"
+description = "ENCRYPT_RAW output decrypted with its own fields. Catches: the ciphertext returned unchanged"
+columns = [{ name = "v", type = "BINARY" }, { name = "k", type = "BINARY" }, { name = "iv", type = "BINARY" }, { name = "a", type = "BINARY" }]
+rows = [
+ ["'48656C6C6F'", "'000102030405060708090A0B0C0D0E0F'", "'A0A1A2A3A4A5A6A7A8A9AAAB'", "'AA'"],
+]
+query = "SELECT TO_VARCHAR(TRY_DECRYPT_RAW(AS_BINARY(GET(e, 'ciphertext')), k, AS_BINARY(GET(e, 'iv')), a, 'AES-GCM', AS_BINARY(GET(e, 'tag'))), 'HEX') AS r FROM (SELECT ordinal, k, a, ENCRYPT_RAW(v, k, iv, a, 'AES-GCM') AS e FROM input)"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_wrong_aad"
+description = "A different AAD, which gives NULL. Catches: an error raised, or the bytes returned anyway"
+columns = [{ name = "v", type = "BINARY" }, { name = "k", type = "BINARY" }, { name = "iv", type = "BINARY" }, { name = "a", type = "BINARY" }]
+rows = [
+ ["'48656C6C6F'", "'000102030405060708090A0B0C0D0E0F'", "'A0A1A2A3A4A5A6A7A8A9AAAB'", "'AA'"],
+]
+query = "SELECT TRY_DECRYPT_RAW(AS_BINARY(GET(e, 'ciphertext')), k, AS_BINARY(GET(e, 'iv')), 'BB'::BINARY, 'AES-GCM', AS_BINARY(GET(e, 'tag'))) IS NULL AS n FROM (SELECT ordinal, k, a, ENCRYPT_RAW(v, k, iv, a, 'AES-GCM') AS e FROM input)"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_value_to_decrypt"
+description = "A NULL ciphertext; the page does not say; the recording decides. Catches: an error where Snowflake returns NULL, or the reverse"
+columns = [{ name = "v", type = "BINARY" }, { name = "k", type = "BINARY" }, { name = "iv", type = "BINARY" }, { name = "a", type = "BINARY" }]
+rows = [
+ ["'48656C6C6F'", "'000102030405060708090A0B0C0D0E0F'", "'A0A1A2A3A4A5A6A7A8A9AAAB'", "'AA'"],
+]
+query = "SELECT TRY_DECRYPT_RAW(NULL::BINARY, k, AS_BINARY(GET(e, 'iv')), a, 'AES-GCM', AS_BINARY(GET(e, 'tag'))) IS NULL AS n FROM (SELECT ordinal, k, a, ENCRYPT_RAW(v, k, iv, a, 'AES-GCM') AS e FROM input)"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/try-decrypt.toml b/cases/translation/functions/try-decrypt.toml
new file mode 100644
index 000000000..aa1d422e1
--- /dev/null
+++ b/cases/translation/functions/try-decrypt.toml
@@ -0,0 +1,37 @@
+category = "translation"
+construct = "sf.functions.try-decrypt"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_round_trip"
+description = "Text encrypted and decrypted, converted back from BINARY with utf-8. Catches: the bytes returned as hex text"
+columns = [{ name = "v", type = "VARCHAR" }, { name = "p", type = "VARCHAR" }]
+rows = [
+ ["'secret text'", "'pass phrase 1'"],
+ ["''", "''"],
+]
+query = "SELECT ordinal, TO_VARCHAR(TRY_DECRYPT(ENCRYPT(v, p), p), 'utf-8') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_mismatch"
+description = "A wrong passphrase and a wrong AAD, which give NULL. Catches: an error raised, or garbage bytes"
+columns = [{ name = "v", type = "VARCHAR" }, { name = "p", type = "VARCHAR" }]
+rows = [
+ ["'secret text'", "'pass phrase 1'"],
+]
+query = "SELECT TRY_DECRYPT(ENCRYPT(v, p), 'other') IS NULL AS pass, TRY_DECRYPT(ENCRYPT(v, p, 'a1'), p, 'a2') IS NULL AS aad FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_value_to_decrypt"
+description = "A NULL value; the page does not say; the recording decides. Catches: an error where Snowflake returns NULL, or the reverse"
+columns = [{ name = "v", type = "VARCHAR" }, { name = "p", type = "VARCHAR" }]
+rows = [
+ ["NULL", "'p'"],
+]
+query = "SELECT TRY_DECRYPT(v::BINARY, p) IS NULL AS n FROM input"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/try-hex-decode-binary.toml b/cases/translation/functions/try-hex-decode-binary.toml
new file mode 100644
index 000000000..376cacc02
--- /dev/null
+++ b/cases/translation/functions/try-hex-decode-binary.toml
@@ -0,0 +1,53 @@
+category = "translation"
+construct = "sf.functions.try-hex-decode-binary"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_varchar"
+description = "TRY_HEX_DECODE_BINARY of upper- and lower-case hex digits. Catches: lower-case digits rejected, nibble order swapped, or UTF-8 read as Latin-1"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'00FF10'"],
+ ["'c3a9'"],
+ ["'7F80'"],
+]
+query = "SELECT TRY_HEX_DECODE_BINARY(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_invalid"
+description = "Invalid hex next to valid hex. Catches: an error (the non-TRY behaviour) instead of NULL, or a partial decode of the odd-length row"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'4G'"],
+ ["'414'"],
+ ["'4142'"],
+]
+query = "SELECT TRY_HEX_DECODE_BINARY(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_input"
+description = "NULL input next to a set one. Catches: NULL decoded as empty"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'41'"],
+]
+query = "SELECT TRY_HEX_DECODE_BINARY(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t004_empty"
+description = "The empty string. Catches: NULL or an error for empty input"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["''"],
+ ["'42'"],
+]
+query = "SELECT TRY_HEX_DECODE_BINARY(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary", "empty"]
diff --git a/cases/translation/functions/try-hex-decode-string.toml b/cases/translation/functions/try-hex-decode-string.toml
new file mode 100644
index 000000000..e9cbf971e
--- /dev/null
+++ b/cases/translation/functions/try-hex-decode-string.toml
@@ -0,0 +1,53 @@
+category = "translation"
+construct = "sf.functions.try-hex-decode-string"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_varchar"
+description = "TRY_HEX_DECODE_STRING of upper- and lower-case hex digits. Catches: lower-case digits rejected, nibble order swapped, or UTF-8 read as Latin-1"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'48C3A96C6C6F'"],
+ ["'e282ac'"],
+]
+query = "SELECT TRY_HEX_DECODE_STRING(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_invalid"
+description = "Invalid hex next to valid hex. Catches: an error (the non-TRY behaviour) instead of NULL, or a partial decode of the odd-length row, or bytes that are not UTF-8 (FF) decoded instead of NULL"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'4G'"],
+ ["'414'"],
+ ["'4142'"],
+ ["'FF'"],
+]
+query = "SELECT TRY_HEX_DECODE_STRING(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_input"
+description = "NULL input next to a set one. Catches: NULL decoded as empty"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'41'"],
+]
+query = "SELECT TRY_HEX_DECODE_STRING(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t004_empty"
+description = "The empty string. Catches: NULL or an error for empty input"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["''"],
+ ["'42'"],
+]
+query = "SELECT TRY_HEX_DECODE_STRING(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary", "empty"]
diff --git a/cases/translation/functions/try-parse-json.toml b/cases/translation/functions/try-parse-json.toml
new file mode 100644
index 000000000..2f4086cc0
--- /dev/null
+++ b/cases/translation/functions/try-parse-json.toml
@@ -0,0 +1,50 @@
+category = "translation"
+construct = "sf.functions.try-parse-json"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_valid"
+description = "Valid documents, including the text null. Catches: a JSON null turned into a SQL NULL"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'{\"a\":[1,2]}'"],
+ ["'null'"],
+]
+query = "SELECT TRY_PARSE_JSON(a) AS r, TRY_PARSE_JSON(a) IS NULL AS n FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_invalid"
+description = "Malformed JSON and duplicate keys under the default strict parameter. Catches: an error raised, or the duplicate accepted"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'{\"a\":}'"],
+ ["'{\"a\":1,\"a\":2}'"],
+]
+query = "SELECT TRY_PARSE_JSON(a) IS NULL AS n FROM input ORDER BY ordinal"
+order = "ordered"
+expected_divergence = "json_fidelity"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_duplicates_d"
+description = "Duplicate keys with the parameter 'd'. Catches: NULL returned, or the first value kept"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'{\"a\":1,\"a\":2}'"],
+]
+query = "SELECT TRY_PARSE_JSON(a, 'd') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_expr"
+description = "A SQL NULL. Catches: a JSON null returned"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TRY_PARSE_JSON(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/try-to-binary.toml b/cases/translation/functions/try-to-binary.toml
new file mode 100644
index 000000000..e38000ae9
--- /dev/null
+++ b/cases/translation/functions/try-to-binary.toml
@@ -0,0 +1,37 @@
+category = "translation"
+construct = "sf.functions.try-to-binary"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_valid"
+description = "Valid HEX and BASE64. Catches: NULL for valid input"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'4A4B'"],
+]
+query = "SELECT TRY_TO_BINARY(v) AS hex, TRY_TO_BINARY('Sks=', 'BASE64') AS b64 FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_invalid"
+description = "Invalid HEX, an odd number of HEX digits, and an unknown format name, which the page says gives NULL. Catches: an error raised"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'xyz'"],
+ ["'ABC'"],
+]
+query = "SELECT TRY_TO_BINARY(v) AS r, TRY_TO_BINARY('4A', 'OCTAL') AS f FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_string_expr"
+description = "A NULL string. Catches: empty bytes returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TRY_TO_BINARY(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/try-to-boolean.toml b/cases/translation/functions/try-to-boolean.toml
new file mode 100644
index 000000000..46f69217d
--- /dev/null
+++ b/cases/translation/functions/try-to-boolean.toml
@@ -0,0 +1,43 @@
+category = "translation"
+construct = "sf.functions.try-to-boolean"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_strings"
+description = "Accepted spellings in mixed case. Catches: only true and false accepted"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'YES'"],
+ ["'Off'"],
+ ["'t'"],
+ ["'n'"],
+ ["'1'"],
+ ["'0'"],
+]
+query = "SELECT TRY_TO_BOOLEAN(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_invalid"
+description = "Strings not in either list, including 2 and an empty string. Catches: an error raised, or 2 read as TRUE"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'maybe'"],
+ ["'2'"],
+ ["''"],
+]
+query = "SELECT TRY_TO_BOOLEAN(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_string_expr"
+description = "A NULL string. Catches: FALSE returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TRY_TO_BOOLEAN(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/try-to-date.toml b/cases/translation/functions/try-to-date.toml
new file mode 100644
index 000000000..aa5b7693e
--- /dev/null
+++ b/cases/translation/functions/try-to-date.toml
@@ -0,0 +1,50 @@
+category = "translation"
+construct = "sf.functions.try-to-date"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_strings"
+description = "An ISO date, and a date in a given format. Catches: the format ignored, or day and month swapped"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'2024-02-29'"],
+]
+query = "SELECT TRY_TO_DATE(v) AS iso, TRY_TO_DATE('29/02/2024', 'DD/MM/YYYY') AS fmt FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_epoch_units"
+description = "Integer strings just below and at the milliseconds boundary, which the page reads as seconds and milliseconds. Catches: every integer read as seconds, or as days"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'86400'"],
+ ["'31535999999'"],
+ ["'31536000000'"],
+]
+query = "SELECT TRY_TO_DATE(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t003_invalid"
+description = "30 February and text that is not a date. Catches: an error raised, or the date rolled over to March"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'2023-02-30'"],
+ ["'abc'"],
+]
+query = "SELECT TRY_TO_DATE(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_string_expr"
+description = "A NULL string. Catches: an error, or the epoch date"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TRY_TO_DATE(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/try-to-decfloat.toml b/cases/translation/functions/try-to-decfloat.toml
new file mode 100644
index 000000000..2329f46f0
--- /dev/null
+++ b/cases/translation/functions/try-to-decfloat.toml
@@ -0,0 +1,38 @@
+category = "translation"
+construct = "sf.functions.try-to-decfloat"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_valid"
+description = "Decimal strings with trailing zeros and an exponent, compared as text; whether the trailing zero is kept the page does not say, so the recording decides. Catches: NULL for valid input, or trailing zeros handled differently from Snowflake"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'1.10'"],
+ ["'-2.5e3'"],
+]
+query = "SELECT TRY_TO_DECFLOAT(v)::VARCHAR AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_invalid"
+description = "Text that is not a number, and inf, which the page says is not supported. Catches: an error raised, or infinity returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'abc'"],
+ ["'inf'"],
+]
+query = "SELECT TRY_TO_DECFLOAT(v) IS NULL AS n FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_string_expr"
+description = "A NULL string. Catches: zero returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TRY_TO_DECFLOAT(v)::VARCHAR AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/try-to-decimal.try-to-decimal.toml b/cases/translation/functions/try-to-decimal.try-to-decimal.toml
new file mode 100644
index 000000000..60a8df02a
--- /dev/null
+++ b/cases/translation/functions/try-to-decimal.try-to-decimal.toml
@@ -0,0 +1,49 @@
+category = "translation"
+construct = "sf.functions.try-to-decimal.try-to-decimal"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_scale"
+description = "A string with three fractional digits at scale 2. Catches: truncation (12.34)"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'12.345'"],
+ ["'-12.345'"],
+]
+query = "SELECT TRY_TO_DECIMAL(v, 10, 2) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t002_invalid"
+description = "Text that is not a number, and a value too wide for the precision. Catches: an error raised"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'abc'"],
+ ["'12345'"],
+]
+query = "SELECT TRY_TO_DECIMAL(v, 4, 0) IS NULL AS n FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_format"
+description = "A string with a currency sign and group separator parsed with a format model. Catches: the format ignored (NULL)"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'$1,234.50'"],
+]
+query = "SELECT TRY_TO_DECIMAL(v, '$9,999.99', 7, 2) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_string_expr"
+description = "A NULL string, which the page says gives NULL. Catches: zero returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TRY_TO_DECIMAL(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/try-to-decimal.try-to-number.toml b/cases/translation/functions/try-to-decimal.try-to-number.toml
new file mode 100644
index 000000000..4cab9e4be
--- /dev/null
+++ b/cases/translation/functions/try-to-decimal.try-to-number.toml
@@ -0,0 +1,49 @@
+category = "translation"
+construct = "sf.functions.try-to-decimal.try-to-number"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_scale"
+description = "A string with three fractional digits at scale 2. Catches: truncation (12.34)"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'12.345'"],
+ ["'-12.345'"],
+]
+query = "SELECT TRY_TO_NUMBER(v, 10, 2) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t002_invalid"
+description = "Text that is not a number, and a value too wide for the precision. Catches: an error raised"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'abc'"],
+ ["'12345'"],
+]
+query = "SELECT TRY_TO_NUMBER(v, 4, 0) IS NULL AS n FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_format"
+description = "A string with a currency sign and group separator parsed with a format model. Catches: the format ignored (NULL)"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'$1,234.50'"],
+]
+query = "SELECT TRY_TO_NUMBER(v, '$9,999.99', 7, 2) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_string_expr"
+description = "A NULL string, which the page says gives NULL. Catches: zero returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TRY_TO_NUMBER(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/try-to-decimal.try-to-numeric.toml b/cases/translation/functions/try-to-decimal.try-to-numeric.toml
new file mode 100644
index 000000000..59fc75746
--- /dev/null
+++ b/cases/translation/functions/try-to-decimal.try-to-numeric.toml
@@ -0,0 +1,49 @@
+category = "translation"
+construct = "sf.functions.try-to-decimal.try-to-numeric"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_scale"
+description = "A string with three fractional digits at scale 2. Catches: truncation (12.34)"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'12.345'"],
+ ["'-12.345'"],
+]
+query = "SELECT TRY_TO_NUMERIC(v, 10, 2) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t002_invalid"
+description = "Text that is not a number, and a value too wide for the precision. Catches: an error raised"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'abc'"],
+ ["'12345'"],
+]
+query = "SELECT TRY_TO_NUMERIC(v, 4, 0) IS NULL AS n FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_format"
+description = "A string with a currency sign and group separator parsed with a format model. Catches: the format ignored (NULL)"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'$1,234.50'"],
+]
+query = "SELECT TRY_TO_NUMERIC(v, '$9,999.99', 7, 2) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_string_expr"
+description = "A NULL string, which the page says gives NULL. Catches: zero returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TRY_TO_NUMERIC(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/try-to-double.toml b/cases/translation/functions/try-to-double.toml
new file mode 100644
index 000000000..3a297ee85
--- /dev/null
+++ b/cases/translation/functions/try-to-double.toml
@@ -0,0 +1,50 @@
+category = "translation"
+construct = "sf.functions.try-to-double"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_valid"
+description = "Scientific notation and a special value. Catches: the special value rejected"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'1.5e2'"],
+ ["'-inf'"],
+]
+query = "SELECT TRY_TO_DOUBLE(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_invalid"
+description = "Text that is not a number. Catches: an error raised"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'abc'"],
+ ["'1.2.3'"],
+]
+query = "SELECT TRY_TO_DOUBLE(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_number_input"
+compile_error = true
+description = "A NUMBER argument; the page says the function only accepts string expressions. Catches: the number converted"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT TRY_TO_DOUBLE(x) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t004_null_string_expr"
+description = "A NULL string. Catches: zero returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TRY_TO_DOUBLE(v) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/try-to-geography.toml b/cases/translation/functions/try-to-geography.toml
new file mode 100644
index 000000000..39f68e865
--- /dev/null
+++ b/cases/translation/functions/try-to-geography.toml
@@ -0,0 +1,52 @@
+category = "translation"
+construct = "sf.functions.try-to-geography"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_formats"
+description = "The same point as WKT, WKB hex, EWKT and EWKB with SRID 4326, and GeoJSON. Catches: a format rejected or misread, or longitude and latitude swapped in one of them"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'POINT(10 20)'"],
+ ["'010100000000000000000024400000000000003440'"],
+ ["'SRID=4326;POINT(10 20)'"],
+ ["'0101000020E610000000000000000024400000000000003440'"],
+ ["'{\"type\":\"Point\",\"coordinates\":[10,20]}'"],
+]
+query = "SELECT ordinal, ST_ASWKT(TRY_TO_GEOGRAPHY(v)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_failures"
+description = "Text in none of the formats and EWKT with SRID 3857, which the page says give NULL. Catches: an error raised, or the SRID 3857 point accepted"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'not a shape'"],
+ ["'SRID=3857;POINT(10 20)'"],
+]
+query = "SELECT ordinal, TRY_TO_GEOGRAPHY(v) IS NULL AS n FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_allow_invalid"
+description = "A self-intersecting polygon with allow_invalid TRUE. Catches: NULL returned for the bow tie despite allow_invalid"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'POLYGON((0 0,10 10,10 0,0 10,0 0))'"],
+]
+query = "SELECT ST_ISVALID(TRY_TO_GEOGRAPHY(v, TRUE)) AS valid FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_varchar_expression"
+description = "A NULL text; the page does not say; the recording decides. Catches: an error"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TRY_TO_GEOGRAPHY(v) IS NULL AS n FROM input"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/try-to-geometry.toml b/cases/translation/functions/try-to-geometry.toml
new file mode 100644
index 000000000..8f39be426
--- /dev/null
+++ b/cases/translation/functions/try-to-geometry.toml
@@ -0,0 +1,49 @@
+category = "translation"
+construct = "sf.functions.try-to-geometry"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_formats"
+description = "The same point as WKT, WKB hex and GeoJSON, with SRID 0. Catches: a format rejected, SRID 4326 assumed, or longitude and latitude swapped"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'POINT(10 20)'"],
+ ["'010100000000000000000024400000000000003440'"],
+ ["'{\"type\":\"Point\",\"coordinates\":[10,20]}'"],
+]
+query = "SELECT ordinal, ST_ASWKT(TRY_TO_GEOMETRY(v)) AS r, ST_SRID(TRY_TO_GEOMETRY(v)) AS srid FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_failure"
+description = "Text in none of the formats, which the page says gives NULL. Catches: an error raised"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'not a shape'"],
+]
+query = "SELECT TRY_TO_GEOMETRY(v) IS NULL AS n FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_allow_invalid"
+description = "A self-intersecting polygon with an srid and allow_invalid TRUE. Catches: NULL returned for the bow tie despite allow_invalid, or TRUE read as the srid"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'POLYGON((0 0,10 10,10 0,0 10,0 0))'"],
+]
+query = "SELECT ST_ISVALID(TRY_TO_GEOMETRY(v, 3857, TRUE)) AS valid, ST_SRID(TRY_TO_GEOMETRY(v, 3857, TRUE)) AS srid FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_varchar_expression"
+description = "A NULL text; the page does not say; the recording decides. Catches: an error"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TRY_TO_GEOMETRY(v) IS NULL AS n FROM input"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/try-to-ipaddress.toml b/cases/translation/functions/try-to-ipaddress.toml
new file mode 100644
index 000000000..e873e57f4
--- /dev/null
+++ b/cases/translation/functions/try-to-ipaddress.toml
@@ -0,0 +1,37 @@
+category = "translation"
+construct = "sf.functions.try-to-ipaddress"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_valid"
+description = "A valid IPv6 address, compared as text. Catches: NULL for a valid address"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'2001:DB8::1'"],
+]
+query = "SELECT TO_VARCHAR(TRY_TO_IPADDRESS(v)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_invalid"
+description = "An octet above 255 and text that is not an address. Catches: an error raised"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'256.1.1.1'"],
+ ["'abc'"],
+]
+query = "SELECT TRY_TO_IPADDRESS(v) IS NULL AS n FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_string_expr"
+description = "A NULL string. Catches: an error"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_VARCHAR(TRY_TO_IPADDRESS(v)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/try-to-time.toml b/cases/translation/functions/try-to-time.toml
new file mode 100644
index 000000000..9a80f4ce2
--- /dev/null
+++ b/cases/translation/functions/try-to-time.toml
@@ -0,0 +1,49 @@
+category = "translation"
+construct = "sf.functions.try-to-time"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_strings"
+description = "A time with nine fractional digits, and a 12-hour time with a format. Catches: the fraction truncated to milliseconds, or the afternoon read as morning"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'13:14:15.123456789'"],
+]
+query = "SELECT TO_VARCHAR(TRY_TO_TIME(v), 'HH24:MI:SS.FF9') AS iso, TO_VARCHAR(TRY_TO_TIME('01:02 PM', 'HH12:MI AM'), 'HH24:MI:SS.FF9') AS fmt FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_epoch_units"
+description = "Integer strings read as seconds and as milliseconds after midnight of the epoch. Catches: every integer read as seconds"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'3661'"],
+ ["'31536003661'"],
+]
+query = "SELECT TO_VARCHAR(TRY_TO_TIME(v), 'HH24:MI:SS.FF9') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t003_invalid"
+description = "Hour 25 and text that is not a time. Catches: an error raised, or the hour wrapped"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'25:00:00'"],
+ ["'abc'"],
+]
+query = "SELECT TO_VARCHAR(TRY_TO_TIME(v), 'HH24:MI:SS.FF9') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_string_expr"
+description = "A NULL string. Catches: midnight returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TO_VARCHAR(TRY_TO_TIME(v), 'HH24:MI:SS.FF9') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/try-to-uuid.toml b/cases/translation/functions/try-to-uuid.toml
new file mode 100644
index 000000000..4949c0ef8
--- /dev/null
+++ b/cases/translation/functions/try-to-uuid.toml
@@ -0,0 +1,37 @@
+category = "translation"
+construct = "sf.functions.try-to-uuid"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_valid"
+description = "A UUID in lower case, compared as text. Catches: NULL for a valid UUID"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'6ba7b810-9dad-11d1-80b4-00c04fd430c8'"],
+]
+query = "SELECT TRY_TO_UUID(v)::VARCHAR AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_invalid"
+description = "A string one digit short and a string with a non-hex digit. Catches: an error raised"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["'6BA7B810-9DAD-11D1-80B4-00C04FD430C'"],
+ ["'GBA7B810-9DAD-11D1-80B4-00C04FD430C8'"],
+]
+query = "SELECT TRY_TO_UUID(v) IS NULL AS n FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_string_expr"
+description = "A NULL string. Catches: a nil UUID returned"
+columns = [{ name = "v", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TRY_TO_UUID(v)::VARCHAR AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/typeof.toml b/cases/translation/functions/typeof.toml
new file mode 100644
index 000000000..369be69da
--- /dev/null
+++ b/cases/translation/functions/typeof.toml
@@ -0,0 +1,45 @@
+category = "translation"
+construct = "sf.functions.typeof"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_json"
+description = "TYPEOF over each JSON kind; the page warns an integer may report DECIMAL, so the recording decides the integer's name. Catches: the JSON null named NULL instead of a distinct name, or a decimal and an exponent number given the same name"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'[1]'"],
+ ["'{\"a\":1}'"],
+ ["'\"x\"'"],
+ ["'\"2024-02-29\"'"],
+ ["'5'"],
+ ["'1.50'"],
+ ["'1.5e0'"],
+ ["'true'"],
+ ["'null'"],
+]
+query = "SELECT TYPEOF(PARSE_JSON(a)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+expected_divergence = "json_fidelity"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_typed"
+description = "TYPEOF over typed values cast to VARIANT. Catches: all timestamps given one name, or a DATE reported as VARCHAR"
+columns = [{ name = "d", type = "DATE" }, { name = "t", type = "TIME" }, { name = "n", type = "TIMESTAMP_NTZ" }, { name = "z", type = "TIMESTAMP_TZ" }, { name = "l", type = "TIMESTAMP_LTZ" }, { name = "b", type = "BINARY" }, { name = "f", type = "DOUBLE" }, { name = "k", type = "NUMBER(10,2)" }]
+rows = [
+ ["'2024-02-29'", "'13:14:15.123'", "'2024-02-29 13:14:15.123'", "'2024-02-29 13:14:15.123 -08:00'", "'2024-02-29 13:14:15.123'", "'ABCD'", "1.5", "2.50"],
+]
+query = "SELECT TYPEOF(d::VARIANT) AS d, TYPEOF(t::VARIANT) AS t, TYPEOF(n::VARIANT) AS n, TYPEOF(z::VARIANT) AS z, TYPEOF(l::VARIANT) AS l, TYPEOF(b::VARIANT) AS b, TYPEOF(f::VARIANT) AS f, TYPEOF(k::VARIANT) AS k FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_expr"
+description = "A SQL NULL. Catches: the string NULL_VALUE returned for a SQL NULL, or an error"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT TYPEOF(PARSE_JSON(a)) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/unicode.toml b/cases/translation/functions/unicode.toml
new file mode 100644
index 000000000..96f9d7dba
--- /dev/null
+++ b/cases/translation/functions/unicode.toml
@@ -0,0 +1,41 @@
+category = "translation"
+construct = "sf.functions.unicode"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_varchar"
+description = "UNICODE of multi-byte and multi-character strings. Catches: the first UTF-8 byte (195 for \u00e9) instead of the code point, or the last character"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'\u00e9'"],
+ ["'\u20acx'"],
+ ["'cde'"],
+ ["'\u2744'"],
+]
+query = "SELECT UNICODE(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_null_input"
+description = "NULL input next to a set one. Catches: NULL read as an empty string (0 instead of NULL)"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'a'"],
+]
+query = "SELECT UNICODE(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t003_empty"
+description = "The empty string, which the example shows as 0. Catches: NULL or an error for an empty string"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["''"],
+ ["'z'"],
+]
+query = "SELECT UNICODE(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary", "empty"]
diff --git a/cases/translation/functions/uniform.toml b/cases/translation/functions/uniform.toml
new file mode 100644
index 000000000..2ea3d8f65
--- /dev/null
+++ b/cases/translation/functions/uniform.toml
@@ -0,0 +1,47 @@
+category = "translation"
+construct = "sf.functions.uniform"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_integers"
+description = "Integer bounds over 1000 rows: the range, every value reached, and integral results. Catches: the upper bound excluded, or a fraction returned"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT MIN(u) AS lo, MAX(u) AS hi, COUNT(DISTINCT u) AS n, COUNT_IF(u <> ROUND(u)) AS fractions FROM (SELECT UNIFORM(1, 6, RANDOM()) AS u FROM TABLE(GENERATOR(ROWCOUNT => 1000)))"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_float"
+description = "A floating-point bound over 1000 rows. Catches: integers returned"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT MIN(u) >= 0 AND MAX(u) <= 1 AS in_range, COUNT_IF(u <> ROUND(u)) > 900 AS fractional FROM (SELECT UNIFORM(0::FLOAT, 1::FLOAT, RANDOM()) AS u FROM TABLE(GENERATOR(ROWCOUNT => 1000)))"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_constant_gen"
+description = "A constant generator, which gives the same value on every row. Catches: a fresh value per row"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT COUNT(DISTINCT UNIFORM(1, 1000, 7)) AS n FROM TABLE(GENERATOR(ROWCOUNT => 1000))"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_gen"
+description = "A NULL generator; the page does not say; the recording decides. Catches: a value in range where Snowflake returns NULL, or the reverse"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT UNIFORM(1, 6, NULL) AS r FROM input"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/upper.toml b/cases/translation/functions/upper.toml
new file mode 100644
index 000000000..ea2e26a29
--- /dev/null
+++ b/cases/translation/functions/upper.toml
@@ -0,0 +1,49 @@
+category = "translation"
+construct = "sf.functions.upper"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_varchar"
+description = "UPPER of the page's mixed-script example. Catches: ASCII-only upper-casing, or \u00df kept instead of SS"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'1\u010d2\u04293\u00df4\u0118!-?abc@'"],
+ ["'\u00c4\u00e4\u00d6\u00f6\u00df\u00dc\u00fc'"],
+]
+query = "SELECT UPPER(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_collation"
+description = "UPPER under the Turkish collation, as the page shows. Catches: the collation ignored (I instead of \u0130)"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'i'"],
+]
+query = "SELECT UPPER(a COLLATE 'tr') AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_expr"
+description = "NULL string. Catches: NULL read as empty"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'a'"],
+]
+query = "SELECT UPPER(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t004_empty"
+description = "The empty string. Catches: NULL for the empty string"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["''"],
+]
+query = "SELECT UPPER(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary", "empty"]
diff --git a/cases/translation/functions/uuid-string.toml b/cases/translation/functions/uuid-string.toml
new file mode 100644
index 000000000..87366ff2a
--- /dev/null
+++ b/cases/translation/functions/uuid-string.toml
@@ -0,0 +1,39 @@
+category = "translation"
+construct = "sf.functions.uuid-string"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_random"
+description = "Properties of the random UUID: length, version and variant digits, lower case, and a new value per call. Catches: a constant folded once per query, another version, or upper case"
+columns = [{ name = "k", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+ ["2"],
+]
+query = "SELECT LENGTH(u) AS n, SUBSTR(u, 15, 1) AS ver, SUBSTR(u, 20, 1) IN ('8', '9', 'a', 'b') AS variant, u = LOWER(u) AS lower_case, u <> UUID_STRING() AS fresh FROM (SELECT ordinal, UUID_STRING() AS u FROM input) ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_named"
+description = "The page's version 5 example, and a second name. Catches: version 3 (MD5) or a random UUID, or the namespace and name swapped"
+columns = [{ name = "ns", type = "VARCHAR" }, { name = "nm", type = "VARCHAR" }]
+rows = [
+ ["'fe971b24-9572-4005-b22f-351e9c09274d'", "'foo'"],
+ ["'fe971b24-9572-4005-b22f-351e9c09274d'", "'bar'"],
+]
+query = "SELECT UUID_STRING(ns, nm) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_name"
+description = "NULL name for the version 5 form. Catches: a random UUID or the empty-name UUID instead of NULL"
+columns = [{ name = "nm", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+ ["'x'"],
+]
+query = "SELECT UUID_STRING('fe971b24-9572-4005-b22f-351e9c09274d', nm) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/var-pop.var-pop-over.toml b/cases/translation/functions/var-pop.var-pop-over.toml
new file mode 100644
index 000000000..4d35dd3bd
--- /dev/null
+++ b/cases/translation/functions/var-pop.var-pop-over.toml
@@ -0,0 +1,137 @@
+category = "translation"
+construct = "sf.functions.var-pop.var-pop-over"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_partition"
+description = "Partitions of different sizes in mixed input order. Catches: the whole input aggregated instead of the partition, or a running value"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2", "20"],
+ ["'q'", "5", "3"],
+ ["'p'", "1", "10"],
+ ["'p'", "3", "1"],
+ ["'q'", "1", "7"],
+ ["'p'", "2", "5"],
+]
+query = "SELECT VAR_POP(x) OVER (PARTITION BY g) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_empty_over"
+description = "An empty OVER clause. Catches: a running (ordered) value instead of the whole input"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2", "20"],
+ ["'q'", "5", "3"],
+ ["'p'", "1", "10"],
+ ["'p'", "3", "1"],
+ ["'q'", "1", "7"],
+ ["'p'", "2", "5"],
+]
+query = "SELECT VAR_POP(x) OVER () AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_order_ties"
+description = "ORDER BY with tied keys (two rows with k = 2) and no frame: with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Catches: a ROWS frame (the tied rows get different values) or the whole partition"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2", "20"],
+ ["'q'", "5", "3"],
+ ["'p'", "1", "10"],
+ ["'p'", "3", "1"],
+ ["'q'", "1", "7"],
+ ["'p'", "2", "5"],
+]
+query = "SELECT VAR_POP(x) OVER (PARTITION BY g ORDER BY k) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_order_desc"
+description = "DESC ordering with a running ROWS frame. Catches: DESC ignored (the running value starts from the smallest key)"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2", "20"],
+ ["'q'", "5", "3"],
+ ["'p'", "1", "10"],
+ ["'p'", "3", "1"],
+ ["'q'", "1", "7"],
+ ["'p'", "2", "5"],
+]
+query = "SELECT VAR_POP(x) OVER (PARTITION BY g ORDER BY k DESC, x ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_nulls_first_frame"
+description = "A NULL ordering key placed first (not the ASC default) and a one-row-back sliding frame. Catches: NULLS FIRST ignored (the NULL-key row last), or the frame read as RANGE or reaching forward"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "3", "1"],
+ ["'p'", "NULL", "4"],
+ ["'p'", "1", "10"],
+ ["'p'", "2", "6"],
+]
+query = "SELECT VAR_POP(x) OVER (PARTITION BY g ORDER BY k ASC NULLS FIRST ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t006_null_expr1"
+description = "NULL values inside partitions, and a partition with only NULL. Catches: NULL read as 0 (a different count and value) or the NULL-only partition not NULL"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "1", "NULL"],
+ ["'p'", "2", "6"],
+ ["'q'", "1", "NULL"],
+ ["'p'", "3", "2"],
+]
+query = "SELECT VAR_POP(x) OVER (PARTITION BY g) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_null_partition_key"
+description = "NULL partition keys, which form one partition. Catches: NULL-keyed rows each in their own partition or dropped"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL", "1", "4"],
+ ["'p'", "1", "9"],
+ ["NULL", "2", "2"],
+]
+query = "SELECT VAR_POP(x) OVER (PARTITION BY g) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t008_distinct_order"
+compile_error = true
+description = "DISTINCT with ORDER BY in the OVER clause, which the page forbids. Catches: a running DISTINCT value returned instead of the compilation error"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "1", "2"],
+ ["'p'", "2", "2"],
+ ["'p'", "3", "6"],
+ ["'q'", "1", "1"],
+]
+query = "SELECT VAR_POP(DISTINCT x) OVER (PARTITION BY g ORDER BY k) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t009_distinct_partition"
+description = "DISTINCT without ORDER BY. Catches: DISTINCT ignored"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "1", "2"],
+ ["'p'", "2", "2"],
+ ["'p'", "3", "6"],
+ ["'q'", "1", "1"],
+]
+query = "SELECT VAR_POP(DISTINCT x) OVER (PARTITION BY g) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["duplicates"]
diff --git a/cases/translation/functions/var-pop.var-pop.toml b/cases/translation/functions/var-pop.var-pop.toml
new file mode 100644
index 000000000..8b3225b03
--- /dev/null
+++ b/cases/translation/functions/var-pop.var-pop.toml
@@ -0,0 +1,106 @@
+category = "translation"
+construct = "sf.functions.var-pop.var-pop"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_number"
+description = "Groups of 8, 2 and 1 rows (population variance 4 and sample 32/7 for the first). Catches: the sample statistic, or the standard deviation"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2"],
+ ["'q'", "1"],
+ ["'r'", "6"],
+ ["'p'", "4"],
+ ["'q'", "3"],
+ ["'p'", "4"],
+ ["'p'", "4"],
+ ["'p'", "5"],
+ ["'p'", "5"],
+ ["'p'", "7"],
+ ["'p'", "9"],
+]
+query = "SELECT g, VAR_POP(x) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_single_row"
+description = "A group with a single row. Catches: NULL for one row instead of 0"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2"],
+ ["'q'", "1"],
+ ["'r'", "6"],
+ ["'p'", "4"],
+ ["'q'", "3"],
+ ["'p'", "4"],
+ ["'p'", "4"],
+ ["'p'", "5"],
+ ["'p'", "5"],
+ ["'p'", "7"],
+ ["'p'", "9"],
+]
+query = "SELECT VAR_POP(x) AS r FROM input WHERE g = 'r'"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t003_varchar"
+description = "Numeric text, which the page says is cast to FLOAT. Catches: text rejected or read as NUMBER"
+columns = [{ name = "x", type = "VARCHAR" }]
+rows = [
+ ["'1'"],
+ ["'2.5'"],
+ ["'6'"],
+]
+query = "SELECT VAR_POP(x) AS r FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_distinct"
+description = "DISTINCT on a group with repeated 4s and 5s. Catches: DISTINCT ignored"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2"],
+ ["'q'", "1"],
+ ["'r'", "6"],
+ ["'p'", "4"],
+ ["'q'", "3"],
+ ["'p'", "4"],
+ ["'p'", "4"],
+ ["'p'", "5"],
+ ["'p'", "5"],
+ ["'p'", "7"],
+ ["'p'", "9"],
+]
+query = "SELECT g, VAR_POP(DISTINCT x) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["duplicates"]
+
+[[case]]
+id = "t005_null_expr1"
+description = "NULL rows beside values, and a group of only NULL. Catches: NULL read as 0 (n and the mean change) or the all-NULL group not NULL"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "1"],
+ ["'q'", "NULL"],
+ ["'p'", "NULL"],
+ ["'q'", "NULL"],
+ ["'p'", "5"],
+ ["'p'", "3"],
+]
+query = "SELECT g, VAR_POP(x) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_empty_input"
+description = "No rows. Catches: an empty input giving 0"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["5"],
+]
+query = "SELECT VAR_POP(x) AS r FROM input WHERE ordinal < 0"
+order = "ordered"
+tags = ["empty-input"]
diff --git a/cases/translation/functions/var-samp.var-samp-over.toml b/cases/translation/functions/var-samp.var-samp-over.toml
new file mode 100644
index 000000000..71fa008a1
--- /dev/null
+++ b/cases/translation/functions/var-samp.var-samp-over.toml
@@ -0,0 +1,123 @@
+category = "translation"
+construct = "sf.functions.var-samp.var-samp-over"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_partition"
+description = "Partitions of different sizes in mixed input order. Catches: the whole input aggregated instead of the partition, or a running value"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2", "20"],
+ ["'q'", "5", "3"],
+ ["'p'", "1", "10"],
+ ["'p'", "3", "1"],
+ ["'q'", "1", "7"],
+ ["'p'", "2", "5"],
+]
+query = "SELECT VAR_SAMP(x) OVER (PARTITION BY g) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_empty_over"
+description = "An empty OVER clause. Catches: a running (ordered) value instead of the whole input"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2", "20"],
+ ["'q'", "5", "3"],
+ ["'p'", "1", "10"],
+ ["'p'", "3", "1"],
+ ["'q'", "1", "7"],
+ ["'p'", "2", "5"],
+]
+query = "SELECT VAR_SAMP(x) OVER () AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_order_ties"
+description = "ORDER BY with tied keys (two rows with k = 2) and no frame: with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Catches: a ROWS frame (the tied rows get different values) or the whole partition"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2", "20"],
+ ["'q'", "5", "3"],
+ ["'p'", "1", "10"],
+ ["'p'", "3", "1"],
+ ["'q'", "1", "7"],
+ ["'p'", "2", "5"],
+]
+query = "SELECT VAR_SAMP(x) OVER (PARTITION BY g ORDER BY k) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_order_desc"
+description = "DESC ordering with a running ROWS frame. Catches: DESC ignored (the running value starts from the smallest key)"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2", "20"],
+ ["'q'", "5", "3"],
+ ["'p'", "1", "10"],
+ ["'p'", "3", "1"],
+ ["'q'", "1", "7"],
+ ["'p'", "2", "5"],
+]
+query = "SELECT VAR_SAMP(x) OVER (PARTITION BY g ORDER BY k DESC, x ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_nulls_first_frame"
+description = "A NULL ordering key placed first (not the ASC default) and a one-row-back sliding frame. Catches: NULLS FIRST ignored (the NULL-key row last), or the frame read as RANGE or reaching forward"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "3", "1"],
+ ["'p'", "NULL", "4"],
+ ["'p'", "1", "10"],
+ ["'p'", "2", "6"],
+]
+query = "SELECT VAR_SAMP(x) OVER (PARTITION BY g ORDER BY k ASC NULLS FIRST ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t006_null_expr1"
+description = "NULL values inside partitions, and a partition with only NULL. Catches: NULL read as 0 (a different count and value) or the NULL-only partition not NULL"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "1", "NULL"],
+ ["'p'", "2", "6"],
+ ["'q'", "1", "NULL"],
+ ["'p'", "3", "2"],
+ ["'q'", "2", "NULL"],
+]
+query = "SELECT VAR_SAMP(x) OVER (PARTITION BY g) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_null_partition_key"
+description = "NULL partition keys, which form one partition. Catches: NULL-keyed rows each in their own partition or dropped"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL", "1", "4"],
+ ["'p'", "1", "9"],
+ ["NULL", "2", "2"],
+]
+query = "SELECT VAR_SAMP(x) OVER (PARTITION BY g) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t008_distinct_ignored"
+description = "DISTINCT as a window function, which the page says is allowed but ignored. Catches: DISTINCT applied (the repeated 2 counted once)"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "1", "2"],
+ ["'p'", "2", "2"],
+ ["'p'", "3", "6"],
+ ["'q'", "1", "1"],
+]
+query = "SELECT VAR_SAMP(DISTINCT x) OVER (PARTITION BY g) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["duplicates"]
diff --git a/cases/translation/functions/var-samp.var-samp.toml b/cases/translation/functions/var-samp.var-samp.toml
new file mode 100644
index 000000000..a128def19
--- /dev/null
+++ b/cases/translation/functions/var-samp.var-samp.toml
@@ -0,0 +1,106 @@
+category = "translation"
+construct = "sf.functions.var-samp.var-samp"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_number"
+description = "Groups of 8, 2 and 1 rows (population variance 4 and sample 32/7 for the first). Catches: the population statistic, or the standard deviation"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2"],
+ ["'q'", "1"],
+ ["'r'", "6"],
+ ["'p'", "4"],
+ ["'q'", "3"],
+ ["'p'", "4"],
+ ["'p'", "4"],
+ ["'p'", "5"],
+ ["'p'", "5"],
+ ["'p'", "7"],
+ ["'p'", "9"],
+]
+query = "SELECT g, VAR_SAMP(x) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_single_row"
+description = "A group with a single row. Catches: 0 for one row (the Oracle STDDEV/VARIANCE reading) instead of NULL"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2"],
+ ["'q'", "1"],
+ ["'r'", "6"],
+ ["'p'", "4"],
+ ["'q'", "3"],
+ ["'p'", "4"],
+ ["'p'", "4"],
+ ["'p'", "5"],
+ ["'p'", "5"],
+ ["'p'", "7"],
+ ["'p'", "9"],
+]
+query = "SELECT VAR_SAMP(x) AS r FROM input WHERE g = 'r'"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t003_varchar"
+description = "Numeric text, which the page says is cast to FLOAT. Catches: text rejected or read as NUMBER"
+columns = [{ name = "x", type = "VARCHAR" }]
+rows = [
+ ["'1'"],
+ ["'2.5'"],
+ ["'6'"],
+]
+query = "SELECT VAR_SAMP(x) AS r FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_distinct"
+description = "DISTINCT on a group with repeated 4s and 5s. Catches: DISTINCT ignored"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2"],
+ ["'q'", "1"],
+ ["'r'", "6"],
+ ["'p'", "4"],
+ ["'q'", "3"],
+ ["'p'", "4"],
+ ["'p'", "4"],
+ ["'p'", "5"],
+ ["'p'", "5"],
+ ["'p'", "7"],
+ ["'p'", "9"],
+]
+query = "SELECT g, VAR_SAMP(DISTINCT x) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["duplicates"]
+
+[[case]]
+id = "t005_null_expr1"
+description = "NULL rows beside values, and a group of only NULL. Catches: NULL read as 0 (n and the mean change) or the all-NULL group not NULL"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "1"],
+ ["'q'", "NULL"],
+ ["'p'", "NULL"],
+ ["'q'", "NULL"],
+ ["'p'", "5"],
+ ["'p'", "3"],
+]
+query = "SELECT g, VAR_SAMP(x) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_empty_input"
+description = "No rows. Catches: an empty input giving 0"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["5"],
+]
+query = "SELECT VAR_SAMP(x) AS r FROM input WHERE ordinal < 0"
+order = "ordered"
+tags = ["empty-input"]
diff --git a/cases/translation/functions/variance-pop.variance-pop-over.toml b/cases/translation/functions/variance-pop.variance-pop-over.toml
new file mode 100644
index 000000000..768ad9769
--- /dev/null
+++ b/cases/translation/functions/variance-pop.variance-pop-over.toml
@@ -0,0 +1,137 @@
+category = "translation"
+construct = "sf.functions.variance-pop.variance-pop-over"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_partition"
+description = "Partitions of different sizes in mixed input order. Catches: the whole input aggregated instead of the partition, or a running value"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2", "20"],
+ ["'q'", "5", "3"],
+ ["'p'", "1", "10"],
+ ["'p'", "3", "1"],
+ ["'q'", "1", "7"],
+ ["'p'", "2", "5"],
+]
+query = "SELECT VARIANCE_POP(x) OVER (PARTITION BY g) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_empty_over"
+description = "An empty OVER clause. Catches: a running (ordered) value instead of the whole input"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2", "20"],
+ ["'q'", "5", "3"],
+ ["'p'", "1", "10"],
+ ["'p'", "3", "1"],
+ ["'q'", "1", "7"],
+ ["'p'", "2", "5"],
+]
+query = "SELECT VARIANCE_POP(x) OVER () AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_order_ties"
+description = "ORDER BY with tied keys (two rows with k = 2) and no frame: with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Catches: a ROWS frame (the tied rows get different values) or the whole partition"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2", "20"],
+ ["'q'", "5", "3"],
+ ["'p'", "1", "10"],
+ ["'p'", "3", "1"],
+ ["'q'", "1", "7"],
+ ["'p'", "2", "5"],
+]
+query = "SELECT VARIANCE_POP(x) OVER (PARTITION BY g ORDER BY k) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_order_desc"
+description = "DESC ordering with a running ROWS frame. Catches: DESC ignored (the running value starts from the smallest key)"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2", "20"],
+ ["'q'", "5", "3"],
+ ["'p'", "1", "10"],
+ ["'p'", "3", "1"],
+ ["'q'", "1", "7"],
+ ["'p'", "2", "5"],
+]
+query = "SELECT VARIANCE_POP(x) OVER (PARTITION BY g ORDER BY k DESC, x ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_nulls_first_frame"
+description = "A NULL ordering key placed first (not the ASC default) and a one-row-back sliding frame. Catches: NULLS FIRST ignored (the NULL-key row last), or the frame read as RANGE or reaching forward"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "3", "1"],
+ ["'p'", "NULL", "4"],
+ ["'p'", "1", "10"],
+ ["'p'", "2", "6"],
+]
+query = "SELECT VARIANCE_POP(x) OVER (PARTITION BY g ORDER BY k ASC NULLS FIRST ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t006_null_expr1"
+description = "NULL values inside partitions, and a partition with only NULL. Catches: NULL read as 0 (a different count and value) or the NULL-only partition not NULL"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "1", "NULL"],
+ ["'p'", "2", "6"],
+ ["'q'", "1", "NULL"],
+ ["'p'", "3", "2"],
+]
+query = "SELECT VARIANCE_POP(x) OVER (PARTITION BY g) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_null_partition_key"
+description = "NULL partition keys, which form one partition. Catches: NULL-keyed rows each in their own partition or dropped"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL", "1", "4"],
+ ["'p'", "1", "9"],
+ ["NULL", "2", "2"],
+]
+query = "SELECT VARIANCE_POP(x) OVER (PARTITION BY g) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t008_distinct_order"
+compile_error = true
+description = "DISTINCT with ORDER BY in the OVER clause, which the page forbids. Catches: a running DISTINCT value returned instead of the compilation error"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "1", "2"],
+ ["'p'", "2", "2"],
+ ["'p'", "3", "6"],
+ ["'q'", "1", "1"],
+]
+query = "SELECT VARIANCE_POP(DISTINCT x) OVER (PARTITION BY g ORDER BY k) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t009_distinct_partition"
+description = "DISTINCT without ORDER BY. Catches: DISTINCT ignored"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "1", "2"],
+ ["'p'", "2", "2"],
+ ["'p'", "3", "6"],
+ ["'q'", "1", "1"],
+]
+query = "SELECT VARIANCE_POP(DISTINCT x) OVER (PARTITION BY g) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["duplicates"]
diff --git a/cases/translation/functions/variance-pop.variance-pop.toml b/cases/translation/functions/variance-pop.variance-pop.toml
new file mode 100644
index 000000000..f0bfc6920
--- /dev/null
+++ b/cases/translation/functions/variance-pop.variance-pop.toml
@@ -0,0 +1,106 @@
+category = "translation"
+construct = "sf.functions.variance-pop.variance-pop"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_number"
+description = "Groups of 8, 2 and 1 rows (population variance 4 and sample 32/7 for the first). Catches: the sample statistic, or the standard deviation"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2"],
+ ["'q'", "1"],
+ ["'r'", "6"],
+ ["'p'", "4"],
+ ["'q'", "3"],
+ ["'p'", "4"],
+ ["'p'", "4"],
+ ["'p'", "5"],
+ ["'p'", "5"],
+ ["'p'", "7"],
+ ["'p'", "9"],
+]
+query = "SELECT g, VARIANCE_POP(x) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_single_row"
+description = "A group with a single row. Catches: NULL for one row instead of 0"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2"],
+ ["'q'", "1"],
+ ["'r'", "6"],
+ ["'p'", "4"],
+ ["'q'", "3"],
+ ["'p'", "4"],
+ ["'p'", "4"],
+ ["'p'", "5"],
+ ["'p'", "5"],
+ ["'p'", "7"],
+ ["'p'", "9"],
+]
+query = "SELECT VARIANCE_POP(x) AS r FROM input WHERE g = 'r'"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t003_varchar"
+description = "Numeric text, which the page says is cast to FLOAT. Catches: text rejected or read as NUMBER"
+columns = [{ name = "x", type = "VARCHAR" }]
+rows = [
+ ["'1'"],
+ ["'2.5'"],
+ ["'6'"],
+]
+query = "SELECT VARIANCE_POP(x) AS r FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_distinct"
+description = "DISTINCT on a group with repeated 4s and 5s. Catches: DISTINCT ignored"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2"],
+ ["'q'", "1"],
+ ["'r'", "6"],
+ ["'p'", "4"],
+ ["'q'", "3"],
+ ["'p'", "4"],
+ ["'p'", "4"],
+ ["'p'", "5"],
+ ["'p'", "5"],
+ ["'p'", "7"],
+ ["'p'", "9"],
+]
+query = "SELECT g, VARIANCE_POP(DISTINCT x) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["duplicates"]
+
+[[case]]
+id = "t005_null_expr1"
+description = "NULL rows beside values, and a group of only NULL. Catches: NULL read as 0 (n and the mean change) or the all-NULL group not NULL"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "1"],
+ ["'q'", "NULL"],
+ ["'p'", "NULL"],
+ ["'q'", "NULL"],
+ ["'p'", "5"],
+ ["'p'", "3"],
+]
+query = "SELECT g, VARIANCE_POP(x) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_empty_input"
+description = "No rows. Catches: an empty input giving 0"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["5"],
+]
+query = "SELECT VARIANCE_POP(x) AS r FROM input WHERE ordinal < 0"
+order = "ordered"
+tags = ["empty-input"]
diff --git a/cases/translation/functions/variance.variance-over.toml b/cases/translation/functions/variance.variance-over.toml
new file mode 100644
index 000000000..adadda0c0
--- /dev/null
+++ b/cases/translation/functions/variance.variance-over.toml
@@ -0,0 +1,124 @@
+category = "translation"
+construct = "sf.functions.variance.variance-over"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_partition"
+description = "Partitions of different sizes in mixed input order. Catches: the whole input aggregated instead of the partition, or a running value"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2", "20"],
+ ["'q'", "5", "3"],
+ ["'p'", "1", "10"],
+ ["'p'", "3", "1"],
+ ["'q'", "1", "7"],
+ ["'p'", "2", "5"],
+]
+query = "SELECT VARIANCE(x) OVER (PARTITION BY g) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_empty_over"
+description = "An empty OVER clause. Catches: a running (ordered) value instead of the whole input"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2", "20"],
+ ["'q'", "5", "3"],
+ ["'p'", "1", "10"],
+ ["'p'", "3", "1"],
+ ["'q'", "1", "7"],
+ ["'p'", "2", "5"],
+]
+query = "SELECT VARIANCE(x) OVER () AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_order_ties"
+description = "ORDER BY with tied keys (two rows with k = 2) and no frame: with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Catches: a ROWS frame (the tied rows get different values) or the whole partition"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2", "20"],
+ ["'q'", "5", "3"],
+ ["'p'", "1", "10"],
+ ["'p'", "3", "1"],
+ ["'q'", "1", "7"],
+ ["'p'", "2", "5"],
+]
+query = "SELECT VARIANCE(x) OVER (PARTITION BY g ORDER BY k) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_order_desc"
+description = "DESC ordering with a running ROWS frame. Catches: DESC ignored (the running value starts from the smallest key)"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2", "20"],
+ ["'q'", "5", "3"],
+ ["'p'", "1", "10"],
+ ["'p'", "3", "1"],
+ ["'q'", "1", "7"],
+ ["'p'", "2", "5"],
+]
+query = "SELECT VARIANCE(x) OVER (PARTITION BY g ORDER BY k DESC, x ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_nulls_first_frame"
+description = "A NULL ordering key placed first (not the ASC default) and a one-row-back sliding frame. Catches: NULLS FIRST ignored (the NULL-key row last), or the frame read as RANGE or reaching forward"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "3", "1"],
+ ["'p'", "NULL", "4"],
+ ["'p'", "1", "10"],
+ ["'p'", "2", "6"],
+]
+query = "SELECT VARIANCE(x) OVER (PARTITION BY g ORDER BY k ASC NULLS FIRST ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t006_null_expr1"
+description = "NULL values inside partitions, and a partition with only NULL. Catches: NULL read as 0 (a different count and value) or the NULL-only partition not NULL"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "1", "NULL"],
+ ["'p'", "2", "6"],
+ ["'q'", "1", "NULL"],
+ ["'p'", "3", "2"],
+ ["'q'", "2", "NULL"],
+]
+query = "SELECT VARIANCE(x) OVER (PARTITION BY g) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_null_partition_key"
+description = "NULL partition keys, which form one partition. Catches: NULL-keyed rows each in their own partition or dropped"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL", "1", "4"],
+ ["'p'", "1", "9"],
+ ["NULL", "2", "2"],
+]
+query = "SELECT VARIANCE(x) OVER (PARTITION BY g) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t008_distinct_order"
+compile_error = true
+description = "DISTINCT with ORDER BY in the OVER clause, which the page forbids. Catches: a running DISTINCT value returned instead of the compilation error"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "k", type = "NUMBER(38,0)" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "1", "2"],
+ ["'p'", "2", "2"],
+ ["'p'", "3", "6"],
+ ["'q'", "1", "1"],
+]
+query = "SELECT VARIANCE(DISTINCT x) OVER (PARTITION BY g ORDER BY k) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
diff --git a/cases/translation/functions/variance.variance.toml b/cases/translation/functions/variance.variance.toml
new file mode 100644
index 000000000..b6a3f87be
--- /dev/null
+++ b/cases/translation/functions/variance.variance.toml
@@ -0,0 +1,107 @@
+category = "translation"
+construct = "sf.functions.variance.variance"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_number"
+description = "Groups of 8, 2 and 1 rows (population variance 4 and sample 32/7 for the first). Catches: the population statistic, or the standard deviation"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2"],
+ ["'q'", "1"],
+ ["'r'", "6"],
+ ["'p'", "4"],
+ ["'q'", "3"],
+ ["'p'", "4"],
+ ["'p'", "4"],
+ ["'p'", "5"],
+ ["'p'", "5"],
+ ["'p'", "7"],
+ ["'p'", "9"],
+]
+query = "SELECT g, VARIANCE(x) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_single_row"
+description = "A group with a single row. Catches: 0 for one row (the Oracle STDDEV/VARIANCE reading) instead of NULL"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2"],
+ ["'q'", "1"],
+ ["'r'", "6"],
+ ["'p'", "4"],
+ ["'q'", "3"],
+ ["'p'", "4"],
+ ["'p'", "4"],
+ ["'p'", "5"],
+ ["'p'", "5"],
+ ["'p'", "7"],
+ ["'p'", "9"],
+]
+query = "SELECT VARIANCE(x) AS r FROM input WHERE g = 'r'"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t003_varchar"
+description = "Numeric text, which the page says is cast to FLOAT. Catches: text rejected or read as NUMBER"
+columns = [{ name = "x", type = "VARCHAR" }]
+rows = [
+ ["'1'"],
+ ["'2.5'"],
+ ["'6'"],
+]
+query = "SELECT VARIANCE(x) AS r FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_distinct"
+compile_error = true
+description = "DISTINCT on a group with repeated 4s and 5s. Snowflake refuses VARIANCE(DISTINCT) as an unsupported feature. Catches: a variance returned instead of the error"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "2"],
+ ["'q'", "1"],
+ ["'r'", "6"],
+ ["'p'", "4"],
+ ["'q'", "3"],
+ ["'p'", "4"],
+ ["'p'", "4"],
+ ["'p'", "5"],
+ ["'p'", "5"],
+ ["'p'", "7"],
+ ["'p'", "9"],
+]
+query = "SELECT g, VARIANCE(DISTINCT x) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["duplicates"]
+
+[[case]]
+id = "t005_null_expr1"
+description = "NULL rows beside values, and a group of only NULL. Catches: NULL read as 0 (n and the mean change) or the all-NULL group not NULL"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["'p'", "1"],
+ ["'q'", "NULL"],
+ ["'p'", "NULL"],
+ ["'q'", "NULL"],
+ ["'p'", "5"],
+ ["'p'", "3"],
+]
+query = "SELECT g, VARIANCE(x) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_empty_input"
+description = "No rows. Catches: an empty input giving 0"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["5"],
+]
+query = "SELECT VARIANCE(x) AS r FROM input WHERE ordinal < 0"
+order = "ordered"
+tags = ["empty-input"]
diff --git a/cases/translation/functions/vector-avg.toml b/cases/translation/functions/vector-avg.toml
new file mode 100644
index 000000000..39f2bd45b
--- /dev/null
+++ b/cases/translation/functions/vector-avg.toml
@@ -0,0 +1,53 @@
+category = "translation"
+construct = "sf.functions.vector-avg"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_float"
+description = "Element-wise averages per group, exact in binary ([1, 2, 0.5] for p). Catches: a scalar or whole-input average, or a non-element-wise result"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "a", type = "VARCHAR" }]
+rows = [
+ ["'p'", "'[1,-4,2.5]'"],
+ ["'q'", "'[0,0,1]'"],
+ ["'p'", "'[-2,6,0.5]'"],
+ ["'p'", "'[4,4,-1.5]'"],
+]
+query = "SELECT g, VECTOR_AVG(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3))) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_int"
+description = "INT vectors whose average is not whole. Catches: an INT result for VECTOR_AVG (or a FLOAT result for the others)"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "a", type = "VARCHAR" }]
+rows = [
+ ["'p'", "'[1,2,3]'"],
+ ["'p'", "'[2,2,6]'"],
+]
+query = "SELECT VECTOR_AVG(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(INT, 3))) AS r FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_vector_column"
+description = "A NULL vector beside vectors, and a group of only NULL. Catches: NULL read as a zero vector"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "a", type = "VARCHAR" }]
+rows = [
+ ["'p'", "NULL"],
+ ["'p'", "'[4,8,2]'"],
+ ["'q'", "NULL"],
+]
+query = "SELECT g, VECTOR_AVG(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3))) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t004_empty_input"
+description = "No rows. Catches: a zero vector instead of NULL"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "a", type = "VARCHAR" }]
+rows = [
+ ["'p'", "'[1,1,1]'"],
+]
+query = "SELECT VECTOR_AVG(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3))) AS r FROM input WHERE ordinal < 0"
+order = "ordered"
+tags = ["empty-input"]
diff --git a/cases/translation/functions/vector-cosine-similarity.toml b/cases/translation/functions/vector-cosine-similarity.toml
new file mode 100644
index 000000000..8ae57096a
--- /dev/null
+++ b/cases/translation/functions/vector-cosine-similarity.toml
@@ -0,0 +1,49 @@
+category = "translation"
+construct = "sf.functions.vector-cosine-similarity"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_float"
+description = "Vectors with a negative element, and parallel vectors; rounded to 2 places for the margin, which keeps the irrational result clear of a rounding boundary. Catches: the inner product returned unnormalised, or the distance"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "b", type = "VARCHAR" }]
+rows = [
+ ["'[1,2,3]'", "'[4,-5,6]'"],
+ ["'[1,0,0]'", "'[2,0,0]'"],
+]
+query = "SELECT ordinal, ROUND(VECTOR_COSINE_SIMILARITY(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3)), CAST(PARSE_JSON(b)::ARRAY AS VECTOR(FLOAT, 3))), 2) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_int"
+description = "INT vectors; rounded to 2 places. Catches: an integer result, or INT vectors rejected"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "b", type = "VARCHAR" }]
+rows = [
+ ["'[1,2,3]'", "'[4,-5,6]'"],
+]
+query = "SELECT ROUND(VECTOR_COSINE_SIMILARITY(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(INT, 3)), CAST(PARSE_JSON(b)::ARRAY AS VECTOR(INT, 3))), 2) AS r FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_dimension_mismatch"
+compile_error = true
+description = "Vectors of different dimensions. Catches: the shorter vector padded"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "b", type = "VARCHAR" }]
+rows = [
+ ["'[1,2,3]'", "'[1,2]'"],
+]
+query = "SELECT VECTOR_COSINE_SIMILARITY(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3)), CAST(PARSE_JSON(b)::ARRAY AS VECTOR(FLOAT, 2))) AS r FROM input"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t004_null_vector"
+description = "A NULL vector; the page does not say; the recording decides. Catches: an error, or 0 returned"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "b", type = "VARCHAR" }]
+rows = [
+ ["NULL", "'[1,2,3]'"],
+]
+query = "SELECT VECTOR_COSINE_SIMILARITY(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3)), CAST(PARSE_JSON(b)::ARRAY AS VECTOR(FLOAT, 3))) IS NULL AS n FROM input"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/vector-inner-product.toml b/cases/translation/functions/vector-inner-product.toml
new file mode 100644
index 000000000..67494261a
--- /dev/null
+++ b/cases/translation/functions/vector-inner-product.toml
@@ -0,0 +1,49 @@
+category = "translation"
+construct = "sf.functions.vector-inner-product"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_float"
+description = "Vectors with a negative element, and parallel vectors; rounded to 3 places for the margin. Catches: the element-wise product summed without signs, or the cosine returned"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "b", type = "VARCHAR" }]
+rows = [
+ ["'[1,2,3]'", "'[4,-5,6]'"],
+ ["'[1,0,0]'", "'[2,0,0]'"],
+]
+query = "SELECT ordinal, ROUND(VECTOR_INNER_PRODUCT(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3)), CAST(PARSE_JSON(b)::ARRAY AS VECTOR(FLOAT, 3))), 3) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_int"
+description = "INT vectors. Catches: an integer result, or INT vectors rejected"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "b", type = "VARCHAR" }]
+rows = [
+ ["'[1,2,3]'", "'[4,-5,6]'"],
+]
+query = "SELECT ROUND(VECTOR_INNER_PRODUCT(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(INT, 3)), CAST(PARSE_JSON(b)::ARRAY AS VECTOR(INT, 3))), 3) AS r FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_dimension_mismatch"
+compile_error = true
+description = "Vectors of different dimensions. Catches: the shorter vector padded"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "b", type = "VARCHAR" }]
+rows = [
+ ["'[1,2,3]'", "'[1,2]'"],
+]
+query = "SELECT VECTOR_INNER_PRODUCT(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3)), CAST(PARSE_JSON(b)::ARRAY AS VECTOR(FLOAT, 2))) AS r FROM input"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t004_null_vector"
+description = "A NULL vector; the page does not say; the recording decides. Catches: an error, or 0 returned"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "b", type = "VARCHAR" }]
+rows = [
+ ["NULL", "'[1,2,3]'"],
+]
+query = "SELECT VECTOR_INNER_PRODUCT(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3)), CAST(PARSE_JSON(b)::ARRAY AS VECTOR(FLOAT, 3))) IS NULL AS n FROM input"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/vector-l1-distance.toml b/cases/translation/functions/vector-l1-distance.toml
new file mode 100644
index 000000000..83663cb1f
--- /dev/null
+++ b/cases/translation/functions/vector-l1-distance.toml
@@ -0,0 +1,49 @@
+category = "translation"
+construct = "sf.functions.vector-l1-distance"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_float"
+description = "Vectors with a negative element, and parallel vectors; rounded to 3 places for the margin. Catches: the L2 distance, or differences without absolute values"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "b", type = "VARCHAR" }]
+rows = [
+ ["'[1,2,3]'", "'[4,-5,6]'"],
+ ["'[1,0,0]'", "'[2,0,0]'"],
+]
+query = "SELECT ordinal, ROUND(VECTOR_L1_DISTANCE(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3)), CAST(PARSE_JSON(b)::ARRAY AS VECTOR(FLOAT, 3))), 3) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_int"
+description = "INT vectors. Catches: an integer result, or INT vectors rejected"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "b", type = "VARCHAR" }]
+rows = [
+ ["'[1,2,3]'", "'[4,-5,6]'"],
+]
+query = "SELECT ROUND(VECTOR_L1_DISTANCE(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(INT, 3)), CAST(PARSE_JSON(b)::ARRAY AS VECTOR(INT, 3))), 3) AS r FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_dimension_mismatch"
+compile_error = true
+description = "Vectors of different dimensions. Catches: the shorter vector padded"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "b", type = "VARCHAR" }]
+rows = [
+ ["'[1,2,3]'", "'[1,2]'"],
+]
+query = "SELECT VECTOR_L1_DISTANCE(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3)), CAST(PARSE_JSON(b)::ARRAY AS VECTOR(FLOAT, 2))) AS r FROM input"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t004_null_vector"
+description = "A NULL vector; the page does not say; the recording decides. Catches: an error, or 0 returned"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "b", type = "VARCHAR" }]
+rows = [
+ ["NULL", "'[1,2,3]'"],
+]
+query = "SELECT VECTOR_L1_DISTANCE(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3)), CAST(PARSE_JSON(b)::ARRAY AS VECTOR(FLOAT, 3))) IS NULL AS n FROM input"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/vector-l2-distance.toml b/cases/translation/functions/vector-l2-distance.toml
new file mode 100644
index 000000000..03e546238
--- /dev/null
+++ b/cases/translation/functions/vector-l2-distance.toml
@@ -0,0 +1,49 @@
+category = "translation"
+construct = "sf.functions.vector-l2-distance"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_float"
+description = "Vectors with a negative element, and parallel vectors; rounded to 2 places for the margin, which keeps the irrational result clear of a rounding boundary. Catches: the squared distance, or the L1 distance"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "b", type = "VARCHAR" }]
+rows = [
+ ["'[1,2,3]'", "'[4,-5,6]'"],
+ ["'[1,0,0]'", "'[2,0,0]'"],
+]
+query = "SELECT ordinal, ROUND(VECTOR_L2_DISTANCE(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3)), CAST(PARSE_JSON(b)::ARRAY AS VECTOR(FLOAT, 3))), 2) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_int"
+description = "INT vectors; rounded to 2 places. Catches: an integer result, or INT vectors rejected"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "b", type = "VARCHAR" }]
+rows = [
+ ["'[1,2,3]'", "'[4,-5,6]'"],
+]
+query = "SELECT ROUND(VECTOR_L2_DISTANCE(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(INT, 3)), CAST(PARSE_JSON(b)::ARRAY AS VECTOR(INT, 3))), 2) AS r FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_dimension_mismatch"
+compile_error = true
+description = "Vectors of different dimensions. Catches: the shorter vector padded"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "b", type = "VARCHAR" }]
+rows = [
+ ["'[1,2,3]'", "'[1,2]'"],
+]
+query = "SELECT VECTOR_L2_DISTANCE(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3)), CAST(PARSE_JSON(b)::ARRAY AS VECTOR(FLOAT, 2))) AS r FROM input"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t004_null_vector"
+description = "A NULL vector; the page does not say; the recording decides. Catches: an error, or 0 returned"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "b", type = "VARCHAR" }]
+rows = [
+ ["NULL", "'[1,2,3]'"],
+]
+query = "SELECT VECTOR_L2_DISTANCE(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3)), CAST(PARSE_JSON(b)::ARRAY AS VECTOR(FLOAT, 3))) IS NULL AS n FROM input"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/vector-max.toml b/cases/translation/functions/vector-max.toml
new file mode 100644
index 000000000..cfcfb2a64
--- /dev/null
+++ b/cases/translation/functions/vector-max.toml
@@ -0,0 +1,53 @@
+category = "translation"
+construct = "sf.functions.vector-max"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_float"
+description = "Element-wise per group, where each element's winner comes from a different row. Catches: a whole-vector comparison or a scalar result"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "a", type = "VARCHAR" }]
+rows = [
+ ["'p'", "'[1,-4,2.5]'"],
+ ["'q'", "'[0,0,1]'"],
+ ["'p'", "'[-2,6,0.5]'"],
+ ["'p'", "'[3,1,-1]'"],
+]
+query = "SELECT g, VECTOR_MAX(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3))) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_int"
+description = "INT vectors whose average is not whole. Catches: an INT result for VECTOR_AVG (or a FLOAT result for the others)"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "a", type = "VARCHAR" }]
+rows = [
+ ["'p'", "'[1,2,3]'"],
+ ["'p'", "'[2,2,6]'"],
+]
+query = "SELECT VECTOR_MAX(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(INT, 3))) AS r FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_vector_column"
+description = "A NULL vector beside vectors, and a group of only NULL. Catches: NULL read as a zero vector"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "a", type = "VARCHAR" }]
+rows = [
+ ["'p'", "NULL"],
+ ["'p'", "'[4,8,2]'"],
+ ["'q'", "NULL"],
+]
+query = "SELECT g, VECTOR_MAX(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3))) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t004_empty_input"
+description = "No rows. Catches: a zero vector instead of NULL"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "a", type = "VARCHAR" }]
+rows = [
+ ["'p'", "'[1,1,1]'"],
+]
+query = "SELECT VECTOR_MAX(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3))) AS r FROM input WHERE ordinal < 0"
+order = "ordered"
+tags = ["empty-input"]
diff --git a/cases/translation/functions/vector-min.toml b/cases/translation/functions/vector-min.toml
new file mode 100644
index 000000000..303b13771
--- /dev/null
+++ b/cases/translation/functions/vector-min.toml
@@ -0,0 +1,53 @@
+category = "translation"
+construct = "sf.functions.vector-min"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_float"
+description = "Element-wise per group, where each element's winner comes from a different row. Catches: a whole-vector comparison or a scalar result"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "a", type = "VARCHAR" }]
+rows = [
+ ["'p'", "'[1,-4,2.5]'"],
+ ["'q'", "'[0,0,1]'"],
+ ["'p'", "'[-2,6,0.5]'"],
+ ["'p'", "'[3,1,-1]'"],
+]
+query = "SELECT g, VECTOR_MIN(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3))) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_int"
+description = "INT vectors whose average is not whole. Catches: an INT result for VECTOR_AVG (or a FLOAT result for the others)"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "a", type = "VARCHAR" }]
+rows = [
+ ["'p'", "'[1,2,3]'"],
+ ["'p'", "'[2,2,6]'"],
+]
+query = "SELECT VECTOR_MIN(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(INT, 3))) AS r FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_vector_column"
+description = "A NULL vector beside vectors, and a group of only NULL. Catches: NULL read as a zero vector"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "a", type = "VARCHAR" }]
+rows = [
+ ["'p'", "NULL"],
+ ["'p'", "'[4,8,2]'"],
+ ["'q'", "NULL"],
+]
+query = "SELECT g, VECTOR_MIN(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3))) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t004_empty_input"
+description = "No rows. Catches: a zero vector instead of NULL"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "a", type = "VARCHAR" }]
+rows = [
+ ["'p'", "'[1,1,1]'"],
+]
+query = "SELECT VECTOR_MIN(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3))) AS r FROM input WHERE ordinal < 0"
+order = "ordered"
+tags = ["empty-input"]
diff --git a/cases/translation/functions/vector-normalize.toml b/cases/translation/functions/vector-normalize.toml
new file mode 100644
index 000000000..bc06657d9
--- /dev/null
+++ b/cases/translation/functions/vector-normalize.toml
@@ -0,0 +1,36 @@
+category = "translation"
+construct = "sf.functions.vector-normalize"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_float"
+description = "A vector with a negative element; elements rounded to 3 places. Catches: L1 normalisation, or the vector returned unchanged"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'[3,-4,0]'"],
+]
+query = "SELECT ROUND(GET(VECTOR_NORMALIZE(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3)))::ARRAY, 0)::FLOAT, 3) AS e0, ROUND(GET(VECTOR_NORMALIZE(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3)))::ARRAY, 1)::FLOAT, 3) AS e1, ROUND(GET(VECTOR_NORMALIZE(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3)))::ARRAY, 2)::FLOAT, 3) AS e2 FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_int"
+description = "An INT vector, which the page says gives FLOAT elements. Catches: INT elements (0 and 1) returned"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'[0,2,0]'"],
+]
+query = "SELECT ROUND(GET(VECTOR_NORMALIZE(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(INT, 3)))::ARRAY, 0)::FLOAT, 3) AS e0, ROUND(GET(VECTOR_NORMALIZE(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(INT, 3)))::ARRAY, 1)::FLOAT, 3) AS e1, ROUND(GET(VECTOR_NORMALIZE(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(INT, 3)))::ARRAY, 2)::FLOAT, 3) AS e2 FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_vector"
+description = "A NULL vector, which the page says gives NULL. Catches: an error"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT VECTOR_NORMALIZE(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3))) IS NULL AS n FROM input"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/vector-sum.toml b/cases/translation/functions/vector-sum.toml
new file mode 100644
index 000000000..002c4b6fa
--- /dev/null
+++ b/cases/translation/functions/vector-sum.toml
@@ -0,0 +1,53 @@
+category = "translation"
+construct = "sf.functions.vector-sum"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_float"
+description = "Element-wise sums per group. Catches: a scalar total or a non-element-wise result"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "a", type = "VARCHAR" }]
+rows = [
+ ["'p'", "'[1,-4,2.5]'"],
+ ["'q'", "'[0,0,1]'"],
+ ["'p'", "'[-2,6,0.5]'"],
+ ["'p'", "'[3,1,-1]'"],
+]
+query = "SELECT g, VECTOR_SUM(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3))) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_int"
+description = "INT vectors whose average is not whole. Catches: an INT result for VECTOR_AVG (or a FLOAT result for the others)"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "a", type = "VARCHAR" }]
+rows = [
+ ["'p'", "'[1,2,3]'"],
+ ["'p'", "'[2,2,6]'"],
+]
+query = "SELECT VECTOR_SUM(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(INT, 3))) AS r FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_vector_column"
+description = "A NULL vector beside vectors, and a group of only NULL. Catches: NULL read as a zero vector"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "a", type = "VARCHAR" }]
+rows = [
+ ["'p'", "NULL"],
+ ["'p'", "'[4,8,2]'"],
+ ["'q'", "NULL"],
+]
+query = "SELECT g, VECTOR_SUM(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3))) AS r FROM input GROUP BY g ORDER BY g"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t004_empty_input"
+description = "No rows. Catches: a zero vector instead of NULL"
+columns = [{ name = "g", type = "VARCHAR" }, { name = "a", type = "VARCHAR" }]
+rows = [
+ ["'p'", "'[1,1,1]'"],
+]
+query = "SELECT VECTOR_SUM(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3))) AS r FROM input WHERE ordinal < 0"
+order = "ordered"
+tags = ["empty-input"]
diff --git a/cases/translation/functions/vector-truncate.toml b/cases/translation/functions/vector-truncate.toml
new file mode 100644
index 000000000..278920d75
--- /dev/null
+++ b/cases/translation/functions/vector-truncate.toml
@@ -0,0 +1,37 @@
+category = "translation"
+construct = "sf.functions.vector-truncate"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_truncate"
+description = "Truncation to 2 elements, which the page says are not normalised. Catches: the result normalised, or the last elements kept"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'[3,4,12]'"],
+]
+query = "SELECT VECTOR_TRUNCATE(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3)), 2)::ARRAY AS r FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_too_long"
+compile_error = true
+description = "A dimension larger than the vector, which the page says is an error; Snowflake refuses it when compiling. Catches: the vector returned unchanged, or padded"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'[3,4,12]'"],
+]
+query = "SELECT VECTOR_TRUNCATE(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3)), 4)::ARRAY AS r FROM input"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t003_null_vector"
+description = "A NULL vector with a constant dimension, which Snowflake requires; the page says NULL gives NULL. Catches: an error, or an empty vector returned"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT VECTOR_TRUNCATE(CAST(PARSE_JSON(a)::ARRAY AS VECTOR(FLOAT, 3)), 2) IS NULL AS r FROM input"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/width-bucket.toml b/cases/translation/functions/width-bucket.toml
new file mode 100644
index 000000000..2a75b6f62
--- /dev/null
+++ b/cases/translation/functions/width-bucket.toml
@@ -0,0 +1,141 @@
+category = "translation"
+construct = "sf.functions.width-bucket"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_int"
+description = "WIDTH_BUCKET of integers, including below and above the range. Catches: buckets numbered from 0, or the overflow buckets 0 and n+1 missing"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["-5"],
+ ["0"],
+ ["24"],
+ ["25"],
+ ["99"],
+ ["100"],
+ ["250"],
+]
+query = "SELECT WIDTH_BUCKET(x, 0, 100, 4) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_dec"
+description = "WIDTH_BUCKET of fixed-point values on bucket edges. Catches: edges assigned to the lower bucket"
+columns = [{ name = "x", type = "NUMBER(10,2)" }]
+rows = [
+ ["12.50"],
+ ["12.49"],
+ ["37.50"],
+ ["0.00"],
+]
+query = "SELECT WIDTH_BUCKET(x, 0, 50, 4) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_float"
+description = "WIDTH_BUCKET of FLOAT values with FLOAT end points. Catches: integer end points assumed"
+columns = [{ name = "x", type = "FLOAT" }]
+rows = [
+ ["0.3"],
+ ["0.75"],
+ ["-0.1"],
+]
+query = "SELECT WIDTH_BUCKET(x, 0.25::FLOAT, 1.25::FLOAT, 5) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_reversed"
+description = "End points given high to low. Catches: the end points swapped back, numbering buckets upwards"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["90"],
+ ["10"],
+ ["101"],
+ ["-1"],
+]
+query = "SELECT WIDTH_BUCKET(x, 100, 0, 4) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t005_null_expr"
+description = "NULL value. Catches: NULL put in bucket 0"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+]
+query = "SELECT WIDTH_BUCKET(x, 0, 100, 4) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t006_null_min"
+description = "NULL low end point. Catches: NULL read as 0"
+columns = [{ name = "x", type = "NUMBER(38,0)" }, { name = "m", type = "NUMBER(38,0)" }]
+rows = [
+ ["50", "NULL"],
+]
+query = "SELECT WIDTH_BUCKET(x, m, 100, 4) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t007_null_max"
+description = "NULL high end point. Catches: NULL read as 0"
+columns = [{ name = "x", type = "NUMBER(38,0)" }, { name = "m", type = "NUMBER(38,0)" }]
+rows = [
+ ["50", "NULL"],
+]
+query = "SELECT WIDTH_BUCKET(x, 0, m, 4) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t008_null_n"
+description = "NULL bucket count. Catches: NULL read as 1"
+columns = [{ name = "x", type = "NUMBER(38,0)" }, { name = "n", type = "NUMBER(38,0)" }]
+rows = [
+ ["50", "NULL"],
+]
+query = "SELECT WIDTH_BUCKET(x, 0, 100, n) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t009_range_edge"
+description = "Values at the documented 2^53 - 1 limit. Catches: precision loss rounding the limit into another bucket"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["9007199254740991"],
+ ["-9007199254740991"],
+ ["-3002399751580331"],
+ ["-3002399751580330"],
+]
+query = "SELECT WIDTH_BUCKET(x, -9007199254740991, 9007199254740991, 3) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t010_zero_buckets"
+description = "num_buckets 0 is an error. Catches: bucket 0 returned"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["5"],
+]
+query = "SELECT WIDTH_BUCKET(x, 0, 10, 0) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t011_equal_ends"
+description = "Equal end points are an error. Catches: division by zero returning NULL or infinity"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["5"],
+]
+query = "SELECT WIDTH_BUCKET(x, 10, 10, 4) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
diff --git a/cases/translation/functions/xmlget.toml b/cases/translation/functions/xmlget.toml
new file mode 100644
index 000000000..9ee7ee4f7
--- /dev/null
+++ b/cases/translation/functions/xmlget.toml
@@ -0,0 +1,37 @@
+category = "translation"
+construct = "sf.functions.xmlget"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_instances"
+description = "The first and second elements, and their content and attribute through GET. Catches: a 1-based instance, or the element content returned instead of the element"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "i", type = "NUMBER(38,0)" }]
+rows = [
+ ["'1.50x'", "0"],
+ ["'1.50x'", "1"],
+]
+query = "SELECT XMLGET(PARSE_XML(a), 'a', i) AS el, GET(XMLGET(PARSE_XML(a), 'a', i), '$') AS content, GET(XMLGET(PARSE_XML(a), 'a', i), '@id') AS id FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_missing"
+description = "A tag that is absent and an instance past the last. Catches: an error, or the last instance returned"
+columns = [{ name = "a", type = "VARCHAR" }]
+rows = [
+ ["'1.50x'"],
+]
+query = "SELECT XMLGET(PARSE_XML(a), 'z') AS absent, XMLGET(PARSE_XML(a), 'a', 2) AS past FROM input"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_instance_number"
+description = "A NULL instance number and a NULL tag name, which the page says give NULL. Catches: instance 0 used, or an error"
+columns = [{ name = "a", type = "VARCHAR" }, { name = "k", type = "VARCHAR" }]
+rows = [
+ ["'1.50x'", "NULL"],
+]
+query = "SELECT XMLGET(PARSE_XML(a), 'a', NULL) AS i, XMLGET(PARSE_XML(a), k) AS t FROM input"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/year.day.toml b/cases/translation/functions/year.day.toml
new file mode 100644
index 000000000..ac0e374c6
--- /dev/null
+++ b/cases/translation/functions/year.day.toml
@@ -0,0 +1,79 @@
+category = "translation"
+construct = "sf.functions.year.day"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "DAY of dates at year, ISO-week and leap boundaries. Catches: an off-by-one or 0-based result at the boundaries"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-12-30'"],
+ ["'2021-01-01'"],
+ ["'2023-01-01'"],
+ ["'2024-02-29'"],
+ ["'2026-12-31'"],
+]
+query = "SELECT DAY(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_timestamp_ntz"
+description = "DAY of a timestamp just before midnight on 31 December. Catches: the timestamp rounded to the next day"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-12-31 23:59:59.999'"],
+ ["'2024-03-01 00:00:00'"],
+]
+query = "SELECT DAY(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp_tz"
+description = "DAY of a TIMESTAMP_TZ whose UTC instant is in the next year. Catches: the day of the UTC instant (1) instead of the wall clock (31)"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-31 23:30:00.250 -0800'"],
+]
+query = "SELECT DAY(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_date_or_timestamp_expr"
+description = "NULL next to a set date. Catches: NULL read as a default date (1970-01-01)"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-06-15'"],
+]
+query = "SELECT DAY(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_leap"
+description = "Days around 29 February in a leap and a common year. Catches: a fixed 365-day year or a non-leap 2024"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-02-29'"],
+ ["'2024-03-01'"],
+ ["'2023-03-01'"],
+ ["'2000-12-31'"],
+ ["'1900-03-01'"],
+]
+query = "SELECT DAY(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t006_interval"
+description = "DAY of the interval type the page allows. Catches: an error for an interval, or the interval read as a date"
+columns = [{ name = "k", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT DAY(INTERVAL '4 05:06:07' DAY TO SECOND) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
diff --git a/cases/translation/functions/year.dayofmonth.toml b/cases/translation/functions/year.dayofmonth.toml
new file mode 100644
index 000000000..409ad775e
--- /dev/null
+++ b/cases/translation/functions/year.dayofmonth.toml
@@ -0,0 +1,68 @@
+category = "translation"
+construct = "sf.functions.year.dayofmonth"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "DAYOFMONTH of dates at year, ISO-week and leap boundaries. Catches: an off-by-one or 0-based result at the boundaries"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-12-30'"],
+ ["'2021-01-01'"],
+ ["'2023-01-01'"],
+ ["'2024-02-29'"],
+ ["'2026-12-31'"],
+]
+query = "SELECT DAYOFMONTH(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_timestamp_ntz"
+description = "DAYOFMONTH of a timestamp just before midnight on 31 December. Catches: the timestamp rounded to the next day"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-12-31 23:59:59.999'"],
+ ["'2024-03-01 00:00:00'"],
+]
+query = "SELECT DAYOFMONTH(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp_tz"
+description = "DAYOFMONTH of a TIMESTAMP_TZ whose UTC instant is in the next year. Catches: the day of the UTC instant instead of the wall clock"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-31 23:30:00.250 -0800'"],
+]
+query = "SELECT DAYOFMONTH(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_date_or_timestamp_expr"
+description = "NULL next to a set date. Catches: NULL read as a default date (1970-01-01)"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-06-15'"],
+]
+query = "SELECT DAYOFMONTH(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_leap"
+description = "Days around 29 February in a leap and a common year. Catches: a fixed 365-day year or a non-leap 2024"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-02-29'"],
+ ["'2024-03-01'"],
+ ["'2023-03-01'"],
+ ["'2000-12-31'"],
+ ["'1900-03-01'"],
+]
+query = "SELECT DAYOFMONTH(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
diff --git a/cases/translation/functions/year.dayofweek.toml b/cases/translation/functions/year.dayofweek.toml
new file mode 100644
index 000000000..04c745832
--- /dev/null
+++ b/cases/translation/functions/year.dayofweek.toml
@@ -0,0 +1,68 @@
+category = "translation"
+construct = "sf.functions.year.dayofweek"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "DAYOFWEEK of dates at year, ISO-week and leap boundaries. Catches: another week numbering (ISO 1-7 or Sunday=1) than WEEK_START 0 gives"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-12-30'"],
+ ["'2021-01-01'"],
+ ["'2023-01-01'"],
+ ["'2024-02-29'"],
+ ["'2026-12-31'"],
+]
+query = "SELECT DAYOFWEEK(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_timestamp_ntz"
+description = "DAYOFWEEK of a timestamp just before midnight on 31 December. Catches: the timestamp rounded to the next day"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-12-31 23:59:59.999'"],
+ ["'2024-03-01 00:00:00'"],
+]
+query = "SELECT DAYOFWEEK(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp_tz"
+description = "DAYOFWEEK of a TIMESTAMP_TZ whose UTC instant is in the next year. Catches: the part taken from the UTC instant instead of the wall clock"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-31 23:30:00.250 -0800'"],
+]
+query = "SELECT DAYOFWEEK(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_date_or_timestamp_expr"
+description = "NULL next to a set date. Catches: NULL read as a default date (1970-01-01)"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-06-15'"],
+]
+query = "SELECT DAYOFWEEK(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_leap"
+description = "Days around 29 February in a leap and a common year. Catches: a fixed 365-day year or a non-leap 2024"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-02-29'"],
+ ["'2024-03-01'"],
+ ["'2023-03-01'"],
+ ["'2000-12-31'"],
+ ["'1900-03-01'"],
+]
+query = "SELECT DAYOFWEEK(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
diff --git a/cases/translation/functions/year.dayofweekiso.toml b/cases/translation/functions/year.dayofweekiso.toml
new file mode 100644
index 000000000..e6f864d82
--- /dev/null
+++ b/cases/translation/functions/year.dayofweekiso.toml
@@ -0,0 +1,68 @@
+category = "translation"
+construct = "sf.functions.year.dayofweekiso"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "DAYOFWEEKISO of dates at year, ISO-week and leap boundaries. Catches: Sunday as 0 or 1 instead of ISO 7"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-12-30'"],
+ ["'2021-01-01'"],
+ ["'2023-01-01'"],
+ ["'2024-02-29'"],
+ ["'2026-12-31'"],
+]
+query = "SELECT DAYOFWEEKISO(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_timestamp_ntz"
+description = "DAYOFWEEKISO of a timestamp just before midnight on 31 December. Catches: the timestamp rounded to the next day"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-12-31 23:59:59.999'"],
+ ["'2024-03-01 00:00:00'"],
+]
+query = "SELECT DAYOFWEEKISO(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp_tz"
+description = "DAYOFWEEKISO of a TIMESTAMP_TZ whose UTC instant is in the next year. Catches: the part taken from the UTC instant instead of the wall clock"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-31 23:30:00.250 -0800'"],
+]
+query = "SELECT DAYOFWEEKISO(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_date_or_timestamp_expr"
+description = "NULL next to a set date. Catches: NULL read as a default date (1970-01-01)"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-06-15'"],
+]
+query = "SELECT DAYOFWEEKISO(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_leap"
+description = "Days around 29 February in a leap and a common year. Catches: a fixed 365-day year or a non-leap 2024"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-02-29'"],
+ ["'2024-03-01'"],
+ ["'2023-03-01'"],
+ ["'2000-12-31'"],
+ ["'1900-03-01'"],
+]
+query = "SELECT DAYOFWEEKISO(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
diff --git a/cases/translation/functions/year.dayofyear.toml b/cases/translation/functions/year.dayofyear.toml
new file mode 100644
index 000000000..6902dd01d
--- /dev/null
+++ b/cases/translation/functions/year.dayofyear.toml
@@ -0,0 +1,69 @@
+category = "translation"
+construct = "sf.functions.year.dayofyear"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "DAYOFYEAR of dates at year, ISO-week and leap boundaries. Catches: a 0-based day, or 365 on the last day of a leap year"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-12-30'"],
+ ["'2021-01-01'"],
+ ["'2023-01-01'"],
+ ["'2024-02-29'"],
+ ["'2026-12-31'"],
+ ["'2024-12-31'"],
+]
+query = "SELECT DAYOFYEAR(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_timestamp_ntz"
+description = "DAYOFYEAR of a timestamp just before midnight on 31 December. Catches: the timestamp rounded to the next day"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-12-31 23:59:59.999'"],
+ ["'2024-03-01 00:00:00'"],
+]
+query = "SELECT DAYOFYEAR(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp_tz"
+description = "DAYOFYEAR of a TIMESTAMP_TZ whose UTC instant is in the next year. Catches: the part taken from the UTC instant instead of the wall clock"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-31 23:30:00.250 -0800'"],
+]
+query = "SELECT DAYOFYEAR(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_date_or_timestamp_expr"
+description = "NULL next to a set date. Catches: NULL read as a default date (1970-01-01)"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-06-15'"],
+]
+query = "SELECT DAYOFYEAR(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_leap"
+description = "Days around 29 February in a leap and a common year. Catches: a fixed 365-day year or a non-leap 2024"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-02-29'"],
+ ["'2024-03-01'"],
+ ["'2023-03-01'"],
+ ["'2000-12-31'"],
+ ["'1900-03-01'"],
+]
+query = "SELECT DAYOFYEAR(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
diff --git a/cases/translation/functions/year.month.toml b/cases/translation/functions/year.month.toml
new file mode 100644
index 000000000..eebac75f7
--- /dev/null
+++ b/cases/translation/functions/year.month.toml
@@ -0,0 +1,79 @@
+category = "translation"
+construct = "sf.functions.year.month"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "MONTH of dates at year, ISO-week and leap boundaries. Catches: an off-by-one or 0-based result at the boundaries"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-12-30'"],
+ ["'2021-01-01'"],
+ ["'2023-01-01'"],
+ ["'2024-02-29'"],
+ ["'2026-12-31'"],
+]
+query = "SELECT MONTH(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_timestamp_ntz"
+description = "MONTH of a timestamp just before midnight on 31 December. Catches: the timestamp rounded to the next day"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-12-31 23:59:59.999'"],
+ ["'2024-03-01 00:00:00'"],
+]
+query = "SELECT MONTH(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp_tz"
+description = "MONTH of a TIMESTAMP_TZ whose UTC instant is in the next year. Catches: the month of the UTC instant instead of the wall clock"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-31 23:30:00.250 -0800'"],
+]
+query = "SELECT MONTH(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_date_or_timestamp_expr"
+description = "NULL next to a set date. Catches: NULL read as a default date (1970-01-01)"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-06-15'"],
+]
+query = "SELECT MONTH(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_leap"
+description = "Days around 29 February in a leap and a common year. Catches: a fixed 365-day year or a non-leap 2024"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-02-29'"],
+ ["'2024-03-01'"],
+ ["'2023-03-01'"],
+ ["'2000-12-31'"],
+ ["'1900-03-01'"],
+]
+query = "SELECT MONTH(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t006_interval"
+description = "MONTH of the interval type the page allows. Catches: an error for an interval, or the interval read as a date"
+columns = [{ name = "k", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT MONTH(INTERVAL '3-5' YEAR TO MONTH) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
diff --git a/cases/translation/functions/year.quarter.toml b/cases/translation/functions/year.quarter.toml
new file mode 100644
index 000000000..db61993b8
--- /dev/null
+++ b/cases/translation/functions/year.quarter.toml
@@ -0,0 +1,70 @@
+category = "translation"
+construct = "sf.functions.year.quarter"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "QUARTER of dates at year, ISO-week and leap boundaries. Catches: month/3 rounded (0 for January) instead of the quarter"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-12-30'"],
+ ["'2021-01-01'"],
+ ["'2023-01-01'"],
+ ["'2024-02-29'"],
+ ["'2026-12-31'"],
+]
+query = "SELECT QUARTER(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_timestamp_ntz"
+description = "QUARTER of a timestamp just before midnight on 31 December. Catches: the timestamp rounded to the next day"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-12-31 23:59:59.999'"],
+ ["'2024-03-01 00:00:00'"],
+]
+query = "SELECT QUARTER(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp_tz"
+description = "QUARTER of TIMESTAMP_TZ values whose UTC instant is in the next quarter. Catches: the quarter of the UTC instant (1 for the December row, 2 for the March row) instead of the wall clock"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-31 23:30:00.250 -0800'"],
+ ["'2024-03-31 23:30:00 -0800'"],
+]
+query = "SELECT QUARTER(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_date_or_timestamp_expr"
+description = "NULL next to a set date. Catches: NULL read as a default date (1970-01-01)"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-06-15'"],
+]
+query = "SELECT QUARTER(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_leap"
+description = "Days around 29 February in a leap and a common year. Catches: quarters by day-of-year thresholds of a 365-day year (Q2 for 2024-03-31)"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-02-29'"],
+ ["'2024-03-01'"],
+ ["'2023-03-01'"],
+ ["'2000-12-31'"],
+ ["'1900-03-01'"],
+ ["'2024-03-31'"],
+]
+query = "SELECT QUARTER(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
diff --git a/cases/translation/functions/year.week.toml b/cases/translation/functions/year.week.toml
new file mode 100644
index 000000000..e0443d2e1
--- /dev/null
+++ b/cases/translation/functions/year.week.toml
@@ -0,0 +1,69 @@
+category = "translation"
+construct = "sf.functions.year.week"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "WEEK of dates at year, ISO-week and leap boundaries. Catches: a calendar-year week (53 for 2024-12-30, 1 for 2021-01-01) instead of the ISO week under WEEK_OF_YEAR_POLICY 0"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-12-30'"],
+ ["'2021-01-01'"],
+ ["'2023-01-01'"],
+ ["'2024-02-29'"],
+ ["'2026-12-31'"],
+]
+query = "SELECT WEEK(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_timestamp_ntz"
+description = "WEEK of a timestamp just before midnight on Sunday 29 December 2024, the last day of ISO week 52. Catches: the timestamp rounded to the next day"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-12-29 23:59:59.999'"],
+ ["'2024-03-01 00:00:00'"],
+]
+query = "SELECT WEEK(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp_tz"
+description = "WEEK of a TIMESTAMP_TZ whose UTC instant is in the next ISO week. Catches: the part taken from the UTC instant instead of the wall clock"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-29 23:30:00.250 -0800'"],
+]
+query = "SELECT WEEK(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_date_or_timestamp_expr"
+description = "NULL next to a set date. Catches: NULL read as a default date (1970-01-01)"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-06-15'"],
+]
+query = "SELECT WEEK(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_leap"
+description = "Days around 29 February in a leap and a common year. Catches: a 365-day year or a Julian 1900, which moves a Sunday/Monday edge after 29 February into the other week"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-03-03'"],
+ ["'2024-03-04'"],
+ ["'2004-03-01'"],
+ ["'1900-03-04'"],
+ ["'2000-03-05'"],
+ ["'2023-03-01'"],
+]
+query = "SELECT WEEK(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
diff --git a/cases/translation/functions/year.weekiso.toml b/cases/translation/functions/year.weekiso.toml
new file mode 100644
index 000000000..6dc3b293b
--- /dev/null
+++ b/cases/translation/functions/year.weekiso.toml
@@ -0,0 +1,69 @@
+category = "translation"
+construct = "sf.functions.year.weekiso"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "WEEKISO of dates at year, ISO-week and leap boundaries. Catches: a Sunday-start week or week 0 for early January"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-12-30'"],
+ ["'2021-01-01'"],
+ ["'2023-01-01'"],
+ ["'2024-02-29'"],
+ ["'2026-12-31'"],
+]
+query = "SELECT WEEKISO(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_timestamp_ntz"
+description = "WEEKISO of a timestamp just before midnight on Sunday 29 December 2024, the last day of ISO week 52. Catches: the timestamp rounded to the next day"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-12-29 23:59:59.999'"],
+ ["'2024-03-01 00:00:00'"],
+]
+query = "SELECT WEEKISO(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp_tz"
+description = "WEEKISO of a TIMESTAMP_TZ whose UTC instant is in the next ISO week. Catches: the part taken from the UTC instant instead of the wall clock"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-29 23:30:00.250 -0800'"],
+]
+query = "SELECT WEEKISO(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_date_or_timestamp_expr"
+description = "NULL next to a set date. Catches: NULL read as a default date (1970-01-01)"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-06-15'"],
+]
+query = "SELECT WEEKISO(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_leap"
+description = "Days around 29 February in a leap and a common year. Catches: a 365-day year or a Julian 1900, which moves a Sunday/Monday edge after 29 February into the other week"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-03-03'"],
+ ["'2024-03-04'"],
+ ["'2004-03-01'"],
+ ["'1900-03-04'"],
+ ["'2000-03-05'"],
+ ["'2023-03-01'"],
+]
+query = "SELECT WEEKISO(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
diff --git a/cases/translation/functions/year.weekofyear.toml b/cases/translation/functions/year.weekofyear.toml
new file mode 100644
index 000000000..53bb747ea
--- /dev/null
+++ b/cases/translation/functions/year.weekofyear.toml
@@ -0,0 +1,69 @@
+category = "translation"
+construct = "sf.functions.year.weekofyear"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "WEEKOFYEAR of dates at year, ISO-week and leap boundaries. Catches: a calendar-year week instead of the ISO week"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-12-30'"],
+ ["'2021-01-01'"],
+ ["'2023-01-01'"],
+ ["'2024-02-29'"],
+ ["'2026-12-31'"],
+]
+query = "SELECT WEEKOFYEAR(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_timestamp_ntz"
+description = "WEEKOFYEAR of a timestamp just before midnight on Sunday 29 December 2024, the last day of ISO week 52. Catches: the timestamp rounded to the next day"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-12-29 23:59:59.999'"],
+ ["'2024-03-01 00:00:00'"],
+]
+query = "SELECT WEEKOFYEAR(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp_tz"
+description = "WEEKOFYEAR of a TIMESTAMP_TZ whose UTC instant is in the next ISO week. Catches: the part taken from the UTC instant instead of the wall clock"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-29 23:30:00.250 -0800'"],
+]
+query = "SELECT WEEKOFYEAR(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_date_or_timestamp_expr"
+description = "NULL next to a set date. Catches: NULL read as a default date (1970-01-01)"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-06-15'"],
+]
+query = "SELECT WEEKOFYEAR(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_leap"
+description = "Days around 29 February in a leap and a common year. Catches: a 365-day year or a Julian 1900, which moves a Sunday/Monday edge after 29 February into the other week"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-03-03'"],
+ ["'2024-03-04'"],
+ ["'2004-03-01'"],
+ ["'1900-03-04'"],
+ ["'2000-03-05'"],
+ ["'2023-03-01'"],
+]
+query = "SELECT WEEKOFYEAR(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
diff --git a/cases/translation/functions/year.year.toml b/cases/translation/functions/year.year.toml
new file mode 100644
index 000000000..969ebe10c
--- /dev/null
+++ b/cases/translation/functions/year.year.toml
@@ -0,0 +1,80 @@
+category = "translation"
+construct = "sf.functions.year.year"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "YEAR of dates at year, ISO-week and leap boundaries. Catches: an off-by-one or 0-based result at the boundaries"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-12-30'"],
+ ["'2021-01-01'"],
+ ["'2023-01-01'"],
+ ["'2024-02-29'"],
+ ["'2026-12-31'"],
+]
+query = "SELECT YEAR(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_timestamp_ntz"
+description = "YEAR of a timestamp just before midnight on 31 December. Catches: the timestamp rounded to the next day"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-12-31 23:59:59.999'"],
+ ["'2024-03-01 00:00:00'"],
+]
+query = "SELECT YEAR(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp_tz"
+description = "YEAR of a TIMESTAMP_TZ whose UTC instant is in the next year. Catches: the year of the UTC instant (2025) instead of the TIMESTAMP_TZ's own wall clock (2024)"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-31 23:30:00.250 -0800'"],
+]
+query = "SELECT YEAR(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_date_or_timestamp_expr"
+description = "NULL next to a set date. Catches: NULL read as a default date (1970-01-01)"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-06-15'"],
+]
+query = "SELECT YEAR(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_leap"
+description = "Days around 29 February in a leap and a common year. Catches: a fixed 365-day year or a non-leap 2024"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-02-29'"],
+ ["'2024-03-01'"],
+ ["'2023-03-01'"],
+ ["'2000-12-31'"],
+ ["'1900-03-01'"],
+ ["'2024-12-31'"],
+]
+query = "SELECT YEAR(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
+
+[[case]]
+id = "t006_interval"
+description = "YEAR of the interval type the page allows. Catches: an error for an interval, or the interval read as a date"
+columns = [{ name = "k", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT YEAR(INTERVAL '3-5' YEAR TO MONTH) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
diff --git a/cases/translation/functions/year.yearofweek.toml b/cases/translation/functions/year.yearofweek.toml
new file mode 100644
index 000000000..df5e1b8e1
--- /dev/null
+++ b/cases/translation/functions/year.yearofweek.toml
@@ -0,0 +1,68 @@
+category = "translation"
+construct = "sf.functions.year.yearofweek"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "YEAROFWEEK of dates at year, ISO-week and leap boundaries. Catches: the calendar year (2024) for 2024-12-30, which belongs to week 1 of 2025"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-12-30'"],
+ ["'2021-01-01'"],
+ ["'2023-01-01'"],
+ ["'2024-02-29'"],
+ ["'2026-12-31'"],
+]
+query = "SELECT YEAROFWEEK(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_timestamp_ntz"
+description = "YEAROFWEEK of a timestamp just before midnight on Sunday 29 December 2024, the last day of ISO week 52. Catches: the timestamp rounded to the next day"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2024-12-29 23:59:59.999'"],
+ ["'2024-03-01 00:00:00'"],
+]
+query = "SELECT YEAROFWEEK(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp_tz"
+description = "YEAROFWEEK of a TIMESTAMP_TZ whose UTC instant is in the next ISO week-year. Catches: the part taken from the UTC instant instead of the wall clock"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2024-12-29 23:30:00.250 -0800'"],
+]
+query = "SELECT YEAROFWEEK(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_date_or_timestamp_expr"
+description = "NULL next to a set date. Catches: NULL read as a default date (1970-01-01)"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-06-15'"],
+]
+query = "SELECT YEAROFWEEK(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_leap"
+description = "Days around 29 February in a leap and a common year. Catches: leap dates rejected; YEAROFWEEK itself is unchanged by them (t001 covers the ISO year edges)"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-02-29'"],
+ ["'2024-03-01'"],
+ ["'2023-03-01'"],
+ ["'2000-12-31'"],
+ ["'1900-03-01'"],
+]
+query = "SELECT YEAROFWEEK(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
diff --git a/cases/translation/functions/year.yearofweekiso.toml b/cases/translation/functions/year.yearofweekiso.toml
new file mode 100644
index 000000000..64e158f4d
--- /dev/null
+++ b/cases/translation/functions/year.yearofweekiso.toml
@@ -0,0 +1,68 @@
+category = "translation"
+construct = "sf.functions.year.yearofweekiso"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_date"
+description = "YEAROFWEEKISO of dates at year, ISO-week and leap boundaries. Catches: the calendar year instead of the ISO week-year"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-12-30'"],
+ ["'2021-01-01'"],
+ ["'2023-01-01'"],
+ ["'2024-02-29'"],
+ ["'2026-12-31'"],
+]
+query = "SELECT YEAROFWEEKISO(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_timestamp_ntz"
+description = "YEAROFWEEKISO of a timestamp just before midnight on Sunday 31 December 2023, the last day of ISO 2023. Catches: the timestamp rounded to the next day"
+columns = [{ name = "t", type = "TIMESTAMP_NTZ" }]
+rows = [
+ ["'2023-12-31 23:59:59.999'"],
+ ["'2024-03-01 00:00:00'"],
+]
+query = "SELECT YEAROFWEEKISO(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_timestamp_tz"
+description = "YEAROFWEEKISO of a TIMESTAMP_TZ whose UTC instant is in the next ISO week-year. Catches: the part taken from the UTC instant instead of the wall clock"
+columns = [{ name = "t", type = "TIMESTAMP_TZ" }]
+rows = [
+ ["'2023-12-31 23:30:00 -0800'"],
+]
+query = "SELECT YEAROFWEEKISO(t) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t004_null_date_or_timestamp_expr"
+description = "NULL next to a set date. Catches: NULL read as a default date (1970-01-01)"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["NULL"],
+ ["'2024-06-15'"],
+]
+query = "SELECT YEAROFWEEKISO(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
+
+[[case]]
+id = "t005_leap"
+description = "Days around 29 February in a leap and a common year. Catches: a 365-day year or a Julian 1900, which moves the ISO week-year edge at a year end"
+columns = [{ name = "d", type = "DATE" }]
+rows = [
+ ["'2024-02-29'"],
+ ["'2000-12-31'"],
+ ["'1900-12-30'"],
+ ["'1900-12-31'"],
+ ["'2024-12-29'"],
+]
+query = "SELECT YEAROFWEEKISO(d) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["boundary"]
diff --git a/cases/translation/functions/zeroifnull.toml b/cases/translation/functions/zeroifnull.toml
new file mode 100644
index 000000000..21b5142b4
--- /dev/null
+++ b/cases/translation/functions/zeroifnull.toml
@@ -0,0 +1,40 @@
+category = "translation"
+construct = "sf.functions.zeroifnull"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_number"
+description = "NULL and set values with a scale. Catches: the result scale dropped (0 instead of 0.00), or values changed"
+columns = [{ name = "a", type = "NUMBER(10,2)" }]
+rows = [
+ ["NULL"],
+ ["-1.25"],
+ ["0.00"],
+]
+query = "SELECT ZEROIFNULL(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_float"
+description = "FLOAT values. Catches: a NUMBER result"
+columns = [{ name = "a", type = "FLOAT" }]
+rows = [
+ ["NULL"],
+ ["2.5e-3"],
+]
+query = "SELECT ZEROIFNULL(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t003_null_expr"
+description = "NULL input. Catches: NULL returned"
+columns = [{ name = "a", type = "NUMBER(38,0)" }]
+rows = [
+ ["NULL"],
+ ["7"],
+]
+query = "SELECT ZEROIFNULL(a) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["null"]
diff --git a/cases/translation/functions/zipf.toml b/cases/translation/functions/zipf.toml
new file mode 100644
index 000000000..fad813088
--- /dev/null
+++ b/cases/translation/functions/zipf.toml
@@ -0,0 +1,36 @@
+category = "translation"
+construct = "sf.functions.zipf"
+session_profile = "utc-v1"
+
+[[case]]
+id = "t001_distribution"
+description = "s = 1 and N = 10 over 1000 rows: the range, and 1 drawn more often than 10. Catches: values from 0, or a uniform distribution"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT MIN(z) AS lo, MAX(z) <= 10 AS hi_ok, COUNT_IF(z = 1) > COUNT_IF(z = 10) AS skewed FROM (SELECT ZIPF(1, 10, RANDOM()) AS z FROM TABLE(GENERATOR(ROWCOUNT => 1000)))"
+order = "ordered"
+tags = ["type-combination"]
+
+[[case]]
+id = "t002_n_zero"
+description = "N = 0, below the range the page states. Catches: the call accepted"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT ZIPF(1, 0, RANDOM()) AS r FROM input ORDER BY ordinal"
+order = "ordered"
+tags = ["error"]
+
+[[case]]
+id = "t003_null_gen"
+description = "A NULL generator; the page does not say; the recording decides. Catches: a value where Snowflake returns NULL, or the reverse"
+columns = [{ name = "x", type = "NUMBER(38,0)" }]
+rows = [
+ ["1"],
+]
+query = "SELECT ZIPF(1, 10, NULL) AS r FROM input"
+order = "ordered"
+tags = ["null"]
diff --git a/coverage/translation/form-links.toml b/coverage/translation/form-links.toml
index 378182460..a4b6c46ab 100644
--- a/coverage/translation/form-links.toml
+++ b/coverage/translation/form-links.toml
@@ -470,6 +470,44 @@ forms = ["sf.expressions.bind-variables"]
constructs = ["node:parameter", "spelling:positional_column"]
forms = ["sf.query.select.select.selecting-specific-columns"]
+# Casts to DECFLOAT, IPADDRESS, PERIOD and VECTOR, ROLLUP and CUBE in GROUP BY, the COLLATE operator,
+# NTH_VALUE with its FROM and NULLS clauses, and unary plus.
+[[link]]
+constructs = ["cast:DECFLOAT"]
+forms = ["sf.expressions.data-types-numeric.decfloat"]
+
+[[link]]
+constructs = ["cast:IPADDRESS"]
+forms = ["sf.expressions.data-types-ipaddress"]
+
+[[link]]
+constructs = ["cast:PERIOD"]
+forms = ["sf.expressions.data-types-period"]
+
+[[link]]
+constructs = ["cast:VECTOR"]
+forms = ["sf.expressions.data-types-vector"]
+
+[[link]]
+constructs = ["fn:ROLLUP"]
+forms = ["sf.query.group-by-rollup"]
+
+[[link]]
+constructs = ["fn:CUBE"]
+forms = ["sf.query.group-by-cube"]
+
+[[link]]
+constructs = ["node:collation"]
+forms = ["sf.functions.collate.collate.syntax"]
+
+[[link]]
+constructs = ["node:nth_value"]
+forms = ["sf.functions.nth-value"]
+
+[[link]]
+constructs = ["node:pos"]
+forms = ["sf.expressions.operators-arithmetic.unary-plus"]
+
[[no_form]]
constructs = ["node:column", "node:alias", "node:null", "node:boolean", "node:paren", "node:annotated"]
reason = "Column references, aliases, NULL, TRUE and FALSE, parentheses and comments are part of every form's grammar rather than forms of their own."
diff --git a/coverage/translation/not-translatable.toml b/coverage/translation/not-translatable.toml
index 16470755b..93cbc18aa 100644
--- a/coverage/translation/not-translatable.toml
+++ b/coverage/translation/not-translatable.toml
@@ -607,7 +607,6 @@ forms = [
"sf.security_admin.show-shares-in-replication-group",
"sf.security_admin.show-snapshot-policies",
"sf.security_admin.show-storage-lifecycle-policies",
- "sf.security_admin.show-user-functions",
"sf.security_admin.show-user-key-pairs",
"sf.security_admin.show-user-procedures",
"sf.security_admin.show-user-programmatic-access-tokens",
@@ -889,44 +888,16 @@ forms = [
[[group]]
id = "stages-and-files"
-reason = "Stages, staged files and file URLs (PUT, GET, LIST, REMOVE, COPY with stages, FILE values, file functions). Stages are Snowflake storage objects with no DuckDB counterpart."
+reason = "External volumes, Git repository listings, COPY error tables, FILE values and the file functions (FL_*, file URLs, TO_FILE). They read files and file metadata that Snowflake stores and serves, with no DuckDB counterpart. Stage objects, PUT, GET, LIST, REMOVE and COPY are counted: each translates through a stage-to-path mapping or is refused with its own code."
forms = [
"sf.ddl.alter-external-volume.add-storage-location-name-cloudproviderparams",
"sf.ddl.alter-external-volume.remove-storage-location",
"sf.ddl.alter-external-volume.set-allow-writes-true-false",
"sf.ddl.alter-external-volume.set-comment",
"sf.ddl.alter-external-volume.update-storage-location-credentials-aws-key-id",
- "sf.ddl.alter-file-format.rename-to",
- "sf.ddl.alter-file-format.set",
- "sf.ddl.alter-stage.refresh",
- "sf.ddl.alter-stage.rename-to",
- "sf.ddl.alter-stage.set",
- "sf.ddl.alter-stage.set-directory-enable-true",
- "sf.ddl.alter-stage.set-tag",
- "sf.ddl.alter-stage.set.syntax",
- "sf.ddl.alter-stage.unset-stage-undrop-retention-time-in-days",
- "sf.ddl.alter-stage.unset-tag",
"sf.ddl.create-external-volume",
- "sf.ddl.create-file-format.create-file-format",
- "sf.ddl.create-file-format.create-or-alter-file",
- "sf.ddl.create-file-format.define-file-format",
- "sf.ddl.create-stage.create-or-alter-stage",
- "sf.ddl.create-stage.create-or-alter-stage.create-or-alter-stage",
- "sf.ddl.create-stage.create-stage-clone",
- "sf.ddl.create-stage.create-stage-externalstageparams-directorytableparams",
- "sf.ddl.create-stage.create-stage-internalstageparams-directorytableparams",
- "sf.ddl.create-stage.define-stage",
"sf.ddl.drop-external-volume",
- "sf.ddl.drop-file-format",
- "sf.ddl.drop-stage",
- "sf.dml.copy-files.from",
- "sf.dml.copy-files.from-select-from",
- "sf.dml.copy-into-table.select-from-internalstage-externalstage",
- "sf.dml.get",
- "sf.dml.list.internalstage-externalstage",
"sf.dml.list.repositoryclone",
- "sf.dml.put",
- "sf.dml.remove",
"sf.dml.truncate-table.truncate-error-table",
"sf.expressions.data-types-unstructured",
"sf.functions.build-scoped-file-url",
@@ -949,7 +920,6 @@ forms = [
"sf.functions.get-presigned-url",
"sf.functions.get-relative-path",
"sf.functions.get-stage-location",
- "sf.functions.infer-schema",
"sf.functions.to-file",
"sf.functions.try-to-file",
]
@@ -996,7 +966,6 @@ forms = [
"sf.ddl.create-backup-set.or-alter-backup-set.create-or-alter-backup-set",
"sf.ddl.create-clone.alert-file-format-sequence",
"sf.ddl.create-clone.database-role-clone",
- "sf.ddl.create-clone.dynamic-table-clone",
"sf.ddl.create-clone.event-table-clone",
"sf.ddl.create-clone.iceberg-table-clone",
"sf.ddl.create-snapshot",
@@ -1017,7 +986,6 @@ forms = [
"sf.ddl.drop-snapshot-set",
"sf.ddl.drop-stream",
"sf.ddl.undrop-database",
- "sf.ddl.undrop-dynamic-table",
"sf.ddl.undrop-external-volume",
"sf.ddl.undrop-iceberg-table",
"sf.ddl.undrop-notebook",
@@ -1035,7 +1003,7 @@ forms = [
[[group]]
id = "snowflake-object-kinds"
-reason = "Object kinds DuckDB does not have: pipelines (pipes, tasks, alerts, dynamic and external tables), applications, services, compute pools, integrations, sharing, listings, replication, backups, tags, data metric objects and schema variables, among others. Iceberg tables are listed as a whole: each is defined by an external volume or catalogue integration and its files, which DuckDB SQL cannot create; a plain table with the same rows is the counted CREATE TABLE form. Creating or altering these objects has no DuckDB translation."
+reason = "Object kinds DuckDB does not have: pipelines (pipes, tasks, alerts), applications, services, compute pools, integrations, sharing, listings, replication, backups, tags, data metric objects and schema variables, among others. Iceberg tables are listed as a whole: each is defined by an external volume or catalogue integration and its files, which DuckDB SQL cannot create; a plain table with the same rows is the counted CREATE TABLE form. Creating or altering these objects has no DuckDB translation. AGG returns a metric of a semantic view, one of these objects, so it has nothing to read without one."
forms = [
"sf.ddl.alter-agent.modify-live-version-set",
"sf.ddl.alter-agent.set",
@@ -1154,18 +1122,6 @@ forms = [
"sf.ddl.alter-dcm-project.drop-deployment",
"sf.ddl.alter-dcm-project.set",
"sf.ddl.alter-dcm-project.unset",
- "sf.ddl.alter-dynamic-table.clusteringaction",
- "sf.ddl.alter-dynamic-table.datagovnpolicytagaction",
- "sf.ddl.alter-dynamic-table.refresh",
- "sf.ddl.alter-dynamic-table.rename-to",
- "sf.ddl.alter-dynamic-table.searchoptimizationaction",
- "sf.ddl.alter-dynamic-table.set",
- "sf.ddl.alter-dynamic-table.set-unset-comment",
- "sf.ddl.alter-dynamic-table.storagelifecyclepolicyaction",
- "sf.ddl.alter-dynamic-table.suspend-resume",
- "sf.ddl.alter-dynamic-table.swap-with",
- "sf.ddl.alter-dynamic-table.tablecolumncommentaction",
- "sf.ddl.alter-dynamic-table.unset",
"sf.ddl.alter-event-routing-table.modify-rule-rename-to",
"sf.ddl.alter-event-routing-table.rename-to",
"sf.ddl.alter-event-routing-table.set-rule-region-group-regions",
@@ -1184,12 +1140,6 @@ forms = [
"sf.ddl.alter-external-consumer.rename-to",
"sf.ddl.alter-external-consumer.set",
"sf.ddl.alter-external-consumer.unset",
- "sf.ddl.alter-external-table.add-files",
- "sf.ddl.alter-external-table.add-partition-location",
- "sf.ddl.alter-external-table.drop-partition-location",
- "sf.ddl.alter-external-table.refresh",
- "sf.ddl.alter-external-table.remove-files",
- "sf.ddl.alter-external-table.set",
"sf.ddl.alter-function-dmf.rename-to",
"sf.ddl.alter-function-dmf.set-comment",
"sf.ddl.alter-function-dmf.set-secure",
@@ -1406,14 +1356,6 @@ forms = [
"sf.ddl.create-dataset",
"sf.ddl.create-dbt-project",
"sf.ddl.create-dcm-project",
- "sf.ddl.create-dynamic-table.create-dynamic-iceberg-table",
- "sf.ddl.create-dynamic-table.create-dynamic-iceberg-table.create-dynamic-iceberg-table",
- "sf.ddl.create-dynamic-table.create-dynamic-table-clone",
- "sf.ddl.create-dynamic-table.create-dynamic-table-from",
- "sf.ddl.create-dynamic-table.create-dynamic-table-target-lag",
- "sf.ddl.create-dynamic-table.create-or-alter-dynamic",
- "sf.ddl.create-dynamic-table.create-or-alter-dynamic-iceberg-table",
- "sf.ddl.create-dynamic-table.define-dynamic-table",
"sf.ddl.create-event-routing-table",
"sf.ddl.create-event-table.table",
"sf.ddl.create-event-table.table-clone",
@@ -1422,10 +1364,6 @@ forms = [
"sf.ddl.create-external-consumer",
"sf.ddl.create-external-function.function",
"sf.ddl.create-external-function.function-returns-api-integration-as",
- "sf.ddl.create-external-table.cloudproviderparams-location-externalstage-file-format",
- "sf.ddl.create-external-table.cloudproviderparams-location-externalstage-partition-type",
- "sf.ddl.create-external-table.cloudproviderparams-location-externalstage-partition-type.syntax",
- "sf.ddl.create-external-table.using-template",
"sf.ddl.create-function-spcs.function-returns-service-endpoint",
"sf.ddl.create-gateway",
"sf.ddl.create-git-repository",
@@ -1500,12 +1438,10 @@ forms = [
"sf.ddl.drop-data-movement-rule",
"sf.ddl.drop-dbt-project",
"sf.ddl.drop-dcm-project",
- "sf.ddl.drop-dynamic-table",
"sf.ddl.drop-event-routing-table",
"sf.ddl.drop-experiment",
"sf.ddl.drop-external-agent",
"sf.ddl.drop-external-consumer",
- "sf.ddl.drop-external-table",
"sf.ddl.drop-function-dmf",
"sf.ddl.drop-gateway",
"sf.ddl.drop-git-repository",
@@ -1540,11 +1476,12 @@ forms = [
"sf.ddl.experiment.show-runs-in-experiment",
"sf.ddl.truncate-materialized-view",
"sf.expressions.schema-variables",
+ "sf.functions.agg",
]
[[group]]
id = "metadata-listings"
-reason = "SHOW and DESCRIBE commands other than those describing a table's or view's columns. Their result columns are Snowflake catalogue metadata (owners, creation and update times, account settings, object properties) with no DuckDB equivalent, so results cannot match."
+reason = "SHOW and DESCRIBE commands other than those with a DuckDB catalogue counterpart (databases, schemas, tables, views, objects, sequences, primary keys, functions and indexes, and dynamic and external tables), which are counted. Their result columns are Snowflake catalogue metadata (owners, creation and update times, account settings, object properties) with no DuckDB equivalent, so results cannot match."
forms = [
"sf.ddl.desc-agent",
"sf.ddl.desc-alert",
@@ -1559,17 +1496,13 @@ forms = [
"sf.ddl.desc-configuration",
"sf.ddl.desc-cortex-search",
"sf.ddl.desc-data-movement-rule",
- "sf.ddl.desc-database",
"sf.ddl.desc-dbt-project",
"sf.ddl.desc-dcm-project",
- "sf.ddl.desc-dynamic-table",
"sf.ddl.desc-event-table",
"sf.ddl.desc-external-agent",
"sf.ddl.desc-external-consumer",
- "sf.ddl.desc-external-table",
"sf.ddl.desc-external-volume",
"sf.ddl.desc-file-format",
- "sf.ddl.desc-function",
"sf.ddl.desc-function-dmf",
"sf.ddl.desc-function-spcs",
"sf.ddl.desc-gateway",
@@ -1588,10 +1521,8 @@ forms = [
"sf.ddl.desc-postgres-instance",
"sf.ddl.desc-procedure",
"sf.ddl.desc-restricted-session-scope",
- "sf.ddl.desc-schema",
"sf.ddl.desc-search-optimization",
"sf.ddl.desc-semantic-view",
- "sf.ddl.desc-sequence",
"sf.ddl.desc-service",
"sf.ddl.desc-snapshot",
"sf.ddl.desc-snapshot-set",
@@ -1631,7 +1562,6 @@ forms = [
"sf.ddl.show-cortex-search",
"sf.ddl.show-data-metric-functions",
"sf.ddl.show-data-movement-rules",
- "sf.ddl.show-databases",
"sf.ddl.show-datasets",
"sf.ddl.show-dbt-projects",
"sf.ddl.show-dcm-projects",
@@ -1639,7 +1569,6 @@ forms = [
"sf.ddl.show-delegated-authorizations.authorizations-by-user",
"sf.ddl.show-delegated-authorizations.authorizations-to-security-integration",
"sf.ddl.show-deployments-in-dcm-project",
- "sf.ddl.show-dynamic-tables",
"sf.ddl.show-endpoints",
"sf.ddl.show-entities-in-dcm-project.in-dcm-project",
"sf.ddl.show-entities-in-dcm-project.in-dcm-project-limit",
@@ -1651,11 +1580,9 @@ forms = [
"sf.ddl.show-external-agents",
"sf.ddl.show-external-consumers",
"sf.ddl.show-external-functions",
- "sf.ddl.show-external-tables",
"sf.ddl.show-external-volumes",
"sf.ddl.show-file-formats",
"sf.ddl.show-files-in-artifact-repository",
- "sf.ddl.show-functions",
"sf.ddl.show-functions-in-model",
"sf.ddl.show-gateways",
"sf.ddl.show-git-branches",
@@ -1666,7 +1593,6 @@ forms = [
"sf.ddl.show-image-repositories",
"sf.ddl.show-images-in-image-repository",
"sf.ddl.show-indexes",
- "sf.ddl.show-indexes-standard-tables",
"sf.ddl.show-interactive-tables",
"sf.ddl.show-listings",
"sf.ddl.show-materialized-views",
@@ -1680,7 +1606,6 @@ forms = [
"sf.ddl.show-notebook-projects.projects-in-database",
"sf.ddl.show-notebook-projects.projects-in-schema",
"sf.ddl.show-notebooks",
- "sf.ddl.show-objects",
"sf.ddl.show-objects-owned-by-application",
"sf.ddl.show-offers",
"sf.ddl.show-online-feature-tables",
@@ -1692,7 +1617,6 @@ forms = [
"sf.ddl.show-pipes",
"sf.ddl.show-postgres-instances",
"sf.ddl.show-pricing-plans",
- "sf.ddl.show-primary-keys",
"sf.ddl.show-procedures",
"sf.ddl.show-references",
"sf.ddl.show-regions",
@@ -1706,13 +1630,11 @@ forms = [
"sf.ddl.show-run-in-experiment.parameters-in-experiment",
"sf.ddl.show-runs-in-experiment",
"sf.ddl.show-schema-variables",
- "sf.ddl.show-schemas",
"sf.ddl.show-semantic-dimensions",
"sf.ddl.show-semantic-dimensions-for-metric",
"sf.ddl.show-semantic-facts",
"sf.ddl.show-semantic-metrics",
"sf.ddl.show-semantic-views",
- "sf.ddl.show-sequences",
"sf.ddl.show-service-containers-in-service",
"sf.ddl.show-service-instances-in-service",
"sf.ddl.show-service-volumes-in-service",
@@ -1725,7 +1647,6 @@ forms = [
"sf.ddl.show-stages",
"sf.ddl.show-streamlits",
"sf.ddl.show-streams",
- "sf.ddl.show-tables",
"sf.ddl.show-tags",
"sf.ddl.show-task-graphs",
"sf.ddl.show-tasks",
@@ -1738,7 +1659,6 @@ forms = [
"sf.ddl.show-versions-in-listing",
"sf.ddl.show-versions-in-model",
"sf.ddl.show-versions-in-openflow-connector",
- "sf.ddl.show-views",
"sf.ddl.show-warehouses",
"sf.ddl.show-workspaces",
"sf.transaction_session.show-variables",
@@ -2345,3 +2265,10 @@ forms = [
"sf.functions.dmf-invalid-timestamp-string-count",
"sf.functions.dmf-invalid-timestamp-string-percent",
]
+
+[[group]]
+id = "obsoleted-functions"
+reason = "The page is marked Obsoleted and Snowflake reports the function as unknown (error 2139), so there is no Snowflake behaviour to translate."
+forms = [
+ "sf.functions.md5-number",
+]
diff --git a/coverage/translation/obligations/expressions.toml b/coverage/translation/obligations/expressions.toml
new file mode 100644
index 000000000..bc62f5722
--- /dev/null
+++ b/coverage/translation/obligations/expressions.toml
@@ -0,0 +1,739 @@
+# Translation obligations of family expressions: per form, the documented type
+# combinations, NULL in each argument, boundaries and error parity, each met by its
+# cases under cases/translation. Hand-written; see coverage/translation/README.md.
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.add.t001"
+form = "sf.expressions.operators-arithmetic.add"
+kind = "type_combination"
+target = "NUMBER(38,0) + NUMBER(38,0)"
+note = "a NUMBER(38,0) + b NUMBER(38,0)."
+catches = "operands swapped for subtraction or truncated to 32 bits"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.add.toml#t001_num_num_380380"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.add.t002"
+form = "sf.expressions.operators-arithmetic.add"
+kind = "type_combination"
+target = "NUMBER(10,2) + NUMBER(38,0)"
+note = "a NUMBER(10,2) + b NUMBER(38,0)."
+catches = "the sum losing the NUMBER(10,2) scale (4 instead of 4.25)"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.add.toml#t002_num_num_102380"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.add.t003"
+form = "sf.expressions.operators-arithmetic.add"
+kind = "type_combination"
+target = "NUMBER(10,2) + NUMBER(12,4)"
+note = "a NUMBER(10,2) + b NUMBER(12,4)."
+catches = "the result scale taken from the left operand only (1.31 instead of 1.3125)"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.add.toml#t003_num_num_102124"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.add.t004"
+form = "sf.expressions.operators-arithmetic.add"
+kind = "type_combination"
+target = "NUMBER(38,0) + FLOAT"
+note = "a NUMBER(38,0) + b FLOAT."
+catches = "a FLOAT operand rounded to the integer side"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.add.toml#t004_num_float_380"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.add.t005"
+form = "sf.expressions.operators-arithmetic.add"
+kind = "type_combination"
+target = "NUMBER(10,2) + FLOAT"
+note = "a NUMBER(10,2) + b FLOAT."
+catches = "the FLOAT operand cast to NUMBER(10,2) (1.00 instead of 1.001) or a fixed-point result type"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.add.toml#t005_num_float_102"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.add.t006"
+form = "sf.expressions.operators-arithmetic.add"
+kind = "type_combination"
+target = "FLOAT + FLOAT"
+note = "a FLOAT + b FLOAT."
+catches = "float addition replaced by decimal addition (0.30000000000000004 vs 0.3)"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.add.toml#t006_float_float"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.add.t007"
+form = "sf.expressions.operators-arithmetic.add"
+kind = "null_argument"
+target = "a"
+note = "NULL as a, the other operand set."
+catches = "NULL treated as 0 instead of propagating"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.add.toml#t007_null_a"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.add.t008"
+form = "sf.expressions.operators-arithmetic.add"
+kind = "null_argument"
+target = "b"
+note = "NULL as b, the other operand set."
+catches = "NULL treated as 0 instead of propagating"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.add.toml#t008_null_b"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.add.t009"
+form = "sf.expressions.operators-arithmetic.add"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "38-digit integers that still fit."
+catches = "arithmetic done in 64 bits"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.add.toml#t009_max_38"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.add.t010"
+form = "sf.expressions.operators-arithmetic.add"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "Negative fixed-point sums crossing zero."
+catches = "sign lost or magnitude added"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.add.toml#t010_negatives"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.add.t011"
+form = "sf.expressions.operators-arithmetic.add"
+kind = "error_parity"
+target = "error"
+note = "The sum of two 38-digit integers overflows NUMBER(38,0)."
+catches = "wrapping or a silent switch to FLOAT instead of the out-of-range error"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.add.toml#t011_overflow"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.divide.t001"
+form = "sf.expressions.operators-arithmetic.divide"
+kind = "type_combination"
+target = "NUMBER(38,0) / NUMBER(38,0)"
+note = "a NUMBER(38,0) / b NUMBER(38,0)."
+catches = "integer division (3 instead of 3.5) or a truncated rather than rounded scale-6 quotient (0.666666 vs 0.666667)"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.divide.toml#t001_num_num_380380"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.divide.t002"
+form = "sf.expressions.operators-arithmetic.divide"
+kind = "type_combination"
+target = "NUMBER(10,2) / NUMBER(38,0)"
+note = "a NUMBER(10,2) / b NUMBER(38,0)."
+catches = "the quotient scale (s+6) handled wrongly"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.divide.toml#t002_num_num_102380"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.divide.t003"
+form = "sf.expressions.operators-arithmetic.divide"
+kind = "type_combination"
+target = "NUMBER(10,2) / NUMBER(12,4)"
+note = "a NUMBER(10,2) / b NUMBER(12,4)."
+catches = "quotient scale taken from the divisor"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.divide.toml#t003_num_num_102124"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.divide.t004"
+form = "sf.expressions.operators-arithmetic.divide"
+kind = "type_combination"
+target = "NUMBER(38,0) / FLOAT"
+note = "a NUMBER(38,0) / b FLOAT."
+catches = "FLOAT divisor rounded"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.divide.toml#t004_num_float_380"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.divide.t005"
+form = "sf.expressions.operators-arithmetic.divide"
+kind = "type_combination"
+target = "NUMBER(10,2) / FLOAT"
+note = "a NUMBER(10,2) / b FLOAT."
+catches = "fixed-point / FLOAT result type swapped"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.divide.toml#t005_num_float_102"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.divide.t006"
+form = "sf.expressions.operators-arithmetic.divide"
+kind = "type_combination"
+target = "FLOAT / FLOAT"
+note = "a FLOAT / b FLOAT."
+catches = "float division done in decimal"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.divide.toml#t006_float_float"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.divide.t007"
+form = "sf.expressions.operators-arithmetic.divide"
+kind = "null_argument"
+target = "a"
+note = "NULL as a, the other operand set."
+catches = "NULL treated as 0"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.divide.toml#t007_null_a"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.divide.t008"
+form = "sf.expressions.operators-arithmetic.divide"
+kind = "null_argument"
+target = "b"
+note = "NULL as b, the other operand set."
+catches = "NULL divisor turned into the division-by-zero error or 0"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.divide.toml#t008_null_b"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.divide.t009"
+form = "sf.expressions.operators-arithmetic.divide"
+kind = "boundary"
+subkind = "rounding_scale"
+target = "rounding_scale"
+note = "Scales near the 12-digit quotient cap."
+catches = "the quotient scale not capped at 12"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.divide.toml#t009_scale_12"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.divide.t010"
+form = "sf.expressions.operators-arithmetic.divide"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "Negative quotients that round at the last digit."
+catches = "rounding toward zero instead of half away from zero"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.divide.toml#t010_neg_quotient"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.divide.t011"
+form = "sf.expressions.operators-arithmetic.divide"
+kind = "error_parity"
+target = "error"
+note = "Division by zero is an error."
+catches = "NULL, 0 or infinity instead of the division-by-zero error"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.divide.toml#t011_div_zero"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.modulo.t001"
+form = "sf.expressions.operators-arithmetic.modulo"
+kind = "type_combination"
+target = "NUMBER(38,0) % NUMBER(38,0)"
+note = "a NUMBER(38,0) % b NUMBER(38,0)."
+catches = "floor-based modulo (2 instead of -1): MOD truncates toward zero"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.modulo.toml#t001_num_num_380380"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.modulo.t002"
+form = "sf.expressions.operators-arithmetic.modulo"
+kind = "type_combination"
+target = "NUMBER(10,2) % NUMBER(38,0)"
+note = "a NUMBER(10,2) % b NUMBER(38,0)."
+catches = "fractional remainder dropped"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.modulo.toml#t002_num_num_102380"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.modulo.t003"
+form = "sf.expressions.operators-arithmetic.modulo"
+kind = "type_combination"
+target = "NUMBER(10,2) % NUMBER(12,4)"
+note = "a NUMBER(10,2) % b NUMBER(12,4)."
+catches = "result scale taken from one operand"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.modulo.toml#t003_num_num_102124"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.modulo.t004"
+form = "sf.expressions.operators-arithmetic.modulo"
+kind = "type_combination"
+target = "NUMBER(38,0) % FLOAT"
+note = "a NUMBER(38,0) % b FLOAT."
+catches = "FLOAT divisor rounded"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.modulo.toml#t004_num_float_380"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.modulo.t005"
+form = "sf.expressions.operators-arithmetic.modulo"
+kind = "type_combination"
+target = "FLOAT % FLOAT"
+note = "a FLOAT % b FLOAT."
+catches = "floor-based float modulo"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.modulo.toml#t005_float_float"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.modulo.t006"
+form = "sf.expressions.operators-arithmetic.modulo"
+kind = "null_argument"
+target = "a"
+note = "NULL as a, the other operand set."
+catches = "NULL treated as 0"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.modulo.toml#t006_null_a"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.modulo.t007"
+form = "sf.expressions.operators-arithmetic.modulo"
+kind = "null_argument"
+target = "b"
+note = "NULL as b, the other operand set."
+catches = "NULL treated as 0"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.modulo.toml#t007_null_b"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.modulo.t008"
+form = "sf.expressions.operators-arithmetic.modulo"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "Both operands negative."
+catches = "remainder made non-negative (Euclidean or ABS): 2 instead of -1 for -7 % -3, 4 instead of -1 for -1 % -5"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.modulo.toml#t008_both_negative"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.modulo.t009"
+form = "sf.expressions.operators-arithmetic.modulo"
+kind = "error_parity"
+target = "error"
+note = "A non-numeric string operand."
+catches = "the string silently read as 0 instead of the conversion error"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.modulo.toml#t009_mod_text"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.modulo.t010"
+form = "sf.expressions.operators-arithmetic.modulo"
+kind = "error_parity"
+target = "error"
+note = "A zero divisor: records what Snowflake returns there; re-kinded to boundary after capture if Snowflake returns a value instead of an error."
+catches = "NULL for x % 0 (DuckDB) where Snowflake raises division by zero"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.modulo.toml#t010_zero_divisor"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.multiply.t001"
+form = "sf.expressions.operators-arithmetic.multiply"
+kind = "type_combination"
+target = "NUMBER(38,0) * NUMBER(38,0)"
+note = "a NUMBER(38,0) * b NUMBER(38,0)."
+catches = "32-bit multiplication overflowing"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.multiply.toml#t001_num_num_380380"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.multiply.t002"
+form = "sf.expressions.operators-arithmetic.multiply"
+kind = "type_combination"
+target = "NUMBER(10,2) * NUMBER(38,0)"
+note = "a NUMBER(10,2) * b NUMBER(38,0)."
+catches = "the NUMBER(10,2) scale lost"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.multiply.toml#t002_num_num_102380"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.multiply.t003"
+form = "sf.expressions.operators-arithmetic.multiply"
+kind = "type_combination"
+target = "NUMBER(10,2) * NUMBER(12,4)"
+note = "a NUMBER(10,2) * b NUMBER(12,4)."
+catches = "the product scale capped or taken from one side (1.5625 vs 1.56)"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.multiply.toml#t003_num_num_102124"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.multiply.t004"
+form = "sf.expressions.operators-arithmetic.multiply"
+kind = "type_combination"
+target = "NUMBER(38,0) * FLOAT"
+note = "a NUMBER(38,0) * b FLOAT."
+catches = "FLOAT operand truncated"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.multiply.toml#t004_num_float_380"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.multiply.t005"
+form = "sf.expressions.operators-arithmetic.multiply"
+kind = "type_combination"
+target = "NUMBER(10,2) * FLOAT"
+note = "a NUMBER(10,2) * b FLOAT."
+catches = "fixed-point / FLOAT result type swapped"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.multiply.toml#t005_num_float_102"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.multiply.t006"
+form = "sf.expressions.operators-arithmetic.multiply"
+kind = "type_combination"
+target = "FLOAT * FLOAT"
+note = "a FLOAT * b FLOAT."
+catches = "underflow to zero handled differently"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.multiply.toml#t006_float_float"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.multiply.t007"
+form = "sf.expressions.operators-arithmetic.multiply"
+kind = "null_argument"
+target = "a"
+note = "NULL as a, the other operand set."
+catches = "NULL treated as 1 or 0"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.multiply.toml#t007_null_a"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.multiply.t008"
+form = "sf.expressions.operators-arithmetic.multiply"
+kind = "null_argument"
+target = "b"
+note = "NULL as b, the other operand set."
+catches = "NULL treated as 1 or 0"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.multiply.toml#t008_null_b"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.multiply.t009"
+form = "sf.expressions.operators-arithmetic.multiply"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "FLOAT products near the double range."
+catches = "overflow to infinity treated differently"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.multiply.toml#t009_float_big"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.multiply.t010"
+form = "sf.expressions.operators-arithmetic.multiply"
+kind = "boundary"
+subkind = "rounding_scale"
+target = "rounding_scale"
+note = "Scales adding past 12 digits."
+catches = "product scale above Snowflake's cap kept or rounded wrongly"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.multiply.toml#t010_scale_sum"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.multiply.t011"
+form = "sf.expressions.operators-arithmetic.multiply"
+kind = "error_parity"
+target = "error"
+note = "A product beyond 38 digits."
+catches = "wrapping or silent FLOAT instead of the out-of-range error"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.multiply.toml#t011_overflow"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.subtract.t001"
+form = "sf.expressions.operators-arithmetic.subtract"
+kind = "type_combination"
+target = "NUMBER(38,0) - NUMBER(38,0)"
+note = "a NUMBER(38,0) - b NUMBER(38,0)."
+catches = "operands in the wrong order (7 instead of -7)"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.subtract.toml#t001_num_num_380380"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.subtract.t002"
+form = "sf.expressions.operators-arithmetic.subtract"
+kind = "type_combination"
+target = "NUMBER(10,2) - NUMBER(38,0)"
+note = "a NUMBER(10,2) - b NUMBER(38,0)."
+catches = "scale of the NUMBER(10,2) side dropped"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.subtract.toml#t002_num_num_102380"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.subtract.t003"
+form = "sf.expressions.operators-arithmetic.subtract"
+kind = "type_combination"
+target = "NUMBER(10,2) - NUMBER(12,4)"
+note = "a NUMBER(10,2) - b NUMBER(12,4)."
+catches = "the result scale taken from one operand only"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.subtract.toml#t003_num_num_102124"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.subtract.t004"
+form = "sf.expressions.operators-arithmetic.subtract"
+kind = "type_combination"
+target = "NUMBER(38,0) - FLOAT"
+note = "a NUMBER(38,0) - b FLOAT."
+catches = "FLOAT rounded to integer"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.subtract.toml#t004_num_float_380"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.subtract.t005"
+form = "sf.expressions.operators-arithmetic.subtract"
+kind = "type_combination"
+target = "NUMBER(10,2) - FLOAT"
+note = "a NUMBER(10,2) - b FLOAT."
+catches = "fixed-point / FLOAT result type swapped"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.subtract.toml#t005_num_float_102"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.subtract.t006"
+form = "sf.expressions.operators-arithmetic.subtract"
+kind = "type_combination"
+target = "FLOAT - FLOAT"
+note = "a FLOAT - b FLOAT."
+catches = "float subtraction done in decimal"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.subtract.toml#t006_float_float"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.subtract.t007"
+form = "sf.expressions.operators-arithmetic.subtract"
+kind = "type_combination"
+target = "DATE - DATE"
+note = "DATE minus DATE: the page says the result is an integer number of days."
+catches = "an INTERVAL instead of an integer count of days, or days counted across the leap day wrongly (29, -730)"
+reading = "operators-arithmetic: \"a - b Subtracts one date expression ( b ) from another ( a ). The result is an integer number of days.\""
+cases = ["cases/translation/expressions/operators-arithmetic.subtract.toml#t007_date_date"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.subtract.t008"
+form = "sf.expressions.operators-arithmetic.subtract"
+kind = "null_argument"
+target = "a"
+note = "NULL as a, the other operand set."
+catches = "NULL treated as 0"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.subtract.toml#t008_null_a"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.subtract.t009"
+form = "sf.expressions.operators-arithmetic.subtract"
+kind = "null_argument"
+target = "b"
+note = "NULL as b, the other operand set."
+catches = "NULL treated as 0"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.subtract.toml#t009_null_b"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.subtract.t010"
+form = "sf.expressions.operators-arithmetic.subtract"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "A smaller minus a larger value."
+catches = "absolute value taken"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.subtract.toml#t010_to_negative"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.subtract.t011"
+form = "sf.expressions.operators-arithmetic.subtract"
+kind = "error_parity"
+target = "error"
+note = "The difference of the most negative and the largest 38-digit integers overflows."
+catches = "wrapping instead of the out-of-range error"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.subtract.toml#t011_overflow"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.unary-minus.t001"
+form = "sf.expressions.operators-arithmetic.unary-minus"
+kind = "type_combination"
+target = "NUMBER(38,0)"
+note = "Negating integers."
+catches = "the sign dropped or the value doubled"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.unary-minus.toml#t001_int"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.unary-minus.t002"
+form = "sf.expressions.operators-arithmetic.unary-minus"
+kind = "type_combination"
+target = "NUMBER(10,2)"
+note = "Negating NUMBER(10,2)."
+catches = "the scale lost (-1 instead of -1.25)"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.unary-minus.toml#t002_dec"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.unary-minus.t003"
+form = "sf.expressions.operators-arithmetic.unary-minus"
+kind = "type_combination"
+target = "FLOAT"
+note = "Negating FLOAT."
+catches = "negative zero or exponent handling"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.unary-minus.toml#t003_float"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.unary-minus.t004"
+form = "sf.expressions.operators-arithmetic.unary-minus"
+kind = "null_argument"
+target = "a"
+note = "Negating NULL."
+catches = "NULL turned into 0"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.unary-minus.toml#t004_null"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.unary-minus.t005"
+form = "sf.expressions.operators-arithmetic.unary-minus"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "Negating the largest 38-digit integer."
+catches = "a 64-bit negation that overflows"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.unary-minus.toml#t005_max"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.unary-minus.t006"
+form = "sf.expressions.operators-arithmetic.unary-minus"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "Negation of a negation and of a subtraction."
+catches = "precedence: - a - b read as -(a - b)"
+reading = "operators-arithmetic: a and b are numeric expressions; the page's scale and precision section covers fixed-point and floating-point operands, so the pairs are integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/expressions/operators-arithmetic.unary-minus.toml#t006_double_neg"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.unary-plus.t001"
+form = "sf.expressions.operators-arithmetic.unary-plus"
+kind = "type_combination"
+target = "VARCHAR"
+note = "Unary plus on numeric strings converts them."
+catches = "the string returned unchanged, so ordering and arithmetic stay textual"
+reading = "operators-arithmetic: \"+a Returns a, which causes implicit conversion of a to a numeric value. If a is a string, but the string can't be converted to a numeric value, an error is returned.\""
+cases = ["cases/translation/expressions/operators-arithmetic.unary-plus.toml#t001_text"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.unary-plus.t002"
+form = "sf.expressions.operators-arithmetic.unary-plus"
+kind = "type_combination"
+target = "NUMBER(10,2)"
+note = "Unary plus keeps a NUMBER(10,2) as it is."
+catches = "a cast that changes the scale"
+reading = "operators-arithmetic: \"+a Returns a, which causes implicit conversion of a to a numeric value. If a is a string, but the string can't be converted to a numeric value, an error is returned.\""
+cases = ["cases/translation/expressions/operators-arithmetic.unary-plus.toml#t002_dec"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.unary-plus.t003"
+form = "sf.expressions.operators-arithmetic.unary-plus"
+kind = "type_combination"
+target = "FLOAT"
+note = "Unary plus on FLOAT."
+catches = "conversion to fixed-point"
+reading = "operators-arithmetic: \"+a Returns a, which causes implicit conversion of a to a numeric value. If a is a string, but the string can't be converted to a numeric value, an error is returned.\""
+cases = ["cases/translation/expressions/operators-arithmetic.unary-plus.toml#t003_float"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.unary-plus.t004"
+form = "sf.expressions.operators-arithmetic.unary-plus"
+kind = "null_argument"
+target = "a"
+note = "Unary plus on NULL text."
+catches = "NULL replaced or converted to 0"
+reading = "operators-arithmetic: \"+a Returns a, which causes implicit conversion of a to a numeric value. If a is a string, but the string can't be converted to a numeric value, an error is returned.\""
+cases = ["cases/translation/expressions/operators-arithmetic.unary-plus.toml#t004_null"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.unary-plus.t005"
+form = "sf.expressions.operators-arithmetic.unary-plus"
+kind = "error_parity"
+target = "error"
+note = "Unary plus on an empty string.: Snowflake raises error 100038 on every row (captured at e8691b63)."
+catches = "NaN, NULL or a value where Snowflake raises an error"
+reading = "operators-arithmetic: \"+a Returns a, which causes implicit conversion of a to a numeric value. If a is a string, but the string can't be converted to a numeric value, an error is returned.\""
+cases = ["cases/translation/expressions/operators-arithmetic.unary-plus.toml#t005_empty"]
+required = true
+
+[[obligation]]
+id = "sf.expressions.operators-arithmetic.unary-plus.t006"
+form = "sf.expressions.operators-arithmetic.unary-plus"
+kind = "error_parity"
+target = "error"
+note = "Unary plus on a non-numeric string is an error."
+catches = "the string passed through unconverted"
+reading = "operators-arithmetic: \"+a Returns a, which causes implicit conversion of a to a numeric value. If a is a string, but the string can't be converted to a numeric value, an error is returned.\""
+cases = ["cases/translation/expressions/operators-arithmetic.unary-plus.toml#t006_bad_text"]
+required = true
diff --git a/coverage/translation/obligations/functions.toml b/coverage/translation/obligations/functions.toml
new file mode 100644
index 000000000..865d5994a
--- /dev/null
+++ b/coverage/translation/obligations/functions.toml
@@ -0,0 +1,33873 @@
+# Translation obligations of family functions: per form, the documented type
+# combinations, NULL in each argument, boundaries and error parity, each met by its
+# cases under cases/translation. Hand-written; see coverage/translation/README.md.
+
+[[obligation]]
+id = "sf.functions.abs.t001"
+form = "sf.functions.abs"
+kind = "type_combination"
+target = "NUMBER(38,0)"
+note = "ABS of integers."
+catches = "the sign kept (ABS as identity) or SIGN computed"
+reading = "the page names no argument type; the numeric category gives integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/functions/abs.toml#t001_int"]
+required = true
+
+[[obligation]]
+id = "sf.functions.abs.t002"
+form = "sf.functions.abs"
+kind = "type_combination"
+target = "NUMBER(10,2)"
+note = "ABS of NUMBER(10,2), keeping its type."
+catches = "the scale lost or FLOAT returned"
+reading = "the page names no argument type; the numeric category gives integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/functions/abs.toml#t002_dec"]
+required = true
+
+[[obligation]]
+id = "sf.functions.abs.t003"
+form = "sf.functions.abs"
+kind = "type_combination"
+target = "FLOAT"
+note = "ABS of FLOAT."
+catches = "a tiny negative flushed to zero or kept negative (1e-300)"
+reading = "the page names no argument type; the numeric category gives integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/functions/abs.toml#t003_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.abs.t004"
+form = "sf.functions.abs"
+kind = "null_argument"
+target = "x"
+note = "ABS of NULL."
+catches = "NULL turned into 0"
+reading = "the page names no argument type; the numeric category gives integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/functions/abs.toml#t004_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.abs.t005"
+form = "sf.functions.abs"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "The 38-digit and double extremes."
+catches = "64-bit arithmetic overflowing"
+reading = "the page names no argument type; the numeric category gives integer NUMBER, NUMBER with a scale and FLOAT"
+cases = ["cases/translation/functions/abs.toml#t005_extremes"]
+required = true
+
+[[obligation]]
+id = "sf.functions.accumulate.t001"
+form = "sf.functions.accumulate"
+kind = "type_combination"
+target = "INT with INT state"
+note = "The page's sum example over 3, 4 and 10, with a NULL row the page says is skipped (17)."
+catches = "the NULL row breaking the sum, or only the last row kept"
+reading = "the page: a user-defined aggregate built from initialize, accumulate, combine and terminate lambdas; NULL rows are skipped; NULL if all rows are NULL or there are none"
+cases = ["cases/translation/functions/accumulate.toml#t001_sum"]
+required = true
+
+[[obligation]]
+id = "sf.functions.accumulate.t002"
+form = "sf.functions.accumulate"
+kind = "type_combination"
+target = "INT with ARRAY state"
+note = "The page's mean example over 3, 4 and 10, tracking the sum and the count in an ARRAY: 17/3, rounded to 2 places (5.67)."
+catches = "the count taken from all rows including NULL (4.25), or integer division (5)"
+reading = "the page: a user-defined aggregate built from initialize, accumulate, combine and terminate lambdas; NULL rows are skipped; NULL if all rows are NULL or there are none"
+cases = ["cases/translation/functions/accumulate.toml#t002_mean"]
+required = true
+
+[[obligation]]
+id = "sf.functions.accumulate.t003"
+form = "sf.functions.accumulate"
+kind = "null_argument"
+target = "input-expr"
+note = "Only NULL rows, which the page says give NULL."
+catches = "0 returned"
+reading = "the page: a user-defined aggregate built from initialize, accumulate, combine and terminate lambdas; NULL rows are skipped; NULL if all rows are NULL or there are none"
+cases = ["cases/translation/functions/accumulate.toml#t003_null_input_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.acos.t001"
+form = "sf.functions.acos"
+kind = "type_combination"
+target = "FLOAT"
+note = "ACOS of FLOAT values."
+catches = "ASIN computed instead (acos(0.25) differs from asin(0.25)) or degrees returned"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/acos.toml#t001_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.acos.t002"
+form = "sf.functions.acos"
+kind = "type_combination"
+target = "NUMBER(12,4)"
+note = "ACOS of NUMBER(12,4), converted to FLOAT."
+catches = "ASIN computed instead (acos(0.25) differs from asin(0.25)) or degrees returned; or the result left fixed-point"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/acos.toml#t002_fixed"]
+required = true
+
+[[obligation]]
+id = "sf.functions.acos.t003"
+form = "sf.functions.acos"
+kind = "null_argument"
+target = "x"
+note = "ACOS of NULL."
+catches = "NULL turned into 0 or NaN"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/acos.toml#t003_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.acos.t004"
+form = "sf.functions.acos"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "The domain edges, 1 and -1."
+catches = "pi returned for 1 or 0 for -1 (sign of the edge)"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/acos.toml#t004_edges"]
+required = true
+
+[[obligation]]
+id = "sf.functions.acos.t005"
+form = "sf.functions.acos"
+kind = "error_parity"
+target = "error"
+note = "ACOS at or outside its domain: Snowflake raises error 100044 on every row (captured at e8691b63)."
+catches = "NaN, NULL or a value where Snowflake raises an error"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/acos.toml#t005_outside"]
+required = true
+
+[[obligation]]
+id = "sf.functions.acosh.t001"
+form = "sf.functions.acosh"
+kind = "type_combination"
+target = "FLOAT"
+note = "ACOSH of FLOAT values."
+catches = "ASINH computed"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/acosh.toml#t001_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.acosh.t002"
+form = "sf.functions.acosh"
+kind = "type_combination"
+target = "NUMBER(12,4)"
+note = "ACOSH of NUMBER(12,4), converted to FLOAT."
+catches = "ASINH computed; or the result left fixed-point"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/acosh.toml#t002_fixed"]
+required = true
+
+[[obligation]]
+id = "sf.functions.acosh.t003"
+form = "sf.functions.acosh"
+kind = "null_argument"
+target = "x"
+note = "ACOSH of NULL."
+catches = "NULL turned into 0 or NaN"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/acosh.toml#t003_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.acosh.t004"
+form = "sf.functions.acosh"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "At 1, the domain edge, and large values."
+catches = "edge returned as NaN"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/acosh.toml#t004_edge"]
+required = true
+
+[[obligation]]
+id = "sf.functions.acosh.t005"
+form = "sf.functions.acosh"
+kind = "error_parity"
+target = "error"
+note = "ACOSH at or outside its domain: Snowflake raises error 100044 on every row (captured at e8691b63)."
+catches = "NaN, NULL or a value where Snowflake raises an error"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/acosh.toml#t005_below_one"]
+required = true
+
+[[obligation]]
+id = "sf.functions.add-months.t001"
+form = "sf.functions.add-months"
+kind = "type_combination"
+target = "DATE, NUMBER"
+note = "Positive and negative month counts."
+catches = "months added as 30 days, or arguments swapped"
+reading = "the page: the result has the input's type; a non-integer count rounds to the nearest integer; a month end gives the result month's end (unlike DATEADD)"
+cases = ["cases/translation/functions/add-months.toml#t001_date"]
+required = true
+
+[[obligation]]
+id = "sf.functions.add-months.t002"
+form = "sf.functions.add-months"
+kind = "type_combination"
+target = "TIMESTAMP_NTZ, NUMBER"
+note = "A timestamp keeps its time and type."
+catches = "a DATE returned, or the time dropped"
+reading = "the page: the result has the input's type; a non-integer count rounds to the nearest integer; a month end gives the result month's end (unlike DATEADD)"
+cases = ["cases/translation/functions/add-months.toml#t002_timestamp"]
+required = true
+
+[[obligation]]
+id = "sf.functions.add-months.t003"
+form = "sf.functions.add-months"
+kind = "type_combination"
+target = "TIMESTAMP_TZ, NUMBER"
+note = "A TIMESTAMP_TZ keeps its offset."
+catches = "the month end of the UTC instant (2024-02-29 04:00Z instead of 2024-03-01 04:00Z), or the offset dropped (the wall column shows the offset, which the instant comparison ignores)"
+reading = "the page: the result has the input's type; a non-integer count rounds to the nearest integer; a month end gives the result month's end (unlike DATEADD)"
+cases = ["cases/translation/functions/add-months.toml#t003_timestamp_tz"]
+required = true
+
+[[obligation]]
+id = "sf.functions.add-months.t004"
+form = "sf.functions.add-months"
+kind = "null_argument"
+target = "date_or_timestamp_expr"
+note = "NULL date."
+catches = "NULL read as a default date"
+reading = "the page: the result has the input's type; a non-integer count rounds to the nearest integer; a month end gives the result month's end (unlike DATEADD)"
+cases = ["cases/translation/functions/add-months.toml#t004_null_date_or_timestamp_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.add-months.t005"
+form = "sf.functions.add-months"
+kind = "null_argument"
+target = "num_months_expr"
+note = "NULL count."
+catches = "NULL read as 0"
+reading = "the page: the result has the input's type; a non-integer count rounds to the nearest integer; a month end gives the result month's end (unlike DATEADD)"
+cases = ["cases/translation/functions/add-months.toml#t005_null_num_months_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.add-months.t006"
+form = "sf.functions.add-months"
+kind = "boundary"
+subkind = "leap_day"
+target = "leap_day"
+note = "Last-day-of-month inputs, which stay at the month end."
+catches = "DATEADD semantics (2023-03-28 from 2023-02-28 instead of 2023-03-31)"
+reading = "the page: the result has the input's type; a non-integer count rounds to the nearest integer; a month end gives the result month's end (unlike DATEADD)"
+cases = ["cases/translation/functions/add-months.toml#t006_month_end"]
+required = true
+
+[[obligation]]
+id = "sf.functions.add-months.t007"
+form = "sf.functions.add-months"
+kind = "boundary"
+subkind = "rounding_scale"
+target = "rounding_scale"
+note = "Fractional counts, which the page says round to the nearest integer."
+catches = "truncation (1 month for 1.6) or half-to-even (2 for 2.5 instead of 3)"
+reading = "the page: the result has the input's type; a non-integer count rounds to the nearest integer; a month end gives the result month's end (unlike DATEADD)"
+cases = ["cases/translation/functions/add-months.toml#t007_fractional"]
+required = true
+
+[[obligation]]
+id = "sf.functions.any-value.any-value.t001"
+form = "sf.functions.any-value.any-value"
+kind = "type_combination"
+target = "VARCHAR"
+note = "Groups whose rows hold one value each (repeated), so any choice gives it."
+catches = "a value from another group, or the grouping column returned"
+reading = "the page: some value of the group, of any type; DISTINCT has no effect; NULL values are not excluded; no ORDER BY or frame as a window function"
+cases = ["cases/translation/functions/any-value.any-value.toml#t001_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.any-value.any-value.t002"
+form = "sf.functions.any-value.any-value"
+kind = "type_combination"
+target = "DATE"
+note = "A DATE value."
+catches = "a different type returned"
+reading = "the page: some value of the group, of any type; DISTINCT has no effect; NULL values are not excluded; no ORDER BY or frame as a window function"
+cases = ["cases/translation/functions/any-value.any-value.toml#t002_date"]
+required = true
+
+[[obligation]]
+id = "sf.functions.any-value.any-value.t003"
+form = "sf.functions.any-value.any-value"
+kind = "type_combination"
+target = "DISTINCT"
+note = "DISTINCT, which has no effect."
+catches = "DISTINCT rejected"
+reading = "the page: some value of the group, of any type; DISTINCT has no effect; NULL values are not excluded; no ORDER BY or frame as a window function"
+cases = ["cases/translation/functions/any-value.any-value.toml#t003_distinct"]
+required = true
+
+[[obligation]]
+id = "sf.functions.any-value.any-value.t004"
+form = "sf.functions.any-value.any-value"
+kind = "null_argument"
+target = "expr1"
+note = "A group of only NULL, beside a group with a value."
+catches = "an error or a value from another group"
+reading = "the page: some value of the group, of any type; DISTINCT has no effect; NULL values are not excluded; no ORDER BY or frame as a window function"
+cases = ["cases/translation/functions/any-value.any-value.toml#t004_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.any-value.any-value.t005"
+form = "sf.functions.any-value.any-value"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "an error instead of NULL"
+reading = "the page: some value of the group, of any type; DISTINCT has no effect; NULL values are not excluded; no ORDER BY or frame as a window function"
+cases = ["cases/translation/functions/any-value.any-value.toml#t005_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.any-value.any-value-over.t001"
+form = "sf.functions.any-value.any-value-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Partitions holding one value each."
+catches = "a value from another partition"
+reading = "the page: some value of the group, of any type; DISTINCT has no effect; NULL values are not excluded; no ORDER BY or frame as a window function"
+cases = ["cases/translation/functions/any-value.any-value-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.any-value.any-value-over.t002"
+form = "sf.functions.any-value.any-value-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause over one repeated value."
+catches = "NULL"
+reading = "the page: some value of the group, of any type; DISTINCT has no effect; NULL values are not excluded; no ORDER BY or frame as a window function"
+cases = ["cases/translation/functions/any-value.any-value-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.any-value.any-value-over.t003"
+form = "sf.functions.any-value.any-value-over"
+kind = "null_argument"
+target = "expr1"
+note = "A partition of only NULL."
+catches = "a value from another partition"
+reading = "the page: some value of the group, of any type; DISTINCT has no effect; NULL values are not excluded; no ORDER BY or frame as a window function"
+cases = ["cases/translation/functions/any-value.any-value-over.toml#t003_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.any-value.any-value-over.t004"
+form = "sf.functions.any-value.any-value-over"
+kind = "null_argument"
+target = "expr2"
+note = "NULL partition keys."
+catches = "NULL-keyed rows dropped or given another partition's value"
+reading = "the page: some value of the group, of any type; DISTINCT has no effect; NULL values are not excluded; no ORDER BY or frame as a window function"
+cases = ["cases/translation/functions/any-value.any-value-over.toml#t004_null_expr2"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-count-distinct.approx-count-distinct.t001"
+form = "sf.functions.approx-count-distinct.approx-count-distinct"
+kind = "type_combination"
+target = "NUMBER"
+note = "Repeated values per group."
+catches = "duplicates counted (COUNT)"
+reading = "the page: an approximate count of distinct non-NULL values, returned as INTEGER and deterministic; with several expressions the distinct combinations; DISTINCT has no effect; no ORDER BY or frame as a window function; small inputs are counted exactly by the sketch"
+cases = ["cases/translation/functions/approx-count-distinct.approx-count-distinct.toml#t001_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-count-distinct.approx-count-distinct.t002"
+form = "sf.functions.approx-count-distinct.approx-count-distinct"
+kind = "type_combination"
+target = "several expressions"
+note = "Distinct (a, b) combinations: 3 without the NULL pair, where a alone has 2 distinct values."
+catches = "distinct values of a only (2), the NULL pair counted (4), or the sum of per-column counts"
+reading = "the page: an approximate count of distinct non-NULL values, returned as INTEGER and deterministic; with several expressions the distinct combinations; DISTINCT has no effect; no ORDER BY or frame as a window function; small inputs are counted exactly by the sketch"
+cases = ["cases/translation/functions/approx-count-distinct.approx-count-distinct.toml#t002_multi"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-count-distinct.approx-count-distinct.t003"
+form = "sf.functions.approx-count-distinct.approx-count-distinct"
+kind = "type_combination"
+target = "VARCHAR"
+note = "Text differing only in case."
+catches = "case-insensitive distinctness"
+reading = "the page: an approximate count of distinct non-NULL values, returned as INTEGER and deterministic; with several expressions the distinct combinations; DISTINCT has no effect; no ORDER BY or frame as a window function; small inputs are counted exactly by the sketch"
+cases = ["cases/translation/functions/approx-count-distinct.approx-count-distinct.toml#t003_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-count-distinct.approx-count-distinct.t004"
+form = "sf.functions.approx-count-distinct.approx-count-distinct"
+kind = "type_combination"
+target = "DISTINCT"
+note = "DISTINCT, which has no effect."
+catches = "DISTINCT rejected"
+reading = "the page: an approximate count of distinct non-NULL values, returned as INTEGER and deterministic; with several expressions the distinct combinations; DISTINCT has no effect; no ORDER BY or frame as a window function; small inputs are counted exactly by the sketch"
+cases = ["cases/translation/functions/approx-count-distinct.approx-count-distinct.toml#t004_distinct"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-count-distinct.approx-count-distinct.t005"
+form = "sf.functions.approx-count-distinct.approx-count-distinct"
+kind = "null_argument"
+target = "expr1"
+note = "NULL rows, which are not counted, and a group of only NULL."
+catches = "NULL counted as a value"
+reading = "the page: an approximate count of distinct non-NULL values, returned as INTEGER and deterministic; with several expressions the distinct combinations; DISTINCT has no effect; no ORDER BY or frame as a window function; small inputs are counted exactly by the sketch"
+cases = ["cases/translation/functions/approx-count-distinct.approx-count-distinct.toml#t005_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-count-distinct.approx-count-distinct.t006"
+form = "sf.functions.approx-count-distinct.approx-count-distinct"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "NULL instead of 0"
+reading = "the page: an approximate count of distinct non-NULL values, returned as INTEGER and deterministic; with several expressions the distinct combinations; DISTINCT has no effect; no ORDER BY or frame as a window function; small inputs are counted exactly by the sketch"
+cases = ["cases/translation/functions/approx-count-distinct.approx-count-distinct.toml#t006_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-count-distinct.approx-count-distinct-over.t001"
+form = "sf.functions.approx-count-distinct.approx-count-distinct-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Partitions of different sizes in mixed input order."
+catches = "the whole input aggregated instead of the partition, or a running value"
+reading = "the page: an approximate count of distinct non-NULL values, returned as INTEGER and deterministic; with several expressions the distinct combinations; DISTINCT has no effect; no ORDER BY or frame as a window function; small inputs are counted exactly by the sketch"
+cases = ["cases/translation/functions/approx-count-distinct.approx-count-distinct-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-count-distinct.approx-count-distinct-over.t002"
+form = "sf.functions.approx-count-distinct.approx-count-distinct-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause."
+catches = "a running (ordered) value instead of the whole input"
+reading = "the page: an approximate count of distinct non-NULL values, returned as INTEGER and deterministic; with several expressions the distinct combinations; DISTINCT has no effect; no ORDER BY or frame as a window function; small inputs are counted exactly by the sketch"
+cases = ["cases/translation/functions/approx-count-distinct.approx-count-distinct-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-count-distinct.approx-count-distinct-over.t003"
+form = "sf.functions.approx-count-distinct.approx-count-distinct-over"
+kind = "null_argument"
+target = "expr1"
+note = "NULL values inside partitions, and a partition with only NULL."
+catches = "NULL counted as a value"
+reading = "the page: an approximate count of distinct non-NULL values, returned as INTEGER and deterministic; with several expressions the distinct combinations; DISTINCT has no effect; no ORDER BY or frame as a window function; small inputs are counted exactly by the sketch"
+cases = ["cases/translation/functions/approx-count-distinct.approx-count-distinct-over.toml#t003_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-count-distinct.approx-count-distinct-over.t004"
+form = "sf.functions.approx-count-distinct.approx-count-distinct-over"
+kind = "null_argument"
+target = "partition-key"
+note = "NULL partition keys, which form one partition."
+catches = "NULL-keyed rows each in their own partition or dropped"
+reading = "the page: an approximate count of distinct non-NULL values, returned as INTEGER and deterministic; with several expressions the distinct combinations; DISTINCT has no effect; no ORDER BY or frame as a window function; small inputs are counted exactly by the sketch"
+cases = ["cases/translation/functions/approx-count-distinct.approx-count-distinct-over.toml#t004_null_partition_key"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-count-distinct.approx-count-distinct-over.syntax.t001"
+form = "sf.functions.approx-count-distinct.approx-count-distinct-over.syntax"
+kind = "type_combination"
+target = "* OVER (PARTITION BY)"
+note = "Distinct whole rows per partition (the partition column included), one with NULL."
+catches = "rows counted instead of distinct rows, or rows with a NULL counted"
+reading = "the page: an approximate count of distinct non-NULL values, returned as INTEGER and deterministic; with several expressions the distinct combinations; DISTINCT has no effect; no ORDER BY or frame as a window function; small inputs are counted exactly by the sketch; * counts the distinct rows of all columns, excluding rows with NULL"
+cases = ["cases/translation/functions/approx-count-distinct.approx-count-distinct-over.syntax.toml#t001_star"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-count-distinct.approx-count-distinct-over.syntax.t002"
+form = "sf.functions.approx-count-distinct.approx-count-distinct-over.syntax"
+kind = "type_combination"
+target = "* OVER ()"
+note = "An empty OVER clause."
+catches = "a running count"
+reading = "the page: an approximate count of distinct non-NULL values, returned as INTEGER and deterministic; with several expressions the distinct combinations; DISTINCT has no effect; no ORDER BY or frame as a window function; small inputs are counted exactly by the sketch; * counts the distinct rows of all columns, excluding rows with NULL"
+cases = ["cases/translation/functions/approx-count-distinct.approx-count-distinct-over.syntax.toml#t002_star_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-count-distinct.approx-count-distinct-over.syntax.t003"
+form = "sf.functions.approx-count-distinct.approx-count-distinct-over.syntax"
+kind = "null_argument"
+target = "expr2"
+note = "A partition key outside the * columns, NULL for the two x rows, so the NULL-keyed rows form one partition of two distinct rows."
+catches = "NULL-keyed rows each in their own partition (1 instead of 2) or dropped"
+reading = "the page: an approximate count of distinct non-NULL values, returned as INTEGER and deterministic; with several expressions the distinct combinations; DISTINCT has no effect; no ORDER BY or frame as a window function; small inputs are counted exactly by the sketch; * counts the distinct rows of all columns, excluding rows with NULL"
+cases = ["cases/translation/functions/approx-count-distinct.approx-count-distinct-over.syntax.toml#t003_null_expr2"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-percentile-accumulate.t001"
+form = "sf.functions.approx-percentile-accumulate"
+kind = "type_combination"
+target = "NUMBER"
+note = "A state per group, read at the median."
+catches = "the whole input's state"
+reading = "the page: an approximate percentile (a t-digest) of numeric values, returned as DOUBLE; is a constant from 0.0 up to but not including 1.0; the value need not be in the data set; no ORDER BY or frame as a window function; APPROX_PERCENTILE_ACCUMULATE builds the state, _COMBINE merges states and _ESTIMATE reads a percentile"
+cases = ["cases/translation/functions/approx-percentile-accumulate.toml#t001_state"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-percentile-accumulate.t002"
+form = "sf.functions.approx-percentile-accumulate"
+kind = "null_argument"
+target = "expr"
+note = "NULL values, which are ignored."
+catches = "NULL counted"
+reading = "the page: an approximate percentile (a t-digest) of numeric values, returned as DOUBLE; is a constant from 0.0 up to but not including 1.0; the value need not be in the data set; no ORDER BY or frame as a window function; APPROX_PERCENTILE_ACCUMULATE builds the state, _COMBINE merges states and _ESTIMATE reads a percentile"
+cases = ["cases/translation/functions/approx-percentile-accumulate.toml#t002_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-percentile-combine.t001"
+form = "sf.functions.approx-percentile-combine"
+kind = "type_combination"
+target = "two states"
+note = "States of the low and the high half merged, read at the median."
+catches = "only the first state used, or the medians averaged"
+reading = "the page: an approximate percentile (a t-digest) of numeric values, returned as DOUBLE; is a constant from 0.0 up to but not including 1.0; the value need not be in the data set; no ORDER BY or frame as a window function; APPROX_PERCENTILE_ACCUMULATE builds the state, _COMBINE merges states and _ESTIMATE reads a percentile"
+cases = ["cases/translation/functions/approx-percentile-combine.toml#t001_merge"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-percentile-combine.t002"
+form = "sf.functions.approx-percentile-combine"
+kind = "null_argument"
+target = "state"
+note = "A NULL state beside states."
+catches = "the result NULL"
+reading = "the page: an approximate percentile (a t-digest) of numeric values, returned as DOUBLE; is a constant from 0.0 up to but not including 1.0; the value need not be in the data set; no ORDER BY or frame as a window function; APPROX_PERCENTILE_ACCUMULATE builds the state, _COMBINE merges states and _ESTIMATE reads a percentile"
+cases = ["cases/translation/functions/approx-percentile-combine.toml#t002_null_state"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-percentile-estimate.t001"
+form = "sf.functions.approx-percentile-estimate"
+kind = "type_combination"
+target = "state, percentile"
+note = "Two percentiles from one state."
+catches = "the percentile read as 0-100 or from the top"
+reading = "the page: an approximate percentile (a t-digest) of numeric values, returned as DOUBLE; is a constant from 0.0 up to but not including 1.0; the value need not be in the data set; no ORDER BY or frame as a window function; APPROX_PERCENTILE_ACCUMULATE builds the state, _COMBINE merges states and _ESTIMATE reads a percentile"
+cases = ["cases/translation/functions/approx-percentile-estimate.toml#t001_percentiles"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-percentile-estimate.t002"
+form = "sf.functions.approx-percentile-estimate"
+kind = "type_combination"
+target = "percentile 0.0"
+note = "The documented lower end 0.0."
+catches = "0.0 rejected"
+reading = "the page: an approximate percentile (a t-digest) of numeric values, returned as DOUBLE; is a constant from 0.0 up to but not including 1.0; the value need not be in the data set; no ORDER BY or frame as a window function; APPROX_PERCENTILE_ACCUMULATE builds the state, _COMBINE merges states and _ESTIMATE reads a percentile"
+cases = ["cases/translation/functions/approx-percentile-estimate.toml#t002_zero"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-percentile-estimate.t003"
+form = "sf.functions.approx-percentile-estimate"
+kind = "null_argument"
+target = "state"
+note = "A NULL state."
+catches = "an error instead of NULL"
+reading = "the page: an approximate percentile (a t-digest) of numeric values, returned as DOUBLE; is a constant from 0.0 up to but not including 1.0; the value need not be in the data set; no ORDER BY or frame as a window function; APPROX_PERCENTILE_ACCUMULATE builds the state, _COMBINE merges states and _ESTIMATE reads a percentile"
+cases = ["cases/translation/functions/approx-percentile-estimate.toml#t003_null_state"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-percentile.approx-percentile.t001"
+form = "sf.functions.approx-percentile.approx-percentile"
+kind = "type_combination"
+target = "NUMBER, 0.5"
+note = "The median of five values and of one value."
+catches = "the percentile read as 0-100, or the whole input"
+reading = "the page: an approximate percentile (a t-digest) of numeric values, returned as DOUBLE; is a constant from 0.0 up to but not including 1.0; the value need not be in the data set; no ORDER BY or frame as a window function; APPROX_PERCENTILE_ACCUMULATE builds the state, _COMBINE merges states and _ESTIMATE reads a percentile"
+cases = ["cases/translation/functions/approx-percentile.approx-percentile.toml#t001_median"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-percentile.approx-percentile.t002"
+form = "sf.functions.approx-percentile.approx-percentile"
+kind = "type_combination"
+target = "NUMBER, 0.2"
+note = "A low percentile."
+catches = "the percentile from the top (1 - p)"
+reading = "the page: an approximate percentile (a t-digest) of numeric values, returned as DOUBLE; is a constant from 0.0 up to but not including 1.0; the value need not be in the data set; no ORDER BY or frame as a window function; APPROX_PERCENTILE_ACCUMULATE builds the state, _COMBINE merges states and _ESTIMATE reads a percentile"
+cases = ["cases/translation/functions/approx-percentile.approx-percentile.toml#t002_low"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-percentile.approx-percentile.t003"
+form = "sf.functions.approx-percentile.approx-percentile"
+kind = "type_combination"
+target = "FLOAT"
+note = "FLOAT values."
+catches = "a fixed-point result"
+reading = "the page: an approximate percentile (a t-digest) of numeric values, returned as DOUBLE; is a constant from 0.0 up to but not including 1.0; the value need not be in the data set; no ORDER BY or frame as a window function; APPROX_PERCENTILE_ACCUMULATE builds the state, _COMBINE merges states and _ESTIMATE reads a percentile"
+cases = ["cases/translation/functions/approx-percentile.approx-percentile.toml#t003_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-percentile.approx-percentile.t004"
+form = "sf.functions.approx-percentile.approx-percentile"
+kind = "null_argument"
+target = "expr"
+note = "NULL values, which are ignored, and a group of only NULL."
+catches = "NULL counted as the lowest value"
+reading = "the page: an approximate percentile (a t-digest) of numeric values, returned as DOUBLE; is a constant from 0.0 up to but not including 1.0; the value need not be in the data set; no ORDER BY or frame as a window function; APPROX_PERCENTILE_ACCUMULATE builds the state, _COMBINE merges states and _ESTIMATE reads a percentile"
+cases = ["cases/translation/functions/approx-percentile.approx-percentile.toml#t004_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-percentile.approx-percentile.t005"
+form = "sf.functions.approx-percentile.approx-percentile"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "an error or 0 instead of NULL"
+reading = "the page: an approximate percentile (a t-digest) of numeric values, returned as DOUBLE; is a constant from 0.0 up to but not including 1.0; the value need not be in the data set; no ORDER BY or frame as a window function; APPROX_PERCENTILE_ACCUMULATE builds the state, _COMBINE merges states and _ESTIMATE reads a percentile"
+cases = ["cases/translation/functions/approx-percentile.approx-percentile.toml#t005_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-percentile.approx-percentile-over.t001"
+form = "sf.functions.approx-percentile.approx-percentile-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Per-partition medians."
+catches = "the whole input"
+reading = "the page: an approximate percentile (a t-digest) of numeric values, returned as DOUBLE; is a constant from 0.0 up to but not including 1.0; the value need not be in the data set; no ORDER BY or frame as a window function; APPROX_PERCENTILE_ACCUMULATE builds the state, _COMBINE merges states and _ESTIMATE reads a percentile"
+cases = ["cases/translation/functions/approx-percentile.approx-percentile-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-percentile.approx-percentile-over.t002"
+form = "sf.functions.approx-percentile.approx-percentile-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause."
+catches = "a running value"
+reading = "the page: an approximate percentile (a t-digest) of numeric values, returned as DOUBLE; is a constant from 0.0 up to but not including 1.0; the value need not be in the data set; no ORDER BY or frame as a window function; APPROX_PERCENTILE_ACCUMULATE builds the state, _COMBINE merges states and _ESTIMATE reads a percentile"
+cases = ["cases/translation/functions/approx-percentile.approx-percentile-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-percentile.approx-percentile-over.t003"
+form = "sf.functions.approx-percentile.approx-percentile-over"
+kind = "null_argument"
+target = "expr"
+note = "NULL values inside a partition."
+catches = "NULL counted"
+reading = "the page: an approximate percentile (a t-digest) of numeric values, returned as DOUBLE; is a constant from 0.0 up to but not including 1.0; the value need not be in the data set; no ORDER BY or frame as a window function; APPROX_PERCENTILE_ACCUMULATE builds the state, _COMBINE merges states and _ESTIMATE reads a percentile"
+cases = ["cases/translation/functions/approx-percentile.approx-percentile-over.toml#t003_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-percentile.approx-percentile-over.t004"
+form = "sf.functions.approx-percentile.approx-percentile-over"
+kind = "null_argument"
+target = "expr3"
+note = "NULL partition keys."
+catches = "NULL-keyed rows split or dropped"
+reading = "the page: an approximate percentile (a t-digest) of numeric values, returned as DOUBLE; is a constant from 0.0 up to but not including 1.0; the value need not be in the data set; no ORDER BY or frame as a window function; APPROX_PERCENTILE_ACCUMULATE builds the state, _COMBINE merges states and _ESTIMATE reads a percentile"
+cases = ["cases/translation/functions/approx-percentile.approx-percentile-over.toml#t004_null_expr3"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-top-k-accumulate.t001"
+form = "sf.functions.approx-top-k-accumulate"
+kind = "type_combination"
+target = "VARCHAR, counters"
+note = "A state per group, read with APPROX_TOP_K_ESTIMATE."
+catches = "the whole input's state, or the top-2 read ignored"
+reading = "the page: the approximately most frequent values and their counts as an ARRAY of [value, count] pairs, most frequent first; defaults to 1 and is at most 100000; bounds the tracked values; APPROX_TOP_K_ACCUMULATE builds the state, _COMBINE merges states (with optional when all states share it) and _ESTIMATE reads the top ; the page does not say how NULL is counted"
+cases = ["cases/translation/functions/approx-top-k-accumulate.toml#t001_state"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-top-k-accumulate.t002"
+form = "sf.functions.approx-top-k-accumulate"
+kind = "null_argument"
+target = "expr"
+note = "NULL as the most frequent value."
+catches = "NULL treated the opposite way to Snowflake"
+reading = "the page: the approximately most frequent values and their counts as an ARRAY of [value, count] pairs, most frequent first; defaults to 1 and is at most 100000; bounds the tracked values; APPROX_TOP_K_ACCUMULATE builds the state, _COMBINE merges states (with optional when all states share it) and _ESTIMATE reads the top ; the page does not say how NULL is counted"
+cases = ["cases/translation/functions/approx-top-k-accumulate.toml#t002_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-top-k-combine.t001"
+form = "sf.functions.approx-top-k-combine"
+kind = "type_combination"
+target = "states"
+note = "States where b is second in p and first in q: merged, b (3) beats a (2)."
+catches = "the first state only, or per-state ranks merged"
+reading = "the page: the approximately most frequent values and their counts as an ARRAY of [value, count] pairs, most frequent first; defaults to 1 and is at most 100000; bounds the tracked values; APPROX_TOP_K_ACCUMULATE builds the state, _COMBINE merges states (with optional when all states share it) and _ESTIMATE reads the top ; the page does not say how NULL is counted"
+cases = ["cases/translation/functions/approx-top-k-combine.toml#t001_merge"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-top-k-combine.t002"
+form = "sf.functions.approx-top-k-combine"
+kind = "type_combination"
+target = "states, counters"
+note = "An explicit number of counters."
+catches = "the explicit counters argument rejected, or the states not merged"
+reading = "the page: the approximately most frequent values and their counts as an ARRAY of [value, count] pairs, most frequent first; defaults to 1 and is at most 100000; bounds the tracked values; APPROX_TOP_K_ACCUMULATE builds the state, _COMBINE merges states (with optional when all states share it) and _ESTIMATE reads the top ; the page does not say how NULL is counted"
+cases = ["cases/translation/functions/approx-top-k-combine.toml#t002_counters"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-top-k-combine.t003"
+form = "sf.functions.approx-top-k-combine"
+kind = "null_argument"
+target = "state"
+note = "A NULL state beside states."
+catches = "the result NULL"
+reading = "the page: the approximately most frequent values and their counts as an ARRAY of [value, count] pairs, most frequent first; defaults to 1 and is at most 100000; bounds the tracked values; APPROX_TOP_K_ACCUMULATE builds the state, _COMBINE merges states (with optional when all states share it) and _ESTIMATE reads the top ; the page does not say how NULL is counted"
+cases = ["cases/translation/functions/approx-top-k-combine.toml#t003_null_state"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-top-k-estimate.t001"
+form = "sf.functions.approx-top-k-estimate"
+kind = "type_combination"
+target = "state, k"
+note = "k = 2 from a state."
+catches = "k ignored"
+reading = "the page: the approximately most frequent values and their counts as an ARRAY of [value, count] pairs, most frequent first; defaults to 1 and is at most 100000; bounds the tracked values; APPROX_TOP_K_ACCUMULATE builds the state, _COMBINE merges states (with optional when all states share it) and _ESTIMATE reads the top ; the page does not say how NULL is counted"
+cases = ["cases/translation/functions/approx-top-k-estimate.toml#t001_k"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-top-k-estimate.t002"
+form = "sf.functions.approx-top-k-estimate"
+kind = "type_combination"
+target = "state"
+note = "k omitted: the page gives 1."
+catches = "every tracked value returned"
+reading = "the page: the approximately most frequent values and their counts as an ARRAY of [value, count] pairs, most frequent first; defaults to 1 and is at most 100000; bounds the tracked values; APPROX_TOP_K_ACCUMULATE builds the state, _COMBINE merges states (with optional when all states share it) and _ESTIMATE reads the top ; the page does not say how NULL is counted"
+cases = ["cases/translation/functions/approx-top-k-estimate.toml#t002_default_k"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-top-k-estimate.t003"
+form = "sf.functions.approx-top-k-estimate"
+kind = "null_argument"
+target = "state"
+note = "A NULL state."
+catches = "an empty array instead of NULL"
+reading = "the page: the approximately most frequent values and their counts as an ARRAY of [value, count] pairs, most frequent first; defaults to 1 and is at most 100000; bounds the tracked values; APPROX_TOP_K_ACCUMULATE builds the state, _COMBINE merges states (with optional when all states share it) and _ESTIMATE reads the top ; the page does not say how NULL is counted"
+cases = ["cases/translation/functions/approx-top-k-estimate.toml#t003_null_state"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-top-k.approx-top-k.t001"
+form = "sf.functions.approx-top-k.approx-top-k"
+kind = "type_combination"
+target = "VARCHAR, k"
+note = "k = 2 over values seen 3, 2 and 1 times."
+catches = "least frequent first, or k ignored"
+reading = "the page: the approximately most frequent values and their counts as an ARRAY of [value, count] pairs, most frequent first; defaults to 1 and is at most 100000; bounds the tracked values; APPROX_TOP_K_ACCUMULATE builds the state, _COMBINE merges states (with optional when all states share it) and _ESTIMATE reads the top ; the page does not say how NULL is counted"
+cases = ["cases/translation/functions/approx-top-k.approx-top-k.toml#t001_k"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-top-k.approx-top-k.t002"
+form = "sf.functions.approx-top-k.approx-top-k"
+kind = "type_combination"
+target = "no k"
+note = "k omitted: the page gives 1."
+catches = "every value returned"
+reading = "the page: the approximately most frequent values and their counts as an ARRAY of [value, count] pairs, most frequent first; defaults to 1 and is at most 100000; bounds the tracked values; APPROX_TOP_K_ACCUMULATE builds the state, _COMBINE merges states (with optional when all states share it) and _ESTIMATE reads the top ; the page does not say how NULL is counted"
+cases = ["cases/translation/functions/approx-top-k.approx-top-k.toml#t002_default_k"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-top-k.approx-top-k.t003"
+form = "sf.functions.approx-top-k.approx-top-k"
+kind = "type_combination"
+target = "k and counters"
+note = "k = 2 with 10 counters."
+catches = "counters read as k"
+reading = "the page: the approximately most frequent values and their counts as an ARRAY of [value, count] pairs, most frequent first; defaults to 1 and is at most 100000; bounds the tracked values; APPROX_TOP_K_ACCUMULATE builds the state, _COMBINE merges states (with optional when all states share it) and _ESTIMATE reads the top ; the page does not say how NULL is counted"
+cases = ["cases/translation/functions/approx-top-k.approx-top-k.toml#t003_counters"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-top-k.approx-top-k.t004"
+form = "sf.functions.approx-top-k.approx-top-k"
+kind = "type_combination"
+target = "NUMBER"
+note = "NUMBER values."
+catches = "values returned as text"
+reading = "the page: the approximately most frequent values and their counts as an ARRAY of [value, count] pairs, most frequent first; defaults to 1 and is at most 100000; bounds the tracked values; APPROX_TOP_K_ACCUMULATE builds the state, _COMBINE merges states (with optional when all states share it) and _ESTIMATE reads the top ; the page does not say how NULL is counted"
+cases = ["cases/translation/functions/approx-top-k.approx-top-k.toml#t004_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-top-k.approx-top-k.t005"
+form = "sf.functions.approx-top-k.approx-top-k"
+kind = "null_argument"
+target = "expr"
+note = "NULL as the most frequent input value."
+catches = "NULL treated the opposite way to Snowflake (counted as a value or skipped)"
+reading = "the page: the approximately most frequent values and their counts as an ARRAY of [value, count] pairs, most frequent first; defaults to 1 and is at most 100000; bounds the tracked values; APPROX_TOP_K_ACCUMULATE builds the state, _COMBINE merges states (with optional when all states share it) and _ESTIMATE reads the top ; the page does not say how NULL is counted"
+cases = ["cases/translation/functions/approx-top-k.approx-top-k.toml#t005_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-top-k.approx-top-k.t006"
+form = "sf.functions.approx-top-k.approx-top-k"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "NULL instead of the empty-input result, or the reverse"
+reading = "the page: the approximately most frequent values and their counts as an ARRAY of [value, count] pairs, most frequent first; defaults to 1 and is at most 100000; bounds the tracked values; APPROX_TOP_K_ACCUMULATE builds the state, _COMBINE merges states (with optional when all states share it) and _ESTIMATE reads the top ; the page does not say how NULL is counted"
+cases = ["cases/translation/functions/approx-top-k.approx-top-k.toml#t006_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-top-k.approx-top-k-over.t001"
+form = "sf.functions.approx-top-k.approx-top-k-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Per-partition top 2."
+catches = "the whole input"
+reading = "the page: the approximately most frequent values and their counts as an ARRAY of [value, count] pairs, most frequent first; defaults to 1 and is at most 100000; bounds the tracked values; APPROX_TOP_K_ACCUMULATE builds the state, _COMBINE merges states (with optional when all states share it) and _ESTIMATE reads the top ; the page does not say how NULL is counted; no ORDER BY or frame as a window function"
+cases = ["cases/translation/functions/approx-top-k.approx-top-k-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-top-k.approx-top-k-over.t002"
+form = "sf.functions.approx-top-k.approx-top-k-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause."
+catches = "a running value"
+reading = "the page: the approximately most frequent values and their counts as an ARRAY of [value, count] pairs, most frequent first; defaults to 1 and is at most 100000; bounds the tracked values; APPROX_TOP_K_ACCUMULATE builds the state, _COMBINE merges states (with optional when all states share it) and _ESTIMATE reads the top ; the page does not say how NULL is counted; no ORDER BY or frame as a window function"
+cases = ["cases/translation/functions/approx-top-k.approx-top-k-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-top-k.approx-top-k-over.t003"
+form = "sf.functions.approx-top-k.approx-top-k-over"
+kind = "null_argument"
+target = "expr4"
+note = "NULL partition keys."
+catches = "NULL-keyed rows split or dropped"
+reading = "the page: the approximately most frequent values and their counts as an ARRAY of [value, count] pairs, most frequent first; defaults to 1 and is at most 100000; bounds the tracked values; APPROX_TOP_K_ACCUMULATE builds the state, _COMBINE merges states (with optional when all states share it) and _ESTIMATE reads the top ; the page does not say how NULL is counted; no ORDER BY or frame as a window function"
+cases = ["cases/translation/functions/approx-top-k.approx-top-k-over.toml#t003_null_expr4"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approx-top-k.approx-top-k-over.t004"
+form = "sf.functions.approx-top-k.approx-top-k-over"
+kind = "null_argument"
+target = "expr"
+note = "A partition of only NULL."
+catches = "NULL treated the opposite way to Snowflake"
+reading = "the page: the approximately most frequent values and their counts as an ARRAY of [value, count] pairs, most frequent first; defaults to 1 and is at most 100000; bounds the tracked values; APPROX_TOP_K_ACCUMULATE builds the state, _COMBINE merges states (with optional when all states share it) and _ESTIMATE reads the top ; the page does not say how NULL is counted; no ORDER BY or frame as a window function"
+cases = ["cases/translation/functions/approx-top-k.approx-top-k-over.toml#t004_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approximate-jaccard-index.approximate-jaccard-index.t001"
+form = "sf.functions.approximate-jaccard-index.approximate-jaccard-index"
+kind = "type_combination"
+target = "MinHash states"
+note = "Two states of equal sets."
+catches = "an order-sensitive or exact-match comparison"
+reading = "the page: MINHASH(k, expr+) builds a MinHash state of k hash functions (at most 1024) over the values or, with *, over whole rows; MINHASH_COMBINE merges states; APPROXIMATE_SIMILARITY and APPROXIMATE_JACCARD_INDEX estimate the Jaccard similarity of the states as a FLOAT from 0.0 to 1.0; states need equal k; DISTINCT has no effect; each can be used as an aggregate or a window function"
+cases = ["cases/translation/functions/approximate-jaccard-index.approximate-jaccard-index.toml#t001_equal"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approximate-jaccard-index.approximate-jaccard-index.t002"
+form = "sf.functions.approximate-jaccard-index.approximate-jaccard-index"
+kind = "type_combination"
+target = "MinHash states"
+note = "Two states of disjoint sets."
+catches = "a similarity above 0.0 or a distance (1.0)"
+reading = "the page: MINHASH(k, expr+) builds a MinHash state of k hash functions (at most 1024) over the values or, with *, over whole rows; MINHASH_COMBINE merges states; APPROXIMATE_SIMILARITY and APPROXIMATE_JACCARD_INDEX estimate the Jaccard similarity of the states as a FLOAT from 0.0 to 1.0; states need equal k; DISTINCT has no effect; each can be used as an aggregate or a window function"
+cases = ["cases/translation/functions/approximate-jaccard-index.approximate-jaccard-index.toml#t002_disjoint"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approximate-jaccard-index.approximate-jaccard-index.t003"
+form = "sf.functions.approximate-jaccard-index.approximate-jaccard-index"
+kind = "type_combination"
+target = "DISTINCT"
+note = "DISTINCT, which has no effect, over a repeated state."
+catches = "DISTINCT rejected"
+reading = "the page: MINHASH(k, expr+) builds a MinHash state of k hash functions (at most 1024) over the values or, with *, over whole rows; MINHASH_COMBINE merges states; APPROXIMATE_SIMILARITY and APPROXIMATE_JACCARD_INDEX estimate the Jaccard similarity of the states as a FLOAT from 0.0 to 1.0; states need equal k; DISTINCT has no effect; each can be used as an aggregate or a window function"
+cases = ["cases/translation/functions/approximate-jaccard-index.approximate-jaccard-index.toml#t003_distinct"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approximate-jaccard-index.approximate-jaccard-index.t004"
+form = "sf.functions.approximate-jaccard-index.approximate-jaccard-index"
+kind = "null_argument"
+target = "expr"
+note = "A NULL state beside two equal states."
+catches = "the result NULL or below 1.0"
+reading = "the page: MINHASH(k, expr+) builds a MinHash state of k hash functions (at most 1024) over the values or, with *, over whole rows; MINHASH_COMBINE merges states; APPROXIMATE_SIMILARITY and APPROXIMATE_JACCARD_INDEX estimate the Jaccard similarity of the states as a FLOAT from 0.0 to 1.0; states need equal k; DISTINCT has no effect; each can be used as an aggregate or a window function"
+cases = ["cases/translation/functions/approximate-jaccard-index.approximate-jaccard-index.toml#t004_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approximate-similarity.approximate-similarity.t001"
+form = "sf.functions.approximate-similarity.approximate-similarity"
+kind = "type_combination"
+target = "MinHash states"
+note = "Two states of equal sets."
+catches = "an order-sensitive or exact-match comparison"
+reading = "the page: MINHASH(k, expr+) builds a MinHash state of k hash functions (at most 1024) over the values or, with *, over whole rows; MINHASH_COMBINE merges states; APPROXIMATE_SIMILARITY and APPROXIMATE_JACCARD_INDEX estimate the Jaccard similarity of the states as a FLOAT from 0.0 to 1.0; states need equal k; DISTINCT has no effect; each can be used as an aggregate or a window function"
+cases = ["cases/translation/functions/approximate-similarity.approximate-similarity.toml#t001_equal"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approximate-similarity.approximate-similarity.t002"
+form = "sf.functions.approximate-similarity.approximate-similarity"
+kind = "type_combination"
+target = "MinHash states"
+note = "Two states of disjoint sets."
+catches = "a similarity above 0.0 or a distance (1.0)"
+reading = "the page: MINHASH(k, expr+) builds a MinHash state of k hash functions (at most 1024) over the values or, with *, over whole rows; MINHASH_COMBINE merges states; APPROXIMATE_SIMILARITY and APPROXIMATE_JACCARD_INDEX estimate the Jaccard similarity of the states as a FLOAT from 0.0 to 1.0; states need equal k; DISTINCT has no effect; each can be used as an aggregate or a window function"
+cases = ["cases/translation/functions/approximate-similarity.approximate-similarity.toml#t002_disjoint"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approximate-similarity.approximate-similarity.t003"
+form = "sf.functions.approximate-similarity.approximate-similarity"
+kind = "type_combination"
+target = "DISTINCT"
+note = "DISTINCT, which has no effect, over a repeated state."
+catches = "DISTINCT rejected"
+reading = "the page: MINHASH(k, expr+) builds a MinHash state of k hash functions (at most 1024) over the values or, with *, over whole rows; MINHASH_COMBINE merges states; APPROXIMATE_SIMILARITY and APPROXIMATE_JACCARD_INDEX estimate the Jaccard similarity of the states as a FLOAT from 0.0 to 1.0; states need equal k; DISTINCT has no effect; each can be used as an aggregate or a window function"
+cases = ["cases/translation/functions/approximate-similarity.approximate-similarity.toml#t003_distinct"]
+required = true
+
+[[obligation]]
+id = "sf.functions.approximate-similarity.approximate-similarity.t004"
+form = "sf.functions.approximate-similarity.approximate-similarity"
+kind = "null_argument"
+target = "expr"
+note = "A NULL state beside two equal states."
+catches = "the result NULL or below 1.0"
+reading = "the page: MINHASH(k, expr+) builds a MinHash state of k hash functions (at most 1024) over the values or, with *, over whole rows; MINHASH_COMBINE merges states; APPROXIMATE_SIMILARITY and APPROXIMATE_JACCARD_INDEX estimate the Jaccard similarity of the states as a FLOAT from 0.0 to 1.0; states need equal k; DISTINCT has no effect; each can be used as an aggregate or a window function"
+cases = ["cases/translation/functions/approximate-similarity.approximate-similarity.toml#t004_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-agg.array-agg.t001"
+form = "sf.functions.array-agg.array-agg"
+kind = "type_combination"
+target = "NUMBER, WITHIN GROUP DESC"
+note = "A descending order per group."
+catches = "ASC or input order"
+reading = "the page: an ARRAY of the non-NULL values; WITHIN GROUP (ORDER BY) fixes the order, otherwise unpredictable; DISTINCT removes duplicates and must name the WITHIN GROUP column; an empty input gives an empty array"
+cases = ["cases/translation/functions/array-agg.array-agg.toml#t001_ordered"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-agg.array-agg.t002"
+form = "sf.functions.array-agg.array-agg"
+kind = "type_combination"
+target = "WITHIN GROUP on another column"
+note = "Order by a second column."
+catches = "the values sorted by themselves"
+reading = "the page: an ARRAY of the non-NULL values; WITHIN GROUP (ORDER BY) fixes the order, otherwise unpredictable; DISTINCT removes duplicates and must name the WITHIN GROUP column; an empty input gives an empty array"
+cases = ["cases/translation/functions/array-agg.array-agg.toml#t002_order_other"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-agg.array-agg.t003"
+form = "sf.functions.array-agg.array-agg"
+kind = "type_combination"
+target = "DATE"
+note = "DATE values in an ARRAY."
+catches = "dates rendered in another format inside the array"
+reading = "the page: an ARRAY of the non-NULL values; WITHIN GROUP (ORDER BY) fixes the order, otherwise unpredictable; DISTINCT removes duplicates and must name the WITHIN GROUP column; an empty input gives an empty array"
+cases = ["cases/translation/functions/array-agg.array-agg.toml#t003_date"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-agg.array-agg.t004"
+form = "sf.functions.array-agg.array-agg"
+kind = "type_combination"
+target = "DISTINCT"
+note = "DISTINCT with WITHIN GROUP on the same column."
+catches = "DISTINCT ignored"
+reading = "the page: an ARRAY of the non-NULL values; WITHIN GROUP (ORDER BY) fixes the order, otherwise unpredictable; DISTINCT removes duplicates and must name the WITHIN GROUP column; an empty input gives an empty array"
+cases = ["cases/translation/functions/array-agg.array-agg.toml#t004_distinct"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-agg.array-agg.t005"
+form = "sf.functions.array-agg.array-agg"
+kind = "error_parity"
+target = "error"
+note = "DISTINCT with WITHIN GROUP on a different column, which the page says is an error."
+catches = "the array returned instead of the compilation error"
+reading = "the page: an ARRAY of the non-NULL values; WITHIN GROUP (ORDER BY) fixes the order, otherwise unpredictable; DISTINCT removes duplicates and must name the WITHIN GROUP column; an empty input gives an empty array"
+cases = ["cases/translation/functions/array-agg.array-agg.toml#t005_distinct_other_order"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-agg.array-agg.t006"
+form = "sf.functions.array-agg.array-agg"
+kind = "null_argument"
+target = "expr1"
+note = "NULL values, which are omitted, and a group of only NULL."
+catches = "NULL kept as an array element (DuckDB list() keeps it), or the all-NULL group giving NULL"
+reading = "the page: an ARRAY of the non-NULL values; WITHIN GROUP (ORDER BY) fixes the order, otherwise unpredictable; DISTINCT removes duplicates and must name the WITHIN GROUP column; an empty input gives an empty array"
+cases = ["cases/translation/functions/array-agg.array-agg.toml#t006_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-agg.array-agg.t007"
+form = "sf.functions.array-agg.array-agg"
+kind = "type_combination"
+target = "no rows"
+note = "No rows; the page gives an empty array."
+catches = "NULL instead of []"
+reading = "the page: an ARRAY of the non-NULL values; WITHIN GROUP (ORDER BY) fixes the order, otherwise unpredictable; DISTINCT removes duplicates and must name the WITHIN GROUP column; an empty input gives an empty array"
+cases = ["cases/translation/functions/array-agg.array-agg.toml#t007_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-agg.array-agg-over.t001"
+form = "sf.functions.array-agg.array-agg-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Per-partition arrays in a fixed order."
+catches = "the whole input collected"
+reading = "the page: an ARRAY of the non-NULL values; WITHIN GROUP (ORDER BY) fixes the order, otherwise unpredictable; DISTINCT removes duplicates and must name the WITHIN GROUP column; an empty input gives an empty array; as a window function WITHIN GROUP and DISTINCT are allowed only when OVER has no ORDER BY; with ORDER BY the array follows the OVER order; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/array-agg.array-agg-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-agg.array-agg-over.t002"
+form = "sf.functions.array-agg.array-agg-over"
+kind = "type_combination"
+target = "OVER (ORDER BY) with ties"
+note = "ORDER BY with tied keys and no frame: with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. ARRAY_SORT makes the order of the tied values irrelevant."
+catches = "a ROWS frame (a tied row missing from its twin's array)"
+reading = "the page: an ARRAY of the non-NULL values; WITHIN GROUP (ORDER BY) fixes the order, otherwise unpredictable; DISTINCT removes duplicates and must name the WITHIN GROUP column; an empty input gives an empty array; as a window function WITHIN GROUP and DISTINCT are allowed only when OVER has no ORDER BY; with ORDER BY the array follows the OVER order; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/array-agg.array-agg-over.toml#t002_order_ties"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-agg.array-agg-over.t003"
+form = "sf.functions.array-agg.array-agg-over"
+kind = "type_combination"
+target = "ASC NULLS FIRST, ROWS frame"
+note = "A NULL ordering key first and a one-row-back frame; the array follows the OVER order."
+catches = "NULLS FIRST ignored, or the frame reaching forward"
+reading = "the page: an ARRAY of the non-NULL values; WITHIN GROUP (ORDER BY) fixes the order, otherwise unpredictable; DISTINCT removes duplicates and must name the WITHIN GROUP column; an empty input gives an empty array; as a window function WITHIN GROUP and DISTINCT are allowed only when OVER has no ORDER BY; with ORDER BY the array follows the OVER order; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/array-agg.array-agg-over.toml#t003_nulls_first_frame"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-agg.array-agg-over.t004"
+form = "sf.functions.array-agg.array-agg-over"
+kind = "type_combination"
+target = "ORDER BY DESC"
+note = "DESC with a running ROWS frame; the array follows the OVER order."
+catches = "DESC ignored"
+reading = "the page: an ARRAY of the non-NULL values; WITHIN GROUP (ORDER BY) fixes the order, otherwise unpredictable; DISTINCT removes duplicates and must name the WITHIN GROUP column; an empty input gives an empty array; as a window function WITHIN GROUP and DISTINCT are allowed only when OVER has no ORDER BY; with ORDER BY the array follows the OVER order; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/array-agg.array-agg-over.toml#t004_order_desc"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-agg.array-agg-over.t005"
+form = "sf.functions.array-agg.array-agg-over"
+kind = "null_argument"
+target = "expr1"
+note = "NULL values inside a partition, which are omitted."
+catches = "NULL kept as an element"
+reading = "the page: an ARRAY of the non-NULL values; WITHIN GROUP (ORDER BY) fixes the order, otherwise unpredictable; DISTINCT removes duplicates and must name the WITHIN GROUP column; an empty input gives an empty array; as a window function WITHIN GROUP and DISTINCT are allowed only when OVER has no ORDER BY; with ORDER BY the array follows the OVER order; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/array-agg.array-agg-over.toml#t005_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-agg.array-agg-over.t006"
+form = "sf.functions.array-agg.array-agg-over"
+kind = "null_argument"
+target = "expr2"
+note = "NULL partition keys."
+catches = "NULL-keyed rows split or dropped"
+reading = "the page: an ARRAY of the non-NULL values; WITHIN GROUP (ORDER BY) fixes the order, otherwise unpredictable; DISTINCT removes duplicates and must name the WITHIN GROUP column; an empty input gives an empty array; as a window function WITHIN GROUP and DISTINCT are allowed only when OVER has no ORDER BY; with ORDER BY the array follows the OVER order; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/array-agg.array-agg-over.toml#t006_null_expr2"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-agg.array-agg-over.t007"
+form = "sf.functions.array-agg.array-agg-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause with WITHIN GROUP."
+catches = "a running array"
+reading = "the page: an ARRAY of the non-NULL values; WITHIN GROUP (ORDER BY) fixes the order, otherwise unpredictable; DISTINCT removes duplicates and must name the WITHIN GROUP column; an empty input gives an empty array; as a window function WITHIN GROUP and DISTINCT are allowed only when OVER has no ORDER BY; with ORDER BY the array follows the OVER order; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/array-agg.array-agg-over.toml#t007_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-agg.array-agg-over.t008"
+form = "sf.functions.array-agg.array-agg-over"
+kind = "type_combination"
+target = "DISTINCT OVER (PARTITION BY)"
+note = "DISTINCT with WITHIN GROUP and no ORDER BY in OVER, on a repeated value."
+catches = "DISTINCT ignored"
+reading = "the page: an ARRAY of the non-NULL values; WITHIN GROUP (ORDER BY) fixes the order, otherwise unpredictable; DISTINCT removes duplicates and must name the WITHIN GROUP column; an empty input gives an empty array; as a window function WITHIN GROUP and DISTINCT are allowed only when OVER has no ORDER BY; with ORDER BY the array follows the OVER order; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/array-agg.array-agg-over.toml#t008_distinct"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-append.t001"
+form = "sf.functions.array-append"
+kind = "type_combination"
+target = "ARRAY, NUMBER(10,1)"
+note = "A decimal appended to an array of a number and text."
+catches = "the element added at the front, or the decimal converted to text or an integer"
+reading = "the page: appends one element of almost any type to a semi-structured array; the element type may differ from the existing elements; NULL array gives NULL"
+cases = ["cases/translation/functions/array-append.toml#t001_mixed_type"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-append.t002"
+form = "sf.functions.array-append"
+kind = "type_combination"
+target = "ARRAY, ARRAY"
+note = "An array appended as one element."
+catches = "the two arrays concatenated (ARRAY_CAT) instead of nested"
+reading = "the page: appends one element of almost any type to a semi-structured array; the element type may differ from the existing elements; NULL array gives NULL"
+cases = ["cases/translation/functions/array-append.toml#t002_nested_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-append.t003"
+form = "sf.functions.array-append"
+kind = "null_argument"
+target = "array"
+note = "A NULL array, and a NULL element appended to an array."
+catches = "a NULL array read as empty ([1]), or the NULL element dropped (the array unchanged)"
+reading = "the page: appends one element of almost any type to a semi-structured array; the element type may differ from the existing elements; NULL array gives NULL"
+cases = ["cases/translation/functions/array-append.toml#t003_null_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-cat.t001"
+form = "sf.functions.array-cat"
+kind = "type_combination"
+target = "ARRAY, ARRAY"
+note = "Arrays with a value in both, in an order that shows which comes first."
+catches = "array2 placed first, or the shared value de-duplicated"
+reading = "the page: array2 appended after array1; both ARRAY or VARIANT holding an array; NULL either side gives NULL"
+cases = ["cases/translation/functions/array-cat.toml#t001_order_duplicates"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-cat.t002"
+form = "sf.functions.array-cat"
+kind = "type_combination"
+target = "ARRAY, ARRAY"
+note = "Arrays whose elements are arrays."
+catches = "one level of nesting flattened"
+reading = "the page: array2 appended after array1; both ARRAY or VARIANT holding an array; NULL either side gives NULL"
+cases = ["cases/translation/functions/array-cat.toml#t002_nested"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-cat.t003"
+form = "sf.functions.array-cat"
+kind = "type_combination"
+target = "ARRAY, ARRAY"
+note = "An empty array on either side."
+catches = "NULL returned for an empty array"
+reading = "the page: array2 appended after array1; both ARRAY or VARIANT holding an array; NULL either side gives NULL"
+cases = ["cases/translation/functions/array-cat.toml#t003_empty_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-cat.t004"
+form = "sf.functions.array-cat"
+kind = "null_argument"
+target = "array1"
+note = "A NULL array on either side."
+catches = "NULL read as an empty array (the other array returned)"
+reading = "the page: array2 appended after array1; both ARRAY or VARIANT holding an array; NULL either side gives NULL"
+cases = ["cases/translation/functions/array-cat.toml#t004_null_array1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-cat.t005"
+form = "sf.functions.array-cat"
+kind = "error_parity"
+target = "error"
+note = "An OBJECT where an array is needed."
+catches = "the object treated as a one-element array"
+reading = "the page: array2 appended after array1; both ARRAY or VARIANT holding an array; NULL either side gives NULL"
+cases = ["cases/translation/functions/array-cat.toml#t005_object"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-compact.t001"
+form = "sf.functions.array-compact"
+kind = "type_combination"
+target = "ARRAY"
+note = "JSON null beside an empty array, an empty object, 0, false and an empty string."
+catches = "the empty containers, 0, false or the empty string removed as well, or the JSON null kept"
+reading = "the page: removes JSON null and SQL NULL elements; argument is an ARRAY or a VARIANT holding one; NULL gives NULL"
+cases = ["cases/translation/functions/array-compact.toml#t001_json_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-compact.t002"
+form = "sf.functions.array-compact"
+kind = "type_combination"
+target = "ARRAY"
+note = "An undefined element (a SQL NULL passed to ARRAY_CONSTRUCT) between values."
+catches = "the undefined element kept"
+reading = "the page: removes JSON null and SQL NULL elements; argument is an ARRAY or a VARIANT holding one; NULL gives NULL"
+cases = ["cases/translation/functions/array-compact.toml#t002_sql_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-compact.t003"
+form = "sf.functions.array-compact"
+kind = "null_argument"
+target = "array1"
+note = "A NULL argument."
+catches = "an empty array returned"
+reading = "the page: removes JSON null and SQL NULL elements; argument is an ARRAY or a VARIANT holding one; NULL gives NULL"
+cases = ["cases/translation/functions/array-compact.toml#t003_null_array1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-construct.t001"
+form = "sf.functions.array-construct"
+kind = "type_combination"
+target = "NUMBER(10,2), VARCHAR, BOOLEAN, DATE"
+note = "Arguments of four types in one array."
+catches = "every element converted to text, or the order changed"
+reading = "the page: an ARRAY of N elements for N arguments of any types; SQL NULL and JSON null may both be elements"
+cases = ["cases/translation/functions/array-construct.toml#t001_mixed_types"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-construct.t002"
+form = "sf.functions.array-construct"
+kind = "type_combination"
+target = "no arguments"
+note = "No arguments."
+catches = "NULL returned instead of an empty array"
+reading = "the page: an ARRAY of N elements for N arguments of any types; SQL NULL and JSON null may both be elements"
+cases = ["cases/translation/functions/array-construct.toml#t002_no_arguments"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-construct.t003"
+form = "sf.functions.array-construct"
+kind = "null_argument"
+target = "expr1"
+note = "A SQL NULL argument, which stays an undefined element, beside a JSON null."
+catches = "the SQL NULL dropped (size 2) or the two nulls made the same element"
+reading = "the page: an ARRAY of N elements for N arguments of any types; SQL NULL and JSON null may both be elements"
+cases = ["cases/translation/functions/array-construct.toml#t003_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-construct-compact.t001"
+form = "sf.functions.array-construct-compact"
+kind = "type_combination"
+target = "NUMBER, VARCHAR"
+note = "A number and text in one array."
+catches = "the number converted to text"
+reading = "the page: like ARRAY_CONSTRUCT, but SQL NULL arguments are skipped, giving a dense array; types may differ"
+cases = ["cases/translation/functions/array-construct-compact.toml#t001_mixed_types"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-construct-compact.t002"
+form = "sf.functions.array-construct-compact"
+kind = "null_argument"
+target = "expr1"
+note = "SQL NULL arguments around values, and only SQL NULL arguments."
+catches = "the NULL kept as an undefined element (size 3), or NULL returned for the all-NULL call"
+reading = "the page: like ARRAY_CONSTRUCT, but SQL NULL arguments are skipped, giving a dense array; types may differ"
+cases = ["cases/translation/functions/array-construct-compact.toml#t002_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-construct-compact.t003"
+form = "sf.functions.array-construct-compact"
+kind = "type_combination"
+target = "VARIANT"
+note = "A JSON null argument. The page says SQL NULL values are skipped, so a JSON null is not."
+catches = "the JSON null skipped like a SQL NULL"
+reading = "the page: like ARRAY_CONSTRUCT, but SQL NULL arguments are skipped, giving a dense array; types may differ"
+cases = ["cases/translation/functions/array-construct-compact.toml#t003_json_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-construct-compact.t004"
+form = "sf.functions.array-construct-compact"
+kind = "type_combination"
+target = "no arguments"
+note = "No arguments."
+catches = "NULL returned instead of an empty array"
+reading = "the page: like ARRAY_CONSTRUCT, but SQL NULL arguments are skipped, giving a dense array; types may differ"
+cases = ["cases/translation/functions/array-construct-compact.toml#t004_no_arguments"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-construct-structured.t001"
+form = "sf.functions.array-construct-structured"
+kind = "type_combination"
+target = "NUMBER, NUMBER(10,1)"
+note = "An integer and a decimal, cast to one element type."
+catches = "the integer kept as an integer type beside the decimal, or the decimal truncated"
+reading = "the page: a structured ARRAY whose element type is the common type of the arguments; NULL inputs are kept; every argument an untyped NULL is an error; at least one argument"
+cases = ["cases/translation/functions/array-construct-structured.toml#t001_common_type"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-construct-structured.t002"
+form = "sf.functions.array-construct-structured"
+kind = "null_argument"
+target = "expr1"
+note = "A typed NULL argument beside a value, which the page says stays an element."
+catches = "the NULL dropped (one element)"
+reading = "the page: a structured ARRAY whose element type is the common type of the arguments; NULL inputs are kept; every argument an untyped NULL is an error; at least one argument"
+cases = ["cases/translation/functions/array-construct-structured.toml#t002_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-construct-structured.t003"
+form = "sf.functions.array-construct-structured"
+kind = "error_parity"
+target = "error"
+note = "Every argument an untyped NULL, which the page says is an error."
+catches = "an array of NULLs returned instead of the error"
+reading = "the page: a structured ARRAY whose element type is the common type of the arguments; NULL inputs are kept; every argument an untyped NULL is an error; at least one argument"
+cases = ["cases/translation/functions/array-construct-structured.toml#t003_untyped_nulls"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-contains.t001"
+form = "sf.functions.array-contains"
+kind = "type_combination"
+target = "VARIANT, ARRAY"
+note = "A present and an absent value."
+catches = "TRUE for the absent value, or a position returned"
+reading = "the page: TRUE when value_expr (a VARIANT) is in the array, including a NULL value with a SQL NULL element and a JSON null with a JSON null element; FALSE otherwise"
+cases = ["cases/translation/functions/array-contains.toml#t001_found"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-contains.t002"
+form = "sf.functions.array-contains"
+kind = "type_combination"
+target = "VARIANT, ARRAY"
+note = "The text \"1\" against an array holding the number 1, and the reverse."
+catches = "values compared as text (TRUE)"
+reading = "the page: TRUE when value_expr (a VARIANT) is in the array, including a NULL value with a SQL NULL element and a JSON null with a JSON null element; FALSE otherwise"
+cases = ["cases/translation/functions/array-contains.toml#t002_number_vs_text"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-contains.t003"
+form = "sf.functions.array-contains"
+kind = "type_combination"
+target = "VARIANT, ARRAY"
+note = "A JSON null searched in arrays with and without a JSON null."
+catches = "JSON null never matched, or matched everywhere"
+reading = "the page: TRUE when value_expr (a VARIANT) is in the array, including a NULL value with a SQL NULL element and a JSON null with a JSON null element; FALSE otherwise"
+cases = ["cases/translation/functions/array-contains.toml#t003_json_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-contains.t004"
+form = "sf.functions.array-contains"
+kind = "null_argument"
+target = "value-expr"
+note = "A NULL value searched in an array with a SQL NULL element and in one without."
+catches = "NULL returned where the page gives TRUE for the array holding a SQL NULL"
+reading = "the page: TRUE when value_expr (a VARIANT) is in the array, including a NULL value with a SQL NULL element and a JSON null with a JSON null element; FALSE otherwise"
+cases = ["cases/translation/functions/array-contains.toml#t004_null_value_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-distinct.t001"
+form = "sf.functions.array-distinct"
+kind = "type_combination"
+target = "ARRAY"
+note = "Repeated numbers beside the same digits as text."
+catches = "the number 1 and the text \"1\" merged (2 left), or duplicates kept (5)"
+reading = "the page: the elements without duplicates, in unspecified order; objects must be identical to be duplicates; NULL is a known value; NULL argument gives NULL"
+cases = ["cases/translation/functions/array-distinct.toml#t001_number_vs_text"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-distinct.t002"
+form = "sf.functions.array-distinct"
+kind = "type_combination"
+target = "ARRAY"
+note = "Identical objects and one with an extra key."
+catches = "objects matched on a shared key (1 left) or never matched (3)"
+reading = "the page: the elements without duplicates, in unspecified order; objects must be identical to be duplicates; NULL is a known value; NULL argument gives NULL"
+cases = ["cases/translation/functions/array-distinct.toml#t002_objects"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-distinct.t003"
+form = "sf.functions.array-distinct"
+kind = "type_combination"
+target = "ARRAY"
+note = "Two undefined elements beside a value; the page treats NULL as a known value."
+catches = "the undefined elements dropped (1 left) or kept apart (3)"
+reading = "the page: the elements without duplicates, in unspecified order; objects must be identical to be duplicates; NULL is a known value; NULL argument gives NULL"
+cases = ["cases/translation/functions/array-distinct.toml#t003_sql_null_elements"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-distinct.t004"
+form = "sf.functions.array-distinct"
+kind = "null_argument"
+target = "array"
+note = "A NULL argument."
+catches = "an empty array returned"
+reading = "the page: the elements without duplicates, in unspecified order; objects must be identical to be duplicates; NULL is a known value; NULL argument gives NULL"
+cases = ["cases/translation/functions/array-distinct.toml#t004_null_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-except.t001"
+form = "sf.functions.array-except"
+kind = "type_combination"
+target = "ARRAY, ARRAY"
+note = "Three copies of 1 less two, and one copy less two."
+catches = "set semantics (every 1 removed), or the excess exclusion going negative"
+reading = "the page: source elements not in the exclusion array, with bag semantics (N - M copies, at least 0); objects must be identical; NULL either side gives NULL; order unspecified"
+cases = ["cases/translation/functions/array-except.toml#t001_bag"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-except.t002"
+form = "sf.functions.array-except"
+kind = "type_combination"
+target = "ARRAY, ARRAY"
+note = "Objects excluded only when identical."
+catches = "an object excluded on a shared key"
+reading = "the page: source elements not in the exclusion array, with bag semantics (N - M copies, at least 0); objects must be identical; NULL either side gives NULL; order unspecified"
+cases = ["cases/translation/functions/array-except.toml#t002_objects"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-except.t003"
+form = "sf.functions.array-except"
+kind = "type_combination"
+target = "ARRAY, ARRAY"
+note = "Every element excluded."
+catches = "NULL returned instead of an empty array"
+reading = "the page: source elements not in the exclusion array, with bag semantics (N - M copies, at least 0); objects must be identical; NULL either side gives NULL; order unspecified"
+cases = ["cases/translation/functions/array-except.toml#t003_all_excluded"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-except.t004"
+form = "sf.functions.array-except"
+kind = "null_argument"
+target = "source-array"
+note = "A NULL array on either side."
+catches = "NULL read as an empty array"
+reading = "the page: source elements not in the exclusion array, with bag semantics (N - M copies, at least 0); objects must be identical; NULL either side gives NULL; order unspecified"
+cases = ["cases/translation/functions/array-except.toml#t004_null_source_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-flatten.t001"
+form = "sf.functions.array-flatten"
+kind = "type_combination"
+target = "ARRAY"
+note = "Three levels of nesting."
+catches = "every level flattened"
+reading = "the page: concatenates the arrays in an array of arrays, removing one level; a non-array element is an error; NULL argument or a NULL element gives NULL"
+cases = ["cases/translation/functions/array-flatten.toml#t001_one_level"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-flatten.t002"
+form = "sf.functions.array-flatten"
+kind = "error_parity"
+target = "error"
+note = "An element that is not an array."
+catches = "the scalar kept as is"
+reading = "the page: concatenates the arrays in an array of arrays, removing one level; a non-array element is an error; NULL argument or a NULL element gives NULL"
+cases = ["cases/translation/functions/array-flatten.toml#t002_non_array_element"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-flatten.t003"
+form = "sf.functions.array-flatten"
+kind = "null_argument"
+target = "array"
+note = "A NULL argument, and an array holding a SQL NULL element."
+catches = "the NULL element skipped instead of NULL returned"
+reading = "the page: concatenates the arrays in an array of arrays, removing one level; a non-array element is an error; NULL argument or a NULL element gives NULL"
+cases = ["cases/translation/functions/array-flatten.toml#t003_null_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-generate-range.t001"
+form = "sf.functions.array-generate-range"
+kind = "type_combination"
+target = "INTEGER, INTEGER"
+note = "The default step, and a step that does not reach stop exactly."
+catches = "stop included, or the step ignored"
+reading = "the page: integers from start up to but not including stop, by step (default 1, not 0, may be negative); empty when start = stop or the step points away; any NULL gives NULL"
+cases = ["cases/translation/functions/array-generate-range.toml#t001_default_step"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-generate-range.t002"
+form = "sf.functions.array-generate-range"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "A negative step."
+catches = "an empty array, or the sign of the step dropped"
+reading = "the page: integers from start up to but not including stop, by step (default 1, not 0, may be negative); empty when start = stop or the step points away; any NULL gives NULL"
+cases = ["cases/translation/functions/array-generate-range.toml#t002_negative_step"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-generate-range.t003"
+form = "sf.functions.array-generate-range"
+kind = "type_combination"
+target = "INTEGER, INTEGER, INTEGER"
+note = "start = stop, and steps that point away from stop."
+catches = "a one-element array, or the range walked the other way"
+reading = "the page: integers from start up to but not including stop, by step (default 1, not 0, may be negative); empty when start = stop or the step points away; any NULL gives NULL"
+cases = ["cases/translation/functions/array-generate-range.toml#t003_empty"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-generate-range.t004"
+form = "sf.functions.array-generate-range"
+kind = "null_argument"
+target = "start"
+note = "NULL in each argument."
+catches = "NULL read as 0, or the default step used for a NULL step"
+reading = "the page: integers from start up to but not including stop, by step (default 1, not 0, may be negative); empty when start = stop or the step points away; any NULL gives NULL"
+cases = ["cases/translation/functions/array-generate-range.toml#t004_null_start"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-generate-range.t005"
+form = "sf.functions.array-generate-range"
+kind = "error_parity"
+target = "error"
+note = "A step of 0, which the page says cannot be given."
+catches = "an empty array or an endless loop"
+reading = "the page: integers from start up to but not including stop, by step (default 1, not 0, may be negative); empty when start = stop or the step points away; any NULL gives NULL"
+cases = ["cases/translation/functions/array-generate-range.toml#t005_zero_step"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-insert.t001"
+form = "sf.functions.array-insert"
+kind = "type_combination"
+target = "ARRAY, INTEGER, VARIANT"
+note = "Positions 0, 1 and the end."
+catches = "1-based positions, or the element replacing the one at the position"
+reading = "the page: inserts at a 0-based position, shifting later elements; a negative position counts from the end (-1 inserts before the last); a position past the end pads with undefined elements"
+cases = ["cases/translation/functions/array-insert.toml#t001_positions"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-insert.t002"
+form = "sf.functions.array-insert"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "Positions -1 and -3 counted from the end."
+catches = "the element appended after the last for -1, or the negative position read as 0"
+reading = "the page: inserts at a 0-based position, shifting later elements; a negative position counts from the end (-1 inserts before the last); a position past the end pads with undefined elements"
+cases = ["cases/translation/functions/array-insert.toml#t002_negative_position"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-insert.t003"
+form = "sf.functions.array-insert"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "A position two past the end, which pads with undefined elements."
+catches = "the element appended without padding (size 4 instead of 6), or an error"
+reading = "the page: inserts at a 0-based position, shifting later elements; a negative position counts from the end (-1 inserts before the last); a position past the end pads with undefined elements"
+cases = ["cases/translation/functions/array-insert.toml#t003_past_end"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-insert.t004"
+form = "sf.functions.array-insert"
+kind = "null_argument"
+target = "pos"
+note = "A NULL position and a NULL array; the page does not say; the recording decides."
+catches = "a NULL position read as 0 (the element inserted first)"
+reading = "the page: inserts at a 0-based position, shifting later elements; a negative position counts from the end (-1 inserts before the last); a position past the end pads with undefined elements"
+cases = ["cases/translation/functions/array-insert.toml#t004_null_pos"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-intersection.t001"
+form = "sf.functions.array-intersection"
+kind = "type_combination"
+target = "ARRAY, ARRAY"
+note = "Three copies of 1 against two."
+catches = "set semantics (one 1), or every copy of the first array kept"
+reading = "the page: elements in both arrays with bag semantics (min(N, M) copies); empty when nothing overlaps; NULL either side gives NULL; order unspecified"
+cases = ["cases/translation/functions/array-intersection.toml#t001_bag"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-intersection.t002"
+form = "sf.functions.array-intersection"
+kind = "type_combination"
+target = "ARRAY, ARRAY"
+note = "The number 1 against the text \"1\"."
+catches = "values compared as text"
+reading = "the page: elements in both arrays with bag semantics (min(N, M) copies); empty when nothing overlaps; NULL either side gives NULL; order unspecified"
+cases = ["cases/translation/functions/array-intersection.toml#t002_number_vs_text"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-intersection.t003"
+form = "sf.functions.array-intersection"
+kind = "type_combination"
+target = "ARRAY, ARRAY"
+note = "No common element."
+catches = "NULL returned instead of an empty array"
+reading = "the page: elements in both arrays with bag semantics (min(N, M) copies); empty when nothing overlaps; NULL either side gives NULL; order unspecified"
+cases = ["cases/translation/functions/array-intersection.toml#t003_disjoint"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-intersection.t004"
+form = "sf.functions.array-intersection"
+kind = "null_argument"
+target = "array1"
+note = "A NULL array on either side."
+catches = "NULL read as an empty array (an empty array returned)"
+reading = "the page: elements in both arrays with bag semantics (min(N, M) copies); empty when nothing overlaps; NULL either side gives NULL; order unspecified"
+cases = ["cases/translation/functions/array-intersection.toml#t004_null_array1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-max.t001"
+form = "sf.functions.array-max"
+kind = "type_combination"
+target = "ARRAY"
+note = "Integers beside a decimal and a negative number."
+catches = "elements compared as text (2.5 against 10), or the first element returned"
+reading = "the page: a VARIANT holding the highest element that is not a SQL NULL; NULL for a NULL, empty or all-NULL array; JSON null elements are considered; NUMBER and FLOAT compare with each other"
+cases = ["cases/translation/functions/array-max.toml#t001_numbers"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-max.t002"
+form = "sf.functions.array-max"
+kind = "type_combination"
+target = "ARRAY"
+note = "Text elements."
+catches = "the first or last element returned instead of the ordered one"
+reading = "the page: a VARIANT holding the highest element that is not a SQL NULL; NULL for a NULL, empty or all-NULL array; JSON null elements are considered; NUMBER and FLOAT compare with each other"
+cases = ["cases/translation/functions/array-max.toml#t002_text"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-max.t003"
+form = "sf.functions.array-max"
+kind = "type_combination"
+target = "ARRAY"
+note = "A JSON null beside numbers. The page example shows an array holding a JSON null returning the JSON null; how it orders against numbers the page does not say, so the recording decides."
+catches = "the JSON null treated as a SQL NULL where Snowflake orders it, or the reverse"
+reading = "the page: a VARIANT holding the highest element that is not a SQL NULL; NULL for a NULL, empty or all-NULL array; JSON null elements are considered; NUMBER and FLOAT compare with each other"
+cases = ["cases/translation/functions/array-max.toml#t003_json_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-max.t004"
+form = "sf.functions.array-max"
+kind = "type_combination"
+target = "ARRAY"
+note = "An empty array."
+catches = "an error or a JSON null instead of NULL"
+reading = "the page: a VARIANT holding the highest element that is not a SQL NULL; NULL for a NULL, empty or all-NULL array; JSON null elements are considered; NUMBER and FLOAT compare with each other"
+cases = ["cases/translation/functions/array-max.toml#t004_empty"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-max.t005"
+form = "sf.functions.array-max"
+kind = "null_argument"
+target = "array"
+note = "A NULL array, and SQL NULL elements beside a value and alone."
+catches = "a SQL NULL element returned as the result, or the NULL array read as empty"
+reading = "the page: a VARIANT holding the highest element that is not a SQL NULL; NULL for a NULL, empty or all-NULL array; JSON null elements are considered; NUMBER and FLOAT compare with each other"
+cases = ["cases/translation/functions/array-max.toml#t005_null_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-min.t001"
+form = "sf.functions.array-min"
+kind = "type_combination"
+target = "ARRAY"
+note = "Integers beside a decimal and a negative number."
+catches = "elements compared as text (2.5 against 10), or the first element returned"
+reading = "the page: a VARIANT holding the lowest element that is not a SQL NULL; NULL for a NULL, empty or all-NULL array; JSON null elements are considered; NUMBER and FLOAT compare with each other"
+cases = ["cases/translation/functions/array-min.toml#t001_numbers"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-min.t002"
+form = "sf.functions.array-min"
+kind = "type_combination"
+target = "ARRAY"
+note = "Text elements."
+catches = "the first or last element returned instead of the ordered one"
+reading = "the page: a VARIANT holding the lowest element that is not a SQL NULL; NULL for a NULL, empty or all-NULL array; JSON null elements are considered; NUMBER and FLOAT compare with each other"
+cases = ["cases/translation/functions/array-min.toml#t002_text"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-min.t003"
+form = "sf.functions.array-min"
+kind = "type_combination"
+target = "ARRAY"
+note = "A JSON null beside numbers. The page says explicit nulls are considered; how they order against numbers it does not say; the recording decides."
+catches = "the JSON null treated as a SQL NULL where Snowflake orders it, or the reverse"
+reading = "the page: a VARIANT holding the lowest element that is not a SQL NULL; NULL for a NULL, empty or all-NULL array; JSON null elements are considered; NUMBER and FLOAT compare with each other"
+cases = ["cases/translation/functions/array-min.toml#t003_json_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-min.t004"
+form = "sf.functions.array-min"
+kind = "type_combination"
+target = "ARRAY"
+note = "An empty array."
+catches = "an error or a JSON null instead of NULL"
+reading = "the page: a VARIANT holding the lowest element that is not a SQL NULL; NULL for a NULL, empty or all-NULL array; JSON null elements are considered; NUMBER and FLOAT compare with each other"
+cases = ["cases/translation/functions/array-min.toml#t004_empty"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-min.t005"
+form = "sf.functions.array-min"
+kind = "null_argument"
+target = "array"
+note = "A NULL array, and SQL NULL elements beside a value and alone."
+catches = "a SQL NULL element returned as the result, or the NULL array read as empty"
+reading = "the page: a VARIANT holding the lowest element that is not a SQL NULL; NULL for a NULL, empty or all-NULL array; JSON null elements are considered; NUMBER and FLOAT compare with each other"
+cases = ["cases/translation/functions/array-min.toml#t005_null_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-position.t001"
+form = "sf.functions.array-position"
+kind = "type_combination"
+target = "VARIANT, ARRAY"
+note = "A value present twice, and one at position 0."
+catches = "1-based positions, or the last match returned"
+reading = "the page: the 0-based position of the first match; NULL when absent; a NULL value finds the first NULL in the array"
+cases = ["cases/translation/functions/array-position.toml#t001_first_match"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-position.t002"
+form = "sf.functions.array-position"
+kind = "type_combination"
+target = "VARIANT, ARRAY"
+note = "An absent value, and the text \"2\" against the number 2."
+catches = "-1 or 0 for an absent value, or values compared as text"
+reading = "the page: the 0-based position of the first match; NULL when absent; a NULL value finds the first NULL in the array"
+cases = ["cases/translation/functions/array-position.toml#t002_absent"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-position.t003"
+form = "sf.functions.array-position"
+kind = "type_combination"
+target = "VARIANT, ARRAY"
+note = "A JSON null searched in an array holding one."
+catches = "JSON null never found"
+reading = "the page: the 0-based position of the first match; NULL when absent; a NULL value finds the first NULL in the array"
+cases = ["cases/translation/functions/array-position.toml#t003_json_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-position.t004"
+form = "sf.functions.array-position"
+kind = "null_argument"
+target = "value-expr"
+note = "A NULL value, which the page says finds the first SQL NULL element."
+catches = "NULL returned instead of the position of the undefined element"
+reading = "the page: the 0-based position of the first match; NULL when absent; a NULL value finds the first NULL in the array"
+cases = ["cases/translation/functions/array-position.toml#t004_null_value_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-prepend.t001"
+form = "sf.functions.array-prepend"
+kind = "type_combination"
+target = "ARRAY, NUMBER"
+note = "An element prepended to a two-element array and to an empty one."
+catches = "the element appended at the end"
+reading = "the page: the array with the new element first"
+cases = ["cases/translation/functions/array-prepend.toml#t001_front"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-prepend.t002"
+form = "sf.functions.array-prepend"
+kind = "type_combination"
+target = "ARRAY, ARRAY"
+note = "An array prepended as one element."
+catches = "the arrays concatenated"
+reading = "the page: the array with the new element first"
+cases = ["cases/translation/functions/array-prepend.toml#t002_nested_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-prepend.t003"
+form = "sf.functions.array-prepend"
+kind = "null_argument"
+target = "array"
+note = "A NULL array, and a NULL element; the page does not say; the recording decides for the NULL array."
+catches = "the NULL element dropped (the array unchanged)"
+reading = "the page: the array with the new element first"
+cases = ["cases/translation/functions/array-prepend.toml#t003_null_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-remove.t001"
+form = "sf.functions.array-remove"
+kind = "type_combination"
+target = "ARRAY, VARIANT"
+note = "A value present twice, and a value removing every element."
+catches = "only the first copy removed, or NULL for the emptied array"
+reading = "the page: removes every element equal to the VARIANT value; an empty array when all are removed; a NULL value gives NULL"
+cases = ["cases/translation/functions/array-remove.toml#t001_every_copy"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-remove.t002"
+form = "sf.functions.array-remove"
+kind = "type_combination"
+target = "ARRAY, VARIANT"
+note = "The number 1 removed from an array holding 1 and \"1\"."
+catches = "the text \"1\" removed as well"
+reading = "the page: removes every element equal to the VARIANT value; an empty array when all are removed; a NULL value gives NULL"
+cases = ["cases/translation/functions/array-remove.toml#t002_number_vs_text"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-remove.t003"
+form = "sf.functions.array-remove"
+kind = "null_argument"
+target = "value-of-elements-to-remove"
+note = "A NULL value to remove."
+catches = "the array returned unchanged instead of NULL"
+reading = "the page: removes every element equal to the VARIANT value; an empty array when all are removed; a NULL value gives NULL"
+cases = ["cases/translation/functions/array-remove.toml#t003_null_value_of_elements_to_remove"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-remove-at.t001"
+form = "sf.functions.array-remove-at"
+kind = "type_combination"
+target = "ARRAY, INTEGER"
+note = "Positions 0 and 1."
+catches = "1-based positions"
+reading = "the page: removes the element at a 0-based position; negative counts from the end (-1 is the last); a position beyond the array leaves it unchanged; NULL position gives NULL"
+cases = ["cases/translation/functions/array-remove-at.toml#t001_position"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-remove-at.t002"
+form = "sf.functions.array-remove-at"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "Positions -1 and -3."
+catches = "the negative position read as 0, or -1 removing the second-last"
+reading = "the page: removes the element at a 0-based position; negative counts from the end (-1 is the last); a position beyond the array leaves it unchanged; NULL position gives NULL"
+cases = ["cases/translation/functions/array-remove-at.toml#t002_negative_position"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-remove-at.t003"
+form = "sf.functions.array-remove-at"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "Positions 3 and -4, just beyond each end."
+catches = "an error, or the last or first element removed"
+reading = "the page: removes the element at a 0-based position; negative counts from the end (-1 is the last); a position beyond the array leaves it unchanged; NULL position gives NULL"
+cases = ["cases/translation/functions/array-remove-at.toml#t003_beyond"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-remove-at.t004"
+form = "sf.functions.array-remove-at"
+kind = "null_argument"
+target = "position"
+note = "A NULL position."
+catches = "NULL read as 0 (the first element removed)"
+reading = "the page: removes the element at a 0-based position; negative counts from the end (-1 is the last); a position beyond the array leaves it unchanged; NULL position gives NULL"
+cases = ["cases/translation/functions/array-remove-at.toml#t004_null_position"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-repeat.t001"
+form = "sf.functions.array-repeat"
+kind = "type_combination"
+target = "VARCHAR, INTEGER"
+note = "Text, a number and an array repeated three times."
+catches = "text repeated as one concatenated string, or the array flattened"
+reading = "the page: an ARRAY of count copies of element (converted to VARIANT); count 0 or negative gives an empty array; NULL count gives NULL; a NULL element gives count NULL elements"
+cases = ["cases/translation/functions/array-repeat.toml#t001_element_types"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-repeat.t002"
+form = "sf.functions.array-repeat"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "Counts 0 and -2."
+catches = "an error or NULL instead of an empty array"
+reading = "the page: an ARRAY of count copies of element (converted to VARIANT); count 0 or negative gives an empty array; NULL count gives NULL; a NULL element gives count NULL elements"
+cases = ["cases/translation/functions/array-repeat.toml#t002_non_positive_count"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-repeat.t003"
+form = "sf.functions.array-repeat"
+kind = "null_argument"
+target = "count"
+note = "A NULL count, and a NULL element repeated twice."
+catches = "NULL returned for the NULL element, or the count read as 0"
+reading = "the page: an ARRAY of count copies of element (converted to VARIANT); count 0 or negative gives an empty array; NULL count gives NULL; a NULL element gives count NULL elements"
+cases = ["cases/translation/functions/array-repeat.toml#t003_null_count"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-reverse.t001"
+form = "sf.functions.array-reverse"
+kind = "type_combination"
+target = "ARRAY"
+note = "Elements of different types, including a nested array."
+catches = "the nested array reversed too"
+reading = "the page: the elements in reverse order; NULL gives NULL"
+cases = ["cases/translation/functions/array-reverse.toml#t001_mixed"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-reverse.t002"
+form = "sf.functions.array-reverse"
+kind = "null_argument"
+target = "array"
+note = "A NULL argument."
+catches = "an empty array returned"
+reading = "the page: the elements in reverse order; NULL gives NULL"
+cases = ["cases/translation/functions/array-reverse.toml#t002_null_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-size.t001"
+form = "sf.functions.array-size"
+kind = "type_combination"
+target = "ARRAY"
+note = "A nested array counts as one element; an empty array."
+catches = "nested elements counted, or NULL for []"
+reading = "the page: the size of the array, the largest index + 1, so a sparse array counts its undefined elements; NULL gives NULL"
+cases = ["cases/translation/functions/array-size.toml#t001_nested"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-size.t002"
+form = "sf.functions.array-size"
+kind = "type_combination"
+target = "ARRAY"
+note = "An array padded with undefined elements by ARRAY_INSERT past its end."
+catches = "only the defined elements counted (2 instead of 4)"
+reading = "the page: the size of the array, the largest index + 1, so a sparse array counts its undefined elements; NULL gives NULL"
+cases = ["cases/translation/functions/array-size.toml#t002_sparse"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-size.t003"
+form = "sf.functions.array-size"
+kind = "type_combination"
+target = "VARIANT"
+note = "A VARIANT holding an object and one holding a number; the page says a VARIANT that does not hold an array returns NULL."
+catches = "the object's key count returned, or 1 for the number"
+reading = "the page: the size of the array, the largest index + 1, so a sparse array counts its undefined elements; NULL gives NULL"
+cases = ["cases/translation/functions/array-size.toml#t003_object"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-size.t004"
+form = "sf.functions.array-size"
+kind = "null_argument"
+target = "array"
+note = "A NULL argument."
+catches = "0 returned"
+reading = "the page: the size of the array, the largest index + 1, so a sparse array counts its undefined elements; NULL gives NULL"
+cases = ["cases/translation/functions/array-size.toml#t004_null_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-slice.t001"
+form = "sf.functions.array-slice"
+kind = "type_combination"
+target = "ARRAY, INTEGER, INTEGER"
+note = "A slice inside the array."
+catches = "the to position included, or 1-based positions"
+reading = "the page: elements from position from (0-based) up to but not including to; negative positions count from the end; both beyond one end gives an empty array; any NULL gives NULL"
+cases = ["cases/translation/functions/array-slice.toml#t001_half_open"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-slice.t002"
+form = "sf.functions.array-slice"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "Negative positions counted from the end."
+catches = "negative positions read as 0"
+reading = "the page: elements from position from (0-based) up to but not including to; negative positions count from the end; both beyond one end gives an empty array; any NULL gives NULL"
+cases = ["cases/translation/functions/array-slice.toml#t002_negative_positions"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-slice.t003"
+form = "sf.functions.array-slice"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "Both positions beyond the upper end, both beyond the lower end, and from after to."
+catches = "an error, or the positions clamped to give the last or first element"
+reading = "the page: elements from position from (0-based) up to but not including to; negative positions count from the end; both beyond one end gives an empty array; any NULL gives NULL"
+cases = ["cases/translation/functions/array-slice.toml#t003_beyond_ends"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-slice.t004"
+form = "sf.functions.array-slice"
+kind = "null_argument"
+target = "from"
+note = "NULL in each argument."
+catches = "a NULL position read as 0 or as the end"
+reading = "the page: elements from position from (0-based) up to but not including to; negative positions count from the end; both beyond one end gives an empty array; any NULL gives NULL"
+cases = ["cases/translation/functions/array-slice.toml#t004_null_from"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-sort.t001"
+form = "sf.functions.array-sort"
+kind = "type_combination"
+target = "ARRAY"
+note = "Ascending with the default NULL placement (last)."
+catches = "SQL NULL elements first, or the input order kept"
+reading = "the page: elements sorted ascending by default; SQL NULL elements last when ascending and first when descending unless nulls_first says otherwise; nulls_first does not move JSON null; any NULL argument gives NULL"
+cases = ["cases/translation/functions/array-sort.toml#t001_defaults"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-sort.t002"
+form = "sf.functions.array-sort"
+kind = "type_combination"
+target = "ARRAY, BOOLEAN"
+note = "Descending, where the default puts SQL NULL elements first."
+catches = "SQL NULL elements left last when descending"
+reading = "the page: elements sorted ascending by default; SQL NULL elements last when ascending and first when descending unless nulls_first says otherwise; nulls_first does not move JSON null; any NULL argument gives NULL"
+cases = ["cases/translation/functions/array-sort.toml#t002_descending"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-sort.t003"
+form = "sf.functions.array-sort"
+kind = "type_combination"
+target = "ARRAY, BOOLEAN, BOOLEAN"
+note = "Ascending with nulls_first TRUE, and descending with FALSE."
+catches = "nulls_first ignored"
+reading = "the page: elements sorted ascending by default; SQL NULL elements last when ascending and first when descending unless nulls_first says otherwise; nulls_first does not move JSON null; any NULL argument gives NULL"
+cases = ["cases/translation/functions/array-sort.toml#t003_nulls_first"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-sort.t004"
+form = "sf.functions.array-sort"
+kind = "type_combination"
+target = "ARRAY, BOOLEAN, BOOLEAN"
+note = "A JSON null sorted with nulls_first TRUE and FALSE; the page says the flag only moves SQL NULL elements."
+catches = "the JSON null moved by nulls_first (the two results differ)"
+reading = "the page: elements sorted ascending by default; SQL NULL elements last when ascending and first when descending unless nulls_first says otherwise; nulls_first does not move JSON null; any NULL argument gives NULL"
+cases = ["cases/translation/functions/array-sort.toml#t004_json_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-sort.t005"
+form = "sf.functions.array-sort"
+kind = "null_argument"
+target = "array"
+note = "A NULL array and a NULL sort_ascending."
+catches = "the default order used for a NULL flag"
+reading = "the page: elements sorted ascending by default; SQL NULL elements last when ascending and first when descending unless nulls_first says otherwise; nulls_first does not move JSON null; any NULL argument gives NULL"
+cases = ["cases/translation/functions/array-sort.toml#t005_null_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-sum.t001"
+form = "sf.functions.array-sum"
+kind = "type_combination"
+target = "ARRAY(FLOAT)"
+note = "Element-wise sums per group."
+catches = "a scalar total of every element, or concatenation"
+reading = "the page: element-wise sums of structured ARRAY(FLOAT) values of equal length; a NULL array is ignored and a NULL element contributes nothing, so a position NULL in every array is NULL; different lengths are an error; an empty or all-NULL group gives NULL"
+cases = ["cases/translation/functions/array-sum.toml#t001_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-sum.t002"
+form = "sf.functions.array-sum"
+kind = "error_parity"
+target = "error"
+note = "Arrays of different lengths, which the page says is an error."
+catches = "padding the short array with 0 or NULL"
+reading = "the page: element-wise sums of structured ARRAY(FLOAT) values of equal length; a NULL array is ignored and a NULL element contributes nothing, so a position NULL in every array is NULL; different lengths are an error; an empty or all-NULL group gives NULL"
+cases = ["cases/translation/functions/array-sum.toml#t002_mismatched"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-sum.t003"
+form = "sf.functions.array-sum"
+kind = "null_argument"
+target = "array"
+note = "A NULL array beside arrays, and NULL elements: one position NULL in every array."
+catches = "NULL elements read as 0 (0 instead of NULL at the third position) or the NULL array making the result NULL"
+reading = "the page: element-wise sums of structured ARRAY(FLOAT) values of equal length; a NULL array is ignored and a NULL element contributes nothing, so a position NULL in every array is NULL; different lengths are an error; an empty or all-NULL group gives NULL"
+cases = ["cases/translation/functions/array-sum.toml#t003_null_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-sum.t004"
+form = "sf.functions.array-sum"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "an empty array instead of NULL"
+reading = "the page: element-wise sums of structured ARRAY(FLOAT) values of equal length; a NULL array is ignored and a NULL element contributes nothing, so a position NULL in every array is NULL; different lengths are an error; an empty or all-NULL group gives NULL"
+cases = ["cases/translation/functions/array-sum.toml#t004_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-to-string.t001"
+form = "sf.functions.array-to-string"
+kind = "type_combination"
+target = "ARRAY, VARCHAR"
+note = "Numbers, text, a boolean and a nested array joined with \", \"."
+catches = "text written with JSON quotes, or the nested array dropped"
+reading = "the page: the elements converted to text and joined by the separator; a NULL element becomes an empty string; a NULL argument gives NULL"
+cases = ["cases/translation/functions/array-to-string.toml#t001_mixed"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-to-string.t002"
+form = "sf.functions.array-to-string"
+kind = "type_combination"
+target = "ARRAY, VARCHAR"
+note = "A SQL NULL element between two values."
+catches = "the NULL element skipped (one separator) or written as \"null\""
+reading = "the page: the elements converted to text and joined by the separator; a NULL element becomes an empty string; a NULL argument gives NULL"
+cases = ["cases/translation/functions/array-to-string.toml#t002_sql_null_element"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-to-string.t003"
+form = "sf.functions.array-to-string"
+kind = "error_parity"
+target = "error"
+note = "A JSON null element. The page covers only SQL NULL elements; the recording shows Snowflake raising 100071 (failed to cast variant value to string) for this input."
+catches = "the JSON null converted to \"null\" or to an empty string, instead of the error"
+reading = "the page: the elements converted to text and joined by the separator; a NULL element becomes an empty string; a NULL argument gives NULL"
+cases = ["cases/translation/functions/array-to-string.toml#t003_json_null_element"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-to-string.t004"
+form = "sf.functions.array-to-string"
+kind = "type_combination"
+target = "ARRAY, VARCHAR"
+note = "An empty array."
+catches = "NULL instead of an empty string"
+reading = "the page: the elements converted to text and joined by the separator; a NULL element becomes an empty string; a NULL argument gives NULL"
+cases = ["cases/translation/functions/array-to-string.toml#t004_empty"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-to-string.t005"
+form = "sf.functions.array-to-string"
+kind = "null_argument"
+target = "array"
+note = "A NULL array and a NULL separator."
+catches = "the NULL separator read as an empty string"
+reading = "the page: the elements converted to text and joined by the separator; a NULL element becomes an empty string; a NULL argument gives NULL"
+cases = ["cases/translation/functions/array-to-string.toml#t005_null_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-union-agg.array-union-agg.t001"
+form = "sf.functions.array-union-agg.array-union-agg"
+kind = "type_combination"
+target = "ARRAY"
+note = "[1,1,2], [1,3] and [2,2]: 1 twice and 2 twice by the largest counts."
+catches = "a set union (each value once) or a concatenation (1 three times)"
+reading = "the page: an array of the values in the input arrays with multiset semantics, so a value occurs as often as its largest count in a single input array; NULL arrays and NULL elements are ignored; no particular order (sorted here); no explicit frames as a window function"
+cases = ["cases/translation/functions/array-union-agg.array-union-agg.toml#t001_multiset"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-union-agg.array-union-agg.t002"
+form = "sf.functions.array-union-agg.array-union-agg"
+kind = "type_combination"
+target = "ARRAY of VARCHAR"
+note = "Text elements differing in case."
+catches = "case-insensitive merging"
+reading = "the page: an array of the values in the input arrays with multiset semantics, so a value occurs as often as its largest count in a single input array; NULL arrays and NULL elements are ignored; no particular order (sorted here); no explicit frames as a window function"
+cases = ["cases/translation/functions/array-union-agg.array-union-agg.toml#t002_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-union-agg.array-union-agg.t003"
+form = "sf.functions.array-union-agg.array-union-agg"
+kind = "null_argument"
+target = "column"
+note = "A NULL array and a JSON null element ([1,null])."
+catches = "the NULL array making the result NULL, or the JSON null element treated the opposite way to Snowflake"
+reading = "the page: an array of the values in the input arrays with multiset semantics, so a value occurs as often as its largest count in a single input array; NULL arrays and NULL elements are ignored; no particular order (sorted here); no explicit frames as a window function"
+cases = ["cases/translation/functions/array-union-agg.array-union-agg.toml#t003_null_column"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-union-agg.array-union-agg.t004"
+form = "sf.functions.array-union-agg.array-union-agg"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "NULL instead of []"
+reading = "the page: an array of the values in the input arrays with multiset semantics, so a value occurs as often as its largest count in a single input array; NULL arrays and NULL elements are ignored; no particular order (sorted here); no explicit frames as a window function"
+cases = ["cases/translation/functions/array-union-agg.array-union-agg.toml#t004_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-union-agg.array-union-agg-over.t001"
+form = "sf.functions.array-union-agg.array-union-agg-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Per-partition multiset unions."
+catches = "a set union or the whole input"
+reading = "the page: an array of the values in the input arrays with multiset semantics, so a value occurs as often as its largest count in a single input array; NULL arrays and NULL elements are ignored; no particular order (sorted here); no explicit frames as a window function"
+cases = ["cases/translation/functions/array-union-agg.array-union-agg-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-union-agg.array-union-agg-over.t002"
+form = "sf.functions.array-union-agg.array-union-agg-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause."
+catches = "a running value"
+reading = "the page: an array of the values in the input arrays with multiset semantics, so a value occurs as often as its largest count in a single input array; NULL arrays and NULL elements are ignored; no particular order (sorted here); no explicit frames as a window function"
+cases = ["cases/translation/functions/array-union-agg.array-union-agg-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-union-agg.array-union-agg-over.t003"
+form = "sf.functions.array-union-agg.array-union-agg-over"
+kind = "null_argument"
+target = "column"
+note = "A partition whose only array is NULL."
+catches = "NULL instead of []"
+reading = "the page: an array of the values in the input arrays with multiset semantics, so a value occurs as often as its largest count in a single input array; NULL arrays and NULL elements are ignored; no particular order (sorted here); no explicit frames as a window function"
+cases = ["cases/translation/functions/array-union-agg.array-union-agg-over.toml#t003_null_column"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-union-agg.array-union-agg-over.t004"
+form = "sf.functions.array-union-agg.array-union-agg-over"
+kind = "null_argument"
+target = "expr"
+note = "NULL partition keys."
+catches = "NULL-keyed rows split or dropped"
+reading = "the page: an array of the values in the input arrays with multiset semantics, so a value occurs as often as its largest count in a single input array; NULL arrays and NULL elements are ignored; no particular order (sorted here); no explicit frames as a window function"
+cases = ["cases/translation/functions/array-union-agg.array-union-agg-over.toml#t004_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-unique-agg.array-unique-agg.t001"
+form = "sf.functions.array-unique-agg.array-unique-agg"
+kind = "type_combination"
+target = "NUMBER"
+note = "Repeated values per group."
+catches = "duplicates kept (ARRAY_AGG semantics)"
+reading = "the page: an array of the distinct non-NULL values in no particular order (sorted here with ARRAY_SORT); an empty or all-NULL input gives an empty array; no explicit frames as a window function"
+cases = ["cases/translation/functions/array-unique-agg.array-unique-agg.toml#t001_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-unique-agg.array-unique-agg.t002"
+form = "sf.functions.array-unique-agg.array-unique-agg"
+kind = "type_combination"
+target = "VARCHAR"
+note = "Values that differ only in case."
+catches = "case-insensitive uniqueness"
+reading = "the page: an array of the distinct non-NULL values in no particular order (sorted here with ARRAY_SORT); an empty or all-NULL input gives an empty array; no explicit frames as a window function"
+cases = ["cases/translation/functions/array-unique-agg.array-unique-agg.toml#t002_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-unique-agg.array-unique-agg.t003"
+form = "sf.functions.array-unique-agg.array-unique-agg"
+kind = "null_argument"
+target = "column"
+note = "NULL values, which are ignored, and a group of only NULL."
+catches = "NULL kept as an element, or the all-NULL group giving NULL instead of []"
+reading = "the page: an array of the distinct non-NULL values in no particular order (sorted here with ARRAY_SORT); an empty or all-NULL input gives an empty array; no explicit frames as a window function"
+cases = ["cases/translation/functions/array-unique-agg.array-unique-agg.toml#t003_null_column"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-unique-agg.array-unique-agg.t004"
+form = "sf.functions.array-unique-agg.array-unique-agg"
+kind = "type_combination"
+target = "no rows"
+note = "No rows; the page gives an empty array."
+catches = "NULL instead of []"
+reading = "the page: an array of the distinct non-NULL values in no particular order (sorted here with ARRAY_SORT); an empty or all-NULL input gives an empty array; no explicit frames as a window function"
+cases = ["cases/translation/functions/array-unique-agg.array-unique-agg.toml#t004_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-unique-agg.array-unique-agg-over.t001"
+form = "sf.functions.array-unique-agg.array-unique-agg-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Per-partition distinct values."
+catches = "the whole input, or duplicates kept"
+reading = "the page: an array of the distinct non-NULL values in no particular order (sorted here with ARRAY_SORT); an empty or all-NULL input gives an empty array; no explicit frames as a window function"
+cases = ["cases/translation/functions/array-unique-agg.array-unique-agg-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-unique-agg.array-unique-agg-over.t002"
+form = "sf.functions.array-unique-agg.array-unique-agg-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause."
+catches = "a running value"
+reading = "the page: an array of the distinct non-NULL values in no particular order (sorted here with ARRAY_SORT); an empty or all-NULL input gives an empty array; no explicit frames as a window function"
+cases = ["cases/translation/functions/array-unique-agg.array-unique-agg-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-unique-agg.array-unique-agg-over.t003"
+form = "sf.functions.array-unique-agg.array-unique-agg-over"
+kind = "null_argument"
+target = "column"
+note = "A partition of only NULL."
+catches = "NULL instead of []"
+reading = "the page: an array of the distinct non-NULL values in no particular order (sorted here with ARRAY_SORT); an empty or all-NULL input gives an empty array; no explicit frames as a window function"
+cases = ["cases/translation/functions/array-unique-agg.array-unique-agg-over.toml#t003_null_column"]
+required = true
+
+[[obligation]]
+id = "sf.functions.array-unique-agg.array-unique-agg-over.t004"
+form = "sf.functions.array-unique-agg.array-unique-agg-over"
+kind = "null_argument"
+target = "expr"
+note = "NULL partition keys."
+catches = "NULL-keyed rows split or dropped"
+reading = "the page: an array of the distinct non-NULL values in no particular order (sorted here with ARRAY_SORT); an empty or all-NULL input gives an empty array; no explicit frames as a window function"
+cases = ["cases/translation/functions/array-unique-agg.array-unique-agg-over.toml#t004_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.arrays-overlap.t001"
+form = "sf.functions.arrays-overlap"
+kind = "type_combination"
+target = "ARRAY, ARRAY"
+note = "Arrays sharing one element, and disjoint arrays."
+catches = "TRUE for the disjoint pair"
+reading = "the page: TRUE when the arrays share an element; objects must be identical"
+cases = ["cases/translation/functions/arrays-overlap.toml#t001_overlap"]
+required = true
+
+[[obligation]]
+id = "sf.functions.arrays-overlap.t002"
+form = "sf.functions.arrays-overlap"
+kind = "type_combination"
+target = "ARRAY, ARRAY"
+note = "An object against an identical one and against a superset."
+catches = "objects matched on a shared key"
+reading = "the page: TRUE when the arrays share an element; objects must be identical"
+cases = ["cases/translation/functions/arrays-overlap.toml#t002_objects"]
+required = true
+
+[[obligation]]
+id = "sf.functions.arrays-overlap.t003"
+form = "sf.functions.arrays-overlap"
+kind = "type_combination"
+target = "ARRAY, ARRAY"
+note = "The number 1 against the text \"1\"."
+catches = "values compared as text"
+reading = "the page: TRUE when the arrays share an element; objects must be identical"
+cases = ["cases/translation/functions/arrays-overlap.toml#t003_number_vs_text"]
+required = true
+
+[[obligation]]
+id = "sf.functions.arrays-overlap.t004"
+form = "sf.functions.arrays-overlap"
+kind = "null_argument"
+target = "array1"
+note = "A NULL array, and SQL NULL elements on both sides. The page calls the function NULL-safe, and its example with SQL NULL elements on both sides gives TRUE; it does not cover a NULL array, so the recording decides that row."
+catches = "a NULL array read as empty (FALSE), or SQL NULL elements not matched"
+reading = "the page: TRUE when the arrays share an element; objects must be identical"
+cases = ["cases/translation/functions/arrays-overlap.toml#t004_null_array1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.arrays-to-object.t001"
+form = "sf.functions.arrays-to-object"
+kind = "type_combination"
+target = "ARRAY, ARRAY"
+note = "Keys paired with values of mixed types."
+catches = "keys and values swapped, or pairs shifted"
+reading = "the page: an OBJECT from a key array of VARCHAR and a value array of equal length; a NULL key drops its pair; a NULL value keeps the key; a non-string key or unequal lengths is an error"
+cases = ["cases/translation/functions/arrays-to-object.toml#t001_pairs"]
+required = true
+
+[[obligation]]
+id = "sf.functions.arrays-to-object.t002"
+form = "sf.functions.arrays-to-object"
+kind = "null_argument"
+target = "key-array"
+note = "A NULL key, and a NULL value beside a non-NULL key."
+catches = "the NULL-keyed pair kept, or the key with a NULL value dropped"
+reading = "the page: an OBJECT from a key array of VARCHAR and a value array of equal length; a NULL key drops its pair; a NULL value keeps the key; a non-string key or unequal lengths is an error"
+cases = ["cases/translation/functions/arrays-to-object.toml#t002_null_key_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.arrays-to-object.t003"
+form = "sf.functions.arrays-to-object"
+kind = "error_parity"
+target = "error"
+note = "Arrays of different lengths, which the page says is error 215001."
+catches = "the extra key dropped or given NULL"
+reading = "the page: an OBJECT from a key array of VARCHAR and a value array of equal length; a NULL key drops its pair; a NULL value keeps the key; a non-string key or unequal lengths is an error"
+cases = ["cases/translation/functions/arrays-to-object.toml#t003_unequal_lengths"]
+required = true
+
+[[obligation]]
+id = "sf.functions.arrays-to-object.t004"
+form = "sf.functions.arrays-to-object"
+kind = "error_parity"
+target = "error"
+note = "A number as a key, which the page says is error 215002."
+catches = "the key converted to text"
+reading = "the page: an OBJECT from a key array of VARCHAR and a value array of equal length; a NULL key drops its pair; a NULL value keeps the key; a non-string key or unequal lengths is an error"
+cases = ["cases/translation/functions/arrays-to-object.toml#t004_non_string_key"]
+required = true
+
+[[obligation]]
+id = "sf.functions.arrays-zip.t001"
+form = "sf.functions.arrays-zip"
+kind = "type_combination"
+target = "ARRAY, ARRAY, ARRAY"
+note = "Three arrays of equal length."
+catches = "keys numbered from 0, or the arrays transposed"
+reading = "the page: an array of objects whose keys $1, $2 \u2026 hold the nth element of each input; as long as the longest input, shorter inputs padded with JSON null; a NULL element gives JSON null; a NULL array gives NULL"
+cases = ["cases/translation/functions/arrays-zip.toml#t001_three_arrays"]
+required = true
+
+[[obligation]]
+id = "sf.functions.arrays-zip.t002"
+form = "sf.functions.arrays-zip"
+kind = "type_combination"
+target = "ARRAY, ARRAY"
+note = "Arrays of different lengths."
+catches = "the result cut to the shorter array, or padded with SQL NULL"
+reading = "the page: an array of objects whose keys $1, $2 \u2026 hold the nth element of each input; as long as the longest input, shorter inputs padded with JSON null; a NULL element gives JSON null; a NULL array gives NULL"
+cases = ["cases/translation/functions/arrays-zip.toml#t002_lengths"]
+required = true
+
+[[obligation]]
+id = "sf.functions.arrays-zip.t003"
+form = "sf.functions.arrays-zip"
+kind = "type_combination"
+target = "ARRAY"
+note = "A single array."
+catches = "the array returned as it is"
+reading = "the page: an array of objects whose keys $1, $2 \u2026 hold the nth element of each input; as long as the longest input, shorter inputs padded with JSON null; a NULL element gives JSON null; a NULL array gives NULL"
+cases = ["cases/translation/functions/arrays-zip.toml#t003_one_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.arrays-zip.t004"
+form = "sf.functions.arrays-zip"
+kind = "null_argument"
+target = "array"
+note = "A NULL array, and a SQL NULL element."
+catches = "the NULL array read as empty, or the NULL element dropped"
+reading = "the page: an array of objects whose keys $1, $2 \u2026 hold the nth element of each input; as long as the longest input, shorter inputs padded with JSON null; a NULL element gives JSON null; a NULL array gives NULL"
+cases = ["cases/translation/functions/arrays-zip.toml#t004_null_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-array.t001"
+form = "sf.functions.as-array"
+kind = "type_combination"
+target = "VARIANT holding ARRAY"
+note = "Arrays, nested and empty."
+catches = "an empty array returned as NULL, or a nested array flattened"
+reading = "the page: the value as ARRAY when the VARIANT holds ARRAY; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-array.toml#t001_match"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-array.t002"
+form = "sf.functions.as-array"
+kind = "type_combination"
+target = "VARIANT holding another type"
+note = "An object, a string holding array text, and a number."
+catches = "the string parsed as JSON, or a scalar wrapped as a one-element array (TO_ARRAY)"
+reading = "the page: the value as ARRAY when the VARIANT holds ARRAY; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-array.toml#t002_mismatch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-array.t003"
+form = "sf.functions.as-array"
+kind = "null_argument"
+target = "variant-expr"
+note = "A SQL NULL and a JSON null; the page returns NULL for a NULL argument and for a type that does not match."
+catches = "the JSON null passed through as a value, or an error"
+reading = "the page: the value as ARRAY when the VARIANT holds ARRAY; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-array.toml#t003_null_variant_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-binary.t001"
+form = "sf.functions.as-binary"
+kind = "type_combination"
+target = "VARIANT holding BINARY"
+note = "A BINARY value cast to VARIANT."
+catches = "the bytes returned as hexadecimal text"
+reading = "the page: the value as BINARY when the VARIANT holds BINARY; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-binary.toml#t001_match"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-binary.t002"
+form = "sf.functions.as-binary"
+kind = "type_combination"
+target = "VARIANT holding another type"
+note = "A string of hexadecimal digits and a number."
+catches = "the hexadecimal text decoded to bytes"
+reading = "the page: the value as BINARY when the VARIANT holds BINARY; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-binary.toml#t002_mismatch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-binary.t003"
+form = "sf.functions.as-binary"
+kind = "null_argument"
+target = "variant-expr"
+note = "A SQL NULL and a JSON null; the page returns NULL for a NULL argument and for a type that does not match."
+catches = "the JSON null passed through as a value, or an error"
+reading = "the page: the value as BINARY when the VARIANT holds BINARY; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-binary.toml#t003_null_variant_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-boolean.t001"
+form = "sf.functions.as-boolean"
+kind = "type_combination"
+target = "VARIANT holding BOOLEAN"
+note = "JSON true and false."
+catches = "false read as NULL, or the values swapped"
+reading = "the page: the value as BOOLEAN when the VARIANT holds BOOLEAN; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-boolean.toml#t001_match"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-boolean.t002"
+form = "sf.functions.as-boolean"
+kind = "type_combination"
+target = "VARIANT holding another type"
+note = "The string \"true\", the number 1 and the number 0."
+catches = "text or numbers converted to BOOLEAN (TO_BOOLEAN)"
+reading = "the page: the value as BOOLEAN when the VARIANT holds BOOLEAN; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-boolean.toml#t002_mismatch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-boolean.t003"
+form = "sf.functions.as-boolean"
+kind = "null_argument"
+target = "variant-expr"
+note = "A SQL NULL and a JSON null; the page returns NULL for a NULL argument and for a type that does not match."
+catches = "the JSON null passed through as a value, or an error"
+reading = "the page: the value as BOOLEAN when the VARIANT holds BOOLEAN; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-boolean.toml#t003_null_variant_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-char-varchar.as-char.t001"
+form = "sf.functions.as-char-varchar.as-char"
+kind = "type_combination"
+target = "VARIANT holding VARCHAR"
+note = "Strings, one holding digits and one empty."
+catches = "the empty string returned as NULL, or the JSON quotes kept"
+reading = "the page: the value as VARCHAR when the VARIANT holds VARCHAR; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-char-varchar.as-char.toml#t001_match"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-char-varchar.as-char.t002"
+form = "sf.functions.as-char-varchar.as-char"
+kind = "type_combination"
+target = "VARIANT holding another type"
+note = "A number, a boolean and an object."
+catches = "scalars rendered as text (5 as \"5\"), or the object serialised"
+reading = "the page: the value as VARCHAR when the VARIANT holds VARCHAR; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-char-varchar.as-char.toml#t002_mismatch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-char-varchar.as-char.t003"
+form = "sf.functions.as-char-varchar.as-char"
+kind = "null_argument"
+target = "variant-expr"
+note = "A SQL NULL and a JSON null; the page returns NULL for a NULL argument and for a type that does not match."
+catches = "the JSON null passed through as a value, or an error"
+reading = "the page: the value as VARCHAR when the VARIANT holds VARCHAR; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-char-varchar.as-char.toml#t003_null_variant_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-char-varchar.as-varchar.t001"
+form = "sf.functions.as-char-varchar.as-varchar"
+kind = "type_combination"
+target = "VARIANT holding VARCHAR"
+note = "Strings, one holding digits and one empty."
+catches = "the empty string returned as NULL, or the JSON quotes kept"
+reading = "the page: the value as VARCHAR when the VARIANT holds VARCHAR; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-char-varchar.as-varchar.toml#t001_match"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-char-varchar.as-varchar.t002"
+form = "sf.functions.as-char-varchar.as-varchar"
+kind = "type_combination"
+target = "VARIANT holding another type"
+note = "A number, a boolean and an object."
+catches = "scalars rendered as text (5 as \"5\"), or the object serialised"
+reading = "the page: the value as VARCHAR when the VARIANT holds VARCHAR; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-char-varchar.as-varchar.toml#t002_mismatch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-char-varchar.as-varchar.t003"
+form = "sf.functions.as-char-varchar.as-varchar"
+kind = "null_argument"
+target = "variant-expr"
+note = "A SQL NULL and a JSON null; the page returns NULL for a NULL argument and for a type that does not match."
+catches = "the JSON null passed through as a value, or an error"
+reading = "the page: the value as VARCHAR when the VARIANT holds VARCHAR; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-char-varchar.as-varchar.toml#t003_null_variant_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-date.t001"
+form = "sf.functions.as-date"
+kind = "type_combination"
+target = "VARIANT holding DATE"
+note = "A DATE cast to VARIANT, on a leap day."
+catches = "the date shifted a day, or returned as text"
+reading = "the page: the value as DATE when the VARIANT holds DATE; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-date.toml#t001_match"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-date.t002"
+form = "sf.functions.as-date"
+kind = "type_combination"
+target = "VARIANT holding another type"
+note = "A string holding a date and a number."
+catches = "date text parsed (TO_DATE), or a number read as days since the epoch"
+reading = "the page: the value as DATE when the VARIANT holds DATE; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-date.toml#t002_mismatch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-date.t003"
+form = "sf.functions.as-date"
+kind = "type_combination"
+target = "VARIANT holding TIMESTAMP_NTZ"
+note = "A TIMESTAMP_NTZ cast to VARIANT; the page returns NULL when the type is not DATE."
+catches = "the timestamp truncated to its date"
+reading = "the page: the value as DATE when the VARIANT holds DATE; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-date.toml#t003_timestamp"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-date.t004"
+form = "sf.functions.as-date"
+kind = "null_argument"
+target = "variant-expr"
+note = "A SQL NULL and a JSON null; the page returns NULL for a NULL argument and for a type that does not match."
+catches = "the JSON null passed through as a value, or an error"
+reading = "the page: the value as DATE when the VARIANT holds DATE; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-date.toml#t004_null_variant_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-decimal-number.as-decimal.t001"
+form = "sf.functions.as-decimal-number.as-decimal"
+kind = "type_combination"
+target = "VARIANT holding NUMBER"
+note = "Decimals with the default precision and scale (38, 0), which the page says round."
+catches = "the fraction truncated (12 and -12) instead of rounded, or the scale of the input kept"
+reading = "the page: the value as NUMBER when the VARIANT holds NUMBER; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-decimal-number.as-decimal.toml#t001_match"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-decimal-number.as-decimal.t002"
+form = "sf.functions.as-decimal-number.as-decimal"
+kind = "type_combination"
+target = "VARIANT holding another type"
+note = "A string holding digits and an exponent number."
+catches = "text parsed as a number, or a floating-point value converted"
+reading = "the page: the value as NUMBER when the VARIANT holds NUMBER; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-decimal-number.as-decimal.toml#t002_mismatch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-decimal-number.as-decimal.t003"
+form = "sf.functions.as-decimal-number.as-decimal"
+kind = "type_combination"
+target = "VARIANT, precision, scale"
+note = "An explicit precision 10 and scale 2 on a value with three fractional digits."
+catches = "the third digit truncated (12.34) instead of rounded (12.35), or the arguments ignored"
+reading = "the page: the value as NUMBER when the VARIANT holds NUMBER; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-decimal-number.as-decimal.toml#t003_scale"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-decimal-number.as-decimal.t004"
+form = "sf.functions.as-decimal-number.as-decimal"
+kind = "error_parity"
+target = "error"
+note = "A value that needs more digits than the precision after rounding; the page says reducing scale can cause out-of-range errors."
+catches = "NULL returned, or the digits silently dropped"
+reading = "the page: the value as NUMBER when the VARIANT holds NUMBER; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-decimal-number.as-decimal.toml#t004_out_of_range"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-decimal-number.as-decimal.t005"
+form = "sf.functions.as-decimal-number.as-decimal"
+kind = "null_argument"
+target = "variant-expr"
+note = "A SQL NULL and a JSON null; the page returns NULL for a NULL argument and for a type that does not match."
+catches = "the JSON null passed through as a value, or an error"
+reading = "the page: the value as NUMBER when the VARIANT holds NUMBER; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-decimal-number.as-decimal.toml#t005_null_variant_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-decimal-number.as-number.t001"
+form = "sf.functions.as-decimal-number.as-number"
+kind = "type_combination"
+target = "VARIANT holding NUMBER"
+note = "Decimals with the default precision and scale (38, 0), which the page says round."
+catches = "the fraction truncated (12 and -12) instead of rounded, or the scale of the input kept"
+reading = "the page: the value as NUMBER when the VARIANT holds NUMBER; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-decimal-number.as-number.toml#t001_match"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-decimal-number.as-number.t002"
+form = "sf.functions.as-decimal-number.as-number"
+kind = "type_combination"
+target = "VARIANT holding another type"
+note = "A string holding digits and an exponent number."
+catches = "text parsed as a number, or a floating-point value converted"
+reading = "the page: the value as NUMBER when the VARIANT holds NUMBER; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-decimal-number.as-number.toml#t002_mismatch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-decimal-number.as-number.t003"
+form = "sf.functions.as-decimal-number.as-number"
+kind = "type_combination"
+target = "VARIANT, precision, scale"
+note = "An explicit precision 10 and scale 2 on a value with three fractional digits."
+catches = "the third digit truncated (12.34) instead of rounded (12.35), or the arguments ignored"
+reading = "the page: the value as NUMBER when the VARIANT holds NUMBER; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-decimal-number.as-number.toml#t003_scale"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-decimal-number.as-number.t004"
+form = "sf.functions.as-decimal-number.as-number"
+kind = "error_parity"
+target = "error"
+note = "A value that needs more digits than the precision after rounding; the page says reducing scale can cause out-of-range errors."
+catches = "NULL returned, or the digits silently dropped"
+reading = "the page: the value as NUMBER when the VARIANT holds NUMBER; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-decimal-number.as-number.toml#t004_out_of_range"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-decimal-number.as-number.t005"
+form = "sf.functions.as-decimal-number.as-number"
+kind = "null_argument"
+target = "variant-expr"
+note = "A SQL NULL and a JSON null; the page returns NULL for a NULL argument and for a type that does not match."
+catches = "the JSON null passed through as a value, or an error"
+reading = "the page: the value as NUMBER when the VARIANT holds NUMBER; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-decimal-number.as-number.toml#t005_null_variant_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-double-real.as-double.t001"
+form = "sf.functions.as-double-real.as-double"
+kind = "type_combination"
+target = "VARIANT holding a floating-point value"
+note = "Numbers written with an exponent, one negative and one large."
+catches = "the exponent misread, or the sign lost"
+reading = "the page: the value as a floating-point value when the VARIANT holds a floating-point value; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-double-real.as-double.toml#t001_match"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-double-real.as-double.t002"
+form = "sf.functions.as-double-real.as-double"
+kind = "type_combination"
+target = "VARIANT holding another type"
+note = "A string holding a number and a boolean."
+catches = "text parsed as a number, or true read as 1"
+reading = "the page: the value as a floating-point value when the VARIANT holds a floating-point value; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-double-real.as-double.toml#t002_mismatch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-double-real.as-double.t003"
+form = "sf.functions.as-double-real.as-double"
+kind = "type_combination"
+target = "VARIANT holding NUMBER"
+note = "Fixed-point and integer numbers; the page names only floating-point values and does not say whether these convert; the recording decides."
+catches = "an implementation that converts every number, or none, where Snowflake does the other"
+reading = "the page: the value as a floating-point value when the VARIANT holds a floating-point value; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-double-real.as-double.toml#t003_fixed_point"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-double-real.as-double.t004"
+form = "sf.functions.as-double-real.as-double"
+kind = "null_argument"
+target = "variant-expr"
+note = "A SQL NULL and a JSON null; the page returns NULL for a NULL argument and for a type that does not match."
+catches = "the JSON null passed through as a value, or an error"
+reading = "the page: the value as a floating-point value when the VARIANT holds a floating-point value; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-double-real.as-double.toml#t004_null_variant_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-double-real.as-real.t001"
+form = "sf.functions.as-double-real.as-real"
+kind = "type_combination"
+target = "VARIANT holding a floating-point value"
+note = "Numbers written with an exponent, one negative and one large."
+catches = "the exponent misread, or the sign lost"
+reading = "the page: the value as a floating-point value when the VARIANT holds a floating-point value; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-double-real.as-real.toml#t001_match"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-double-real.as-real.t002"
+form = "sf.functions.as-double-real.as-real"
+kind = "type_combination"
+target = "VARIANT holding another type"
+note = "A string holding a number and a boolean."
+catches = "text parsed as a number, or true read as 1"
+reading = "the page: the value as a floating-point value when the VARIANT holds a floating-point value; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-double-real.as-real.toml#t002_mismatch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-double-real.as-real.t003"
+form = "sf.functions.as-double-real.as-real"
+kind = "type_combination"
+target = "VARIANT holding NUMBER"
+note = "Fixed-point and integer numbers; the page names only floating-point values and does not say whether these convert; the recording decides."
+catches = "an implementation that converts every number, or none, where Snowflake does the other"
+reading = "the page: the value as a floating-point value when the VARIANT holds a floating-point value; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-double-real.as-real.toml#t003_fixed_point"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-double-real.as-real.t004"
+form = "sf.functions.as-double-real.as-real"
+kind = "null_argument"
+target = "variant-expr"
+note = "A SQL NULL and a JSON null; the page returns NULL for a NULL argument and for a type that does not match."
+catches = "the JSON null passed through as a value, or an error"
+reading = "the page: the value as a floating-point value when the VARIANT holds a floating-point value; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-double-real.as-real.toml#t004_null_variant_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-integer.t001"
+form = "sf.functions.as-integer"
+kind = "type_combination"
+target = "VARIANT holding INTEGER"
+note = "Integers, negative and beyond 32 bits."
+catches = "values wrapped to 32 bits, or the sign lost"
+reading = "the page: the value as INTEGER when the VARIANT holds INTEGER; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-integer.toml#t001_match"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-integer.t002"
+form = "sf.functions.as-integer"
+kind = "type_combination"
+target = "VARIANT holding another type"
+note = "A string holding digits and a boolean."
+catches = "text parsed as an integer, or true read as 1"
+reading = "the page: the value as INTEGER when the VARIANT holds INTEGER; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-integer.toml#t002_mismatch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-integer.t003"
+form = "sf.functions.as-integer"
+kind = "type_combination"
+target = "VARIANT holding NUMBER with a fraction"
+note = "Decimals with and without a fractional part; the page does not say whether they count as INTEGER; the recording decides."
+catches = "a fraction truncated or rounded where Snowflake returns NULL, or the reverse"
+reading = "the page: the value as INTEGER when the VARIANT holds INTEGER; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-integer.toml#t003_fraction"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-integer.t004"
+form = "sf.functions.as-integer"
+kind = "null_argument"
+target = "variant-expr"
+note = "A SQL NULL and a JSON null; the page returns NULL for a NULL argument and for a type that does not match."
+catches = "the JSON null passed through as a value, or an error"
+reading = "the page: the value as INTEGER when the VARIANT holds INTEGER; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-integer.toml#t004_null_variant_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-object.t001"
+form = "sf.functions.as-object"
+kind = "type_combination"
+target = "VARIANT holding OBJECT"
+note = "Objects, nested and empty."
+catches = "an empty object returned as NULL, or nested keys flattened"
+reading = "the page: the value as OBJECT when the VARIANT holds OBJECT; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-object.toml#t001_match"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-object.t002"
+form = "sf.functions.as-object"
+kind = "type_combination"
+target = "VARIANT holding another type"
+note = "An array of pairs, a string holding object text and a number."
+catches = "the string parsed as JSON, or the array read as key-value pairs"
+reading = "the page: the value as OBJECT when the VARIANT holds OBJECT; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-object.toml#t002_mismatch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-object.t003"
+form = "sf.functions.as-object"
+kind = "null_argument"
+target = "variant-expr"
+note = "A SQL NULL and a JSON null; the page returns NULL for a NULL argument and for a type that does not match."
+catches = "the JSON null passed through as a value, or an error"
+reading = "the page: the value as OBJECT when the VARIANT holds OBJECT; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-object.toml#t003_null_variant_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-time.t001"
+form = "sf.functions.as-time"
+kind = "type_combination"
+target = "VARIANT holding TIME"
+note = "A TIME with milliseconds cast to VARIANT."
+catches = "the fraction dropped, or a 12-hour clock"
+reading = "the page: the value as TIME when the VARIANT holds TIME; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-time.toml#t001_match"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-time.t002"
+form = "sf.functions.as-time"
+kind = "type_combination"
+target = "VARIANT holding another type"
+note = "A string holding a time and a number of seconds."
+catches = "time text parsed, or a number read as seconds since midnight"
+reading = "the page: the value as TIME when the VARIANT holds TIME; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-time.toml#t002_mismatch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-time.t003"
+form = "sf.functions.as-time"
+kind = "null_argument"
+target = "variant-expr"
+note = "A SQL NULL and a JSON null; the page returns NULL for a NULL argument and for a type that does not match."
+catches = "the JSON null passed through as a value, or an error"
+reading = "the page: the value as TIME when the VARIANT holds TIME; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-time.toml#t003_null_variant_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-timestamp.as-timestamp-ltz.t001"
+form = "sf.functions.as-timestamp.as-timestamp-ltz"
+kind = "type_combination"
+target = "VARIANT holding TIMESTAMP_LTZ"
+note = "A TIMESTAMP_LTZ with milliseconds cast to VARIANT."
+catches = "the fraction dropped, or the offset or zone changed"
+reading = "the page: the value as TIMESTAMP_LTZ when the VARIANT holds TIMESTAMP_LTZ; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-timestamp.as-timestamp-ltz.toml#t001_match"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-timestamp.as-timestamp-ltz.t002"
+form = "sf.functions.as-timestamp.as-timestamp-ltz"
+kind = "type_combination"
+target = "VARIANT holding another type"
+note = "A string holding a timestamp and a DATE-like string."
+catches = "timestamp text parsed (TO_TIMESTAMP)"
+reading = "the page: the value as TIMESTAMP_LTZ when the VARIANT holds TIMESTAMP_LTZ; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-timestamp.as-timestamp-ltz.toml#t002_mismatch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-timestamp.as-timestamp-ltz.t003"
+form = "sf.functions.as-timestamp.as-timestamp-ltz"
+kind = "type_combination"
+target = "VARIANT holding another timestamp type"
+note = "The other two timestamp types cast to VARIANT. The page says a timestamp type returns the same timestamp type, without saying whether that holds when it differs from LTZ; the recording decides."
+catches = "the other type converted to this one, or NULL where Snowflake returns the value, or the reverse"
+reading = "the page: the value as TIMESTAMP_LTZ when the VARIANT holds TIMESTAMP_LTZ; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-timestamp.as-timestamp-ltz.toml#t003_other_timestamp"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-timestamp.as-timestamp-ltz.t004"
+form = "sf.functions.as-timestamp.as-timestamp-ltz"
+kind = "null_argument"
+target = "variant-expr"
+note = "A SQL NULL and a JSON null; the page returns NULL for a NULL argument and for a type that does not match."
+catches = "the JSON null passed through as a value, or an error"
+reading = "the page: the value as TIMESTAMP_LTZ when the VARIANT holds TIMESTAMP_LTZ; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-timestamp.as-timestamp-ltz.toml#t004_null_variant_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-timestamp.as-timestamp-ntz.t001"
+form = "sf.functions.as-timestamp.as-timestamp-ntz"
+kind = "type_combination"
+target = "VARIANT holding TIMESTAMP_NTZ"
+note = "A TIMESTAMP_NTZ with milliseconds cast to VARIANT."
+catches = "the fraction dropped, or the offset or zone changed"
+reading = "the page: the value as TIMESTAMP_NTZ when the VARIANT holds TIMESTAMP_NTZ; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-timestamp.as-timestamp-ntz.toml#t001_match"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-timestamp.as-timestamp-ntz.t002"
+form = "sf.functions.as-timestamp.as-timestamp-ntz"
+kind = "type_combination"
+target = "VARIANT holding another type"
+note = "A string holding a timestamp and a DATE-like string."
+catches = "timestamp text parsed (TO_TIMESTAMP)"
+reading = "the page: the value as TIMESTAMP_NTZ when the VARIANT holds TIMESTAMP_NTZ; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-timestamp.as-timestamp-ntz.toml#t002_mismatch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-timestamp.as-timestamp-ntz.t003"
+form = "sf.functions.as-timestamp.as-timestamp-ntz"
+kind = "type_combination"
+target = "VARIANT holding another timestamp type"
+note = "The other two timestamp types cast to VARIANT. The page says a timestamp type returns the same timestamp type, without saying whether that holds when it differs from NTZ; the recording decides."
+catches = "the other type converted to this one, or NULL where Snowflake returns the value, or the reverse"
+reading = "the page: the value as TIMESTAMP_NTZ when the VARIANT holds TIMESTAMP_NTZ; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-timestamp.as-timestamp-ntz.toml#t003_other_timestamp"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-timestamp.as-timestamp-ntz.t004"
+form = "sf.functions.as-timestamp.as-timestamp-ntz"
+kind = "null_argument"
+target = "variant-expr"
+note = "A SQL NULL and a JSON null; the page returns NULL for a NULL argument and for a type that does not match."
+catches = "the JSON null passed through as a value, or an error"
+reading = "the page: the value as TIMESTAMP_NTZ when the VARIANT holds TIMESTAMP_NTZ; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-timestamp.as-timestamp-ntz.toml#t004_null_variant_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-timestamp.as-timestamp-tz.t001"
+form = "sf.functions.as-timestamp.as-timestamp-tz"
+kind = "type_combination"
+target = "VARIANT holding TIMESTAMP_TZ"
+note = "A TIMESTAMP_TZ with milliseconds cast to VARIANT."
+catches = "the fraction dropped, or the offset or zone changed"
+reading = "the page: the value as TIMESTAMP_TZ when the VARIANT holds TIMESTAMP_TZ; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-timestamp.as-timestamp-tz.toml#t001_match"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-timestamp.as-timestamp-tz.t002"
+form = "sf.functions.as-timestamp.as-timestamp-tz"
+kind = "type_combination"
+target = "VARIANT holding another type"
+note = "A string holding a timestamp and a DATE-like string."
+catches = "timestamp text parsed (TO_TIMESTAMP)"
+reading = "the page: the value as TIMESTAMP_TZ when the VARIANT holds TIMESTAMP_TZ; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-timestamp.as-timestamp-tz.toml#t002_mismatch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-timestamp.as-timestamp-tz.t003"
+form = "sf.functions.as-timestamp.as-timestamp-tz"
+kind = "type_combination"
+target = "VARIANT holding another timestamp type"
+note = "The other two timestamp types cast to VARIANT. The page says a timestamp type returns the same timestamp type, without saying whether that holds when it differs from TZ; the recording decides."
+catches = "the other type converted to this one, or NULL where Snowflake returns the value, or the reverse"
+reading = "the page: the value as TIMESTAMP_TZ when the VARIANT holds TIMESTAMP_TZ; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-timestamp.as-timestamp-tz.toml#t003_other_timestamp"]
+required = true
+
+[[obligation]]
+id = "sf.functions.as-timestamp.as-timestamp-tz.t004"
+form = "sf.functions.as-timestamp.as-timestamp-tz"
+kind = "null_argument"
+target = "variant-expr"
+note = "A SQL NULL and a JSON null; the page returns NULL for a NULL argument and for a type that does not match."
+catches = "the JSON null passed through as a value, or an error"
+reading = "the page: the value as TIMESTAMP_TZ when the VARIANT holds TIMESTAMP_TZ; NULL when its type does not match; NULL for NULL"
+cases = ["cases/translation/functions/as-timestamp.as-timestamp-tz.toml#t004_null_variant_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.ascii.t001"
+form = "sf.functions.ascii"
+kind = "type_combination"
+target = "VARCHAR"
+note = "ASCII of strings whose first and last characters differ."
+catches = "the code of the last character, or the string length, instead of the first character's code, or the first UTF-8 byte (195 for \u00e9) instead of the code point (records what Snowflake returns)"
+reading = "the page: is a string; returns the integer code of its first character; \"The value 0 is returned for ... The string is empty\""
+cases = ["cases/translation/functions/ascii.toml#t001_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.ascii.t002"
+form = "sf.functions.ascii"
+kind = "null_argument"
+target = "input"
+note = "NULL input next to a set one."
+catches = "NULL read as an empty string (0 instead of NULL)"
+reading = "the page: is a string; returns the integer code of its first character; \"The value 0 is returned for ... The string is empty\""
+cases = ["cases/translation/functions/ascii.toml#t002_null_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.ascii.t003"
+form = "sf.functions.ascii"
+kind = "boundary"
+subkind = "empty_string"
+target = "empty_string"
+note = "The empty string, which the page says gives 0."
+catches = "NULL for an empty string instead of 0"
+reading = "the page: is a string; returns the integer code of its first character; \"The value 0 is returned for ... The string is empty\""
+cases = ["cases/translation/functions/ascii.toml#t003_empty"]
+required = true
+
+[[obligation]]
+id = "sf.functions.asin.t001"
+form = "sf.functions.asin"
+kind = "type_combination"
+target = "FLOAT"
+note = "ASIN of FLOAT values."
+catches = "ACOS computed instead, or degrees returned"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/asin.toml#t001_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.asin.t002"
+form = "sf.functions.asin"
+kind = "type_combination"
+target = "NUMBER(12,4)"
+note = "ASIN of NUMBER(12,4), converted to FLOAT."
+catches = "ACOS computed instead, or degrees returned; or the result left fixed-point"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/asin.toml#t002_fixed"]
+required = true
+
+[[obligation]]
+id = "sf.functions.asin.t003"
+form = "sf.functions.asin"
+kind = "null_argument"
+target = "x"
+note = "ASIN of NULL."
+catches = "NULL turned into 0 or NaN"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/asin.toml#t003_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.asin.t004"
+form = "sf.functions.asin"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "Odd symmetry at negatives."
+catches = "absolute value taken"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/asin.toml#t004_odd"]
+required = true
+
+[[obligation]]
+id = "sf.functions.asin.t005"
+form = "sf.functions.asin"
+kind = "error_parity"
+target = "error"
+note = "ASIN at or outside its domain: Snowflake raises error 100044 on every row (captured at e8691b63)."
+catches = "NaN, NULL or a value where Snowflake raises an error"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/asin.toml#t005_outside"]
+required = true
+
+[[obligation]]
+id = "sf.functions.asinh.t001"
+form = "sf.functions.asinh"
+kind = "type_combination"
+target = "FLOAT"
+note = "ASINH of FLOAT values."
+catches = "ACOSH computed"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/asinh.toml#t001_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.asinh.t002"
+form = "sf.functions.asinh"
+kind = "type_combination"
+target = "NUMBER(12,4)"
+note = "ASINH of NUMBER(12,4), converted to FLOAT."
+catches = "ACOSH computed; or the result left fixed-point"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/asinh.toml#t002_fixed"]
+required = true
+
+[[obligation]]
+id = "sf.functions.asinh.t003"
+form = "sf.functions.asinh"
+kind = "null_argument"
+target = "x"
+note = "ASINH of NULL."
+catches = "NULL turned into 0 or NaN"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/asinh.toml#t003_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.asinh.t004"
+form = "sf.functions.asinh"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "Large arguments."
+catches = "precision loss in the log formula"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/asinh.toml#t004_large"]
+required = true
+
+[[obligation]]
+id = "sf.functions.atan.t001"
+form = "sf.functions.atan"
+kind = "type_combination"
+target = "FLOAT"
+note = "ATAN of FLOAT values."
+catches = "ATAN2 with a swapped argument, or degrees returned"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/atan.toml#t001_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.atan.t002"
+form = "sf.functions.atan"
+kind = "type_combination"
+target = "NUMBER(12,4)"
+note = "ATAN of NUMBER(12,4), converted to FLOAT."
+catches = "ATAN2 with a swapped argument, or degrees returned; or the result left fixed-point"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/atan.toml#t002_fixed"]
+required = true
+
+[[obligation]]
+id = "sf.functions.atan.t003"
+form = "sf.functions.atan"
+kind = "null_argument"
+target = "x"
+note = "ATAN of NULL."
+catches = "NULL turned into 0 or NaN"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/atan.toml#t003_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.atan.t004"
+form = "sf.functions.atan"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "Very large arguments approach pi/2."
+catches = "overflow handling"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/atan.toml#t004_huge"]
+required = true
+
+[[obligation]]
+id = "sf.functions.atan2.t001"
+form = "sf.functions.atan2"
+kind = "type_combination"
+target = "FLOAT"
+note = "ATAN2 in all four quadrants."
+catches = "arguments swapped (ATAN2(y, x) vs ATAN2(x, y)) or ATAN(y/x) losing the quadrant"
+reading = "the page: y and x \"must be FLOAT\"; \"If the data type of an argument is a numeric data type other than DOUBLE, then the value is converted to DOUBLE. If ... string, the value is converted to DOUBLE if possible. If ... any other data type, the function returns an error.\""
+cases = ["cases/translation/functions/atan2.toml#t001_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.atan2.t002"
+form = "sf.functions.atan2"
+kind = "type_combination"
+target = "NUMBER(12,4)"
+note = "ATAN2 of NUMBER(12,4), converted to DOUBLE."
+catches = "the result left fixed-point"
+reading = "the page: y and x \"must be FLOAT\"; \"If the data type of an argument is a numeric data type other than DOUBLE, then the value is converted to DOUBLE. If ... string, the value is converted to DOUBLE if possible. If ... any other data type, the function returns an error.\""
+cases = ["cases/translation/functions/atan2.toml#t002_fixed"]
+required = true
+
+[[obligation]]
+id = "sf.functions.atan2.t003"
+form = "sf.functions.atan2"
+kind = "type_combination"
+target = "VARCHAR"
+note = "ATAN2 of numeric strings, converted to DOUBLE."
+catches = "the strings rejected"
+reading = "the page: y and x \"must be FLOAT\"; \"If the data type of an argument is a numeric data type other than DOUBLE, then the value is converted to DOUBLE. If ... string, the value is converted to DOUBLE if possible. If ... any other data type, the function returns an error.\""
+cases = ["cases/translation/functions/atan2.toml#t003_text"]
+required = true
+
+[[obligation]]
+id = "sf.functions.atan2.t004"
+form = "sf.functions.atan2"
+kind = "null_argument"
+target = "y"
+note = "NULL y."
+catches = "NULL turned into 0 (giving 0 or pi)"
+reading = "the page: y and x \"must be FLOAT\"; \"If the data type of an argument is a numeric data type other than DOUBLE, then the value is converted to DOUBLE. If ... string, the value is converted to DOUBLE if possible. If ... any other data type, the function returns an error.\""
+cases = ["cases/translation/functions/atan2.toml#t004_null_y"]
+required = true
+
+[[obligation]]
+id = "sf.functions.atan2.t005"
+form = "sf.functions.atan2"
+kind = "null_argument"
+target = "x"
+note = "NULL x."
+catches = "NULL turned into 0 (giving pi/2)"
+reading = "the page: y and x \"must be FLOAT\"; \"If the data type of an argument is a numeric data type other than DOUBLE, then the value is converted to DOUBLE. If ... string, the value is converted to DOUBLE if possible. If ... any other data type, the function returns an error.\""
+cases = ["cases/translation/functions/atan2.toml#t005_null_x"]
+required = true
+
+[[obligation]]
+id = "sf.functions.atan2.t006"
+form = "sf.functions.atan2"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "Zeros on the negative x axis; Snowflake gives +pi for both signs of zero y (captured at e8691b63)."
+catches = "IEEE signed-zero atan2 (-pi for the -0 row) where Snowflake returns +pi"
+reading = "the page: y and x \"must be FLOAT\"; \"If the data type of an argument is a numeric data type other than DOUBLE, then the value is converted to DOUBLE. If ... string, the value is converted to DOUBLE if possible. If ... any other data type, the function returns an error.\""
+cases = ["cases/translation/functions/atan2.toml#t006_zeros"]
+required = true
+
+[[obligation]]
+id = "sf.functions.atan2.t007"
+form = "sf.functions.atan2"
+kind = "error_parity"
+target = "error"
+note = "A string that is not a number is an error."
+catches = "the string read as 0"
+reading = "the page: y and x \"must be FLOAT\"; \"If the data type of an argument is a numeric data type other than DOUBLE, then the value is converted to DOUBLE. If ... string, the value is converted to DOUBLE if possible. If ... any other data type, the function returns an error.\""
+cases = ["cases/translation/functions/atan2.toml#t007_bad_text"]
+required = true
+
+[[obligation]]
+id = "sf.functions.atan2.t008"
+form = "sf.functions.atan2"
+kind = "error_parity"
+target = "error"
+note = "A BOOLEAN argument is an error (\"any other data type\"). Snowflake rejects it at compile time."
+catches = "TRUE read as 1"
+reading = "the page: y and x \"must be FLOAT\"; \"If the data type of an argument is a numeric data type other than DOUBLE, then the value is converted to DOUBLE. If ... string, the value is converted to DOUBLE if possible. If ... any other data type, the function returns an error.\""
+cases = ["cases/translation/functions/atan2.toml#t008_boolean"]
+required = true
+
+[[obligation]]
+id = "sf.functions.atanh.t001"
+form = "sf.functions.atanh"
+kind = "type_combination"
+target = "FLOAT"
+note = "ATANH of FLOAT values."
+catches = "TANH computed, or the odd symmetry lost"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/atanh.toml#t001_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.atanh.t002"
+form = "sf.functions.atanh"
+kind = "type_combination"
+target = "NUMBER(12,4)"
+note = "ATANH of NUMBER(12,4), converted to FLOAT."
+catches = "TANH computed, or the odd symmetry lost; or the result left fixed-point"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/atanh.toml#t002_fixed"]
+required = true
+
+[[obligation]]
+id = "sf.functions.atanh.t003"
+form = "sf.functions.atanh"
+kind = "null_argument"
+target = "x"
+note = "ATANH of NULL."
+catches = "NULL turned into 0 or NaN"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/atanh.toml#t003_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.atanh.t004"
+form = "sf.functions.atanh"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "Close to the domain edges."
+catches = "a naive 0.5*LN((1+x)/(1-x)): 0 instead of 1e-17 near zero, or precision lost near the asymptote"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/atanh.toml#t004_near_one"]
+required = true
+
+[[obligation]]
+id = "sf.functions.atanh.t005"
+form = "sf.functions.atanh"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "ATANH at or outside its domain: records what Snowflake returns there (NaN, Infinity, NULL or an error); re-kinded to error_parity after capture if Snowflake raises an error."
+catches = "NaN, Infinity, NULL or an error differing from Snowflake"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/atanh.toml#t005_edge"]
+required = true
+
+[[obligation]]
+id = "sf.functions.atanh.t006"
+form = "sf.functions.atanh"
+kind = "error_parity"
+target = "error"
+note = "ATANH at or outside its domain: Snowflake raises error 100044 on every row (captured at e8691b63)."
+catches = "NaN, NULL or a value where Snowflake raises an error"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/atanh.toml#t006_outside"]
+required = true
+
+[[obligation]]
+id = "sf.functions.avg.avg-over.t001"
+form = "sf.functions.avg.avg-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Partitions of different sizes in mixed input order."
+catches = "the whole input aggregated instead of the partition, or a running value"
+reading = "the page: the average of the non-NULL values; VARCHAR input is cast to FLOAT; an empty or all-NULL input gives NULL; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW; DISTINCT cannot be used with ORDER BY"
+cases = ["cases/translation/functions/avg.avg-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.avg.avg-over.t002"
+form = "sf.functions.avg.avg-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause."
+catches = "a running (ordered) value instead of the whole input"
+reading = "the page: the average of the non-NULL values; VARCHAR input is cast to FLOAT; an empty or all-NULL input gives NULL; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW; DISTINCT cannot be used with ORDER BY"
+cases = ["cases/translation/functions/avg.avg-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.avg.avg-over.t003"
+form = "sf.functions.avg.avg-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY ORDER BY)"
+note = "ORDER BY with tied keys (two rows with k = 2) and no frame: with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW."
+catches = "a ROWS frame (the tied rows get different values) or the whole partition"
+reading = "the page: the average of the non-NULL values; VARCHAR input is cast to FLOAT; an empty or all-NULL input gives NULL; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW; DISTINCT cannot be used with ORDER BY"
+cases = ["cases/translation/functions/avg.avg-over.toml#t003_order_ties"]
+required = true
+
+[[obligation]]
+id = "sf.functions.avg.avg-over.t004"
+form = "sf.functions.avg.avg-over"
+kind = "type_combination"
+target = "ORDER BY DESC"
+note = "DESC ordering with a running ROWS frame."
+catches = "DESC ignored (the running value starts from the smallest key)"
+reading = "the page: the average of the non-NULL values; VARCHAR input is cast to FLOAT; an empty or all-NULL input gives NULL; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW; DISTINCT cannot be used with ORDER BY"
+cases = ["cases/translation/functions/avg.avg-over.toml#t004_order_desc"]
+required = true
+
+[[obligation]]
+id = "sf.functions.avg.avg-over.t005"
+form = "sf.functions.avg.avg-over"
+kind = "type_combination"
+target = "ASC NULLS FIRST, ROWS frame"
+note = "A NULL ordering key placed first (not the ASC default) and a one-row-back sliding frame."
+catches = "NULLS FIRST ignored (the NULL-key row last), or the frame read as RANGE or reaching forward"
+reading = "the page: the average of the non-NULL values; VARCHAR input is cast to FLOAT; an empty or all-NULL input gives NULL; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW; DISTINCT cannot be used with ORDER BY"
+cases = ["cases/translation/functions/avg.avg-over.toml#t005_nulls_first_frame"]
+required = true
+
+[[obligation]]
+id = "sf.functions.avg.avg-over.t006"
+form = "sf.functions.avg.avg-over"
+kind = "null_argument"
+target = "expr1"
+note = "NULL values inside partitions, and a partition with only NULL."
+catches = "NULL read as 0 (a different count and value) or the NULL-only partition not NULL"
+reading = "the page: the average of the non-NULL values; VARCHAR input is cast to FLOAT; an empty or all-NULL input gives NULL; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW; DISTINCT cannot be used with ORDER BY"
+cases = ["cases/translation/functions/avg.avg-over.toml#t006_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.avg.avg-over.t007"
+form = "sf.functions.avg.avg-over"
+kind = "null_argument"
+target = "partition-key"
+note = "NULL partition keys, which form one partition."
+catches = "NULL-keyed rows each in their own partition or dropped"
+reading = "the page: the average of the non-NULL values; VARCHAR input is cast to FLOAT; an empty or all-NULL input gives NULL; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW; DISTINCT cannot be used with ORDER BY"
+cases = ["cases/translation/functions/avg.avg-over.toml#t007_null_partition_key"]
+required = true
+
+[[obligation]]
+id = "sf.functions.avg.avg-over.t008"
+form = "sf.functions.avg.avg-over"
+kind = "type_combination"
+target = "DISTINCT OVER (PARTITION BY)"
+note = "DISTINCT inside a partition with a duplicate."
+catches = "DISTINCT ignored"
+reading = "the page: the average of the non-NULL values; VARCHAR input is cast to FLOAT; an empty or all-NULL input gives NULL; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW; DISTINCT cannot be used with ORDER BY"
+cases = ["cases/translation/functions/avg.avg-over.toml#t008_distinct_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.avg.avg-over.t009"
+form = "sf.functions.avg.avg-over"
+kind = "error_parity"
+target = "error"
+note = "DISTINCT with ORDER BY in the OVER clause, which the page forbids."
+catches = "a running DISTINCT value returned instead of the compilation error"
+reading = "the page: the average of the non-NULL values; VARCHAR input is cast to FLOAT; an empty or all-NULL input gives NULL; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW; DISTINCT cannot be used with ORDER BY"
+cases = ["cases/translation/functions/avg.avg-over.toml#t009_distinct_order"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-decode-binary.t001"
+form = "sf.functions.base64-decode-binary"
+kind = "type_combination"
+target = "VARCHAR -> BINARY"
+note = "BASE64_DECODE_BINARY of standard Base64 with zero, one and two padding characters."
+catches = "a byte-order or sign error in the decoded bytes, or a dropped final group"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; returns BINARY"
+cases = ["cases/translation/functions/base64-decode-binary.toml#t001_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-decode-binary.t002"
+form = "sf.functions.base64-decode-binary"
+kind = "type_combination"
+target = "VARCHAR, alphabet -> BINARY"
+note = "A custom alphabet for indexes 62 and 63 and the padding character. The constant argument is a literal per call, as Snowflake requires."
+catches = "the alphabet argument ignored (an error or other bytes for -_ and ~ padding)"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; returns BINARY"
+cases = ["cases/translation/functions/base64-decode-binary.toml#t002_alphabet"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-decode-binary.t003"
+form = "sf.functions.base64-decode-binary"
+kind = "null_argument"
+target = "input"
+note = "NULL input next to a set one."
+catches = "NULL decoded as empty instead of NULL"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; returns BINARY"
+cases = ["cases/translation/functions/base64-decode-binary.toml#t003_null_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-decode-binary.t004"
+form = "sf.functions.base64-decode-binary"
+kind = "null_argument"
+target = "alphabet"
+note = "NULL alphabet next to a set one. The constant argument is a literal per call, as Snowflake requires."
+catches = "a NULL alphabet treated as the default (a value instead of NULL)"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; returns BINARY"
+cases = ["cases/translation/functions/base64-decode-binary.toml#t004_null_alphabet"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-decode-binary.t005"
+form = "sf.functions.base64-decode-binary"
+kind = "boundary"
+subkind = "empty_string"
+target = "empty_string"
+note = "The empty string decodes to an empty value."
+catches = "NULL or an error for empty input"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; returns BINARY"
+cases = ["cases/translation/functions/base64-decode-binary.toml#t005_empty"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-decode-binary.t006"
+form = "sf.functions.base64-decode-binary"
+kind = "error_parity"
+target = "error"
+note = "Text that is not Base64."
+catches = "NULL (the TRY_ behaviour) where Snowflake raises an error"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; returns BINARY"
+cases = ["cases/translation/functions/base64-decode-binary.toml#t006_invalid"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-decode-string.t001"
+form = "sf.functions.base64-decode-string"
+kind = "type_combination"
+target = "VARCHAR -> VARCHAR"
+note = "BASE64_DECODE_STRING of standard Base64 with zero, one and two padding characters."
+catches = "a byte-order or sign error in the decoded bytes, a dropped final group, or UTF-8 text read as Latin-1"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; returns VARCHAR"
+cases = ["cases/translation/functions/base64-decode-string.toml#t001_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-decode-string.t002"
+form = "sf.functions.base64-decode-string"
+kind = "type_combination"
+target = "VARCHAR, alphabet -> VARCHAR"
+note = "A custom alphabet for indexes 62 and 63 and the padding character. The constant argument is a literal per call, as Snowflake requires."
+catches = "the alphabet argument ignored (an error or other bytes for -_ and ~ padding)"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; returns VARCHAR"
+cases = ["cases/translation/functions/base64-decode-string.toml#t002_alphabet"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-decode-string.t003"
+form = "sf.functions.base64-decode-string"
+kind = "null_argument"
+target = "input"
+note = "NULL input next to a set one."
+catches = "NULL decoded as empty instead of NULL"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; returns VARCHAR"
+cases = ["cases/translation/functions/base64-decode-string.toml#t003_null_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-decode-string.t004"
+form = "sf.functions.base64-decode-string"
+kind = "null_argument"
+target = "alphabet"
+note = "NULL alphabet next to a set one. The constant argument is a literal per call, as Snowflake requires."
+catches = "a NULL alphabet treated as the default (a value instead of NULL)"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; returns VARCHAR"
+cases = ["cases/translation/functions/base64-decode-string.toml#t004_null_alphabet"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-decode-string.t005"
+form = "sf.functions.base64-decode-string"
+kind = "boundary"
+subkind = "empty_string"
+target = "empty_string"
+note = "The empty string decodes to an empty value."
+catches = "NULL or an error for empty input"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; returns VARCHAR"
+cases = ["cases/translation/functions/base64-decode-string.toml#t005_empty"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-decode-string.t006"
+form = "sf.functions.base64-decode-string"
+kind = "error_parity"
+target = "error"
+note = "Text that is not Base64."
+catches = "NULL (the TRY_ behaviour) where Snowflake raises an error"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; returns VARCHAR"
+cases = ["cases/translation/functions/base64-decode-string.toml#t006_invalid"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-encode.t001"
+form = "sf.functions.base64-encode"
+kind = "type_combination"
+target = "VARCHAR"
+note = "BASE64_ENCODE of UTF-8 text of lengths 1, 2 and 3 modulo 3."
+catches = "Latin-1 instead of UTF-8 bytes (\u00e9 as one byte), or missing padding"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; is a positive integer, 0 meaning no line breaks; returns a string"
+cases = ["cases/translation/functions/base64-encode.toml#t001_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-encode.t002"
+form = "sf.functions.base64-encode"
+kind = "type_combination"
+target = "BINARY"
+note = "BASE64_ENCODE of bytes that use indexes 62 and 63."
+catches = "the binary read as its hex text (encoding 'FBFF' as characters)"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; is a positive integer, 0 meaning no line breaks; returns a string"
+cases = ["cases/translation/functions/base64-encode.toml#t002_binary"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-encode.t003"
+form = "sf.functions.base64-encode"
+kind = "type_combination"
+target = "VARCHAR, max_line_length"
+note = "A line length shorter than the output. The constant argument is a literal per call, as Snowflake requires."
+catches = "max_line_length ignored (no line breaks) or applied to the input length"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; is a positive integer, 0 meaning no line breaks; returns a string"
+cases = ["cases/translation/functions/base64-encode.toml#t003_max_line_length"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-encode.t004"
+form = "sf.functions.base64-encode"
+kind = "type_combination"
+target = "BINARY, max_line_length, alphabet"
+note = "A custom alphabet with a custom padding character. The constant argument is a literal per call, as Snowflake requires."
+catches = "the alphabet argument ignored (+/ and = in the output)"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; is a positive integer, 0 meaning no line breaks; returns a string"
+cases = ["cases/translation/functions/base64-encode.toml#t004_alphabet"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-encode.t005"
+form = "sf.functions.base64-encode"
+kind = "null_argument"
+target = "input"
+note = "NULL input next to a set one."
+catches = "NULL encoded as an empty string"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; is a positive integer, 0 meaning no line breaks; returns a string"
+cases = ["cases/translation/functions/base64-encode.toml#t005_null_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-encode.t006"
+form = "sf.functions.base64-encode"
+kind = "null_argument"
+target = "max_line_length"
+note = "NULL line length next to a set one. The constant argument is a literal per call, as Snowflake requires."
+catches = "NULL read as 0 (no line breaks) instead of NULL"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; is a positive integer, 0 meaning no line breaks; returns a string"
+cases = ["cases/translation/functions/base64-encode.toml#t006_null_max_line_length"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-encode.t007"
+form = "sf.functions.base64-encode"
+kind = "null_argument"
+target = "alphabet"
+note = "NULL alphabet next to a set one. The constant argument is a literal per call, as Snowflake requires."
+catches = "NULL read as the default alphabet"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; is a positive integer, 0 meaning no line breaks; returns a string"
+cases = ["cases/translation/functions/base64-encode.toml#t007_null_alphabet"]
+required = true
+
+[[obligation]]
+id = "sf.functions.base64-encode.t008"
+form = "sf.functions.base64-encode"
+kind = "boundary"
+subkind = "empty_string"
+target = "empty_string"
+note = "The empty string encodes to an empty string."
+catches = "NULL or padding for empty input"
+reading = "the page: is a Base64 string (decode) or a string or binary (encode); sets indexes 62 and 63 and the padding character, positionally; is a positive integer, 0 meaning no line breaks; returns a string"
+cases = ["cases/translation/functions/base64-encode.toml#t008_empty"]
+required = true
+
+[[obligation]]
+id = "sf.functions.between.t001"
+form = "sf.functions.between"
+kind = "type_combination"
+target = "NUMBER BETWEEN NUMBER AND NUMBER"
+note = "Values on, inside and outside the bounds, and reversed bounds."
+catches = "exclusive bounds, or symmetric BETWEEN (TRUE for 5 BETWEEN 10 AND 1)"
+reading = "the page: expr BETWEEN lo AND hi is expr >= lo AND expr <= hi; DATE bounds against a TIMESTAMP are cast to midnight"
+cases = ["cases/translation/functions/between.toml#t001_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.between.t002"
+form = "sf.functions.between"
+kind = "type_combination"
+target = "VARCHAR"
+note = "Strings compared in binary order."
+catches = "case-insensitive or locale order (b BETWEEN A AND Z is FALSE)"
+reading = "the page: expr BETWEEN lo AND hi is expr >= lo AND expr <= hi; DATE bounds against a TIMESTAMP are cast to midnight"
+cases = ["cases/translation/functions/between.toml#t002_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.between.t003"
+form = "sf.functions.between"
+kind = "type_combination"
+target = "TIMESTAMP BETWEEN DATE AND DATE"
+note = "The page's case: DATE bounds cast to midnight."
+catches = "the upper DATE read as end of day (TRUE for noon)"
+reading = "the page: expr BETWEEN lo AND hi is expr >= lo AND expr <= hi; DATE bounds against a TIMESTAMP are cast to midnight"
+cases = ["cases/translation/functions/between.toml#t003_timestamp_date"]
+required = true
+
+[[obligation]]
+id = "sf.functions.between.t004"
+form = "sf.functions.between"
+kind = "type_combination"
+target = "NOT BETWEEN"
+note = "NOT BETWEEN on and off the bounds."
+catches = "NOT applied to one comparison only"
+reading = "the page: expr BETWEEN lo AND hi is expr >= lo AND expr <= hi; DATE bounds against a TIMESTAMP are cast to midnight"
+cases = ["cases/translation/functions/between.toml#t004_not"]
+required = true
+
+[[obligation]]
+id = "sf.functions.between.t005"
+form = "sf.functions.between"
+kind = "null_argument"
+target = "expr"
+note = "NULL value."
+catches = "FALSE instead of NULL"
+reading = "the page: expr BETWEEN lo AND hi is expr >= lo AND expr <= hi; DATE bounds against a TIMESTAMP are cast to midnight"
+cases = ["cases/translation/functions/between.toml#t005_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.between.t006"
+form = "sf.functions.between"
+kind = "null_argument"
+target = "lower_bound"
+note = "NULL lower bound where the upper bound already decides."
+catches = "NULL for 9 BETWEEN NULL AND 3 (which is FALSE: NULL AND FALSE), or TRUE"
+reading = "the page: expr BETWEEN lo AND hi is expr >= lo AND expr <= hi; DATE bounds against a TIMESTAMP are cast to midnight"
+cases = ["cases/translation/functions/between.toml#t006_null_lower_bound"]
+required = true
+
+[[obligation]]
+id = "sf.functions.between.t007"
+form = "sf.functions.between"
+kind = "null_argument"
+target = "upper_bound"
+note = "NULL upper bound where the lower bound already decides."
+catches = "NULL instead of FALSE for 0 BETWEEN 1 AND NULL"
+reading = "the page: expr BETWEEN lo AND hi is expr >= lo AND expr <= hi; DATE bounds against a TIMESTAMP are cast to midnight"
+cases = ["cases/translation/functions/between.toml#t007_null_upper_bound"]
+required = true
+
+[[obligation]]
+id = "sf.functions.between.t008"
+form = "sf.functions.between"
+kind = "type_combination"
+target = "VARCHAR BETWEEN NUMBER AND NUMBER"
+note = "A string compared with numeric bounds, which converts to a number."
+catches = "a text comparison ('9' and '10' both FALSE against 2 and 10)"
+reading = "the page: expr BETWEEN lo AND hi is expr >= lo AND expr <= hi; DATE bounds against a TIMESTAMP are cast to midnight"
+cases = ["cases/translation/functions/between.toml#t008_varchar_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bit-length.t001"
+form = "sf.functions.bit-length"
+kind = "type_combination"
+target = "VARCHAR"
+note = "BIT_LENGTH of multi-byte text."
+catches = "the character count instead of UTF-8 bits (5 characters, 6 bytes)"
+reading = "the page: is a string or binary; the length in bits"
+cases = ["cases/translation/functions/bit-length.toml#t001_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bit-length.t002"
+form = "sf.functions.bit-length"
+kind = "type_combination"
+target = "BINARY"
+note = "BIT_LENGTH of binary."
+catches = "the hex text length instead of the byte length"
+reading = "the page: is a string or binary; the length in bits"
+cases = ["cases/translation/functions/bit-length.toml#t002_binary"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bit-length.t003"
+form = "sf.functions.bit-length"
+kind = "null_argument"
+target = "string_or_binary"
+note = "NULL next to a set value."
+catches = "NULL read as 0"
+reading = "the page: is a string or binary; the length in bits"
+cases = ["cases/translation/functions/bit-length.toml#t003_null_string_or_binary"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bit-length.t004"
+form = "sf.functions.bit-length"
+kind = "boundary"
+subkind = "empty_string"
+target = "empty_string"
+note = "The empty string."
+catches = "NULL for an empty string"
+reading = "the page: is a string or binary; the length in bits"
+cases = ["cases/translation/functions/bit-length.toml#t004_empty"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitand.t001"
+form = "sf.functions.bitand"
+kind = "type_combination"
+target = "INTEGER, INTEGER"
+note = "Integers whose bits overlap in part, and a negative."
+catches = "a logical AND (1 instead of 8), or the sign bit lost"
+reading = "the page: the bitwise AND of two INTEGER or two BINARY values; other numbers are cast to INTEGER; a string is cast to INTEGER if possible (12.3 to 12) and otherwise treated as NULL; BINARY values of different lengths need padside LEFT or RIGHT; NULL gives NULL"
+cases = ["cases/translation/functions/bitand.toml#t001_integers"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitand.t002"
+form = "sf.functions.bitand"
+kind = "type_combination"
+target = "DECIMAL and VARCHAR"
+note = "A decimal and the string 12.3, which the page says cast to integers."
+catches = "the decimal truncated where Snowflake rounds, or the string rejected"
+reading = "the page: the bitwise AND of two INTEGER or two BINARY values; other numbers are cast to INTEGER; a string is cast to INTEGER if possible (12.3 to 12) and otherwise treated as NULL; BINARY values of different lengths need padside LEFT or RIGHT; NULL gives NULL"
+cases = ["cases/translation/functions/bitand.toml#t002_casts"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitand.t003"
+form = "sf.functions.bitand"
+kind = "type_combination"
+target = "BINARY, BINARY, padside"
+note = "BINARY values of different lengths padded on the left and on the right."
+catches = "both paddings giving the same result, or padside ignored"
+reading = "the page: the bitwise AND of two INTEGER or two BINARY values; other numbers are cast to INTEGER; a string is cast to INTEGER if possible (12.3 to 12) and otherwise treated as NULL; BINARY values of different lengths need padside LEFT or RIGHT; NULL gives NULL"
+cases = ["cases/translation/functions/bitand.toml#t003_binary_pad"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitand.t004"
+form = "sf.functions.bitand"
+kind = "error_parity"
+target = "error"
+note = "BINARY values of different lengths without padside, which the page says requires it."
+catches = "a default padding applied"
+reading = "the page: the bitwise AND of two INTEGER or two BINARY values; other numbers are cast to INTEGER; a string is cast to INTEGER if possible (12.3 to 12) and otherwise treated as NULL; BINARY values of different lengths need padside LEFT or RIGHT; NULL gives NULL"
+cases = ["cases/translation/functions/bitand.toml#t004_binary_unequal"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitand.t005"
+form = "sf.functions.bitand"
+kind = "error_parity"
+target = "error"
+note = "A NULL on either side next to a string that cannot be cast; Snowflake raised 100038 for the string, so it is not treated as NULL."
+catches = "the string read as NULL or 0"
+reading = "the page: the bitwise AND of two INTEGER or two BINARY values; other numbers are cast to INTEGER; a string is cast to INTEGER if possible (12.3 to 12) and otherwise treated as NULL; BINARY values of different lengths need padside LEFT or RIGHT; NULL gives NULL"
+cases = ["cases/translation/functions/bitand.toml#t005_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitand.t006"
+form = "sf.functions.bitand"
+kind = "null_argument"
+target = "expr1"
+note = "A NULL on either side alone; the BITAND page, which also covers BITOR and BITXOR, says either NULL input gives NULL."
+catches = "the NULL read as 0"
+reading = "the page: the bitwise AND of two INTEGER or two BINARY values; other numbers are cast to INTEGER; a string is cast to INTEGER if possible (12.3 to 12) and otherwise treated as NULL; BINARY values of different lengths need padside LEFT or RIGHT; NULL gives NULL"
+cases = ["cases/translation/functions/bitand.toml#t006_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitand-agg.bitand-agg.t001"
+form = "sf.functions.bitand-agg.bitand-agg"
+kind = "type_combination"
+target = "NUMBER"
+note = "12, 10 and 14 per group (1100, 1010, 1110)."
+catches = "another bitwise operator"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitand-agg.bitand-agg.toml#t001_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitand-agg.bitand-agg.t002"
+form = "sf.functions.bitand-agg.bitand-agg"
+kind = "type_combination"
+target = "negative NUMBER"
+note = "A negative value in two's complement."
+catches = "the sign dropped (abs value) or a NUMBER(38) treated as unsigned"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitand-agg.bitand-agg.toml#t002_negative"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitand-agg.bitand-agg.t003"
+form = "sf.functions.bitand-agg.bitand-agg"
+kind = "type_combination"
+target = "NUMBER(10,1)"
+note = "Halves, rounded to the nearest integer (half away from zero) before the operation."
+catches = "truncation (2.5 to 2) or half-to-even rounding"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitand-agg.bitand-agg.toml#t003_decimal"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitand-agg.bitand-agg.t004"
+form = "sf.functions.bitand-agg.bitand-agg"
+kind = "type_combination"
+target = "FLOAT"
+note = "FLOAT values just below and above a half."
+catches = "truncation instead of rounding"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitand-agg.bitand-agg.toml#t004_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitand-agg.bitand-agg.t005"
+form = "sf.functions.bitand-agg.bitand-agg"
+kind = "type_combination"
+target = "VARCHAR"
+note = "Numeric text and text that cannot be cast. The page says such text is treated as NULL, but Snowflake raises an error for the uncastable text (100038)."
+catches = "the uncastable text treated as NULL or read as 0 where Snowflake raises an error"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitand-agg.bitand-agg.toml#t005_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitand-agg.bitand-agg.t006"
+form = "sf.functions.bitand-agg.bitand-agg"
+kind = "null_argument"
+target = "expr1"
+note = "NULL rows beside values, and a group of only NULL."
+catches = "NULL read as 0 (changing AND), or the NULL group 0"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitand-agg.bitand-agg.toml#t006_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitand-agg.bitand-agg.t007"
+form = "sf.functions.bitand-agg.bitand-agg"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "0 or -1 instead of NULL"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitand-agg.bitand-agg.toml#t007_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitand-agg.bitand-agg-over.t001"
+form = "sf.functions.bitand-agg.bitand-agg-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Partitions of different sizes in mixed input order."
+catches = "the whole input aggregated instead of the partition, or a running value"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitand-agg.bitand-agg-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitand-agg.bitand-agg-over.t002"
+form = "sf.functions.bitand-agg.bitand-agg-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause."
+catches = "a running (ordered) value instead of the whole input"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitand-agg.bitand-agg-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitand-agg.bitand-agg-over.t003"
+form = "sf.functions.bitand-agg.bitand-agg-over"
+kind = "null_argument"
+target = "expr1"
+note = "NULL values inside partitions, and a partition with only NULL."
+catches = "NULL read as 0 (a different count and value) or the NULL-only partition not NULL"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitand-agg.bitand-agg-over.toml#t003_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitand-agg.bitand-agg-over.t004"
+form = "sf.functions.bitand-agg.bitand-agg-over"
+kind = "null_argument"
+target = "partition-key"
+note = "NULL partition keys, which form one partition."
+catches = "NULL-keyed rows each in their own partition or dropped"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitand-agg.bitand-agg-over.toml#t004_null_partition_key"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-absolute-position.t001"
+form = "sf.functions.bitmap-absolute-position"
+kind = "type_combination"
+target = "bucket, position"
+note = "Bucket and position of the edge values, mapped back."
+catches = "an off-by-one at the bucket edge (32768 and 32769 swapped buckets)"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-absolute-position.toml#t001_round_trip"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-absolute-position.t002"
+form = "sf.functions.bitmap-absolute-position"
+kind = "type_combination"
+target = "NUMBER, NUMBER"
+note = "Bucket 2 position 0 and bucket 1 position 32767."
+catches = "zero-based buckets"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-absolute-position.toml#t002_literal"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-absolute-position.t003"
+form = "sf.functions.bitmap-absolute-position"
+kind = "null_argument"
+target = "bucket-number"
+note = "NULL bucket."
+catches = "bucket read as 0"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-absolute-position.toml#t003_null_bucket_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-absolute-position.t004"
+form = "sf.functions.bitmap-absolute-position"
+kind = "null_argument"
+target = "relative-position"
+note = "NULL position."
+catches = "position read as 0"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-absolute-position.toml#t004_null_relative_position"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-and.t001"
+form = "sf.functions.bitmap-and"
+kind = "type_combination"
+target = "bitmap, bitmap"
+note = "Bucket-1 bitmaps of groups p {1, 5} and q {5, 9}."
+catches = "OR (the union 1, 5, 9 positions)"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-and.toml#t001_two"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-and.t002"
+form = "sf.functions.bitmap-and"
+kind = "null_argument"
+target = "bitmap1"
+note = "NULL first bitmap."
+catches = "the other bitmap returned"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-and.toml#t002_null_bitmap1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-and.t003"
+form = "sf.functions.bitmap-and"
+kind = "null_argument"
+target = "bitmap2"
+note = "NULL second bitmap."
+catches = "the other bitmap returned"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-and.toml#t003_null_bitmap2"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-and-agg.t001"
+form = "sf.functions.bitmap-and-agg"
+kind = "type_combination"
+target = "bitmaps"
+note = "The bucket-1 bitmaps of p and q combined."
+catches = "OR across the group"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-and-agg.toml#t001_group"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-and-agg.t002"
+form = "sf.functions.bitmap-and-agg"
+kind = "null_argument"
+target = "bitmap"
+note = "A NULL bitmap beside bitmaps."
+catches = "the result NULL, or NULL read as an empty bitmap (AND empty)"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-and-agg.toml#t002_null_bitmap"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-and-agg.t003"
+form = "sf.functions.bitmap-and-agg"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "an empty bitmap instead of NULL, or the reverse"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-and-agg.toml#t003_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-bit-position.t001"
+form = "sf.functions.bitmap-bit-position"
+kind = "type_combination"
+target = "NUMBER"
+note = "Values at both sides of the bucket edges, 0 and a negative value."
+catches = "x mod 32768 (32768 at position 0) or a one-based position"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-bit-position.toml#t001_edges"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-bit-position.t002"
+form = "sf.functions.bitmap-bit-position"
+kind = "null_argument"
+target = "numeric-expr"
+note = "NULL value."
+catches = "0 instead of NULL"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-bit-position.toml#t002_null_numeric_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-bucket-number.t001"
+form = "sf.functions.bitmap-bucket-number"
+kind = "type_combination"
+target = "NUMBER"
+note = "Values at both sides of the 32,768 bucket edges, 0 and a negative value."
+catches = "zero-based buckets (32768 put in the second bucket) or another bucket size"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-bucket-number.toml#t001_edges"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-bucket-number.t002"
+form = "sf.functions.bitmap-bucket-number"
+kind = "type_combination"
+target = "NUMBER(10,1)"
+note = "A value with a fraction, cast to NUMBER."
+catches = "truncation where the cast rounds"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-bucket-number.toml#t002_decimal"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-bucket-number.t003"
+form = "sf.functions.bitmap-bucket-number"
+kind = "null_argument"
+target = "numeric-expr"
+note = "NULL value."
+catches = "0 or bucket 1 instead of NULL"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-bucket-number.toml#t003_null_numeric_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-construct-agg.t001"
+form = "sf.functions.bitmap-construct-agg"
+kind = "type_combination"
+target = "NUMBER"
+note = "Per bucket, positions with a duplicate."
+catches = "duplicates counted, or values not reduced to positions"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-construct-agg.toml#t001_positions"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-construct-agg.t002"
+form = "sf.functions.bitmap-construct-agg"
+kind = "null_argument"
+target = "relative-position"
+note = "NULL positions, which set no bit."
+catches = "NULL read as position 0"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-construct-agg.toml#t002_null_relative_position"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-construct-agg.t003"
+form = "sf.functions.bitmap-construct-agg"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "an empty bitmap instead of NULL, or the reverse"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-construct-agg.toml#t003_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-count.t001"
+form = "sf.functions.bitmap-count"
+kind = "type_combination"
+target = "bitmap"
+note = "Bitmaps per group and bucket."
+catches = "bytes counted instead of set bits"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-count.toml#t001_count"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-count.t002"
+form = "sf.functions.bitmap-count"
+kind = "null_argument"
+target = "bitmap"
+note = "NULL bitmap."
+catches = "0 instead of NULL"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-count.toml#t002_null_bitmap"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-or.t001"
+form = "sf.functions.bitmap-or"
+kind = "type_combination"
+target = "bitmap, bitmap"
+note = "Bucket-1 bitmaps of groups p {1, 5} and q {5, 9}."
+catches = "AND (only position of 5)"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-or.toml#t001_two"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-or.t002"
+form = "sf.functions.bitmap-or"
+kind = "null_argument"
+target = "bitmap1"
+note = "NULL first bitmap."
+catches = "the other bitmap returned"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-or.toml#t002_null_bitmap1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-or.t003"
+form = "sf.functions.bitmap-or"
+kind = "null_argument"
+target = "bitmap2"
+note = "NULL second bitmap."
+catches = "the other bitmap returned"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-or.toml#t003_null_bitmap2"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-or-agg.t001"
+form = "sf.functions.bitmap-or-agg"
+kind = "type_combination"
+target = "bitmaps"
+note = "The bucket-1 bitmaps of p and q combined."
+catches = "AND across the group"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-or-agg.toml#t001_group"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-or-agg.t002"
+form = "sf.functions.bitmap-or-agg"
+kind = "null_argument"
+target = "bitmap"
+note = "A NULL bitmap beside bitmaps."
+catches = "the result NULL"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-or-agg.toml#t002_null_bitmap"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-or-agg.t003"
+form = "sf.functions.bitmap-or-agg"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "an empty bitmap instead of NULL, or the reverse"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-or-agg.toml#t003_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-to-array.t001"
+form = "sf.functions.bitmap-to-array"
+kind = "type_combination"
+target = "bitmap"
+note = "Set positions of bitmaps built from rows in descending order within a bucket."
+catches = "relative zero-based positions instead of bucket-1 absolute positions, or the reverse, or positions in input order"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-to-array.toml#t001_positions"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-to-array.t002"
+form = "sf.functions.bitmap-to-array"
+kind = "type_combination"
+target = "bitmap, bucket_number"
+note = "The same bitmaps with their bucket number: absolute positions."
+catches = "the bucket argument ignored"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-to-array.toml#t002_bucket"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-to-array.t003"
+form = "sf.functions.bitmap-to-array"
+kind = "null_argument"
+target = "bitmap"
+note = "NULL bitmap."
+catches = "an empty array instead of NULL"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-to-array.toml#t003_null_bitmap"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitmap-to-array.t004"
+form = "sf.functions.bitmap-to-array"
+kind = "null_argument"
+target = "bucket-number"
+note = "NULL bucket."
+catches = "the default bucket used instead of NULL"
+reading = "the pages: BITMAP_BUCKET_NUMBER and BITMAP_BIT_POSITION split a value into a bucket and a relative bit position within buckets of 32,768 values, and BITMAP_ABSOLUTE_POSITION reverses them; BITMAP_CONSTRUCT_AGG sets the bits of the distinct positions in a group as a BINARY bitmap; BITMAP_OR_AGG and BITMAP_AND_AGG combine bitmaps of a group, BITMAP_OR and BITMAP_AND combine two; BITMAP_COUNT counts the set bits; BITMAP_TO_ARRAY lists the set positions, as absolute positions for (default 1); bitmaps are compared through BITMAP_COUNT and BITMAP_TO_ARRAY"
+cases = ["cases/translation/functions/bitmap-to-array.toml#t004_null_bucket_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitnot.t001"
+form = "sf.functions.bitnot"
+kind = "type_combination"
+target = "INTEGER"
+note = "Zero, a positive and a negative integer."
+catches = "a logical NOT, or an unsigned result"
+reading = "the page: the bitwise negation of an INTEGER or BINARY value; other numbers are cast to INTEGER; NULL gives NULL"
+cases = ["cases/translation/functions/bitnot.toml#t001_integers"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitnot.t002"
+form = "sf.functions.bitnot"
+kind = "type_combination"
+target = "BINARY"
+note = "Bytes negated."
+catches = "the bytes read as an integer"
+reading = "the page: the bitwise negation of an INTEGER or BINARY value; other numbers are cast to INTEGER; NULL gives NULL"
+cases = ["cases/translation/functions/bitnot.toml#t002_binary"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitnot.t003"
+form = "sf.functions.bitnot"
+kind = "null_argument"
+target = "expr"
+note = "A NULL."
+catches = "the NULL read as 0 (-1 returned)"
+reading = "the page: the bitwise negation of an INTEGER or BINARY value; other numbers are cast to INTEGER; NULL gives NULL"
+cases = ["cases/translation/functions/bitnot.toml#t003_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitor.t001"
+form = "sf.functions.bitor"
+kind = "type_combination"
+target = "INTEGER, INTEGER"
+note = "Integers whose bits overlap in part, and a negative."
+catches = "a logical OR (1 instead of 14), or the sign bit lost"
+reading = "the page: the bitwise OR of two INTEGER or two BINARY values; other numbers are cast to INTEGER; a string is cast to INTEGER if possible (12.3 to 12) and otherwise treated as NULL; BINARY values of different lengths need padside LEFT or RIGHT; NULL gives NULL"
+cases = ["cases/translation/functions/bitor.toml#t001_integers"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitor.t002"
+form = "sf.functions.bitor"
+kind = "type_combination"
+target = "DECIMAL and VARCHAR"
+note = "A decimal and the string 12.3, which the page says cast to integers."
+catches = "the decimal truncated where Snowflake rounds, or the string rejected"
+reading = "the page: the bitwise OR of two INTEGER or two BINARY values; other numbers are cast to INTEGER; a string is cast to INTEGER if possible (12.3 to 12) and otherwise treated as NULL; BINARY values of different lengths need padside LEFT or RIGHT; NULL gives NULL"
+cases = ["cases/translation/functions/bitor.toml#t002_casts"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitor.t003"
+form = "sf.functions.bitor"
+kind = "type_combination"
+target = "BINARY, BINARY, padside"
+note = "BINARY values of different lengths padded on the left and on the right."
+catches = "both paddings giving the same result, or padside ignored"
+reading = "the page: the bitwise OR of two INTEGER or two BINARY values; other numbers are cast to INTEGER; a string is cast to INTEGER if possible (12.3 to 12) and otherwise treated as NULL; BINARY values of different lengths need padside LEFT or RIGHT; NULL gives NULL"
+cases = ["cases/translation/functions/bitor.toml#t003_binary_pad"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitor.t004"
+form = "sf.functions.bitor"
+kind = "error_parity"
+target = "error"
+note = "BINARY values of different lengths without padside, which the page says requires it."
+catches = "a default padding applied"
+reading = "the page: the bitwise OR of two INTEGER or two BINARY values; other numbers are cast to INTEGER; a string is cast to INTEGER if possible (12.3 to 12) and otherwise treated as NULL; BINARY values of different lengths need padside LEFT or RIGHT; NULL gives NULL"
+cases = ["cases/translation/functions/bitor.toml#t004_binary_unequal"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitor.t005"
+form = "sf.functions.bitor"
+kind = "error_parity"
+target = "error"
+note = "A NULL on either side next to a string that cannot be cast; Snowflake raised 100038 for the string, so it is not treated as NULL."
+catches = "the string read as NULL or 0"
+reading = "the page: the bitwise OR of two INTEGER or two BINARY values; other numbers are cast to INTEGER; a string is cast to INTEGER if possible (12.3 to 12) and otherwise treated as NULL; BINARY values of different lengths need padside LEFT or RIGHT; NULL gives NULL"
+cases = ["cases/translation/functions/bitor.toml#t005_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitor.t006"
+form = "sf.functions.bitor"
+kind = "null_argument"
+target = "expr1"
+note = "A NULL on either side alone; the BITAND page, which also covers BITOR and BITXOR, says either NULL input gives NULL."
+catches = "the NULL read as 0"
+reading = "the page: the bitwise OR of two INTEGER or two BINARY values; other numbers are cast to INTEGER; a string is cast to INTEGER if possible (12.3 to 12) and otherwise treated as NULL; BINARY values of different lengths need padside LEFT or RIGHT; NULL gives NULL"
+cases = ["cases/translation/functions/bitor.toml#t006_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitor-agg.bitor-agg.t001"
+form = "sf.functions.bitor-agg.bitor-agg"
+kind = "type_combination"
+target = "NUMBER"
+note = "12, 10 and 14 per group (1100, 1010, 1110)."
+catches = "another bitwise operator"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitor-agg.bitor-agg.toml#t001_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitor-agg.bitor-agg.t002"
+form = "sf.functions.bitor-agg.bitor-agg"
+kind = "type_combination"
+target = "negative NUMBER"
+note = "A negative value in two's complement."
+catches = "the sign dropped (abs value) or a NUMBER(38) treated as unsigned"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitor-agg.bitor-agg.toml#t002_negative"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitor-agg.bitor-agg.t003"
+form = "sf.functions.bitor-agg.bitor-agg"
+kind = "type_combination"
+target = "NUMBER(10,1)"
+note = "Halves, rounded to the nearest integer (half away from zero) before the operation."
+catches = "truncation (2.5 to 2) or half-to-even rounding"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitor-agg.bitor-agg.toml#t003_decimal"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitor-agg.bitor-agg.t004"
+form = "sf.functions.bitor-agg.bitor-agg"
+kind = "type_combination"
+target = "FLOAT"
+note = "FLOAT values just below and above a half."
+catches = "truncation instead of rounding"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitor-agg.bitor-agg.toml#t004_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitor-agg.bitor-agg.t005"
+form = "sf.functions.bitor-agg.bitor-agg"
+kind = "type_combination"
+target = "VARCHAR"
+note = "Numeric text, and text that cannot be cast, alone in group q. The page says such text is treated as NULL, while its example raises an error; the recording decides."
+catches = "uncastable text treated the opposite way to Snowflake (NULL against an error), or read as 0 (q giving 0 instead of NULL)"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitor-agg.bitor-agg.toml#t005_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitor-agg.bitor-agg.t006"
+form = "sf.functions.bitor-agg.bitor-agg"
+kind = "null_argument"
+target = "expr1"
+note = "NULL rows beside values, and a group of only NULL."
+catches = "NULL read as 0 (changing AND), or the NULL group 0"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitor-agg.bitor-agg.toml#t006_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitor-agg.bitor-agg.t007"
+form = "sf.functions.bitor-agg.bitor-agg"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "0 or -1 instead of NULL"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitor-agg.bitor-agg.toml#t007_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitor-agg.bitor-agg-over.t001"
+form = "sf.functions.bitor-agg.bitor-agg-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Partitions of different sizes in mixed input order."
+catches = "the whole input aggregated instead of the partition, or a running value"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitor-agg.bitor-agg-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitor-agg.bitor-agg-over.t002"
+form = "sf.functions.bitor-agg.bitor-agg-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause."
+catches = "a running (ordered) value instead of the whole input"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitor-agg.bitor-agg-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitor-agg.bitor-agg-over.t003"
+form = "sf.functions.bitor-agg.bitor-agg-over"
+kind = "null_argument"
+target = "expr1"
+note = "NULL values inside partitions, and a partition with only NULL."
+catches = "NULL read as 0 (a different count and value) or the NULL-only partition not NULL"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitor-agg.bitor-agg-over.toml#t003_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitor-agg.bitor-agg-over.t004"
+form = "sf.functions.bitor-agg.bitor-agg-over"
+kind = "null_argument"
+target = "partition-key"
+note = "NULL partition keys, which form one partition."
+catches = "NULL-keyed rows each in their own partition or dropped"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitor-agg.bitor-agg-over.toml#t004_null_partition_key"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitshiftleft.t001"
+form = "sf.functions.bitshiftleft"
+kind = "type_combination"
+target = "INTEGER, INTEGER"
+note = "Shifts of positive and negative integers."
+catches = "a 64-bit result (bit 127 lost), or the sign kept when the high bit changes"
+reading = "the page: the INTEGER or BINARY value shifted left by n bits; an INTEGER result is a signed 128-bit integer; NULL gives NULL"
+cases = ["cases/translation/functions/bitshiftleft.toml#t001_integers"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitshiftleft.t002"
+form = "sf.functions.bitshiftleft"
+kind = "type_combination"
+target = "BINARY, INTEGER"
+note = "Bytes shifted by four bits."
+catches = "the bytes read as an integer, or the shift in bytes"
+reading = "the page: the INTEGER or BINARY value shifted left by n bits; an INTEGER result is a signed 128-bit integer; NULL gives NULL"
+cases = ["cases/translation/functions/bitshiftleft.toml#t002_binary"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitshiftleft.t003"
+form = "sf.functions.bitshiftleft"
+kind = "null_argument"
+target = "expr1"
+note = "A NULL value and a NULL shift."
+catches = "the NULL read as 0"
+reading = "the page: the INTEGER or BINARY value shifted left by n bits; an INTEGER result is a signed 128-bit integer; NULL gives NULL"
+cases = ["cases/translation/functions/bitshiftleft.toml#t003_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitshiftright.t001"
+form = "sf.functions.bitshiftright"
+kind = "type_combination"
+target = "INTEGER, INTEGER"
+note = "Shifts of positive and negative integers."
+catches = "a logical shift on the negative (sign bit not kept), or a rotation"
+reading = "the page: the INTEGER or BINARY value shifted right by n bits; an INTEGER result is a signed 128-bit integer; NULL gives NULL"
+cases = ["cases/translation/functions/bitshiftright.toml#t001_integers"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitshiftright.t002"
+form = "sf.functions.bitshiftright"
+kind = "type_combination"
+target = "BINARY, INTEGER"
+note = "Bytes shifted by four bits."
+catches = "the bytes read as an integer, or the shift in bytes"
+reading = "the page: the INTEGER or BINARY value shifted right by n bits; an INTEGER result is a signed 128-bit integer; NULL gives NULL"
+cases = ["cases/translation/functions/bitshiftright.toml#t002_binary"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitshiftright.t003"
+form = "sf.functions.bitshiftright"
+kind = "null_argument"
+target = "expr1"
+note = "A NULL value and a NULL shift."
+catches = "the NULL read as 0"
+reading = "the page: the INTEGER or BINARY value shifted right by n bits; an INTEGER result is a signed 128-bit integer; NULL gives NULL"
+cases = ["cases/translation/functions/bitshiftright.toml#t003_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitxor.t001"
+form = "sf.functions.bitxor"
+kind = "type_combination"
+target = "INTEGER, INTEGER"
+note = "Integers whose bits overlap in part, and a negative."
+catches = "OR computed instead of XOR (14 instead of 6), or the sign bit lost"
+reading = "the page: the bitwise XOR of two INTEGER or two BINARY values; other numbers are cast to INTEGER; a string is cast to INTEGER if possible (12.3 to 12) and otherwise treated as NULL; BINARY values of different lengths need padside LEFT or RIGHT; NULL gives NULL"
+cases = ["cases/translation/functions/bitxor.toml#t001_integers"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitxor.t002"
+form = "sf.functions.bitxor"
+kind = "type_combination"
+target = "DECIMAL and VARCHAR"
+note = "A decimal and the string 12.3, which the page says cast to integers."
+catches = "the decimal truncated where Snowflake rounds, or the string rejected"
+reading = "the page: the bitwise XOR of two INTEGER or two BINARY values; other numbers are cast to INTEGER; a string is cast to INTEGER if possible (12.3 to 12) and otherwise treated as NULL; BINARY values of different lengths need padside LEFT or RIGHT; NULL gives NULL"
+cases = ["cases/translation/functions/bitxor.toml#t002_casts"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitxor.t003"
+form = "sf.functions.bitxor"
+kind = "type_combination"
+target = "BINARY, BINARY, padside"
+note = "BINARY values of different lengths padded on the left and on the right."
+catches = "both paddings giving the same result, or padside ignored"
+reading = "the page: the bitwise XOR of two INTEGER or two BINARY values; other numbers are cast to INTEGER; a string is cast to INTEGER if possible (12.3 to 12) and otherwise treated as NULL; BINARY values of different lengths need padside LEFT or RIGHT; NULL gives NULL"
+cases = ["cases/translation/functions/bitxor.toml#t003_binary_pad"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitxor.t004"
+form = "sf.functions.bitxor"
+kind = "error_parity"
+target = "error"
+note = "BINARY values of different lengths without padside, which the page says requires it."
+catches = "a default padding applied"
+reading = "the page: the bitwise XOR of two INTEGER or two BINARY values; other numbers are cast to INTEGER; a string is cast to INTEGER if possible (12.3 to 12) and otherwise treated as NULL; BINARY values of different lengths need padside LEFT or RIGHT; NULL gives NULL"
+cases = ["cases/translation/functions/bitxor.toml#t004_binary_unequal"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitxor.t005"
+form = "sf.functions.bitxor"
+kind = "error_parity"
+target = "error"
+note = "A NULL on either side next to a string that cannot be cast; Snowflake raised 100038 for the string, so it is not treated as NULL."
+catches = "the string read as NULL or 0"
+reading = "the page: the bitwise XOR of two INTEGER or two BINARY values; other numbers are cast to INTEGER; a string is cast to INTEGER if possible (12.3 to 12) and otherwise treated as NULL; BINARY values of different lengths need padside LEFT or RIGHT; NULL gives NULL"
+cases = ["cases/translation/functions/bitxor.toml#t005_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitxor.t006"
+form = "sf.functions.bitxor"
+kind = "null_argument"
+target = "expr1"
+note = "A NULL on either side alone; the BITAND page, which also covers BITOR and BITXOR, says either NULL input gives NULL."
+catches = "the NULL read as 0"
+reading = "the page: the bitwise XOR of two INTEGER or two BINARY values; other numbers are cast to INTEGER; a string is cast to INTEGER if possible (12.3 to 12) and otherwise treated as NULL; BINARY values of different lengths need padside LEFT or RIGHT; NULL gives NULL"
+cases = ["cases/translation/functions/bitxor.toml#t006_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitxor-agg.bitxor-agg.t001"
+form = "sf.functions.bitxor-agg.bitxor-agg"
+kind = "type_combination"
+target = "NUMBER"
+note = "12, 10 and 14 per group (1100, 1010, 1110)."
+catches = "another bitwise operator"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitxor-agg.bitxor-agg.toml#t001_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitxor-agg.bitxor-agg.t002"
+form = "sf.functions.bitxor-agg.bitxor-agg"
+kind = "type_combination"
+target = "negative NUMBER"
+note = "A negative value in two's complement."
+catches = "the sign dropped (abs value) or a NUMBER(38) treated as unsigned"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitxor-agg.bitxor-agg.toml#t002_negative"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitxor-agg.bitxor-agg.t003"
+form = "sf.functions.bitxor-agg.bitxor-agg"
+kind = "type_combination"
+target = "NUMBER(10,1)"
+note = "Halves, rounded to the nearest integer (half away from zero) before the operation."
+catches = "truncation (2.5 to 2) or half-to-even rounding"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitxor-agg.bitxor-agg.toml#t003_decimal"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitxor-agg.bitxor-agg.t004"
+form = "sf.functions.bitxor-agg.bitxor-agg"
+kind = "type_combination"
+target = "FLOAT"
+note = "FLOAT values just below and above a half."
+catches = "truncation instead of rounding"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitxor-agg.bitxor-agg.toml#t004_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitxor-agg.bitxor-agg.t005"
+form = "sf.functions.bitxor-agg.bitxor-agg"
+kind = "type_combination"
+target = "VARCHAR"
+note = "Numeric text, and text that cannot be cast, alone in group q. The page says such text is treated as NULL, while its example raises an error; the recording decides."
+catches = "uncastable text treated the opposite way to Snowflake (NULL against an error), or read as 0 (q giving 0 instead of NULL)"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitxor-agg.bitxor-agg.toml#t005_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitxor-agg.bitxor-agg.t006"
+form = "sf.functions.bitxor-agg.bitxor-agg"
+kind = "null_argument"
+target = "expr1"
+note = "NULL rows beside values, and a group of only NULL."
+catches = "the NULL-only group giving 0 instead of NULL (0 is the XOR identity, so NULL read as 0 shows only there)"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitxor-agg.bitxor-agg.toml#t006_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitxor-agg.bitxor-agg.t007"
+form = "sf.functions.bitxor-agg.bitxor-agg"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "0 or -1 instead of NULL"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitxor-agg.bitxor-agg.toml#t007_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitxor-agg.bitxor-agg.t008"
+form = "sf.functions.bitxor-agg.bitxor-agg"
+kind = "type_combination"
+target = "DISTINCT"
+note = "DISTINCT, which the page says has no effect, on a repeated value: r and p show whether Snowflake honours it."
+catches = "DISTINCT honoured where Snowflake ignores it, or the reverse"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitxor-agg.bitxor-agg.toml#t008_distinct"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitxor-agg.bitxor-agg-over.t001"
+form = "sf.functions.bitxor-agg.bitxor-agg-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Partitions of different sizes in mixed input order."
+catches = "the whole input aggregated instead of the partition, or a running value"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitxor-agg.bitxor-agg-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitxor-agg.bitxor-agg-over.t002"
+form = "sf.functions.bitxor-agg.bitxor-agg-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause."
+catches = "a running (ordered) value instead of the whole input"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitxor-agg.bitxor-agg-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitxor-agg.bitxor-agg-over.t003"
+form = "sf.functions.bitxor-agg.bitxor-agg-over"
+kind = "null_argument"
+target = "expr1"
+note = "NULL values inside partitions, and a partition with only NULL."
+catches = "the NULL-only partition giving 0 instead of NULL"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitxor-agg.bitxor-agg-over.toml#t003_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.bitxor-agg.bitxor-agg-over.t004"
+form = "sf.functions.bitxor-agg.bitxor-agg-over"
+kind = "null_argument"
+target = "partition-key"
+note = "NULL partition keys, which form one partition."
+catches = "NULL-keyed rows each in their own partition or dropped"
+reading = "the page: returns NUMBER(38,0); DECIMAL and FLOAT values are rounded to the nearest integer first; VARCHAR is cast to FLOAT and rounded, and text that cannot be cast is treated as NULL; NULL values are ignored; no ORDER BY or frame as a window function; DISTINCT has no effect"
+cases = ["cases/translation/functions/bitxor-agg.bitxor-agg-over.toml#t004_null_partition_key"]
+required = true
+
+[[obligation]]
+id = "sf.functions.booland.t001"
+form = "sf.functions.booland"
+kind = "type_combination"
+target = "NUMBER, NUMBER"
+note = "BOOLAND over zero, non-zero and negative values."
+catches = "negative values read as FALSE, or another operator than AND"
+reading = "the page: numeric arguments; non-zero is TRUE; floating-point numbers are rounded first, so a value rounding to 0 is FALSE; returns BOOLEAN or NULL"
+cases = ["cases/translation/functions/booland.toml#t001_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.booland.t002"
+form = "sf.functions.booland"
+kind = "type_combination"
+target = "FLOAT, FLOAT"
+note = "FLOAT values that round to zero, as the page warns."
+catches = "non-zero floats read as TRUE (0.3 is FALSE after rounding)"
+reading = "the page: numeric arguments; non-zero is TRUE; floating-point numbers are rounded first, so a value rounding to 0 is FALSE; returns BOOLEAN or NULL"
+cases = ["cases/translation/functions/booland.toml#t002_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.booland.t003"
+form = "sf.functions.booland"
+kind = "type_combination"
+target = "NUMBER(10,2), NUMBER"
+note = "Fixed-point fractions."
+catches = "fixed-point treated like FLOAT (rounded) or the reverse"
+reading = "the page: numeric arguments; non-zero is TRUE; floating-point numbers are rounded first, so a value rounding to 0 is FALSE; returns BOOLEAN or NULL"
+cases = ["cases/translation/functions/booland.toml#t003_decimal"]
+required = true
+
+[[obligation]]
+id = "sf.functions.booland.t004"
+form = "sf.functions.booland"
+kind = "null_argument"
+target = "expr1"
+note = "NULL first argument, beside values that do and do not decide the result."
+catches = "NULL read as FALSE, or NULL even where three-valued logic gives FALSE (NULL AND FALSE)"
+reading = "the page: numeric arguments; non-zero is TRUE; floating-point numbers are rounded first, so a value rounding to 0 is FALSE; returns BOOLEAN or NULL"
+cases = ["cases/translation/functions/booland.toml#t004_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.booland.t005"
+form = "sf.functions.booland"
+kind = "null_argument"
+target = "expr2"
+note = "NULL second argument."
+catches = "NULL read as FALSE"
+reading = "the page: numeric arguments; non-zero is TRUE; floating-point numbers are rounded first, so a value rounding to 0 is FALSE; returns BOOLEAN or NULL"
+cases = ["cases/translation/functions/booland.toml#t005_null_expr2"]
+required = true
+
+[[obligation]]
+id = "sf.functions.booland-agg.booland-agg.t001"
+form = "sf.functions.booland-agg.booland-agg"
+kind = "type_combination"
+target = "BOOLEAN"
+note = "Groups with no, one, two and three TRUE values."
+catches = "the other operator (AND for OR)"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when every non-NULL value is TRUE"
+cases = ["cases/translation/functions/booland-agg.booland-agg.toml#t001_boolean"]
+required = true
+
+[[obligation]]
+id = "sf.functions.booland-agg.booland-agg.t002"
+form = "sf.functions.booland-agg.booland-agg"
+kind = "type_combination"
+target = "NUMBER"
+note = "Non-zero values, including a negative one, are TRUE."
+catches = "negative values read as FALSE, or numbers rejected"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when every non-NULL value is TRUE"
+cases = ["cases/translation/functions/booland-agg.booland-agg.toml#t002_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.booland-agg.booland-agg.t003"
+form = "sf.functions.booland-agg.booland-agg"
+kind = "type_combination"
+target = "NUMBER(10,1)"
+note = "A small non-zero decimal."
+catches = "the value rounded to 0 first (as the scalar BOOL functions do)"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when every non-NULL value is TRUE"
+cases = ["cases/translation/functions/booland-agg.booland-agg.toml#t003_decimal"]
+required = true
+
+[[obligation]]
+id = "sf.functions.booland-agg.booland-agg.t004"
+form = "sf.functions.booland-agg.booland-agg"
+kind = "null_argument"
+target = "expr"
+note = "NULL beside a single value of each kind, and a group of only NULL."
+catches = "NULL read as FALSE (FALSE for the group with a NULL)"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when every non-NULL value is TRUE"
+cases = ["cases/translation/functions/booland-agg.booland-agg.toml#t004_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.booland-agg.booland-agg.t005"
+form = "sf.functions.booland-agg.booland-agg"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "TRUE or FALSE instead of NULL"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when every non-NULL value is TRUE"
+cases = ["cases/translation/functions/booland-agg.booland-agg.toml#t005_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.booland-agg.booland-agg.t006"
+form = "sf.functions.booland-agg.booland-agg"
+kind = "type_combination"
+target = "FLOAT"
+note = "A FLOAT. Snowflake refuses it at compile (1007), although the page says numeric values convert, so a translation must refuse it too."
+catches = "the FLOAT converted where Snowflake refuses it"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when every non-NULL value is TRUE"
+cases = ["cases/translation/functions/booland-agg.booland-agg.toml#t006_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.booland-agg.booland-agg-over.t001"
+form = "sf.functions.booland-agg.booland-agg-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Partitions with one and with three TRUE values."
+catches = "the whole input aggregated"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when every non-NULL value is TRUE"
+cases = ["cases/translation/functions/booland-agg.booland-agg-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.booland-agg.booland-agg-over.t002"
+form = "sf.functions.booland-agg.booland-agg-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause."
+catches = "a running value"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when every non-NULL value is TRUE"
+cases = ["cases/translation/functions/booland-agg.booland-agg-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.booland-agg.booland-agg-over.t003"
+form = "sf.functions.booland-agg.booland-agg-over"
+kind = "null_argument"
+target = "expr"
+note = "NULL inside partitions, and a partition of only NULL."
+catches = "NULL read as FALSE (FALSE for the group with a NULL)"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when every non-NULL value is TRUE"
+cases = ["cases/translation/functions/booland-agg.booland-agg-over.toml#t003_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.booland-agg.booland-agg-over.t004"
+form = "sf.functions.booland-agg.booland-agg-over"
+kind = "null_argument"
+target = "partition-expr"
+note = "NULL partition keys."
+catches = "NULL-keyed rows split or dropped"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when every non-NULL value is TRUE"
+cases = ["cases/translation/functions/booland-agg.booland-agg-over.toml#t004_null_partition_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolnot.t001"
+form = "sf.functions.boolnot"
+kind = "type_combination"
+target = "NUMBER"
+note = "BOOLNOT of zero, positive and negative values."
+catches = "a negative value read as FALSE"
+reading = "the page: numeric arguments; non-zero is TRUE; floating-point numbers are rounded first, so a value rounding to 0 is FALSE; returns BOOLEAN or NULL"
+cases = ["cases/translation/functions/boolnot.toml#t001_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolnot.t002"
+form = "sf.functions.boolnot"
+kind = "type_combination"
+target = "FLOAT"
+note = "FLOAT values around rounding to zero."
+catches = "non-zero floats read as TRUE before rounding"
+reading = "the page: numeric arguments; non-zero is TRUE; floating-point numbers are rounded first, so a value rounding to 0 is FALSE; returns BOOLEAN or NULL"
+cases = ["cases/translation/functions/boolnot.toml#t002_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolnot.t003"
+form = "sf.functions.boolnot"
+kind = "null_argument"
+target = "expr"
+note = "NULL argument."
+catches = "NULL read as FALSE (TRUE instead of NULL)"
+reading = "the page: numeric arguments; non-zero is TRUE; floating-point numbers are rounded first, so a value rounding to 0 is FALSE; returns BOOLEAN or NULL"
+cases = ["cases/translation/functions/boolnot.toml#t003_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolor.t001"
+form = "sf.functions.boolor"
+kind = "type_combination"
+target = "NUMBER, NUMBER"
+note = "BOOLOR over zero, non-zero and negative values."
+catches = "negative values read as FALSE, or another operator than OR"
+reading = "the page: numeric arguments; non-zero is TRUE; floating-point numbers are rounded first, so a value rounding to 0 is FALSE; returns BOOLEAN or NULL"
+cases = ["cases/translation/functions/boolor.toml#t001_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolor.t002"
+form = "sf.functions.boolor"
+kind = "type_combination"
+target = "FLOAT, FLOAT"
+note = "FLOAT values that round to zero, as the page warns."
+catches = "non-zero floats read as TRUE (0.3 is FALSE after rounding), or truncation instead of rounding (0.7 FALSE)"
+reading = "the page: numeric arguments; non-zero is TRUE; floating-point numbers are rounded first, so a value rounding to 0 is FALSE; returns BOOLEAN or NULL"
+cases = ["cases/translation/functions/boolor.toml#t002_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolor.t003"
+form = "sf.functions.boolor"
+kind = "type_combination"
+target = "NUMBER(10,2), NUMBER"
+note = "Fixed-point fractions."
+catches = "fixed-point treated like FLOAT (rounded) or the reverse"
+reading = "the page: numeric arguments; non-zero is TRUE; floating-point numbers are rounded first, so a value rounding to 0 is FALSE; returns BOOLEAN or NULL"
+cases = ["cases/translation/functions/boolor.toml#t003_decimal"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolor.t004"
+form = "sf.functions.boolor"
+kind = "null_argument"
+target = "expr1"
+note = "NULL first argument, beside values that do and do not decide the result."
+catches = "NULL read as FALSE (FALSE instead of NULL on the zero row)"
+reading = "the page: numeric arguments; non-zero is TRUE; floating-point numbers are rounded first, so a value rounding to 0 is FALSE; returns BOOLEAN or NULL"
+cases = ["cases/translation/functions/boolor.toml#t004_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolor.t005"
+form = "sf.functions.boolor"
+kind = "null_argument"
+target = "expr2"
+note = "NULL second argument."
+catches = "NULL read as FALSE (FALSE instead of NULL on the zero row)"
+reading = "the page: numeric arguments; non-zero is TRUE; floating-point numbers are rounded first, so a value rounding to 0 is FALSE; returns BOOLEAN or NULL"
+cases = ["cases/translation/functions/boolor.toml#t005_null_expr2"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolor-agg.boolor-agg.t001"
+form = "sf.functions.boolor-agg.boolor-agg"
+kind = "type_combination"
+target = "BOOLEAN"
+note = "Groups with no, one, two and three TRUE values."
+catches = "the other operator (AND for OR)"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when at least one non-NULL value is TRUE"
+cases = ["cases/translation/functions/boolor-agg.boolor-agg.toml#t001_boolean"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolor-agg.boolor-agg.t002"
+form = "sf.functions.boolor-agg.boolor-agg"
+kind = "type_combination"
+target = "NUMBER"
+note = "Non-zero values, including a negative one, are TRUE."
+catches = "negative values read as FALSE, or numbers rejected"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when at least one non-NULL value is TRUE"
+cases = ["cases/translation/functions/boolor-agg.boolor-agg.toml#t002_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolor-agg.boolor-agg.t003"
+form = "sf.functions.boolor-agg.boolor-agg"
+kind = "type_combination"
+target = "NUMBER(10,1)"
+note = "A small non-zero decimal."
+catches = "the value rounded to 0 first (as the scalar BOOL functions do)"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when at least one non-NULL value is TRUE"
+cases = ["cases/translation/functions/boolor-agg.boolor-agg.toml#t003_decimal"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolor-agg.boolor-agg.t004"
+form = "sf.functions.boolor-agg.boolor-agg"
+kind = "null_argument"
+target = "expr"
+note = "NULL beside a single value of each kind, and a group of only NULL."
+catches = "NULL read as TRUE"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when at least one non-NULL value is TRUE"
+cases = ["cases/translation/functions/boolor-agg.boolor-agg.toml#t004_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolor-agg.boolor-agg.t005"
+form = "sf.functions.boolor-agg.boolor-agg"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "TRUE or FALSE instead of NULL"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when at least one non-NULL value is TRUE"
+cases = ["cases/translation/functions/boolor-agg.boolor-agg.toml#t005_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolor-agg.boolor-agg.t006"
+form = "sf.functions.boolor-agg.boolor-agg"
+kind = "type_combination"
+target = "FLOAT"
+note = "A FLOAT. Snowflake refuses it at compile (1007), although the page says numeric values convert, so a translation must refuse it too."
+catches = "the FLOAT converted where Snowflake refuses it"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when at least one non-NULL value is TRUE"
+cases = ["cases/translation/functions/boolor-agg.boolor-agg.toml#t006_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolor-agg.boolor-agg-over.t001"
+form = "sf.functions.boolor-agg.boolor-agg-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "A partition with no TRUE value and one with three."
+catches = "the whole input aggregated"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when at least one non-NULL value is TRUE"
+cases = ["cases/translation/functions/boolor-agg.boolor-agg-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolor-agg.boolor-agg-over.t002"
+form = "sf.functions.boolor-agg.boolor-agg-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause."
+catches = "a running value"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when at least one non-NULL value is TRUE"
+cases = ["cases/translation/functions/boolor-agg.boolor-agg-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolor-agg.boolor-agg-over.t003"
+form = "sf.functions.boolor-agg.boolor-agg-over"
+kind = "null_argument"
+target = "expr"
+note = "NULL inside partitions, and a partition of only NULL."
+catches = "NULL read as TRUE"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when at least one non-NULL value is TRUE"
+cases = ["cases/translation/functions/boolor-agg.boolor-agg-over.toml#t003_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolor-agg.boolor-agg-over.t004"
+form = "sf.functions.boolor-agg.boolor-agg-over"
+kind = "null_argument"
+target = "partition-expr"
+note = "NULL partition keys."
+catches = "NULL-keyed rows split or dropped"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when at least one non-NULL value is TRUE"
+cases = ["cases/translation/functions/boolor-agg.boolor-agg-over.toml#t004_null_partition_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolxor.t001"
+form = "sf.functions.boolxor"
+kind = "type_combination"
+target = "NUMBER, NUMBER"
+note = "BOOLXOR over zero, non-zero and negative values."
+catches = "negative values read as FALSE, or another operator than XOR"
+reading = "the page: numeric arguments; non-zero is TRUE; floating-point numbers are rounded first, so a value rounding to 0 is FALSE; returns BOOLEAN or NULL"
+cases = ["cases/translation/functions/boolxor.toml#t001_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolxor.t002"
+form = "sf.functions.boolxor"
+kind = "type_combination"
+target = "FLOAT, FLOAT"
+note = "FLOAT values that round to zero, as the page warns."
+catches = "non-zero floats read as TRUE (0.3 is FALSE after rounding)"
+reading = "the page: numeric arguments; non-zero is TRUE; floating-point numbers are rounded first, so a value rounding to 0 is FALSE; returns BOOLEAN or NULL"
+cases = ["cases/translation/functions/boolxor.toml#t002_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolxor.t003"
+form = "sf.functions.boolxor"
+kind = "type_combination"
+target = "NUMBER(10,2), NUMBER"
+note = "Fixed-point fractions."
+catches = "fixed-point treated like FLOAT (rounded) or the reverse"
+reading = "the page: numeric arguments; non-zero is TRUE; floating-point numbers are rounded first, so a value rounding to 0 is FALSE; returns BOOLEAN or NULL"
+cases = ["cases/translation/functions/boolxor.toml#t003_decimal"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolxor.t004"
+form = "sf.functions.boolxor"
+kind = "null_argument"
+target = "expr1"
+note = "NULL first argument, beside zero and non-zero values."
+catches = "NULL read as FALSE or TRUE (a value instead of NULL)"
+reading = "the page: numeric arguments; non-zero is TRUE; floating-point numbers are rounded first, so a value rounding to 0 is FALSE; returns BOOLEAN or NULL"
+cases = ["cases/translation/functions/boolxor.toml#t004_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolxor.t005"
+form = "sf.functions.boolxor"
+kind = "null_argument"
+target = "expr2"
+note = "NULL second argument, beside zero and non-zero values."
+catches = "NULL read as FALSE or TRUE (a value instead of NULL)"
+reading = "the page: numeric arguments; non-zero is TRUE; floating-point numbers are rounded first, so a value rounding to 0 is FALSE; returns BOOLEAN or NULL"
+cases = ["cases/translation/functions/boolxor.toml#t005_null_expr2"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolxor-agg.boolxor-agg.t001"
+form = "sf.functions.boolxor-agg.boolxor-agg"
+kind = "type_combination"
+target = "BOOLEAN"
+note = "Groups with no, one, two and three TRUE values."
+catches = "parity (three TRUE values give TRUE) instead of exactly one"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when exactly one value is TRUE"
+cases = ["cases/translation/functions/boolxor-agg.boolxor-agg.toml#t001_boolean"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolxor-agg.boolxor-agg.t002"
+form = "sf.functions.boolxor-agg.boolxor-agg"
+kind = "type_combination"
+target = "NUMBER"
+note = "Non-zero values, including a negative one, are TRUE."
+catches = "negative values read as FALSE, or numbers rejected"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when exactly one value is TRUE"
+cases = ["cases/translation/functions/boolxor-agg.boolxor-agg.toml#t002_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolxor-agg.boolxor-agg.t003"
+form = "sf.functions.boolxor-agg.boolxor-agg"
+kind = "type_combination"
+target = "NUMBER(10,1)"
+note = "A small non-zero decimal."
+catches = "the value rounded to 0 first (as the scalar BOOL functions do)"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when exactly one value is TRUE"
+cases = ["cases/translation/functions/boolxor-agg.boolxor-agg.toml#t003_decimal"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolxor-agg.boolxor-agg.t004"
+form = "sf.functions.boolxor-agg.boolxor-agg"
+kind = "null_argument"
+target = "expr"
+note = "NULL beside a single value of each kind, and a group of only NULL."
+catches = "NULL read as TRUE (two TRUE values give FALSE)"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when exactly one value is TRUE"
+cases = ["cases/translation/functions/boolxor-agg.boolxor-agg.toml#t004_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolxor-agg.boolxor-agg.t005"
+form = "sf.functions.boolxor-agg.boolxor-agg"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "TRUE or FALSE instead of NULL"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when exactly one value is TRUE"
+cases = ["cases/translation/functions/boolxor-agg.boolxor-agg.toml#t005_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolxor-agg.boolxor-agg.t006"
+form = "sf.functions.boolxor-agg.boolxor-agg"
+kind = "type_combination"
+target = "FLOAT"
+note = "A FLOAT. Snowflake refuses it at compile (1007), although the page says numeric values convert, so a translation must refuse it too."
+catches = "the FLOAT converted where Snowflake refuses it"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when exactly one value is TRUE"
+cases = ["cases/translation/functions/boolxor-agg.boolxor-agg.toml#t006_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolxor-agg.boolxor-agg-over.t001"
+form = "sf.functions.boolxor-agg.boolxor-agg-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Partitions with one and with three TRUE values."
+catches = "parity, or the whole input"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when exactly one value is TRUE"
+cases = ["cases/translation/functions/boolxor-agg.boolxor-agg-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolxor-agg.boolxor-agg-over.t002"
+form = "sf.functions.boolxor-agg.boolxor-agg-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause."
+catches = "a running value"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when exactly one value is TRUE"
+cases = ["cases/translation/functions/boolxor-agg.boolxor-agg-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolxor-agg.boolxor-agg-over.t003"
+form = "sf.functions.boolxor-agg.boolxor-agg-over"
+kind = "null_argument"
+target = "expr"
+note = "NULL inside partitions, and a partition of only NULL."
+catches = "NULL read as TRUE (two TRUE values give FALSE)"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when exactly one value is TRUE"
+cases = ["cases/translation/functions/boolxor-agg.boolxor-agg-over.toml#t003_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.boolxor-agg.boolxor-agg-over.t004"
+form = "sf.functions.boolxor-agg.boolxor-agg-over"
+kind = "null_argument"
+target = "partition-expr"
+note = "NULL partition keys."
+catches = "NULL-keyed rows split or dropped"
+reading = "the page: numeric values are TRUE when non-zero; NULL values are ignored and an all-NULL or empty group gives NULL; no ORDER BY or frame as a window function; TRUE when exactly one value is TRUE"
+cases = ["cases/translation/functions/boolxor-agg.boolxor-agg-over.toml#t004_null_partition_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.case.end.t001"
+form = "sf.functions.case.end"
+kind = "type_combination"
+target = "BOOLEAN conditions"
+note = "Two true conditions, where the first must win."
+catches = "the last true condition winning"
+reading = "the page: the first true condition's result is returned; a NULL condition is not true; no ELSE gives NULL; in the simple form each value must match expr's type or be castable to it"
+cases = ["cases/translation/functions/case.end.toml#t001_first_true"]
+required = true
+
+[[obligation]]
+id = "sf.functions.case.end.t002"
+form = "sf.functions.case.end"
+kind = "type_combination"
+target = "NUMBER and NUMBER(10,2) results"
+note = "Results of different scales."
+catches = "the result type taken from the first branch (2 instead of 2.50)"
+reading = "the page: the first true condition's result is returned; a NULL condition is not true; no ELSE gives NULL; in the simple form each value must match expr's type or be castable to it"
+cases = ["cases/translation/functions/case.end.toml#t002_result_type"]
+required = true
+
+[[obligation]]
+id = "sf.functions.case.end.t003"
+form = "sf.functions.case.end"
+kind = "type_combination"
+target = "no ELSE"
+note = "No ELSE branch."
+catches = "a default other than NULL (0 or '')"
+reading = "the page: the first true condition's result is returned; a NULL condition is not true; no ELSE gives NULL; in the simple form each value must match expr's type or be castable to it"
+cases = ["cases/translation/functions/case.end.toml#t003_no_else"]
+required = true
+
+[[obligation]]
+id = "sf.functions.case.end.t004"
+form = "sf.functions.case.end"
+kind = "null_argument"
+target = "condition"
+note = "A NULL condition before a true one."
+catches = "a NULL condition treated as true, or the CASE returning NULL"
+reading = "the page: the first true condition's result is returned; a NULL condition is not true; no ELSE gives NULL; in the simple form each value must match expr's type or be castable to it"
+cases = ["cases/translation/functions/case.end.toml#t004_null_condition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.case.end.t005"
+form = "sf.functions.case.end"
+kind = "null_argument"
+target = "result"
+note = "A NULL result selected."
+catches = "the ELSE value instead of NULL"
+reading = "the page: the first true condition's result is returned; a NULL condition is not true; no ELSE gives NULL; in the simple form each value must match expr's type or be castable to it"
+cases = ["cases/translation/functions/case.end.toml#t005_null_result"]
+required = true
+
+[[obligation]]
+id = "sf.functions.case.end.t006"
+form = "sf.functions.case.end"
+kind = "type_combination"
+target = "searched CASE, mixed branch types"
+note = "A later true branch, a branch that would divide by zero, a NULL condition and no ELSE."
+catches = "eager evaluation (10/0 raising on the 0 row), the last true branch winning (0.5 for 4), a NULL condition treated as true, or a default for no ELSE"
+reading = "the page: the first true condition's result is returned; a NULL condition is not true; no ELSE gives NULL; in the simple form each value must match expr's type or be castable to it"
+cases = ["cases/translation/functions/case.end.toml#t006_branches"]
+required = true
+
+[[obligation]]
+id = "sf.functions.case.end.syntax.t001"
+form = "sf.functions.case.end.syntax"
+kind = "type_combination"
+target = "simple CASE on NUMBER"
+note = "The first matching value wins."
+catches = "the last match winning, or no match falling through to NULL with ELSE"
+reading = "the page: the first true condition's result is returned; a NULL condition is not true; no ELSE gives NULL; in the simple form each value must match expr's type or be castable to it"
+cases = ["cases/translation/functions/case.end.syntax.toml#t001_match"]
+required = true
+
+[[obligation]]
+id = "sf.functions.case.end.syntax.t002"
+form = "sf.functions.case.end.syntax"
+kind = "type_combination"
+target = "VARCHAR value against NUMBER expr"
+note = "Values cast to expr's type, as the page says."
+catches = "string comparison ('1.0' not matching 1)"
+reading = "the page: the first true condition's result is returned; a NULL condition is not true; no ELSE gives NULL; in the simple form each value must match expr's type or be castable to it"
+cases = ["cases/translation/functions/case.end.syntax.toml#t002_cast"]
+required = true
+
+[[obligation]]
+id = "sf.functions.case.end.syntax.t003"
+form = "sf.functions.case.end.syntax"
+kind = "null_argument"
+target = "expr"
+note = "NULL expr against a WHEN NULL branch."
+catches = "NULL matching WHEN NULL (DECODE semantics) instead of falling to ELSE"
+reading = "the page: the first true condition's result is returned; a NULL condition is not true; no ELSE gives NULL; in the simple form each value must match expr's type or be castable to it"
+cases = ["cases/translation/functions/case.end.syntax.toml#t003_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cast.cast-as.t001"
+form = "sf.functions.cast.cast-as"
+kind = "boundary"
+subkind = "rounding_scale"
+target = "rounding_scale"
+note = "Values with three fractional digits cast to NUMBER(5,1), including halves."
+catches = "truncation (1.2 for 1.25), or half-even rounding (1.2 for 1.25, -1.2 for -1.25)"
+reading = "the page: converts the expression to the target type; a scale too small to hold the value rounds it; a precision too small raises an error"
+cases = ["cases/translation/functions/cast.cast-as.toml#t001_scale"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cast.cast-as.t002"
+form = "sf.functions.cast.cast-as"
+kind = "type_combination"
+target = "DOUBLE to INTEGER"
+note = "Floating-point halves cast to INTEGER."
+catches = "truncation toward zero, or half-even rounding (2 for 2.5)"
+reading = "the page: converts the expression to the target type; a scale too small to hold the value rounds it; a precision too small raises an error"
+cases = ["cases/translation/functions/cast.cast-as.toml#t002_float_to_integer"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cast.cast-as.t003"
+form = "sf.functions.cast.cast-as"
+kind = "type_combination"
+target = "VARCHAR to DATE, NUMBER and BOOLEAN"
+note = "Text cast to a date, a number with spaces around it, and a boolean spelled yes. The yes is documented under TO_BOOLEAN; whether the spaces are accepted the page does not say, so the recording decides."
+catches = "the yes rejected (only true and false accepted), or the spaced number handled differently from Snowflake"
+reading = "the page: converts the expression to the target type; a scale too small to hold the value rounds it; a precision too small raises an error"
+cases = ["cases/translation/functions/cast.cast-as.toml#t003_text"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cast.cast-as.t004"
+form = "sf.functions.cast.cast-as"
+kind = "error_parity"
+target = "error"
+note = "A value with more integer digits than the target precision, which the page says raises an error."
+catches = "the value rounded, truncated or returned as NULL"
+reading = "the page: converts the expression to the target type; a scale too small to hold the value rounds it; a precision too small raises an error"
+cases = ["cases/translation/functions/cast.cast-as.toml#t004_precision"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cast.cast-as.t005"
+form = "sf.functions.cast.cast-as"
+kind = "null_argument"
+target = "source-expr"
+note = "A NULL source."
+catches = "an error, or a default value such as 0"
+reading = "the page: converts the expression to the target type; a scale too small to hold the value rounds it; a precision too small raises an error"
+cases = ["cases/translation/functions/cast.cast-as.toml#t005_null_source_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cast.double-colon.t001"
+form = "sf.functions.cast.double-colon"
+kind = "boundary"
+subkind = "rounding_scale"
+target = "rounding_scale"
+note = "Values with three fractional digits cast to NUMBER(5,1), including halves."
+catches = "truncation (1.2 for 1.25), or half-even rounding (1.2 for 1.25, -1.2 for -1.25)"
+reading = "the page: converts the expression to the target type; a scale too small to hold the value rounds it; a precision too small raises an error"
+cases = ["cases/translation/functions/cast.double-colon.toml#t001_scale"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cast.double-colon.t002"
+form = "sf.functions.cast.double-colon"
+kind = "type_combination"
+target = "DOUBLE to INTEGER"
+note = "Floating-point halves cast to INTEGER."
+catches = "truncation toward zero, or half-even rounding (2 for 2.5)"
+reading = "the page: converts the expression to the target type; a scale too small to hold the value rounds it; a precision too small raises an error"
+cases = ["cases/translation/functions/cast.double-colon.toml#t002_float_to_integer"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cast.double-colon.t003"
+form = "sf.functions.cast.double-colon"
+kind = "type_combination"
+target = "VARCHAR to DATE, NUMBER and BOOLEAN"
+note = "Text cast to a date, a number with spaces around it, and a boolean spelled yes. The yes is documented under TO_BOOLEAN; whether the spaces are accepted the page does not say, so the recording decides."
+catches = "the yes rejected (only true and false accepted), or the spaced number handled differently from Snowflake"
+reading = "the page: converts the expression to the target type; a scale too small to hold the value rounds it; a precision too small raises an error"
+cases = ["cases/translation/functions/cast.double-colon.toml#t003_text"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cast.double-colon.t004"
+form = "sf.functions.cast.double-colon"
+kind = "error_parity"
+target = "error"
+note = "A value with more integer digits than the target precision, which the page says raises an error."
+catches = "the value rounded, truncated or returned as NULL"
+reading = "the page: converts the expression to the target type; a scale too small to hold the value rounds it; a precision too small raises an error"
+cases = ["cases/translation/functions/cast.double-colon.toml#t004_precision"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cast.double-colon.t005"
+form = "sf.functions.cast.double-colon"
+kind = "null_argument"
+target = "source-expr"
+note = "A NULL source."
+catches = "an error, or a default value such as 0"
+reading = "the page: converts the expression to the target type; a scale too small to hold the value rounds it; a precision too small raises an error"
+cases = ["cases/translation/functions/cast.double-colon.toml#t005_null_source_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cbrt.t001"
+form = "sf.functions.cbrt"
+kind = "type_combination"
+target = "FLOAT"
+note = "CBRT of FLOAT values."
+catches = "SQRT computed, or POW(x, 1/3) giving NaN for negatives"
+reading = "the page: DECFLOAT returns DECFLOAT, otherwise FLOAT; the numeric category gives NUMBER and FLOAT arguments"
+cases = ["cases/translation/functions/cbrt.toml#t001_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cbrt.t002"
+form = "sf.functions.cbrt"
+kind = "type_combination"
+target = "NUMBER(12,4)"
+note = "CBRT of NUMBER(12,4), converted to FLOAT."
+catches = "SQRT computed, or POW(x, 1/3) giving NaN for negatives; or the result left fixed-point"
+reading = "the page: DECFLOAT returns DECFLOAT, otherwise FLOAT; the numeric category gives NUMBER and FLOAT arguments"
+cases = ["cases/translation/functions/cbrt.toml#t002_fixed"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cbrt.t003"
+form = "sf.functions.cbrt"
+kind = "type_combination"
+target = "DECFLOAT"
+note = "CBRT of DECFLOAT, which returns DECFLOAT. The result is cast to VARCHAR because the recorder cannot decode a DECFLOAT column."
+catches = "a DECFLOAT argument computed and returned as FLOAT"
+reading = "the page: DECFLOAT returns DECFLOAT, otherwise FLOAT; the numeric category gives NUMBER and FLOAT arguments"
+cases = ["cases/translation/functions/cbrt.toml#t003_decfloat"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cbrt.t004"
+form = "sf.functions.cbrt"
+kind = "null_argument"
+target = "x"
+note = "CBRT of NULL."
+catches = "NULL turned into 0 or NaN"
+reading = "the page: DECFLOAT returns DECFLOAT, otherwise FLOAT; the numeric category gives NUMBER and FLOAT arguments"
+cases = ["cases/translation/functions/cbrt.toml#t004_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cbrt.t005"
+form = "sf.functions.cbrt"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "Cube roots of negatives are negative."
+catches = "NaN from a fractional power of a negative"
+reading = "the page: DECFLOAT returns DECFLOAT, otherwise FLOAT; the numeric category gives NUMBER and FLOAT arguments"
+cases = ["cases/translation/functions/cbrt.toml#t005_neg"]
+required = true
+
+[[obligation]]
+id = "sf.functions.ceil.t001"
+form = "sf.functions.ceil"
+kind = "type_combination"
+target = "NUMBER(38,0)"
+note = "CEIL of integers with a negative scale."
+catches = "FLOOR or truncation used (CEIL(-2.5) is -2, CEIL(2.1) is 3); or a negative scale ignored"
+reading = "the page: input_expr \"must be one of the numeric data types, such as DECFLOAT, FLOAT, or NUMBER\"; scale_expr an integer, default 0, negative allowed; the result keeps the input type"
+cases = ["cases/translation/functions/ceil.toml#t001_int"]
+required = true
+
+[[obligation]]
+id = "sf.functions.ceil.t002"
+form = "sf.functions.ceil"
+kind = "type_combination"
+target = "NUMBER(12,4)"
+note = "CEIL of NUMBER(12,4) with and without a scale."
+catches = "FLOOR or truncation used (CEIL(-2.5) is -2, CEIL(2.1) is 3)"
+reading = "the page: input_expr \"must be one of the numeric data types, such as DECFLOAT, FLOAT, or NUMBER\"; scale_expr an integer, default 0, negative allowed; the result keeps the input type"
+cases = ["cases/translation/functions/ceil.toml#t002_dec"]
+required = true
+
+[[obligation]]
+id = "sf.functions.ceil.t003"
+form = "sf.functions.ceil"
+kind = "type_combination"
+target = "FLOAT"
+note = "CEIL of FLOAT returns FLOAT."
+catches = "FLOOR or truncation used (CEIL(-2.5) is -2, CEIL(2.1) is 3); or a NUMBER returned"
+reading = "the page: input_expr \"must be one of the numeric data types, such as DECFLOAT, FLOAT, or NUMBER\"; scale_expr an integer, default 0, negative allowed; the result keeps the input type"
+cases = ["cases/translation/functions/ceil.toml#t003_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.ceil.t004"
+form = "sf.functions.ceil"
+kind = "type_combination"
+target = "DECFLOAT"
+note = "CEIL of DECFLOAT returns DECFLOAT. The result is cast to VARCHAR because the recorder cannot decode a DECFLOAT column."
+catches = "computed in FLOAT: the fraction of a 20-digit value lost, or 0.28 at scale 2 rounded up to 0.29"
+reading = "the page: input_expr \"must be one of the numeric data types, such as DECFLOAT, FLOAT, or NUMBER\"; scale_expr an integer, default 0, negative allowed; the result keeps the input type"
+cases = ["cases/translation/functions/ceil.toml#t004_decfloat"]
+required = true
+
+[[obligation]]
+id = "sf.functions.ceil.t005"
+form = "sf.functions.ceil"
+kind = "null_argument"
+target = "input_expr"
+note = "CEIL of NULL."
+catches = "NULL turned into 0"
+reading = "the page: input_expr \"must be one of the numeric data types, such as DECFLOAT, FLOAT, or NUMBER\"; scale_expr an integer, default 0, negative allowed; the result keeps the input type"
+cases = ["cases/translation/functions/ceil.toml#t005_null_x"]
+required = true
+
+[[obligation]]
+id = "sf.functions.ceil.t006"
+form = "sf.functions.ceil"
+kind = "null_argument"
+target = "scale_expr"
+note = "CEIL with a NULL scale."
+catches = "the NULL scale read as 0"
+reading = "the page: input_expr \"must be one of the numeric data types, such as DECFLOAT, FLOAT, or NUMBER\"; scale_expr an integer, default 0, negative allowed; the result keeps the input type"
+cases = ["cases/translation/functions/ceil.toml#t006_null_scale"]
+required = true
+
+[[obligation]]
+id = "sf.functions.ceil.t007"
+form = "sf.functions.ceil"
+kind = "boundary"
+subkind = "rounding_scale"
+target = "rounding_scale"
+note = "Scales past the input scale and far negative."
+catches = "scale capped or wrapping"
+reading = "the page: input_expr \"must be one of the numeric data types, such as DECFLOAT, FLOAT, or NUMBER\"; scale_expr an integer, default 0, negative allowed; the result keeps the input type"
+cases = ["cases/translation/functions/ceil.toml#t007_scale_past"]
+required = true
+
+[[obligation]]
+id = "sf.functions.ceil.t008"
+form = "sf.functions.ceil"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "Negative values at the rounding step."
+catches = "FLOOR or truncation used (CEIL(-2.5) is -2, CEIL(2.1) is 3)"
+reading = "the page: input_expr \"must be one of the numeric data types, such as DECFLOAT, FLOAT, or NUMBER\"; scale_expr an integer, default 0, negative allowed; the result keeps the input type"
+cases = ["cases/translation/functions/ceil.toml#t008_negatives"]
+required = true
+
+[[obligation]]
+id = "sf.functions.charindex.t001"
+form = "sf.functions.charindex"
+kind = "type_combination"
+target = "VARCHAR, VARCHAR"
+note = "Search for a substring after multi-byte characters."
+catches = "arguments swapped (searching the needle in the haystack), a 0-based result, or a byte offset (5 instead of 3 for \u00e9\u00e9l)"
+reading = "the page: and are both strings or both binary; is 1-based; not found or start past the end gives 0; an empty gives 1; NULL gives NULL"
+cases = ["cases/translation/functions/charindex.toml#t001_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.charindex.t002"
+form = "sf.functions.charindex"
+kind = "type_combination"
+target = "VARCHAR, VARCHAR, NUMBER"
+note = "A start position past the first match."
+catches = "the start position ignored (2 instead of 4) or the result counted from the start position"
+reading = "the page: and are both strings or both binary; is 1-based; not found or start past the end gives 0; an empty gives 1; NULL gives NULL"
+cases = ["cases/translation/functions/charindex.toml#t002_start_pos"]
+required = true
+
+[[obligation]]
+id = "sf.functions.charindex.t003"
+form = "sf.functions.charindex"
+kind = "type_combination"
+target = "BINARY, BINARY"
+note = "Search in binary values."
+catches = "a hex-text offset (3 instead of 2)"
+reading = "the page: and are both strings or both binary; is 1-based; not found or start past the end gives 0; an empty gives 1; NULL gives NULL"
+cases = ["cases/translation/functions/charindex.toml#t003_binary"]
+required = true
+
+[[obligation]]
+id = "sf.functions.charindex.t004"
+form = "sf.functions.charindex"
+kind = "null_argument"
+target = "expr1"
+note = "NULL needle next to a set one."
+catches = "NULL read as an empty needle (1 instead of NULL)"
+reading = "the page: and are both strings or both binary; is 1-based; not found or start past the end gives 0; an empty gives 1; NULL gives NULL"
+cases = ["cases/translation/functions/charindex.toml#t004_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.charindex.t005"
+form = "sf.functions.charindex"
+kind = "null_argument"
+target = "expr2"
+note = "NULL haystack next to a set one."
+catches = "NULL read as an empty string (0 instead of NULL)"
+reading = "the page: and are both strings or both binary; is 1-based; not found or start past the end gives 0; an empty gives 1; NULL gives NULL"
+cases = ["cases/translation/functions/charindex.toml#t005_null_expr2"]
+required = true
+
+[[obligation]]
+id = "sf.functions.charindex.t006"
+form = "sf.functions.charindex"
+kind = "boundary"
+subkind = "empty_string"
+target = "empty_string"
+note = "An empty needle, which the page says gives 1."
+catches = "NULL or 0 for an empty needle"
+reading = "the page: and are both strings or both binary; is 1-based; not found or start past the end gives 0; an empty gives 1; NULL gives NULL"
+cases = ["cases/translation/functions/charindex.toml#t006_empty_needle"]
+required = true
+
+[[obligation]]
+id = "sf.functions.charindex.t007"
+form = "sf.functions.charindex"
+kind = "null_argument"
+target = "start_pos"
+note = "NULL start next to a set one."
+catches = "NULL read as the default 1"
+reading = "the page: and are both strings or both binary; is 1-based; not found or start past the end gives 0; an empty gives 1; NULL gives NULL"
+cases = ["cases/translation/functions/charindex.toml#t007_null_start_pos"]
+required = true
+
+[[obligation]]
+id = "sf.functions.charindex.t008"
+form = "sf.functions.charindex"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "A start position past the end of the string."
+catches = "an error or the last match instead of 0"
+reading = "the page: and are both strings or both binary; is 1-based; not found or start past the end gives 0; an empty gives 1; NULL gives NULL"
+cases = ["cases/translation/functions/charindex.toml#t008_start_past_end"]
+required = true
+
+[[obligation]]
+id = "sf.functions.charindex.t009"
+form = "sf.functions.charindex"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "Start positions 0 and -1: records what Snowflake returns."
+catches = "0 read other than as Snowflake does (as 1, as an error, or as no match), or a negative start counted from the end"
+reading = "the page: and are both strings or both binary; is 1-based; not found or start past the end gives 0; an empty gives 1; NULL gives NULL"
+cases = ["cases/translation/functions/charindex.toml#t009_start_zero_negative"]
+required = true
+
+[[obligation]]
+id = "sf.functions.check-json.t001"
+form = "sf.functions.check-json"
+kind = "type_combination"
+target = "VARCHAR"
+note = "Valid JSON documents of several kinds, including a bare string and a bare number."
+catches = "a bare scalar rejected (only objects and arrays accepted)"
+reading = "the page: NULL when the input is valid JSON or NULL; otherwise a string describing the parse error. The page does not give the message text, so the cases measure whether the result is NULL"
+cases = ["cases/translation/functions/check-json.toml#t001_valid"]
+required = true
+
+[[obligation]]
+id = "sf.functions.check-json.t002"
+form = "sf.functions.check-json"
+kind = "type_combination"
+target = "VARCHAR"
+note = "Malformed documents: a missing value, a trailing comma and an unclosed array."
+catches = "NULL returned for an invalid document, or an error raised instead of a message"
+reading = "the page: NULL when the input is valid JSON or NULL; otherwise a string describing the parse error. The page does not give the message text, so the cases measure whether the result is NULL"
+cases = ["cases/translation/functions/check-json.toml#t002_invalid"]
+required = true
+
+[[obligation]]
+id = "sf.functions.check-json.t003"
+form = "sf.functions.check-json"
+kind = "type_combination"
+target = "VARIANT holding a string"
+note = "A VARIANT holding a string, which the page says is checked as JSON text."
+catches = "the VARIANT treated as already-valid JSON (NULL for the malformed text)"
+reading = "the page: NULL when the input is valid JSON or NULL; otherwise a string describing the parse error. The page does not give the message text, so the cases measure whether the result is NULL"
+cases = ["cases/translation/functions/check-json.toml#t003_variant"]
+required = true
+
+[[obligation]]
+id = "sf.functions.check-json.t004"
+form = "sf.functions.check-json"
+kind = "null_argument"
+target = "string-or-variant-expr"
+note = "A SQL NULL, which the page treats as valid."
+catches = "an error message returned for NULL"
+reading = "the page: NULL when the input is valid JSON or NULL; otherwise a string describing the parse error. The page does not give the message text, so the cases measure whether the result is NULL"
+cases = ["cases/translation/functions/check-json.toml#t004_null_string_or_variant_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.check-xml.check-xml.t001"
+form = "sf.functions.check-xml.check-xml"
+kind = "type_combination"
+target = "VARCHAR"
+note = "Valid documents."
+catches = "a valid document reported as an error"
+reading = "the page: NULL when the input is valid XML; otherwise a VARCHAR describing the error. The page does not give the message text, so the cases measure whether the result is NULL"
+cases = ["cases/translation/functions/check-xml.check-xml.toml#t001_valid"]
+required = true
+
+[[obligation]]
+id = "sf.functions.check-xml.check-xml.t002"
+form = "sf.functions.check-xml.check-xml"
+kind = "type_combination"
+target = "VARCHAR"
+note = "Mismatched and unclosed tags."
+catches = "NULL returned for invalid XML, or an error raised instead of a message"
+reading = "the page: NULL when the input is valid XML; otherwise a VARCHAR describing the error. The page does not give the message text, so the cases measure whether the result is NULL"
+cases = ["cases/translation/functions/check-xml.check-xml.toml#t002_invalid"]
+required = true
+
+[[obligation]]
+id = "sf.functions.check-xml.check-xml.t003"
+form = "sf.functions.check-xml.check-xml"
+kind = "null_argument"
+target = "string-containing-xml"
+note = "A SQL NULL, which the page says gives NULL, as for a valid document."
+catches = "an error, or an error message returned for NULL"
+reading = "the page: NULL when the input is valid XML; otherwise a VARCHAR describing the error. The page does not give the message text, so the cases measure whether the result is NULL"
+cases = ["cases/translation/functions/check-xml.check-xml.toml#t003_null_string_containing_xml"]
+required = true
+
+[[obligation]]
+id = "sf.functions.check-xml.check-xml-str.t001"
+form = "sf.functions.check-xml.check-xml-str"
+kind = "type_combination"
+target = "VARCHAR"
+note = "Valid documents."
+catches = "a valid document reported as an error"
+reading = "the page: NULL when the input is valid XML; otherwise a VARCHAR describing the error. The page does not give the message text, so the cases measure whether the result is NULL"
+cases = ["cases/translation/functions/check-xml.check-xml-str.toml#t001_valid"]
+required = true
+
+[[obligation]]
+id = "sf.functions.check-xml.check-xml-str.t002"
+form = "sf.functions.check-xml.check-xml-str"
+kind = "type_combination"
+target = "VARCHAR"
+note = "Mismatched and unclosed tags."
+catches = "NULL returned for invalid XML, or an error raised instead of a message"
+reading = "the page: NULL when the input is valid XML; otherwise a VARCHAR describing the error. The page does not give the message text, so the cases measure whether the result is NULL"
+cases = ["cases/translation/functions/check-xml.check-xml-str.toml#t002_invalid"]
+required = true
+
+[[obligation]]
+id = "sf.functions.check-xml.check-xml-str.t003"
+form = "sf.functions.check-xml.check-xml-str"
+kind = "null_argument"
+target = "string-containing-xml"
+note = "A SQL NULL, which the page says gives NULL, as for a valid document."
+catches = "an error, or an error message returned for NULL"
+reading = "the page: NULL when the input is valid XML; otherwise a VARCHAR describing the error. The page does not give the message text, so the cases measure whether the result is NULL"
+cases = ["cases/translation/functions/check-xml.check-xml-str.toml#t003_null_string_containing_xml"]
+required = true
+
+[[obligation]]
+id = "sf.functions.chr.t001"
+form = "sf.functions.chr"
+kind = "type_combination"
+target = "NUMBER"
+note = "CHR of ASCII and non-ASCII code points."
+catches = "a byte or Latin-1 conversion (233 and 8364 not giving \u00e9 and \u20ac), or an error past 255"
+reading = "the page: is a Unicode code point; returns VARCHAR"
+cases = ["cases/translation/functions/chr.toml#t001_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.chr.t002"
+form = "sf.functions.chr"
+kind = "null_argument"
+target = "input"
+note = "NULL code point next to a set one."
+catches = "NULL read as 0 (a NUL character instead of NULL)"
+reading = "the page: is a Unicode code point; returns VARCHAR"
+cases = ["cases/translation/functions/chr.toml#t002_null_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.chr.t003"
+form = "sf.functions.chr"
+kind = "error_parity"
+target = "error"
+note = "A negative code point: records what Snowflake returns there; it moves to boundary if Snowflake returns a value instead of an error."
+catches = "an empty string or a wrapped code point where Snowflake raises an error"
+reading = "the page: is a Unicode code point; returns VARCHAR"
+cases = ["cases/translation/functions/chr.toml#t003_negative"]
+required = true
+
+[[obligation]]
+id = "sf.functions.coalesce.t001"
+form = "sf.functions.coalesce"
+kind = "type_combination"
+target = "NUMBER..."
+note = "The first non-NULL among several."
+catches = "the last non-NULL, or stopping after two arguments"
+reading = "the page: the first non-NULL argument; arguments are implicitly converted to a common type, numeric when one argument is numeric ('17' and 1 give 17); an unconvertible string fails"
+cases = ["cases/translation/functions/coalesce.toml#t001_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.coalesce.t002"
+form = "sf.functions.coalesce"
+kind = "type_combination"
+target = "VARCHAR, NUMBER"
+note = "A numeric string with a number, which converts to NUMBER."
+catches = "a VARCHAR result ('17.50' kept as text) instead of Snowflake's numeric conversion"
+reading = "the page: the first non-NULL argument; arguments are implicitly converted to a common type, numeric when one argument is numeric ('17' and 1 give 17); an unconvertible string fails"
+cases = ["cases/translation/functions/coalesce.toml#t002_mixed"]
+required = true
+
+[[obligation]]
+id = "sf.functions.coalesce.t003"
+form = "sf.functions.coalesce"
+kind = "type_combination"
+target = "NUMBER(10,2), NUMBER"
+note = "Scales widened."
+catches = "the first argument's scale lost (1.25 returned as 1)"
+reading = "the page: the first non-NULL argument; arguments are implicitly converted to a common type, numeric when one argument is numeric ('17' and 1 give 17); an unconvertible string fails"
+cases = ["cases/translation/functions/coalesce.toml#t003_scale"]
+required = true
+
+[[obligation]]
+id = "sf.functions.coalesce.t004"
+form = "sf.functions.coalesce"
+kind = "null_argument"
+target = "expr"
+note = "Every argument NULL."
+catches = "a default value (0 or '') instead of NULL"
+reading = "the page: the first non-NULL argument; arguments are implicitly converted to a common type, numeric when one argument is numeric ('17' and 1 give 17); an unconvertible string fails"
+cases = ["cases/translation/functions/coalesce.toml#t004_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.coalesce.t005"
+form = "sf.functions.coalesce"
+kind = "error_parity"
+target = "error"
+note = "A non-numeric string with a number, which the page says fails."
+catches = "the string returned as text"
+reading = "the page: the first non-NULL argument; arguments are implicitly converted to a common type, numeric when one argument is numeric ('17' and 1 give 17); an unconvertible string fails"
+cases = ["cases/translation/functions/coalesce.toml#t005_unconvertible"]
+required = true
+
+[[obligation]]
+id = "sf.functions.collate.collate.t001"
+form = "sf.functions.collate.collate"
+kind = "type_combination"
+target = "VARCHAR, 'en-ci'"
+note = "Equality under a case-insensitive collation."
+catches = "the collation dropped (FALSE for ABC = abc)"
+reading = "the page: COLLATE copies a string with a new collation specification; when used as an infix operator the specification must be a constant string"
+cases = ["cases/translation/functions/collate.collate.toml#t001_case_insensitive"]
+required = true
+
+[[obligation]]
+id = "sf.functions.collate.collate.t002"
+form = "sf.functions.collate.collate"
+kind = "type_combination"
+target = "VARCHAR, 'en'"
+note = "Ordering under a locale collation."
+catches = "binary order kept ('a' < 'B' is FALSE in byte order, TRUE in en), or a case-insensitive comparison standing in for 'en' (a < A)"
+reading = "the page: COLLATE copies a string with a new collation specification; when used as an infix operator the specification must be a constant string"
+cases = ["cases/translation/functions/collate.collate.toml#t002_locale_order"]
+required = true
+
+[[obligation]]
+id = "sf.functions.collate.collate.t003"
+form = "sf.functions.collate.collate"
+kind = "type_combination"
+target = "VARCHAR, 'en-ci-rtrim'"
+note = "The returned string itself, with trailing blanks."
+catches = "the string case-folded or trimmed by the copy (the page: \"The string itself is unchanged\")"
+reading = "the page: COLLATE copies a string with a new collation specification; when used as an infix operator the specification must be a constant string"
+cases = ["cases/translation/functions/collate.collate.toml#t003_value_unchanged"]
+required = true
+
+[[obligation]]
+id = "sf.functions.collate.collate.t004"
+form = "sf.functions.collate.collate"
+kind = "null_argument"
+target = "string_expression"
+note = "NULL string next to a set one."
+catches = "NULL compared as an empty string"
+reading = "the page: COLLATE copies a string with a new collation specification; when used as an infix operator the specification must be a constant string"
+cases = ["cases/translation/functions/collate.collate.toml#t004_null_string_expression"]
+required = true
+
+[[obligation]]
+id = "sf.functions.collate.collate.t005"
+form = "sf.functions.collate.collate"
+kind = "type_combination"
+target = "VARCHAR, 'en-ci-rtrim'"
+note = "Equality under an rtrim collation, beside the same comparison without rtrim."
+catches = "the rtrim specifier dropped in comparisons (FALSE for 'ab ' = 'AB')"
+reading = "the page: COLLATE copies a string with a new collation specification; when used as an infix operator the specification must be a constant string"
+cases = ["cases/translation/functions/collate.collate.toml#t005_rtrim_compare"]
+required = true
+
+[[obligation]]
+id = "sf.functions.collate.collate.syntax.t001"
+form = "sf.functions.collate.collate.syntax"
+kind = "type_combination"
+target = "VARCHAR, 'en-ci'"
+note = "Equality under a case-insensitive collation."
+catches = "the collation dropped (FALSE for ABC = abc)"
+reading = "the page: COLLATE copies a string with a new collation specification; when used as an infix operator the specification must be a constant string"
+cases = ["cases/translation/functions/collate.collate.syntax.toml#t001_case_insensitive"]
+required = true
+
+[[obligation]]
+id = "sf.functions.collate.collate.syntax.t002"
+form = "sf.functions.collate.collate.syntax"
+kind = "type_combination"
+target = "VARCHAR, 'en'"
+note = "Ordering under a locale collation."
+catches = "binary order kept ('a' < 'B' is FALSE in byte order, TRUE in en), or a case-insensitive comparison standing in for 'en' (a < A)"
+reading = "the page: COLLATE copies a string with a new collation specification; when used as an infix operator the specification must be a constant string"
+cases = ["cases/translation/functions/collate.collate.syntax.toml#t002_locale_order"]
+required = true
+
+[[obligation]]
+id = "sf.functions.collate.collate.syntax.t003"
+form = "sf.functions.collate.collate.syntax"
+kind = "type_combination"
+target = "VARCHAR, 'en-ci-rtrim'"
+note = "The returned string itself, with trailing blanks."
+catches = "the string case-folded or trimmed by the copy (the page: \"The string itself is unchanged\")"
+reading = "the page: COLLATE copies a string with a new collation specification; when used as an infix operator the specification must be a constant string"
+cases = ["cases/translation/functions/collate.collate.syntax.toml#t003_value_unchanged"]
+required = true
+
+[[obligation]]
+id = "sf.functions.collate.collate.syntax.t004"
+form = "sf.functions.collate.collate.syntax"
+kind = "null_argument"
+target = "string_expression"
+note = "NULL string next to a set one."
+catches = "NULL compared as an empty string"
+reading = "the page: COLLATE copies a string with a new collation specification; when used as an infix operator the specification must be a constant string"
+cases = ["cases/translation/functions/collate.collate.syntax.toml#t004_null_string_expression"]
+required = true
+
+[[obligation]]
+id = "sf.functions.collate.collate.syntax.t005"
+form = "sf.functions.collate.collate.syntax"
+kind = "type_combination"
+target = "VARCHAR, 'en-ci-rtrim'"
+note = "Equality under an rtrim collation, beside the same comparison without rtrim."
+catches = "the rtrim specifier dropped in comparisons (FALSE for 'ab ' = 'AB')"
+reading = "the page: COLLATE copies a string with a new collation specification; when used as an infix operator the specification must be a constant string"
+cases = ["cases/translation/functions/collate.collate.syntax.toml#t005_rtrim_compare"]
+required = true
+
+[[obligation]]
+id = "sf.functions.collation.t001"
+form = "sf.functions.collation"
+kind = "type_combination"
+target = "VARCHAR"
+note = "COLLATION of the same string under two explicit collations."
+catches = "a fixed or empty answer, or the collation specification dropped or normalised (de-ci read as de)"
+reading = "the page: returns a VARCHAR holding the collation specification of the expression"
+cases = ["cases/translation/functions/collation.toml#t001_collated"]
+required = true
+
+[[obligation]]
+id = "sf.functions.collation.t002"
+form = "sf.functions.collation"
+kind = "null_argument"
+target = "expression"
+note = "NULL collated value."
+catches = "records whatever Snowflake returns for a NULL collated value ('de' or NULL)"
+reading = "the page: returns a VARCHAR holding the collation specification of the expression"
+cases = ["cases/translation/functions/collation.toml#t002_null_expression"]
+required = true
+
+[[obligation]]
+id = "sf.functions.compress.t001"
+form = "sf.functions.compress"
+kind = "type_combination"
+target = "VARCHAR, 'SNAPPY'"
+note = "COMPRESS of the page's example and of multi-byte text with SNAPPY."
+catches = "another codec or framing (the page gives 0920536E6F77666C616B65)"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; returns BINARY"
+cases = ["cases/translation/functions/compress.toml#t001_varchar_snappy"]
+required = true
+
+[[obligation]]
+id = "sf.functions.compress.t002"
+form = "sf.functions.compress"
+kind = "type_combination"
+target = "BINARY, 'SNAPPY'"
+note = "COMPRESS of a BINARY value."
+catches = "the binary read as its hex text"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; returns BINARY"
+cases = ["cases/translation/functions/compress.toml#t002_binary"]
+required = true
+
+[[obligation]]
+id = "sf.functions.compress.t003"
+form = "sf.functions.compress"
+kind = "type_combination"
+target = "VARCHAR, method"
+note = "Each method, with a level and lower-case names, read back through DECOMPRESS_STRING."
+catches = "a method name or level rejected, or compress and decompress disagreeing on the codec or its framing"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; returns BINARY"
+cases = ["cases/translation/functions/compress.toml#t003_methods"]
+required = true
+
+[[obligation]]
+id = "sf.functions.compress.t004"
+form = "sf.functions.compress"
+kind = "null_argument"
+target = "input"
+note = "NULL input next to a set one."
+catches = "NULL compressed as an empty value"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; returns BINARY"
+cases = ["cases/translation/functions/compress.toml#t004_null_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.compress.t005"
+form = "sf.functions.compress"
+kind = "null_argument"
+target = "method"
+note = "NULL method."
+catches = "NULL read as a default method"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; returns BINARY"
+cases = ["cases/translation/functions/compress.toml#t005_null_method"]
+required = true
+
+[[obligation]]
+id = "sf.functions.compress.t006"
+form = "sf.functions.compress"
+kind = "boundary"
+subkind = "empty_string"
+target = "empty_string"
+note = "The empty string with SNAPPY."
+catches = "NULL for empty input instead of a length-0 block"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; returns BINARY"
+cases = ["cases/translation/functions/compress.toml#t006_empty"]
+required = true
+
+[[obligation]]
+id = "sf.functions.compress.t007"
+form = "sf.functions.compress"
+kind = "error_parity"
+target = "error"
+note = "An unknown method, which the page says fails; Snowflake refuses it when compiling (SQLSTATE 42P19)."
+catches = "a fallback codec or NULL instead of an error"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; returns BINARY"
+cases = ["cases/translation/functions/compress.toml#t007_unknown_method"]
+required = true
+
+[[obligation]]
+id = "sf.functions.concat-ws.t001"
+form = "sf.functions.concat-ws"
+kind = "type_combination"
+target = "VARCHAR..."
+note = "A multi-character separator between three strings."
+catches = "the separator after the last argument, the separator taken as the first value, or arguments reordered"
+reading = "the page: the inputs must be all strings or all binary; the result has their type; any NULL input gives NULL; \"CONCAT_WS puts separators between arguments, not after the last\""
+cases = ["cases/translation/functions/concat-ws.toml#t001_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.concat-ws.t002"
+form = "sf.functions.concat-ws"
+kind = "type_combination"
+target = "VARCHAR, VARCHAR"
+note = "One argument after the separator."
+catches = "a separator appended"
+reading = "the page: the inputs must be all strings or all binary; the result has their type; any NULL input gives NULL; \"CONCAT_WS puts separators between arguments, not after the last\""
+cases = ["cases/translation/functions/concat-ws.toml#t002_single"]
+required = true
+
+[[obligation]]
+id = "sf.functions.concat-ws.t003"
+form = "sf.functions.concat-ws"
+kind = "type_combination"
+target = "BINARY..."
+note = "Binary separator and values."
+catches = "hex text concatenated as VARCHAR (the length column tells 3 bytes from 6 hex characters even when both render as hex)"
+reading = "the page: the inputs must be all strings or all binary; the result has their type; any NULL input gives NULL; \"CONCAT_WS puts separators between arguments, not after the last\""
+cases = ["cases/translation/functions/concat-ws.toml#t003_binary"]
+required = true
+
+[[obligation]]
+id = "sf.functions.concat-ws.t004"
+form = "sf.functions.concat-ws"
+kind = "null_argument"
+target = "separator"
+note = "NULL separator."
+catches = "NULL read as an empty separator (ab instead of NULL)"
+reading = "the page: the inputs must be all strings or all binary; the result has their type; any NULL input gives NULL; \"CONCAT_WS puts separators between arguments, not after the last\""
+cases = ["cases/translation/functions/concat-ws.toml#t004_null_separator"]
+required = true
+
+[[obligation]]
+id = "sf.functions.concat-ws.t005"
+form = "sf.functions.concat-ws"
+kind = "null_argument"
+target = "expression"
+note = "NULL value between set ones."
+catches = "NULL skipped (Postgres CONCAT_WS gives a,c) instead of NULL"
+reading = "the page: the inputs must be all strings or all binary; the result has their type; any NULL input gives NULL; \"CONCAT_WS puts separators between arguments, not after the last\""
+cases = ["cases/translation/functions/concat-ws.toml#t005_null_expression"]
+required = true
+
+[[obligation]]
+id = "sf.functions.concat-ws.t006"
+form = "sf.functions.concat-ws"
+kind = "boundary"
+subkind = "empty_string"
+target = "empty_string"
+note = "Empty values and an empty separator."
+catches = "empty values dropped (a,c instead of a,,c)"
+reading = "the page: the inputs must be all strings or all binary; the result has their type; any NULL input gives NULL; \"CONCAT_WS puts separators between arguments, not after the last\""
+cases = ["cases/translation/functions/concat-ws.toml#t006_empty"]
+required = true
+
+[[obligation]]
+id = "sf.functions.concat.concat.t001"
+form = "sf.functions.concat.concat"
+kind = "type_combination"
+target = "VARCHAR..."
+note = "Three strings in order."
+catches = "arguments reordered or a separator inserted"
+reading = "the page: the inputs must be all strings or all binary; the result has their type; any NULL input gives NULL"
+cases = ["cases/translation/functions/concat.concat.toml#t001_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.concat.concat.t002"
+form = "sf.functions.concat.concat"
+kind = "type_combination"
+target = "BINARY..."
+note = "Three binary values."
+catches = "the hex texts concatenated as a VARCHAR (the length column tells 3 bytes from 6 hex characters even when both render as hex)"
+reading = "the page: the inputs must be all strings or all binary; the result has their type; any NULL input gives NULL"
+cases = ["cases/translation/functions/concat.concat.toml#t002_binary"]
+required = true
+
+[[obligation]]
+id = "sf.functions.concat.concat.t003"
+form = "sf.functions.concat.concat"
+kind = "null_argument"
+target = "expr"
+note = "NULL in the middle argument."
+catches = "NULL skipped or read as '' (Postgres CONCAT gives 'ac')"
+reading = "the page: the inputs must be all strings or all binary; the result has their type; any NULL input gives NULL"
+cases = ["cases/translation/functions/concat.concat.toml#t003_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.concat.concat.t004"
+form = "sf.functions.concat.concat"
+kind = "boundary"
+subkind = "empty_string"
+target = "empty_string"
+note = "Empty strings between set ones."
+catches = "an empty string treated as NULL (NULL instead of 'ac')"
+reading = "the page: the inputs must be all strings or all binary; the result has their type; any NULL input gives NULL"
+cases = ["cases/translation/functions/concat.concat.toml#t004_empty"]
+required = true
+
+[[obligation]]
+id = "sf.functions.concat.concat.t005"
+form = "sf.functions.concat.concat"
+kind = "type_combination"
+target = "VARCHAR"
+note = "CONCAT of one argument."
+catches = "one argument rejected or NULL"
+reading = "the page: the inputs must be all strings or all binary; the result has their type; any NULL input gives NULL"
+cases = ["cases/translation/functions/concat.concat.toml#t005_single"]
+required = true
+
+[[obligation]]
+id = "sf.functions.concat.concat-subscript-dot.t001"
+form = "sf.functions.concat.concat-subscript-dot"
+kind = "type_combination"
+target = "VARCHAR..."
+note = "Three strings in order."
+catches = "arguments reordered or a separator inserted"
+reading = "the page: the inputs must be all strings or all binary; the result has their type; any NULL input gives NULL"
+cases = ["cases/translation/functions/concat.concat-subscript-dot.toml#t001_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.concat.concat-subscript-dot.t002"
+form = "sf.functions.concat.concat-subscript-dot"
+kind = "type_combination"
+target = "BINARY..."
+note = "Three binary values."
+catches = "the hex texts concatenated as a VARCHAR (the length column tells 3 bytes from 6 hex characters even when both render as hex)"
+reading = "the page: the inputs must be all strings or all binary; the result has their type; any NULL input gives NULL"
+cases = ["cases/translation/functions/concat.concat-subscript-dot.toml#t002_binary"]
+required = true
+
+[[obligation]]
+id = "sf.functions.concat.concat-subscript-dot.t003"
+form = "sf.functions.concat.concat-subscript-dot"
+kind = "null_argument"
+target = "expr"
+note = "NULL in the middle argument."
+catches = "NULL skipped or read as '' (Postgres CONCAT gives 'ac')"
+reading = "the page: the inputs must be all strings or all binary; the result has their type; any NULL input gives NULL"
+cases = ["cases/translation/functions/concat.concat-subscript-dot.toml#t003_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.concat.concat-subscript-dot.t004"
+form = "sf.functions.concat.concat-subscript-dot"
+kind = "boundary"
+subkind = "empty_string"
+target = "empty_string"
+note = "Empty strings between set ones."
+catches = "an empty string treated as NULL (NULL instead of 'ac')"
+reading = "the page: the inputs must be all strings or all binary; the result has their type; any NULL input gives NULL"
+cases = ["cases/translation/functions/concat.concat-subscript-dot.toml#t004_empty"]
+required = true
+
+[[obligation]]
+id = "sf.functions.conditional-change-event.t001"
+form = "sf.functions.conditional-change-event"
+kind = "type_combination"
+target = "NUMBER"
+note = "Values that repeat and change."
+catches = "repeats counted as changes, or the counter starting at 1"
+reading = "the page: a window event number incremented when the value differs from the previous row, computed as CONDITIONAL_TRUE_EVENT(expr != LAG(expr))"
+cases = ["cases/translation/functions/conditional-change-event.toml#t001_changes"]
+required = true
+
+[[obligation]]
+id = "sf.functions.conditional-change-event.t002"
+form = "sf.functions.conditional-change-event"
+kind = "null_argument"
+target = "expr1"
+note = "NULL values, where the page formula compares with NULL and so counts no change, giving 0 throughout."
+catches = "a change to or from NULL counted, or NULLs skipped so that 10 to 40 counts as a change"
+reading = "the page: a window event number incremented when the value differs from the previous row, computed as CONDITIONAL_TRUE_EVENT(expr != LAG(expr))"
+cases = ["cases/translation/functions/conditional-change-event.toml#t002_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.conditional-true-event.t001"
+form = "sf.functions.conditional-true-event"
+kind = "type_combination"
+target = "BOOLEAN"
+note = "A condition true on some rows, NULL on others."
+catches = "the counter starting at 1, incremented on the following row, or NULL counted as TRUE"
+reading = "the page: a window event number per row, starting at 0 and incremented on each row where the boolean expression is TRUE"
+cases = ["cases/translation/functions/conditional-true-event.toml#t001_events"]
+required = true
+
+[[obligation]]
+id = "sf.functions.conditional-true-event.t002"
+form = "sf.functions.conditional-true-event"
+kind = "type_combination"
+target = "BOOLEAN using LAG"
+note = "A condition using LAG with the same OVER clause, as the page allows."
+catches = "the LAG evaluated over a different window"
+reading = "the page: a window event number per row, starting at 0 and incremented on each row where the boolean expression is TRUE"
+cases = ["cases/translation/functions/conditional-true-event.toml#t002_with_lag"]
+required = true
+
+[[obligation]]
+id = "sf.functions.conditional-true-event.t003"
+form = "sf.functions.conditional-true-event"
+kind = "null_argument"
+target = "expr1"
+note = "A condition that is NULL on some rows, which is not TRUE."
+catches = "NULL counted as TRUE, or NULL returned for those rows"
+reading = "the page: a window event number per row, starting at 0 and incremented on each row where the boolean expression is TRUE"
+cases = ["cases/translation/functions/conditional-true-event.toml#t003_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.contains.t001"
+form = "sf.functions.contains"
+kind = "type_combination"
+target = "VARCHAR, VARCHAR"
+note = "CONTAINS with the arguments in both orders and in another case."
+catches = "arguments swapped (TRUE for CONTAINS(nan, banana)) or a case-insensitive match"
+reading = "the page: CONTAINS(, ) tests against ; NULL if either is NULL"
+cases = ["cases/translation/functions/contains.toml#t001_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.contains.t002"
+form = "sf.functions.contains"
+kind = "null_argument"
+target = "expr1"
+note = "NULL first argument."
+catches = "NULL read as an empty string (FALSE instead of NULL)"
+reading = "the page: CONTAINS(, ) tests against ; NULL if either is NULL"
+cases = ["cases/translation/functions/contains.toml#t002_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.contains.t003"
+form = "sf.functions.contains"
+kind = "null_argument"
+target = "expr2"
+note = "NULL second argument."
+catches = "NULL read as an empty string (TRUE instead of NULL)"
+reading = "the page: CONTAINS(, ) tests against ; NULL if either is NULL"
+cases = ["cases/translation/functions/contains.toml#t003_null_expr2"]
+required = true
+
+[[obligation]]
+id = "sf.functions.contains.t004"
+form = "sf.functions.contains"
+kind = "boundary"
+subkind = "empty_string"
+target = "empty_string"
+note = "An empty search string, and an empty subject."
+catches = "FALSE for the empty search string"
+reading = "the page: CONTAINS(, ) tests against ; NULL if either is NULL"
+cases = ["cases/translation/functions/contains.toml#t004_empty"]
+required = true
+
+[[obligation]]
+id = "sf.functions.convert-timezone.t001"
+form = "sf.functions.convert-timezone"
+kind = "type_combination"
+target = "VARCHAR, VARCHAR, TIMESTAMP_NTZ"
+note = "Conversions between zones with whole and half-hour offsets."
+catches = "source and target swapped, or offsets applied with the wrong sign"
+reading = "the page: three arguments convert a TIMESTAMP_NTZ wall clock from source_tz to target_tz and return TIMESTAMP_NTZ; NULL if any argument is NULL"
+cases = ["cases/translation/functions/convert-timezone.toml#t001_ntz"]
+required = true
+
+[[obligation]]
+id = "sf.functions.convert-timezone.t002"
+form = "sf.functions.convert-timezone"
+kind = "type_combination"
+target = "VARCHAR, VARCHAR, TIMESTAMP_NTZ"
+note = "Zone names in another case, and a fixed-offset name."
+catches = "zone names compared case-sensitively"
+reading = "the page: three arguments convert a TIMESTAMP_NTZ wall clock from source_tz to target_tz and return TIMESTAMP_NTZ; NULL if any argument is NULL"
+cases = ["cases/translation/functions/convert-timezone.toml#t002_offset_names"]
+required = true
+
+[[obligation]]
+id = "sf.functions.convert-timezone.t003"
+form = "sf.functions.convert-timezone"
+kind = "null_argument"
+target = "source_tz"
+note = "NULL source zone."
+catches = "NULL read as UTC"
+reading = "the page: three arguments convert a TIMESTAMP_NTZ wall clock from source_tz to target_tz and return TIMESTAMP_NTZ; NULL if any argument is NULL"
+cases = ["cases/translation/functions/convert-timezone.toml#t003_null_source_tz"]
+required = true
+
+[[obligation]]
+id = "sf.functions.convert-timezone.t004"
+form = "sf.functions.convert-timezone"
+kind = "null_argument"
+target = "target_tz"
+note = "NULL target zone."
+catches = "NULL read as UTC"
+reading = "the page: three arguments convert a TIMESTAMP_NTZ wall clock from source_tz to target_tz and return TIMESTAMP_NTZ; NULL if any argument is NULL"
+cases = ["cases/translation/functions/convert-timezone.toml#t004_null_target_tz"]
+required = true
+
+[[obligation]]
+id = "sf.functions.convert-timezone.t005"
+form = "sf.functions.convert-timezone"
+kind = "null_argument"
+target = "source_timestamp_ntz"
+note = "NULL timestamp."
+catches = "NULL read as the epoch"
+reading = "the page: three arguments convert a TIMESTAMP_NTZ wall clock from source_tz to target_tz and return TIMESTAMP_NTZ; NULL if any argument is NULL"
+cases = ["cases/translation/functions/convert-timezone.toml#t005_null_source_timestamp_ntz"]
+required = true
+
+[[obligation]]
+id = "sf.functions.convert-timezone.t006"
+form = "sf.functions.convert-timezone"
+kind = "boundary"
+subkind = "timezone_dst"
+target = "timezone_dst"
+note = "Wall clocks in the spring gap and the autumn overlap of America/New_York, and either side."
+catches = "a fixed standard offset all year, or the overlap resolved to the other offset"
+reading = "the page: three arguments convert a TIMESTAMP_NTZ wall clock from source_tz to target_tz and return TIMESTAMP_NTZ; NULL if any argument is NULL"
+cases = ["cases/translation/functions/convert-timezone.toml#t006_dst"]
+required = true
+
+[[obligation]]
+id = "sf.functions.convert-timezone.t007"
+form = "sf.functions.convert-timezone"
+kind = "boundary"
+subkind = "leap_day"
+target = "leap_day"
+note = "A conversion that crosses into 29 February."
+catches = "a skipped leap day"
+reading = "the page: three arguments convert a TIMESTAMP_NTZ wall clock from source_tz to target_tz and return TIMESTAMP_NTZ; NULL if any argument is NULL"
+cases = ["cases/translation/functions/convert-timezone.toml#t007_leap"]
+required = true
+
+[[obligation]]
+id = "sf.functions.convert-timezone.t008"
+form = "sf.functions.convert-timezone"
+kind = "error_parity"
+target = "error"
+note = "An unknown zone."
+catches = "UTC or NULL instead of an error"
+reading = "the page: three arguments convert a TIMESTAMP_NTZ wall clock from source_tz to target_tz and return TIMESTAMP_NTZ; NULL if any argument is NULL"
+cases = ["cases/translation/functions/convert-timezone.toml#t008_bad_zone"]
+required = true
+
+[[obligation]]
+id = "sf.functions.convert-timezone.t009"
+form = "sf.functions.convert-timezone"
+kind = "type_combination"
+target = "VARCHAR, TIMESTAMP_TZ"
+note = "The two-argument form on a TIMESTAMP_TZ, which returns a TIMESTAMP_TZ in the target zone."
+catches = "the three-argument NTZ path (the wall clock read as UTC: 05:00 instead of 13:00 Kolkata), an NTZ result (r), or the right instant kept in the source or UTC offset (wall 23:30 or 07:30 instead of 13:00)"
+reading = "the page: three arguments convert a TIMESTAMP_NTZ wall clock from source_tz to target_tz and return TIMESTAMP_NTZ; NULL if any argument is NULL; two arguments convert a TIMESTAMP_TZ instant to target_tz and return a TIMESTAMP_TZ in that zone"
+cases = ["cases/translation/functions/convert-timezone.toml#t009_two_argument"]
+required = true
+
+[[obligation]]
+id = "sf.functions.corr.corr.t001"
+form = "sf.functions.corr.corr"
+kind = "type_combination"
+target = "NUMBER, NUMBER"
+note = "Asymmetric pairs per group, with a NULL in x in one pair and in y in another."
+catches = "pairs with one NULL kept (pairwise deletion) or a covariance"
+reading = "the page: the correlation coefficient of the pairs; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT"
+cases = ["cases/translation/functions/corr.corr.toml#t001_pairs"]
+required = true
+
+[[obligation]]
+id = "sf.functions.corr.corr.t002"
+form = "sf.functions.corr.corr"
+kind = "type_combination"
+target = "FLOAT, NUMBER"
+note = "FLOAT y beside NUMBER x."
+catches = "an integer result type"
+reading = "the page: the correlation coefficient of the pairs; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT"
+cases = ["cases/translation/functions/corr.corr.toml#t002_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.corr.corr.t003"
+form = "sf.functions.corr.corr"
+kind = "type_combination"
+target = "constant x"
+note = "Every x equal, so the variance of x is 0."
+catches = "a division by zero error, or 0 where Snowflake gives NULL (or the reverse)"
+reading = "the page: the correlation coefficient of the pairs; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT"
+cases = ["cases/translation/functions/corr.corr.toml#t003_constant_x"]
+required = true
+
+[[obligation]]
+id = "sf.functions.corr.corr.t004"
+form = "sf.functions.corr.corr"
+kind = "null_argument"
+target = "y"
+note = "NULL y in every pair."
+catches = "a value from the x column alone"
+reading = "the page: the correlation coefficient of the pairs; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT"
+cases = ["cases/translation/functions/corr.corr.toml#t004_null_y"]
+required = true
+
+[[obligation]]
+id = "sf.functions.corr.corr.t005"
+form = "sf.functions.corr.corr"
+kind = "null_argument"
+target = "x"
+note = "NULL x in one pair of four."
+catches = "the pair kept with x read as 0"
+reading = "the page: the correlation coefficient of the pairs; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT"
+cases = ["cases/translation/functions/corr.corr.toml#t005_null_x"]
+required = true
+
+[[obligation]]
+id = "sf.functions.corr.corr.t006"
+form = "sf.functions.corr.corr"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "an error or 0 instead of the empty-input result"
+reading = "the page: the correlation coefficient of the pairs; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT"
+cases = ["cases/translation/functions/corr.corr.toml#t006_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.corr.x-over.t001"
+form = "sf.functions.corr.x-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Per-partition pairs with NULLs on either side."
+catches = "pairs with one NULL kept (pairwise deletion) or a covariance, or the whole input"
+reading = "the page: the correlation coefficient of the pairs; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT; as a window function, no ORDER BY or frame"
+cases = ["cases/translation/functions/corr.x-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.corr.x-over.t002"
+form = "sf.functions.corr.x-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause."
+catches = "a running value"
+reading = "the page: the correlation coefficient of the pairs; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT; as a window function, no ORDER BY or frame"
+cases = ["cases/translation/functions/corr.x-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.corr.x-over.t003"
+form = "sf.functions.corr.x-over"
+kind = "null_argument"
+target = "y"
+note = "A partition whose y values are all NULL."
+catches = "a value from x alone"
+reading = "the page: the correlation coefficient of the pairs; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT; as a window function, no ORDER BY or frame"
+cases = ["cases/translation/functions/corr.x-over.toml#t003_null_y"]
+required = true
+
+[[obligation]]
+id = "sf.functions.corr.x-over.t004"
+form = "sf.functions.corr.x-over"
+kind = "null_argument"
+target = "partition-key"
+note = "NULL partition keys."
+catches = "NULL-keyed rows split or dropped"
+reading = "the page: the correlation coefficient of the pairs; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT; as a window function, no ORDER BY or frame"
+cases = ["cases/translation/functions/corr.x-over.toml#t004_null_partition_key"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cos.t001"
+form = "sf.functions.cos"
+kind = "type_combination"
+target = "FLOAT"
+note = "COS of FLOAT values."
+catches = "SIN computed, or the argument read in degrees"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/cos.toml#t001_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cos.t002"
+form = "sf.functions.cos"
+kind = "type_combination"
+target = "NUMBER(12,4)"
+note = "COS of NUMBER(12,4), converted to FLOAT."
+catches = "SIN computed, or the argument read in degrees; or the result left fixed-point"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/cos.toml#t002_fixed"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cos.t003"
+form = "sf.functions.cos"
+kind = "null_argument"
+target = "x"
+note = "COS of NULL."
+catches = "NULL turned into 0 or NaN"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/cos.toml#t003_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cos.t004"
+form = "sf.functions.cos"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "Large arguments."
+catches = "argument reduction differing"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/cos.toml#t004_large"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cosh.t001"
+form = "sf.functions.cosh"
+kind = "type_combination"
+target = "FLOAT"
+note = "COSH of FLOAT values."
+catches = "SINH computed (cosh is even, sinh odd)"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/cosh.toml#t001_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cosh.t002"
+form = "sf.functions.cosh"
+kind = "type_combination"
+target = "NUMBER(12,4)"
+note = "COSH of NUMBER(12,4), converted to FLOAT."
+catches = "SINH computed (cosh is even, sinh odd); or the result left fixed-point"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/cosh.toml#t002_fixed"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cosh.t003"
+form = "sf.functions.cosh"
+kind = "null_argument"
+target = "x"
+note = "COSH of NULL."
+catches = "NULL turned into 0 or NaN"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/cosh.toml#t003_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cosh.t004"
+form = "sf.functions.cosh"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "Arguments just past the double range (710.48)."
+catches = "infinity versus error"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/cosh.toml#t004_overflow_inf"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cot.t001"
+form = "sf.functions.cot"
+kind = "type_combination"
+target = "FLOAT"
+note = "COT of FLOAT values."
+catches = "TAN computed instead of its reciprocal"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/cot.toml#t001_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cot.t002"
+form = "sf.functions.cot"
+kind = "type_combination"
+target = "NUMBER(12,4)"
+note = "COT of NUMBER(12,4), converted to FLOAT."
+catches = "TAN computed instead of its reciprocal; or the result left fixed-point"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/cot.toml#t002_fixed"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cot.t003"
+form = "sf.functions.cot"
+kind = "null_argument"
+target = "x"
+note = "COT of NULL."
+catches = "NULL turned into 0 or NaN"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/cot.toml#t003_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cot.t004"
+form = "sf.functions.cot"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "Very small arguments give large cotangents."
+catches = "reciprocal computed in fixed-point"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/cot.toml#t004_small"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cot.t005"
+form = "sf.functions.cot"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "COT at or outside its domain: records what Snowflake returns there (NaN, Infinity, NULL or an error); re-kinded to error_parity after capture if Snowflake raises an error."
+catches = "NaN, Infinity, NULL or an error differing from Snowflake"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/cot.toml#t005_pole"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count-if.count-if.t001"
+form = "sf.functions.count-if.count-if"
+kind = "type_combination"
+target = "BOOLEAN condition"
+note = "A comparison per group, true for some rows."
+catches = "rows counted regardless of the condition, or FALSE counted"
+reading = "the page: counts rows whose condition is TRUE; FALSE and NULL are not counted; returns NUMBER; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count-if.count-if.toml#t001_condition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count-if.count-if.t002"
+form = "sf.functions.count-if.count-if"
+kind = "type_combination"
+target = "BOOLEAN column"
+note = "A BOOLEAN column directly."
+catches = "the column read as a count of non-NULL values"
+reading = "the page: counts rows whose condition is TRUE; FALSE and NULL are not counted; returns NUMBER; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count-if.count-if.toml#t002_boolean_column"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count-if.count-if.t003"
+form = "sf.functions.count-if.count-if"
+kind = "null_argument"
+target = "condition"
+note = "A condition that is NULL for a NULL value."
+catches = "a NULL condition counted as TRUE"
+reading = "the page: counts rows whose condition is TRUE; FALSE and NULL are not counted; returns NUMBER; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count-if.count-if.toml#t003_null_condition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count-if.count-if.t004"
+form = "sf.functions.count-if.count-if"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "NULL instead of 0"
+reading = "the page: counts rows whose condition is TRUE; FALSE and NULL are not counted; returns NUMBER; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count-if.count-if.toml#t004_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count-if.count-if-over.t001"
+form = "sf.functions.count-if.count-if-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Partitions of different sizes in mixed input order."
+catches = "the whole input aggregated instead of the partition, or a running value"
+reading = "the page: counts rows whose condition is TRUE; FALSE and NULL are not counted; returns NUMBER; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count-if.count-if-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count-if.count-if-over.t002"
+form = "sf.functions.count-if.count-if-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause."
+catches = "a running (ordered) value instead of the whole input"
+reading = "the page: counts rows whose condition is TRUE; FALSE and NULL are not counted; returns NUMBER; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count-if.count-if-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count-if.count-if-over.t003"
+form = "sf.functions.count-if.count-if-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY ORDER BY)"
+note = "ORDER BY with tied keys (two rows with k = 2) and no frame: with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW."
+catches = "a ROWS frame (the tied rows get different values) or the whole partition"
+reading = "the page: counts rows whose condition is TRUE; FALSE and NULL are not counted; returns NUMBER; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count-if.count-if-over.toml#t003_order_ties"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count-if.count-if-over.t004"
+form = "sf.functions.count-if.count-if-over"
+kind = "type_combination"
+target = "ORDER BY DESC"
+note = "DESC ordering with a running ROWS frame."
+catches = "DESC ignored (the running value starts from the smallest key)"
+reading = "the page: counts rows whose condition is TRUE; FALSE and NULL are not counted; returns NUMBER; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count-if.count-if-over.toml#t004_order_desc"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count-if.count-if-over.t005"
+form = "sf.functions.count-if.count-if-over"
+kind = "type_combination"
+target = "ASC NULLS FIRST, ROWS frame"
+note = "A NULL ordering key placed first (not the ASC default) and a one-row-back sliding frame."
+catches = "NULLS FIRST ignored (the NULL-key row last), or the frame read as RANGE or reaching forward"
+reading = "the page: counts rows whose condition is TRUE; FALSE and NULL are not counted; returns NUMBER; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count-if.count-if-over.toml#t005_nulls_first_frame"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count-if.count-if-over.t006"
+form = "sf.functions.count-if.count-if-over"
+kind = "null_argument"
+target = "expr1"
+note = "NULL values inside partitions, and a partition with only NULL."
+catches = "a NULL condition counted"
+reading = "the page: counts rows whose condition is TRUE; FALSE and NULL are not counted; returns NUMBER; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count-if.count-if-over.toml#t006_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count-if.count-if-over.t007"
+form = "sf.functions.count-if.count-if-over"
+kind = "null_argument"
+target = "partition-key"
+note = "NULL partition keys, which form one partition."
+catches = "NULL-keyed rows each in their own partition or dropped"
+reading = "the page: counts rows whose condition is TRUE; FALSE and NULL are not counted; returns NUMBER; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count-if.count-if-over.toml#t007_null_partition_key"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count.count-over.t001"
+form = "sf.functions.count.count-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Partitions of different sizes in mixed input order."
+catches = "the whole input aggregated instead of the partition, or a running value"
+reading = "the page: COUNT(expr, ...) counts rows where every expression is non-NULL; DISTINCT applies to the combination of all expressions; * counts every row; alias.* and a wildcard with ILIKE or EXCLUDE count rows with no NULL column; JSON null in a VARIANT counts as NULL; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count.count-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count.count-over.t002"
+form = "sf.functions.count.count-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause."
+catches = "a running (ordered) value instead of the whole input"
+reading = "the page: COUNT(expr, ...) counts rows where every expression is non-NULL; DISTINCT applies to the combination of all expressions; * counts every row; alias.* and a wildcard with ILIKE or EXCLUDE count rows with no NULL column; JSON null in a VARIANT counts as NULL; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count.count-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count.count-over.t003"
+form = "sf.functions.count.count-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY ORDER BY)"
+note = "ORDER BY with tied keys (two rows with k = 2) and no frame: with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW."
+catches = "a ROWS frame (the tied rows get different values) or the whole partition"
+reading = "the page: COUNT(expr, ...) counts rows where every expression is non-NULL; DISTINCT applies to the combination of all expressions; * counts every row; alias.* and a wildcard with ILIKE or EXCLUDE count rows with no NULL column; JSON null in a VARIANT counts as NULL; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count.count-over.toml#t003_order_ties"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count.count-over.t004"
+form = "sf.functions.count.count-over"
+kind = "type_combination"
+target = "ORDER BY DESC"
+note = "DESC ordering with a running ROWS frame."
+catches = "DESC ignored (the running value starts from the smallest key)"
+reading = "the page: COUNT(expr, ...) counts rows where every expression is non-NULL; DISTINCT applies to the combination of all expressions; * counts every row; alias.* and a wildcard with ILIKE or EXCLUDE count rows with no NULL column; JSON null in a VARIANT counts as NULL; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count.count-over.toml#t004_order_desc"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count.count-over.t005"
+form = "sf.functions.count.count-over"
+kind = "type_combination"
+target = "ASC NULLS FIRST, ROWS frame"
+note = "A NULL ordering key placed first (not the ASC default) and a one-row-back sliding frame."
+catches = "NULLS FIRST ignored (the NULL-key row last), or the frame read as RANGE or reaching forward"
+reading = "the page: COUNT(expr, ...) counts rows where every expression is non-NULL; DISTINCT applies to the combination of all expressions; * counts every row; alias.* and a wildcard with ILIKE or EXCLUDE count rows with no NULL column; JSON null in a VARIANT counts as NULL; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count.count-over.toml#t005_nulls_first_frame"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count.count-over.t006"
+form = "sf.functions.count.count-over"
+kind = "null_argument"
+target = "expr1"
+note = "NULL values inside partitions, and a partition with only NULL."
+catches = "NULL rows counted"
+reading = "the page: COUNT(expr, ...) counts rows where every expression is non-NULL; DISTINCT applies to the combination of all expressions; * counts every row; alias.* and a wildcard with ILIKE or EXCLUDE count rows with no NULL column; JSON null in a VARIANT counts as NULL; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count.count-over.toml#t006_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count.count-over.t007"
+form = "sf.functions.count.count-over"
+kind = "null_argument"
+target = "partition-key"
+note = "NULL partition keys, which form one partition."
+catches = "NULL-keyed rows each in their own partition or dropped"
+reading = "the page: COUNT(expr, ...) counts rows where every expression is non-NULL; DISTINCT applies to the combination of all expressions; * counts every row; alias.* and a wildcard with ILIKE or EXCLUDE count rows with no NULL column; JSON null in a VARIANT counts as NULL; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count.count-over.toml#t007_null_partition_key"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count.count-over.t008"
+form = "sf.functions.count.count-over"
+kind = "type_combination"
+target = "DISTINCT OVER (PARTITION BY)"
+note = "DISTINCT inside a partition with a duplicate."
+catches = "DISTINCT ignored"
+reading = "the page: COUNT(expr, ...) counts rows where every expression is non-NULL; DISTINCT applies to the combination of all expressions; * counts every row; alias.* and a wildcard with ILIKE or EXCLUDE count rows with no NULL column; JSON null in a VARIANT counts as NULL; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count.count-over.toml#t008_distinct_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.count.count-over.t009"
+form = "sf.functions.count.count-over"
+kind = "type_combination"
+target = "* OVER"
+note = "COUNT(*) as a window function counts NULL rows."
+catches = "COUNT(*) skipping NULL rows"
+reading = "the page: COUNT(expr, ...) counts rows where every expression is non-NULL; DISTINCT applies to the combination of all expressions; * counts every row; alias.* and a wildcard with ILIKE or EXCLUDE count rows with no NULL column; JSON null in a VARIANT counts as NULL; with ORDER BY and no frame the page implies RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
+cases = ["cases/translation/functions/count.count-over.toml#t009_star"]
+required = true
+
+[[obligation]]
+id = "sf.functions.covar-pop.covar-pop.t001"
+form = "sf.functions.covar-pop.covar-pop"
+kind = "type_combination"
+target = "NUMBER, NUMBER"
+note = "Asymmetric pairs per group, with a NULL in x in one pair and in y in another."
+catches = "the sample covariance (divide by n - 1), or pairs with one NULL kept"
+reading = "the page: the population covariance; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT"
+cases = ["cases/translation/functions/covar-pop.covar-pop.toml#t001_pairs"]
+required = true
+
+[[obligation]]
+id = "sf.functions.covar-pop.covar-pop.t002"
+form = "sf.functions.covar-pop.covar-pop"
+kind = "type_combination"
+target = "FLOAT, NUMBER"
+note = "FLOAT y beside NUMBER x."
+catches = "an integer result type"
+reading = "the page: the population covariance; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT"
+cases = ["cases/translation/functions/covar-pop.covar-pop.toml#t002_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.covar-pop.covar-pop.t003"
+form = "sf.functions.covar-pop.covar-pop"
+kind = "null_argument"
+target = "y"
+note = "NULL y in every pair."
+catches = "a value from the x column alone"
+reading = "the page: the population covariance; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT"
+cases = ["cases/translation/functions/covar-pop.covar-pop.toml#t003_null_y"]
+required = true
+
+[[obligation]]
+id = "sf.functions.covar-pop.covar-pop.t004"
+form = "sf.functions.covar-pop.covar-pop"
+kind = "null_argument"
+target = "x"
+note = "NULL x in one pair of four."
+catches = "the pair kept with x read as 0"
+reading = "the page: the population covariance; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT"
+cases = ["cases/translation/functions/covar-pop.covar-pop.toml#t004_null_x"]
+required = true
+
+[[obligation]]
+id = "sf.functions.covar-pop.covar-pop.t005"
+form = "sf.functions.covar-pop.covar-pop"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "an error or 0 instead of the empty-input result"
+reading = "the page: the population covariance; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT"
+cases = ["cases/translation/functions/covar-pop.covar-pop.toml#t005_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.covar-pop.x-over.t001"
+form = "sf.functions.covar-pop.x-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Per-partition pairs with NULLs on either side."
+catches = "the sample covariance (divide by n - 1), or pairs with one NULL kept, or the whole input"
+reading = "the page: the population covariance; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT; as a window function, no ORDER BY or frame"
+cases = ["cases/translation/functions/covar-pop.x-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.covar-pop.x-over.t002"
+form = "sf.functions.covar-pop.x-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause."
+catches = "a running value"
+reading = "the page: the population covariance; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT; as a window function, no ORDER BY or frame"
+cases = ["cases/translation/functions/covar-pop.x-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.covar-pop.x-over.t003"
+form = "sf.functions.covar-pop.x-over"
+kind = "null_argument"
+target = "y"
+note = "A partition whose y values are all NULL."
+catches = "a value from x alone"
+reading = "the page: the population covariance; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT; as a window function, no ORDER BY or frame"
+cases = ["cases/translation/functions/covar-pop.x-over.toml#t003_null_y"]
+required = true
+
+[[obligation]]
+id = "sf.functions.covar-pop.x-over.t004"
+form = "sf.functions.covar-pop.x-over"
+kind = "null_argument"
+target = "partition-key"
+note = "NULL partition keys."
+catches = "NULL-keyed rows split or dropped"
+reading = "the page: the population covariance; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT; as a window function, no ORDER BY or frame"
+cases = ["cases/translation/functions/covar-pop.x-over.toml#t004_null_partition_key"]
+required = true
+
+[[obligation]]
+id = "sf.functions.covar-samp.covar-samp.t001"
+form = "sf.functions.covar-samp.covar-samp"
+kind = "type_combination"
+target = "NUMBER, NUMBER"
+note = "Asymmetric pairs per group, with a NULL in x in one pair and in y in another."
+catches = "the population covariance, or pairs with one NULL kept"
+reading = "the page: the sample covariance; NULL for one pair; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT"
+cases = ["cases/translation/functions/covar-samp.covar-samp.toml#t001_pairs"]
+required = true
+
+[[obligation]]
+id = "sf.functions.covar-samp.covar-samp.t002"
+form = "sf.functions.covar-samp.covar-samp"
+kind = "type_combination"
+target = "FLOAT, NUMBER"
+note = "FLOAT y beside NUMBER x."
+catches = "an integer result type"
+reading = "the page: the sample covariance; NULL for one pair; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT"
+cases = ["cases/translation/functions/covar-samp.covar-samp.toml#t002_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.covar-samp.covar-samp.t003"
+form = "sf.functions.covar-samp.covar-samp"
+kind = "null_argument"
+target = "y"
+note = "NULL y in every pair."
+catches = "a value from the x column alone"
+reading = "the page: the sample covariance; NULL for one pair; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT"
+cases = ["cases/translation/functions/covar-samp.covar-samp.toml#t003_null_y"]
+required = true
+
+[[obligation]]
+id = "sf.functions.covar-samp.covar-samp.t004"
+form = "sf.functions.covar-samp.covar-samp"
+kind = "null_argument"
+target = "x"
+note = "NULL x in one pair of four."
+catches = "the pair kept with x read as 0"
+reading = "the page: the sample covariance; NULL for one pair; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT"
+cases = ["cases/translation/functions/covar-samp.covar-samp.toml#t004_null_x"]
+required = true
+
+[[obligation]]
+id = "sf.functions.covar-samp.covar-samp.t005"
+form = "sf.functions.covar-samp.covar-samp"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "an error or 0 instead of the empty-input result"
+reading = "the page: the sample covariance; NULL for one pair; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT"
+cases = ["cases/translation/functions/covar-samp.covar-samp.toml#t005_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.covar-samp.x-over.t001"
+form = "sf.functions.covar-samp.x-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Per-partition pairs with NULLs on either side."
+catches = "the population covariance, or pairs with one NULL kept, or the whole input"
+reading = "the page: the sample covariance; NULL for one pair; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT; as a window function, no ORDER BY or frame"
+cases = ["cases/translation/functions/covar-samp.x-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.covar-samp.x-over.t002"
+form = "sf.functions.covar-samp.x-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause."
+catches = "a running value"
+reading = "the page: the sample covariance; NULL for one pair; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT; as a window function, no ORDER BY or frame"
+cases = ["cases/translation/functions/covar-samp.x-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.covar-samp.x-over.t003"
+form = "sf.functions.covar-samp.x-over"
+kind = "null_argument"
+target = "y"
+note = "A partition whose y values are all NULL."
+catches = "a value from x alone"
+reading = "the page: the sample covariance; NULL for one pair; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT; as a window function, no ORDER BY or frame"
+cases = ["cases/translation/functions/covar-samp.x-over.toml#t003_null_y"]
+required = true
+
+[[obligation]]
+id = "sf.functions.covar-samp.x-over.t004"
+form = "sf.functions.covar-samp.x-over"
+kind = "null_argument"
+target = "partition-key"
+note = "NULL partition keys."
+catches = "NULL-keyed rows split or dropped"
+reading = "the page: the sample covariance; NULL for one pair; both values of a pair must be non-NULL; the dependent variable y comes first; no DISTINCT; as a window function, no ORDER BY or frame"
+cases = ["cases/translation/functions/covar-samp.x-over.toml#t004_null_partition_key"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cume-dist.t001"
+form = "sf.functions.cume-dist"
+kind = "type_combination"
+target = "ORDER BY"
+note = "A tie and a one-row partition."
+catches = "ties counted one at a time (0.4 instead of 0.6), or PERCENT_RANK computed"
+reading = "the page: the cumulative distribution, the share of rows ordered at or before the current row, as DOUBLE; no explicit frame"
+cases = ["cases/translation/functions/cume-dist.toml#t001_ties"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cume-dist.t002"
+form = "sf.functions.cume-dist"
+kind = "error_parity"
+target = "error"
+note = "An explicit window frame, which the page says CUME_DIST does not support."
+catches = "the frame accepted and ignored"
+reading = "the page: the cumulative distribution, the share of rows ordered at or before the current row, as DOUBLE; no explicit frame"
+cases = ["cases/translation/functions/cume-dist.toml#t002_frame"]
+required = true
+
+[[obligation]]
+id = "sf.functions.cume-dist.t003"
+form = "sf.functions.cume-dist"
+kind = "null_argument"
+target = "order-expr"
+note = "NULL order keys sorted first; tied NULLs share one cumulative share."
+catches = "NULLs sorted last, or counted one at a time"
+reading = "the page: the cumulative distribution, the share of rows ordered at or before the current row, as DOUBLE; no explicit frame"
+cases = ["cases/translation/functions/cume-dist.toml#t003_null_order_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-database.t001"
+form = "sf.functions.current-database"
+kind = "type_combination"
+target = "none"
+note = "The type of the name, and whether it is set, in a session with no database in use: the recording returned NULL (type NULL, not set)."
+catches = "a database name returned when none is in use"
+reading = "the page: the name of the database in use for the session, as VARCHAR"
+cases = ["cases/translation/functions/current-database.toml#t001_type"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-database.t002"
+form = "sf.functions.current-database"
+kind = "null_argument"
+target = "none"
+note = "The function takes no argument; a NULL value compared with its result gives NULL."
+catches = "FALSE returned"
+reading = "the page: the name of the database in use for the session, as VARCHAR"
+cases = ["cases/translation/functions/current-database.toml#t002_null_none"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-date.current-date.t001"
+form = "sf.functions.current-date.current-date"
+kind = "type_combination"
+target = "none"
+note = "The type, agreement with the date of CURRENT_TIMESTAMP in the UTC session, and a lower bound."
+catches = "a timestamp returned, or the date taken in another zone"
+reading = "the page: the current DATE in the session time zone (UTC here); callable without parentheses"
+cases = ["cases/translation/functions/current-date.current-date.toml#t001_type"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-date.current-date.t002"
+form = "sf.functions.current-date.current-date"
+kind = "null_argument"
+target = "none"
+note = "The function takes no argument; a NULL value compared with its result gives NULL."
+catches = "the comparison with NULL returning FALSE, or the NULL replaced by the current value"
+reading = "the page: the current DATE in the session time zone (UTC here); callable without parentheses"
+cases = ["cases/translation/functions/current-date.current-date.toml#t002_null_none"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-date.current-date.syntax.t001"
+form = "sf.functions.current-date.current-date.syntax"
+kind = "type_combination"
+target = "none"
+note = "The type, agreement with the date of CURRENT_TIMESTAMP in the UTC session, and a lower bound."
+catches = "a timestamp returned, or the date taken in another zone"
+reading = "the page: the current DATE in the session time zone (UTC here); callable without parentheses"
+cases = ["cases/translation/functions/current-date.current-date.syntax.toml#t001_type"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-date.current-date.syntax.t002"
+form = "sf.functions.current-date.current-date.syntax"
+kind = "null_argument"
+target = "none"
+note = "The function takes no argument; a NULL value compared with its result gives NULL."
+catches = "the comparison with NULL returning FALSE, or the NULL replaced by the current value"
+reading = "the page: the current DATE in the session time zone (UTC here); callable without parentheses"
+cases = ["cases/translation/functions/current-date.current-date.syntax.toml#t002_null_none"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-schema.t001"
+form = "sf.functions.current-schema"
+kind = "type_combination"
+target = "none"
+note = "The type of the name, and whether it is set, in a session with no schema in use: the recording returned NULL (type NULL, not set)."
+catches = "a schema name returned when none is in use"
+reading = "the page: the name of the schema in use for the session, as VARCHAR"
+cases = ["cases/translation/functions/current-schema.toml#t001_type"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-schema.t002"
+form = "sf.functions.current-schema"
+kind = "null_argument"
+target = "none"
+note = "The function takes no argument; a NULL value compared with its result gives NULL."
+catches = "FALSE returned"
+reading = "the page: the name of the schema in use for the session, as VARCHAR"
+cases = ["cases/translation/functions/current-schema.toml#t002_null_none"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-schemas.t001"
+form = "sf.functions.current-schemas"
+kind = "type_combination"
+target = "none"
+note = "The type, and whether the text parses as a JSON array."
+catches = "an ARRAY returned, or a single schema name"
+reading = "the page: the schemas in the session search path, as VARCHAR holding a JSON array"
+cases = ["cases/translation/functions/current-schemas.toml#t001_array"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-schemas.t002"
+form = "sf.functions.current-schemas"
+kind = "null_argument"
+target = "none"
+note = "The function takes no argument; a NULL value compared with its result gives NULL."
+catches = "FALSE returned"
+reading = "the page: the schemas in the session search path, as VARCHAR holding a JSON array"
+cases = ["cases/translation/functions/current-schemas.toml#t002_null_none"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-time.current-time.t001"
+form = "sf.functions.current-time.current-time"
+kind = "type_combination"
+target = "none"
+note = "The type, and agreement with the time of CURRENT_TIMESTAMP in the UTC session to within a minute."
+catches = "a timestamp returned, or the time taken in another zone"
+reading = "the page: the current TIME in the session time zone (UTC here), with an optional precision of 0 to 9 digits (default 9); callable without parentheses"
+cases = ["cases/translation/functions/current-time.current-time.toml#t001_type"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-time.current-time.t002"
+form = "sf.functions.current-time.current-time"
+kind = "boundary"
+subkind = "rounding_scale"
+target = "rounding_scale"
+note = "Precision 0 and 3, whose unused fractional digits the page makes zero."
+catches = "the precision ignored (non-zero digits past the requested ones)"
+reading = "the page: the current TIME in the session time zone (UTC here), with an optional precision of 0 to 9 digits (default 9); callable without parentheses"
+cases = ["cases/translation/functions/current-time.current-time.toml#t002_precision"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-time.current-time.t003"
+form = "sf.functions.current-time.current-time"
+kind = "error_parity"
+target = "error"
+note = "Precision 10, outside the valid range 0 to 9 the page states; Snowflake refuses it when compiling."
+catches = "the precision clamped to 9"
+reading = "the page: the current TIME in the session time zone (UTC here), with an optional precision of 0 to 9 digits (default 9); callable without parentheses"
+cases = ["cases/translation/functions/current-time.current-time.toml#t003_precision_range"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-time.current-time.t004"
+form = "sf.functions.current-time.current-time"
+kind = "null_argument"
+target = "none"
+note = "The function takes no argument; a NULL value compared with its result gives NULL."
+catches = "the comparison with NULL returning FALSE, or the NULL replaced by the current value"
+reading = "the page: the current TIME in the session time zone (UTC here), with an optional precision of 0 to 9 digits (default 9); callable without parentheses"
+cases = ["cases/translation/functions/current-time.current-time.toml#t004_null_none"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-time.current-time.syntax.t001"
+form = "sf.functions.current-time.current-time.syntax"
+kind = "type_combination"
+target = "none"
+note = "The type, and agreement with the time of CURRENT_TIMESTAMP in the UTC session to within a minute."
+catches = "a timestamp returned, or the time taken in another zone"
+reading = "the page: the current TIME in the session time zone (UTC here), with an optional precision of 0 to 9 digits (default 9); callable without parentheses"
+cases = ["cases/translation/functions/current-time.current-time.syntax.toml#t001_type"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-time.current-time.syntax.t002"
+form = "sf.functions.current-time.current-time.syntax"
+kind = "null_argument"
+target = "none"
+note = "The function takes no argument; a NULL value compared with its result gives NULL."
+catches = "the comparison with NULL returning FALSE, or the NULL replaced by the current value"
+reading = "the page: the current TIME in the session time zone (UTC here), with an optional precision of 0 to 9 digits (default 9); callable without parentheses"
+cases = ["cases/translation/functions/current-time.current-time.syntax.toml#t002_null_none"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-timestamp.current-timestamp.t001"
+form = "sf.functions.current-timestamp.current-timestamp"
+kind = "type_combination"
+target = "none"
+note = "The type, which the page says is TIMESTAMP_LTZ whatever TIMESTAMP_TYPE_MAPPING says (NTZ here), and agreement with SYSDATE in the UTC session to within a minute."
+catches = "TIMESTAMP_NTZ returned under the type mapping, or the time taken in another zone"
+reading = "the page: the current time as TIMESTAMP_LTZ in the session time zone, whatever TIMESTAMP_TYPE_MAPPING says; an optional precision of 0 to 9 digits; callable without parentheses"
+cases = ["cases/translation/functions/current-timestamp.current-timestamp.toml#t001_type"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-timestamp.current-timestamp.t002"
+form = "sf.functions.current-timestamp.current-timestamp"
+kind = "boundary"
+subkind = "rounding_scale"
+target = "rounding_scale"
+note = "Precision 0 and 3, whose unused fractional digits the page makes zero."
+catches = "the precision ignored"
+reading = "the page: the current time as TIMESTAMP_LTZ in the session time zone, whatever TIMESTAMP_TYPE_MAPPING says; an optional precision of 0 to 9 digits; callable without parentheses"
+cases = ["cases/translation/functions/current-timestamp.current-timestamp.toml#t002_precision"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-timestamp.current-timestamp.t003"
+form = "sf.functions.current-timestamp.current-timestamp"
+kind = "null_argument"
+target = "none"
+note = "The function takes no argument; a NULL value compared with its result gives NULL."
+catches = "the comparison with NULL returning FALSE, or the NULL replaced by the current value"
+reading = "the page: the current time as TIMESTAMP_LTZ in the session time zone, whatever TIMESTAMP_TYPE_MAPPING says; an optional precision of 0 to 9 digits; callable without parentheses"
+cases = ["cases/translation/functions/current-timestamp.current-timestamp.toml#t003_null_none"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-timestamp.current-timestamp.syntax.t001"
+form = "sf.functions.current-timestamp.current-timestamp.syntax"
+kind = "type_combination"
+target = "none"
+note = "The type, which the page says is TIMESTAMP_LTZ whatever TIMESTAMP_TYPE_MAPPING says (NTZ here), and agreement with SYSDATE in the UTC session to within a minute."
+catches = "TIMESTAMP_NTZ returned under the type mapping, or the time taken in another zone"
+reading = "the page: the current time as TIMESTAMP_LTZ in the session time zone, whatever TIMESTAMP_TYPE_MAPPING says; an optional precision of 0 to 9 digits; callable without parentheses"
+cases = ["cases/translation/functions/current-timestamp.current-timestamp.syntax.toml#t001_type"]
+required = true
+
+[[obligation]]
+id = "sf.functions.current-timestamp.current-timestamp.syntax.t002"
+form = "sf.functions.current-timestamp.current-timestamp.syntax"
+kind = "null_argument"
+target = "none"
+note = "The function takes no argument; a NULL value compared with its result gives NULL."
+catches = "the comparison with NULL returning FALSE, or the NULL replaced by the current value"
+reading = "the page: the current time as TIMESTAMP_LTZ in the session time zone, whatever TIMESTAMP_TYPE_MAPPING says; an optional precision of 0 to 9 digits; callable without parentheses"
+cases = ["cases/translation/functions/current-timestamp.current-timestamp.syntax.toml#t002_null_none"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll-accumulate.t001"
+form = "sf.functions.datasketches-hll-accumulate"
+kind = "type_combination"
+target = "NUMBER"
+note = "Sketches per group, read with DATASKETCHES_HLL_ESTIMATE."
+catches = "duplicates counted"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll-accumulate.toml#t001_estimate"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll-accumulate.t002"
+form = "sf.functions.datasketches-hll-accumulate"
+kind = "type_combination"
+target = "FLOAT"
+note = "FLOAT values, hashed as DOUBLE: 1.0 and 1.5 are two values."
+catches = "values truncated to integers"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll-accumulate.toml#t002_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll-accumulate.t003"
+form = "sf.functions.datasketches-hll-accumulate"
+kind = "type_combination"
+target = "with max_log_k"
+note = "An explicit max_log_k of 10."
+catches = "the argument rejected"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll-accumulate.toml#t003_max_log_k"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll-accumulate.t004"
+form = "sf.functions.datasketches-hll-accumulate"
+kind = "null_argument"
+target = "expr"
+note = "NULL values, which are not counted."
+catches = "NULL counted"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll-accumulate.toml#t004_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll-accumulate.t005"
+form = "sf.functions.datasketches-hll-accumulate"
+kind = "type_combination"
+target = "no rows"
+note = "No rows."
+catches = "a NULL sketch (a NULL estimate) instead of an empty one, or the reverse"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll-accumulate.toml#t005_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll-combine.t001"
+form = "sf.functions.datasketches-hll-combine"
+kind = "type_combination"
+target = "NUMBER and VARCHAR sketches"
+note = "Sketches of the same numbers as NUMBER and as text: the page says they hash differently, so the union counts 4."
+catches = "values hashed by their text (2)"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll-combine.toml#t001_text_vs_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll-combine.t002"
+form = "sf.functions.datasketches-hll-combine"
+kind = "type_combination"
+target = "two sketches"
+note = "Sketches for overlapping groups: the union."
+catches = "the estimates summed"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll-combine.toml#t002_union"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll-combine.t003"
+form = "sf.functions.datasketches-hll-combine"
+kind = "type_combination"
+target = "with max_log_k"
+note = "An explicit max_log_k."
+catches = "the argument rejected"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll-combine.toml#t003_max_log_k"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll-combine.t004"
+form = "sf.functions.datasketches-hll-combine"
+kind = "null_argument"
+target = "state"
+note = "A NULL sketch beside sketches."
+catches = "the result NULL"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll-combine.toml#t004_null_state"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll-estimate.t001"
+form = "sf.functions.datasketches-hll-estimate"
+kind = "type_combination"
+target = "BINARY sketch"
+note = "The estimate per group, returned as DOUBLE (HLL_ESTIMATE returns INTEGER)."
+catches = "an INTEGER result"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll-estimate.toml#t001_sketch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll-estimate.t002"
+form = "sf.functions.datasketches-hll-estimate"
+kind = "null_argument"
+target = "binary-sketch"
+note = "A NULL sketch."
+catches = "0.0 instead of NULL"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll-estimate.toml#t002_null_binary_sketch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll.datasketches-hll.t001"
+form = "sf.functions.datasketches-hll.datasketches-hll"
+kind = "type_combination"
+target = "NUMBER"
+note = "Repeated values per group."
+catches = "duplicates counted, or an INTEGER result"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll.datasketches-hll.toml#t001_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll.datasketches-hll.t002"
+form = "sf.functions.datasketches-hll.datasketches-hll"
+kind = "type_combination"
+target = "with max_log_k"
+note = "An explicit max_log_k of 5."
+catches = "the argument rejected"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll.datasketches-hll.toml#t002_max_log_k"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll.datasketches-hll.t003"
+form = "sf.functions.datasketches-hll.datasketches-hll"
+kind = "type_combination"
+target = "VARCHAR"
+note = "Text hashed as bytes: values differing in case or a leading zero are distinct."
+catches = "case-insensitive or numeric reading of text (01 and 1 as one value)"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll.datasketches-hll.toml#t003_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll.datasketches-hll.t004"
+form = "sf.functions.datasketches-hll.datasketches-hll"
+kind = "error_parity"
+target = "error"
+note = "A NUMBER with a non-zero scale, which the page says is not supported."
+catches = "the value cast silently"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll.datasketches-hll.toml#t004_scale"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll.datasketches-hll.t005"
+form = "sf.functions.datasketches-hll.datasketches-hll"
+kind = "type_combination"
+target = "DISTINCT"
+note = "DISTINCT, which has no effect."
+catches = "DISTINCT rejected"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll.datasketches-hll.toml#t005_distinct"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll.datasketches-hll.t006"
+form = "sf.functions.datasketches-hll.datasketches-hll"
+kind = "null_argument"
+target = "expr1"
+note = "NULL values, which are not counted."
+catches = "NULL counted"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll.datasketches-hll.toml#t006_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll.datasketches-hll.t007"
+form = "sf.functions.datasketches-hll.datasketches-hll"
+kind = "type_combination"
+target = "no rows"
+note = "No rows; the page gives 0.0."
+catches = "NULL instead of 0.0"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll.datasketches-hll.toml#t007_empty_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll.datasketches-hll-over.t001"
+form = "sf.functions.datasketches-hll.datasketches-hll-over"
+kind = "type_combination"
+target = "OVER (PARTITION BY)"
+note = "Partitions of different sizes in mixed input order."
+catches = "the whole input aggregated instead of the partition, or a running value"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll.datasketches-hll-over.toml#t001_partition"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll.datasketches-hll-over.t002"
+form = "sf.functions.datasketches-hll.datasketches-hll-over"
+kind = "type_combination"
+target = "OVER ()"
+note = "An empty OVER clause."
+catches = "a running (ordered) value instead of the whole input"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll.datasketches-hll-over.toml#t002_empty_over"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll.datasketches-hll-over.t003"
+form = "sf.functions.datasketches-hll.datasketches-hll-over"
+kind = "null_argument"
+target = "expr1"
+note = "NULL values inside partitions, and a partition with only NULL."
+catches = "NULL counted"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll.datasketches-hll-over.toml#t003_null_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datasketches-hll.datasketches-hll-over.t004"
+form = "sf.functions.datasketches-hll.datasketches-hll-over"
+kind = "null_argument"
+target = "partition-key"
+note = "NULL partition keys, which form one partition."
+catches = "NULL-keyed rows each in their own partition or dropped"
+reading = "the page: Apache DataSketches HLL sketches; DATASKETCHES_HLL returns the estimate as DOUBLE (0.0 for an empty input); DATASKETCHES_HLL_ACCUMULATE and _COMBINE return BINARY sketches, read with DATASKETCHES_HLL_ESTIMATE; values are hashed by type (text and binary as bytes, floating-point as DOUBLE, scale-0 numbers as 64-bit integers); numbers with a non-zero scale are not supported; DISTINCT has no effect; sets the sketch size"
+cases = ["cases/translation/functions/datasketches-hll.datasketches-hll-over.toml#t004_null_partition_key"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-from-parts.t001"
+form = "sf.functions.date-from-parts"
+kind = "type_combination"
+target = "NUMBER, NUMBER, NUMBER"
+note = "Normal dates in year-month-day order."
+catches = "month and day swapped"
+reading = "the page: integer parts, usually in their normal range, but values outside it carry into the next unit (the N-th day of a year); negative values are allowed"
+cases = ["cases/translation/functions/date-from-parts.toml#t001_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-from-parts.t002"
+form = "sf.functions.date-from-parts"
+kind = "type_combination"
+target = "NUMBER, NUMBER, NUMBER"
+note = "Day and month values outside their ranges, which carry."
+catches = "an error or NULL instead of carrying (2024-02-30 is 2024-03-01)"
+reading = "the page: integer parts, usually in their normal range, but values outside it carry into the next unit (the N-th day of a year); negative values are allowed"
+cases = ["cases/translation/functions/date-from-parts.toml#t002_carry"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-from-parts.t003"
+form = "sf.functions.date-from-parts"
+kind = "null_argument"
+target = "year"
+note = "NULL year."
+catches = "NULL read as 0"
+reading = "the page: integer parts, usually in their normal range, but values outside it carry into the next unit (the N-th day of a year); negative values are allowed"
+cases = ["cases/translation/functions/date-from-parts.toml#t003_null_year"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-from-parts.t004"
+form = "sf.functions.date-from-parts"
+kind = "null_argument"
+target = "month"
+note = "NULL month."
+catches = "NULL read as 1"
+reading = "the page: integer parts, usually in their normal range, but values outside it carry into the next unit (the N-th day of a year); negative values are allowed"
+cases = ["cases/translation/functions/date-from-parts.toml#t004_null_month"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-from-parts.t005"
+form = "sf.functions.date-from-parts"
+kind = "null_argument"
+target = "day"
+note = "NULL day."
+catches = "NULL read as 1"
+reading = "the page: integer parts, usually in their normal range, but values outside it carry into the next unit (the N-th day of a year); negative values are allowed"
+cases = ["cases/translation/functions/date-from-parts.toml#t005_null_day"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-from-parts.t006"
+form = "sf.functions.date-from-parts"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "Negative and zero months and days, which the page calls not intuitive."
+catches = "month 0 read as January, or a negative day counted from the month start"
+reading = "the page: integer parts, usually in their normal range, but values outside it carry into the next unit (the N-th day of a year); negative values are allowed"
+cases = ["cases/translation/functions/date-from-parts.toml#t006_negative"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-from-parts.t007"
+form = "sf.functions.date-from-parts"
+kind = "boundary"
+subkind = "leap_day"
+target = "leap_day"
+note = "29 February in leap and common years."
+catches = "an error for 2023-02-29 instead of carrying to 03-01"
+reading = "the page: integer parts, usually in their normal range, but values outside it carry into the next unit (the N-th day of a year); negative values are allowed"
+cases = ["cases/translation/functions/date-from-parts.toml#t007_leap"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-part.date-part.t001"
+form = "sf.functions.date-part.date-part"
+kind = "type_combination"
+target = "DATE"
+note = "Calendar and week parts of dates at year and ISO-week boundaries."
+catches = "dayofweek on another numbering, week by calendar year, or dayofyear 0-based, or ISO numbering for dayofweek (7 instead of 0 on the Sunday row)"
+reading = "the page: a part from Supported date and time parts, quoted or unquoted; a date, an interval, a time or a timestamp; returns NUMBER"
+cases = ["cases/translation/functions/date-part.date-part.toml#t001_date_parts"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-part.date-part.t002"
+form = "sf.functions.date-part.date-part"
+kind = "type_combination"
+target = "TIMESTAMP_NTZ"
+note = "Time parts with fractional seconds."
+catches = "seconds rounded, or nanosecond scaled to milliseconds"
+reading = "the page: a part from Supported date and time parts, quoted or unquoted; a date, an interval, a time or a timestamp; returns NUMBER"
+cases = ["cases/translation/functions/date-part.date-part.toml#t002_time_parts"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-part.date-part.t003"
+form = "sf.functions.date-part.date-part"
+kind = "type_combination"
+target = "TIMESTAMP_TZ"
+note = "Parts of a TIMESTAMP_TZ, including its offset and epoch."
+catches = "parts from the UTC instant instead of the wall clock, timezone_hour sign flipped, or epoch from the wall clock"
+reading = "the page: a part from Supported date and time parts, quoted or unquoted; a date, an interval, a time or a timestamp; returns NUMBER"
+cases = ["cases/translation/functions/date-part.date-part.toml#t003_timestamp_tz"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-part.date-part.t004"
+form = "sf.functions.date-part.date-part"
+kind = "type_combination"
+target = "TIME"
+note = "Parts of a TIME."
+catches = "an error for a TIME, or seconds rounded (46)"
+reading = "the page: a part from Supported date and time parts, quoted or unquoted; a date, an interval, a time or a timestamp; returns NUMBER"
+cases = ["cases/translation/functions/date-part.date-part.toml#t004_time"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-part.date-part.t005"
+form = "sf.functions.date-part.date-part"
+kind = "type_combination"
+target = "'part' string"
+note = "The part given as a string literal."
+catches = "a quoted part rejected"
+reading = "the page: a part from Supported date and time parts, quoted or unquoted; a date, an interval, a time or a timestamp; returns NUMBER"
+cases = ["cases/translation/functions/date-part.date-part.toml#t005_quoted_part"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-part.date-part.t006"
+form = "sf.functions.date-part.date-part"
+kind = "null_argument"
+target = "date_interval_time_or_timestamp_expr"
+note = "NULL next to a set date."
+catches = "NULL read as a default date"
+reading = "the page: a part from Supported date and time parts, quoted or unquoted; a date, an interval, a time or a timestamp; returns NUMBER"
+cases = ["cases/translation/functions/date-part.date-part.toml#t006_null_date_interval_time_or_timestamp_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-part.date-part.t007"
+form = "sf.functions.date-part.date-part"
+kind = "boundary"
+subkind = "leap_day"
+target = "leap_day"
+note = "Day of year around 29 February."
+catches = "a 365-day year in 2024"
+reading = "the page: a part from Supported date and time parts, quoted or unquoted; a date, an interval, a time or a timestamp; returns NUMBER"
+cases = ["cases/translation/functions/date-part.date-part.toml#t007_leap"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-part.date-part.t008"
+form = "sf.functions.date-part.date-part"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "Epoch parts before 1970."
+catches = "a truncation toward zero instead of floor for negative epochs"
+reading = "the page: a part from Supported date and time parts, quoted or unquoted; a date, an interval, a time or a timestamp; returns NUMBER"
+cases = ["cases/translation/functions/date-part.date-part.toml#t008_pre_epoch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-trunc.t001"
+form = "sf.functions.date-trunc"
+kind = "type_combination"
+target = "DATE"
+note = "Calendar parts of a Sunday in a leap year."
+catches = "week truncated to Sunday, quarter to the wrong month, or a TIMESTAMP returned for a DATE"
+reading = "the page: the result has the input's type; week follows WEEK_START (0: Monday); a TIME cannot be truncated to day or larger"
+cases = ["cases/translation/functions/date-trunc.toml#t001_date"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-trunc.t002"
+form = "sf.functions.date-trunc"
+kind = "type_combination"
+target = "TIMESTAMP_NTZ"
+note = "Time parts with fractional seconds."
+catches = "rounding instead of truncation (12:00 for 11:59:59.999)"
+reading = "the page: the result has the input's type; week follows WEEK_START (0: Monday); a TIME cannot be truncated to day or larger"
+cases = ["cases/translation/functions/date-trunc.toml#t002_timestamp_ntz"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-trunc.t003"
+form = "sf.functions.date-trunc"
+kind = "type_combination"
+target = "TIMESTAMP_TZ"
+note = "A TIMESTAMP_TZ truncated in its own offset."
+catches = "truncation in UTC (2025-01-01) instead of the wall clock (2024-12-31 00:00 -0800), or the offset dropped (the wall columns show the offset, which the instant comparison ignores)"
+reading = "the page: the result has the input's type; week follows WEEK_START (0: Monday); a TIME cannot be truncated to day or larger"
+cases = ["cases/translation/functions/date-trunc.toml#t003_timestamp_tz"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-trunc.t004"
+form = "sf.functions.date-trunc"
+kind = "type_combination"
+target = "TIME"
+note = "A TIME truncated to hour and minute."
+catches = "an error for a TIME, or a timestamp returned"
+reading = "the page: the result has the input's type; week follows WEEK_START (0: Monday); a TIME cannot be truncated to day or larger"
+cases = ["cases/translation/functions/date-trunc.toml#t004_time"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-trunc.t005"
+form = "sf.functions.date-trunc"
+kind = "null_argument"
+target = "date_or_time_expr"
+note = "NULL next to a set date."
+catches = "NULL read as a default date"
+reading = "the page: the result has the input's type; week follows WEEK_START (0: Monday); a TIME cannot be truncated to day or larger"
+cases = ["cases/translation/functions/date-trunc.toml#t005_null_date_or_time_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-trunc.t006"
+form = "sf.functions.date-trunc"
+kind = "boundary"
+subkind = "timezone_dst"
+target = "timezone_dst"
+note = "TIMESTAMP_TZ values on both sides of a DST change."
+catches = "the offset re-derived from a zone rule (or UTC) instead of kept (the wall columns show the offset, which the instant comparison ignores)"
+reading = "the page: the result has the input's type; week follows WEEK_START (0: Monday); a TIME cannot be truncated to day or larger"
+cases = ["cases/translation/functions/date-trunc.toml#t006_dst"]
+required = true
+
+[[obligation]]
+id = "sf.functions.date-trunc.t007"
+form = "sf.functions.date-trunc"
+kind = "error_parity"
+target = "error"
+note = "A TIME truncated to day, which the page says is an error."
+catches = "midnight returned instead of an error"
+reading = "the page: the result has the input's type; week follows WEEK_START (0: Monday); a TIME cannot be truncated to day or larger"
+cases = ["cases/translation/functions/date-trunc.toml#t007_time_day"]
+required = true
+
+[[obligation]]
+id = "sf.functions.dateadd.t001"
+form = "sf.functions.dateadd"
+kind = "type_combination"
+target = "part, NUMBER, DATE"
+note = "Days, months and years added to dates, including negatives."
+catches = "value and expression swapped, or months added as 30 days"
+reading = "the page: DATEADD(part, value, expr) with TIMEADD and TIMESTAMPADD as aliases; a DATE with an hour-or-smaller part becomes a TIMESTAMP at midnight; a TIME takes only hour or smaller parts; a month end clamps to the last day of the result month"
+cases = ["cases/translation/functions/dateadd.toml#t001_date"]
+required = true
+
+[[obligation]]
+id = "sf.functions.dateadd.t002"
+form = "sf.functions.dateadd"
+kind = "type_combination"
+target = "hour, NUMBER, DATE"
+note = "Hours added to a DATE, which the page says gives a TIMESTAMP."
+catches = "a DATE result (the hours dropped) or an error"
+reading = "the page: DATEADD(part, value, expr) with TIMEADD and TIMESTAMPADD as aliases; a DATE with an hour-or-smaller part becomes a TIMESTAMP at midnight; a TIME takes only hour or smaller parts; a month end clamps to the last day of the result month"
+cases = ["cases/translation/functions/dateadd.toml#t002_date_hour"]
+required = true
+
+[[obligation]]
+id = "sf.functions.dateadd.t003"
+form = "sf.functions.dateadd"
+kind = "type_combination"
+target = "part, NUMBER, TIMESTAMP_NTZ"
+note = "The part aliases ms, mi and mm on a timestamp."
+catches = "mm read as minutes (Snowflake: months), mi as months, or ms as microseconds"
+reading = "the page: DATEADD(part, value, expr) with TIMEADD and TIMESTAMPADD as aliases; a DATE with an hour-or-smaller part becomes a TIMESTAMP at midnight; a TIME takes only hour or smaller parts; a month end clamps to the last day of the result month"
+cases = ["cases/translation/functions/dateadd.toml#t003_timestamp_ntz"]
+required = true
+
+[[obligation]]
+id = "sf.functions.dateadd.t004"
+form = "sf.functions.dateadd"
+kind = "type_combination"
+target = "part, NUMBER, TIME"
+note = "Hours added to a TIME past midnight."
+catches = "an error or a timestamp instead of wrapping to 01:30"
+reading = "the page: DATEADD(part, value, expr) with TIMEADD and TIMESTAMPADD as aliases; a DATE with an hour-or-smaller part becomes a TIMESTAMP at midnight; a TIME takes only hour or smaller parts; a month end clamps to the last day of the result month"
+cases = ["cases/translation/functions/dateadd.toml#t004_time"]
+required = true
+
+[[obligation]]
+id = "sf.functions.dateadd.t005"
+form = "sf.functions.dateadd"
+kind = "type_combination"
+target = "part, NUMBER, TIMESTAMP_TZ"
+note = "A day added to a TIMESTAMP_TZ."
+catches = "the offset dropped or converted to UTC (the wall column shows the offset, which the instant comparison ignores)"
+reading = "the page: DATEADD(part, value, expr) with TIMEADD and TIMESTAMPADD as aliases; a DATE with an hour-or-smaller part becomes a TIMESTAMP at midnight; a TIME takes only hour or smaller parts; a month end clamps to the last day of the result month"
+cases = ["cases/translation/functions/dateadd.toml#t005_timestamp_tz"]
+required = true
+
+[[obligation]]
+id = "sf.functions.dateadd.t006"
+form = "sf.functions.dateadd"
+kind = "null_argument"
+target = "value"
+note = "NULL value."
+catches = "NULL read as 0 (the date unchanged)"
+reading = "the page: DATEADD(part, value, expr) with TIMEADD and TIMESTAMPADD as aliases; a DATE with an hour-or-smaller part becomes a TIMESTAMP at midnight; a TIME takes only hour or smaller parts; a month end clamps to the last day of the result month"
+cases = ["cases/translation/functions/dateadd.toml#t006_null_value"]
+required = true
+
+[[obligation]]
+id = "sf.functions.dateadd.t007"
+form = "sf.functions.dateadd"
+kind = "null_argument"
+target = "date_or_time_expr"
+note = "NULL date."
+catches = "NULL read as a default date"
+reading = "the page: DATEADD(part, value, expr) with TIMEADD and TIMESTAMPADD as aliases; a DATE with an hour-or-smaller part becomes a TIMESTAMP at midnight; a TIME takes only hour or smaller parts; a month end clamps to the last day of the result month"
+cases = ["cases/translation/functions/dateadd.toml#t007_null_date_or_time_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.dateadd.t008"
+form = "sf.functions.dateadd"
+kind = "boundary"
+subkind = "leap_day"
+target = "leap_day"
+note = "Month and year steps from month ends and 29 February."
+catches = "overflow into the next month (2024-03-02) instead of clamping to 2024-02-29, or 2025-03-01 from 2024-02-29"
+reading = "the page: DATEADD(part, value, expr) with TIMEADD and TIMESTAMPADD as aliases; a DATE with an hour-or-smaller part becomes a TIMESTAMP at midnight; a TIME takes only hour or smaller parts; a month end clamps to the last day of the result month"
+cases = ["cases/translation/functions/dateadd.toml#t008_month_end"]
+required = true
+
+[[obligation]]
+id = "sf.functions.dateadd.t009"
+form = "sf.functions.dateadd"
+kind = "boundary"
+subkind = "rounding_scale"
+target = "rounding_scale"
+note = "A fractional value: records how Snowflake rounds it."
+catches = "truncation (1 day) where Snowflake rounds 1.7 to 2, or the reverse"
+reading = "the page: DATEADD(part, value, expr) with TIMEADD and TIMESTAMPADD as aliases; a DATE with an hour-or-smaller part becomes a TIMESTAMP at midnight; a TIME takes only hour or smaller parts; a month end clamps to the last day of the result month"
+cases = ["cases/translation/functions/dateadd.toml#t009_fractional_value"]
+required = true
+
+[[obligation]]
+id = "sf.functions.dateadd.t010"
+form = "sf.functions.dateadd"
+kind = "boundary"
+subkind = "timezone_dst"
+target = "timezone_dst"
+note = "An hour added at 01:30 New York time on both DST change days, through the two-argument CONVERT_TIMEZONE of a UTC timestamp."
+catches = "wall-clock arithmetic in the zone (07:30Z instead of the elapsed 06:30Z on the autumn row) (the wall column shows the offset, which the instant comparison ignores)"
+reading = "the page: DATEADD(part, value, expr) with TIMEADD and TIMESTAMPADD as aliases; a DATE with an hour-or-smaller part becomes a TIMESTAMP at midnight; a TIME takes only hour or smaller parts; a month end clamps to the last day of the result month"
+cases = ["cases/translation/functions/dateadd.toml#t010_dst"]
+required = true
+
+[[obligation]]
+id = "sf.functions.dateadd.t011"
+form = "sf.functions.dateadd"
+kind = "error_parity"
+target = "error"
+note = "Days added to a TIME, which the page says is an error."
+catches = "a wrapped TIME instead of an error"
+reading = "the page: DATEADD(part, value, expr) with TIMEADD and TIMESTAMPADD as aliases; a DATE with an hour-or-smaller part becomes a TIMESTAMP at midnight; a TIME takes only hour or smaller parts; a month end clamps to the last day of the result month"
+cases = ["cases/translation/functions/dateadd.toml#t011_time_day"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datediff.datediff.t001"
+form = "sf.functions.datediff.datediff"
+kind = "type_combination"
+target = "part, DATE, DATE"
+note = "Year, month, day and week boundaries crossed by one day."
+catches = "elapsed whole units (0 years for 31 Dec to 1 Jan) instead of crossed boundaries, or the sign reversed"
+reading = "the page: DATEDIFF(part, expr1, expr2) subtracts expr1 from expr2 and counts crossed unit boundaries; TIMEDIFF and TIMESTAMPDIFF are aliases; NULL if any argument is NULL"
+cases = ["cases/translation/functions/datediff.datediff.toml#t001_date"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datediff.datediff.t002"
+form = "sf.functions.datediff.datediff"
+kind = "type_combination"
+target = "part, TIMESTAMP_NTZ, TIMESTAMP_NTZ"
+note = "Hour, minute and millisecond counts."
+catches = "elapsed time (0 hours for 10:59 to 11:00) instead of crossed boundaries, or seconds counted in milliseconds"
+reading = "the page: DATEDIFF(part, expr1, expr2) subtracts expr1 from expr2 and counts crossed unit boundaries; TIMEDIFF and TIMESTAMPDIFF are aliases; NULL if any argument is NULL"
+cases = ["cases/translation/functions/datediff.datediff.toml#t002_timestamp_ntz"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datediff.datediff.t003"
+form = "sf.functions.datediff.datediff"
+kind = "type_combination"
+target = "part, TIME, TIME"
+note = "Differences between TIMEs."
+catches = "an error for TIME arguments"
+reading = "the page: DATEDIFF(part, expr1, expr2) subtracts expr1 from expr2 and counts crossed unit boundaries; TIMEDIFF and TIMESTAMPDIFF are aliases; NULL if any argument is NULL"
+cases = ["cases/translation/functions/datediff.datediff.toml#t003_time"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datediff.datediff.t004"
+form = "sf.functions.datediff.datediff"
+kind = "type_combination"
+target = "part, DATE, TIMESTAMP_NTZ"
+note = "A DATE against a TIMESTAMP."
+catches = "an error, or the date read as end of day"
+reading = "the page: DATEDIFF(part, expr1, expr2) subtracts expr1 from expr2 and counts crossed unit boundaries; TIMEDIFF and TIMESTAMPDIFF are aliases; NULL if any argument is NULL"
+cases = ["cases/translation/functions/datediff.datediff.toml#t004_mixed"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datediff.datediff.t005"
+form = "sf.functions.datediff.datediff"
+kind = "null_argument"
+target = "date_or_time_expr1"
+note = "NULL first expression."
+catches = "NULL read as a default date"
+reading = "the page: DATEDIFF(part, expr1, expr2) subtracts expr1 from expr2 and counts crossed unit boundaries; TIMEDIFF and TIMESTAMPDIFF are aliases; NULL if any argument is NULL"
+cases = ["cases/translation/functions/datediff.datediff.toml#t005_null_date_or_time_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datediff.datediff.t006"
+form = "sf.functions.datediff.datediff"
+kind = "null_argument"
+target = "date_or_time_expr2"
+note = "NULL second expression."
+catches = "NULL read as a default date"
+reading = "the page: DATEDIFF(part, expr1, expr2) subtracts expr1 from expr2 and counts crossed unit boundaries; TIMEDIFF and TIMESTAMPDIFF are aliases; NULL if any argument is NULL"
+cases = ["cases/translation/functions/datediff.datediff.toml#t006_null_date_or_time_expr2"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datediff.datediff.t007"
+form = "sf.functions.datediff.datediff"
+kind = "boundary"
+subkind = "leap_day"
+target = "leap_day"
+note = "Day counts across 29 February."
+catches = "a 365-day year"
+reading = "the page: DATEDIFF(part, expr1, expr2) subtracts expr1 from expr2 and counts crossed unit boundaries; TIMEDIFF and TIMESTAMPDIFF are aliases; NULL if any argument is NULL"
+cases = ["cases/translation/functions/datediff.datediff.toml#t007_leap"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datediff.datediff.t008"
+form = "sf.functions.datediff.datediff"
+kind = "boundary"
+subkind = "timezone_dst"
+target = "timezone_dst"
+note = "Hours between TIMESTAMP_TZ values across a DST change."
+catches = "wall-clock difference (2) instead of elapsed hours (1)"
+reading = "the page: DATEDIFF(part, expr1, expr2) subtracts expr1 from expr2 and counts crossed unit boundaries; TIMEDIFF and TIMESTAMPDIFF are aliases; NULL if any argument is NULL"
+cases = ["cases/translation/functions/datediff.datediff.toml#t008_dst"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datediff.datediff.t009"
+form = "sf.functions.datediff.datediff"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "A later first argument."
+catches = "an absolute value"
+reading = "the page: DATEDIFF(part, expr1, expr2) subtracts expr1 from expr2 and counts crossed unit boundaries; TIMEDIFF and TIMESTAMPDIFF are aliases; NULL if any argument is NULL"
+cases = ["cases/translation/functions/datediff.datediff.toml#t009_negative"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datediff.syntax.t001"
+form = "sf.functions.datediff.syntax"
+kind = "type_combination"
+target = "DATE - DATE"
+note = "Day differences in both directions and across a leap day."
+catches = "an INTERVAL instead of an integer, or the sign reversed"
+reading = "the page: date_expr2 - date_expr1 gives the days between two DATEs; \"Returns an error if date_expr2 or date_expr1 is NULL\""
+cases = ["cases/translation/functions/datediff.syntax.toml#t001_date"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datediff.syntax.t002"
+form = "sf.functions.datediff.syntax"
+kind = "null_argument"
+target = "date_expr1"
+note = "NULL first date: the page says this is an error; records what Snowflake returns."
+catches = "NULL where Snowflake raises an error, or the reverse"
+reading = "the page: date_expr2 - date_expr1 gives the days between two DATEs; \"Returns an error if date_expr2 or date_expr1 is NULL\""
+cases = ["cases/translation/functions/datediff.syntax.toml#t002_null_date_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datediff.syntax.t003"
+form = "sf.functions.datediff.syntax"
+kind = "null_argument"
+target = "date_expr2"
+note = "NULL second date: the page says this is an error; records what Snowflake returns."
+catches = "NULL where Snowflake raises an error, or the reverse"
+reading = "the page: date_expr2 - date_expr1 gives the days between two DATEs; \"Returns an error if date_expr2 or date_expr1 is NULL\""
+cases = ["cases/translation/functions/datediff.syntax.toml#t003_null_date_expr2"]
+required = true
+
+[[obligation]]
+id = "sf.functions.datediff.syntax.t004"
+form = "sf.functions.datediff.syntax"
+kind = "boundary"
+subkind = "leap_day"
+target = "leap_day"
+note = "A year containing 29 February."
+catches = "a fixed 365-day year"
+reading = "the page: date_expr2 - date_expr1 gives the days between two DATEs; \"Returns an error if date_expr2 or date_expr1 is NULL\""
+cases = ["cases/translation/functions/datediff.syntax.toml#t004_leap"]
+required = true
+
+[[obligation]]
+id = "sf.functions.dayname.t001"
+form = "sf.functions.dayname"
+kind = "type_combination"
+target = "DATE"
+note = "DAYNAME of several dates."
+catches = "full names (Monday), upper case, or a Sunday-based index"
+reading = "the page: a date or timestamp; returns a three-letter VARCHAR"
+cases = ["cases/translation/functions/dayname.toml#t001_date"]
+required = true
+
+[[obligation]]
+id = "sf.functions.dayname.t002"
+form = "sf.functions.dayname"
+kind = "type_combination"
+target = "TIMESTAMP_TZ"
+note = "DAYNAME of a TIMESTAMP_TZ whose UTC instant is a day later."
+catches = "the name of the UTC instant (Wed) instead of the wall clock (Tue)"
+reading = "the page: a date or timestamp; returns a three-letter VARCHAR"
+cases = ["cases/translation/functions/dayname.toml#t002_timestamp_tz"]
+required = true
+
+[[obligation]]
+id = "sf.functions.dayname.t003"
+form = "sf.functions.dayname"
+kind = "null_argument"
+target = "date_or_timestamp_expr"
+note = "NULL next to a set date."
+catches = "NULL read as a default date"
+reading = "the page: a date or timestamp; returns a three-letter VARCHAR"
+cases = ["cases/translation/functions/dayname.toml#t003_null_date_or_timestamp_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decode.t001"
+form = "sf.functions.decode"
+kind = "type_combination"
+target = "NUMBER searches"
+note = "Matches, a repeated search and the default."
+catches = "the last match winning, or the default missed"
+reading = "the page: the first search equal to expr gives its result; NULL matches NULL; no match and no default gives NULL"
+cases = ["cases/translation/functions/decode.toml#t001_number"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decode.t002"
+form = "sf.functions.decode"
+kind = "type_combination"
+target = "no default"
+note = "No default argument, with an even number of trailing arguments."
+catches = "the last result read as the default"
+reading = "the page: the first search equal to expr gives its result; NULL matches NULL; no match and no default gives NULL"
+cases = ["cases/translation/functions/decode.toml#t002_no_default"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decode.t003"
+form = "sf.functions.decode"
+kind = "null_argument"
+target = "expr"
+note = "NULL expr against a NULL search, which the page says matches."
+catches = "CASE semantics (NULL never matches: the default)"
+reading = "the page: the first search equal to expr gives its result; NULL matches NULL; no match and no default gives NULL"
+cases = ["cases/translation/functions/decode.toml#t003_null_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decode.t004"
+form = "sf.functions.decode"
+kind = "null_argument"
+target = "result"
+note = "A NULL result selected."
+catches = "the default instead of NULL"
+reading = "the page: the first search equal to expr gives its result; NULL matches NULL; no match and no default gives NULL"
+cases = ["cases/translation/functions/decode.toml#t004_null_result"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decompress-binary.t001"
+form = "sf.functions.decompress-binary"
+kind = "type_combination"
+target = "BINARY, 'SNAPPY'"
+note = "DECOMPRESS_BINARY of fixed Snappy blocks, one from the page."
+catches = "another codec or a framing offset (the varint length read as data)"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; the level is ignored; returns BINARY"
+cases = ["cases/translation/functions/decompress-binary.toml#t001_snappy"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decompress-binary.t002"
+form = "sf.functions.decompress-binary"
+kind = "type_combination"
+target = "BINARY, 'snappy(3)'"
+note = "A lower-case method name with a level, which the page says is ignored."
+catches = "the level or the lower-case name rejected"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; the level is ignored; returns BINARY"
+cases = ["cases/translation/functions/decompress-binary.toml#t002_method_level"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decompress-binary.t003"
+form = "sf.functions.decompress-binary"
+kind = "null_argument"
+target = "input"
+note = "NULL input next to a set one."
+catches = "NULL decompressed as an empty value"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; the level is ignored; returns BINARY"
+cases = ["cases/translation/functions/decompress-binary.toml#t003_null_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decompress-binary.t004"
+form = "sf.functions.decompress-binary"
+kind = "null_argument"
+target = "method"
+note = "NULL method."
+catches = "NULL read as a default method"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; the level is ignored; returns BINARY"
+cases = ["cases/translation/functions/decompress-binary.toml#t004_null_method"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decompress-binary.t005"
+form = "sf.functions.decompress-binary"
+kind = "error_parity"
+target = "error"
+note = "Bytes that are not a valid Snappy block."
+catches = "NULL or partial output instead of an error"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; the level is ignored; returns BINARY"
+cases = ["cases/translation/functions/decompress-binary.toml#t005_corrupt"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decompress-binary.t006"
+form = "sf.functions.decompress-binary"
+kind = "error_parity"
+target = "error"
+note = "An unknown method; Snowflake refuses it when compiling (SQLSTATE 42P19)."
+catches = "a fallback codec instead of an error"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; the level is ignored; returns BINARY"
+cases = ["cases/translation/functions/decompress-binary.toml#t006_unknown_method"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decompress-binary.t007"
+form = "sf.functions.decompress-binary"
+kind = "type_combination"
+target = "BINARY, ZLIB/ZSTD/BZ2"
+note = "Round trips through each non-Snappy method."
+catches = "only SNAPPY supported, or COMPRESS and DECOMPRESS disagreeing on the framing of another method"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; the level is ignored; returns BINARY"
+cases = ["cases/translation/functions/decompress-binary.toml#t007_other_methods"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decompress-string.t001"
+form = "sf.functions.decompress-string"
+kind = "type_combination"
+target = "BINARY, 'SNAPPY'"
+note = "DECOMPRESS_STRING of fixed Snappy blocks, one from the page."
+catches = "another codec or a framing offset (the varint length read as data)"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; the level is ignored; returns VARCHAR"
+cases = ["cases/translation/functions/decompress-string.toml#t001_snappy"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decompress-string.t002"
+form = "sf.functions.decompress-string"
+kind = "type_combination"
+target = "BINARY, 'snappy(3)'"
+note = "A lower-case method name with a level, which the page says is ignored."
+catches = "the level or the lower-case name rejected"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; the level is ignored; returns VARCHAR"
+cases = ["cases/translation/functions/decompress-string.toml#t002_method_level"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decompress-string.t003"
+form = "sf.functions.decompress-string"
+kind = "null_argument"
+target = "input"
+note = "NULL input next to a set one."
+catches = "NULL decompressed as an empty value"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; the level is ignored; returns VARCHAR"
+cases = ["cases/translation/functions/decompress-string.toml#t003_null_input"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decompress-string.t004"
+form = "sf.functions.decompress-string"
+kind = "null_argument"
+target = "method"
+note = "NULL method."
+catches = "NULL read as a default method"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; the level is ignored; returns VARCHAR"
+cases = ["cases/translation/functions/decompress-string.toml#t004_null_method"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decompress-string.t005"
+form = "sf.functions.decompress-string"
+kind = "error_parity"
+target = "error"
+note = "Bytes that are not a valid Snappy block."
+catches = "NULL or partial output instead of an error"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; the level is ignored; returns VARCHAR"
+cases = ["cases/translation/functions/decompress-string.toml#t005_corrupt"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decompress-string.t006"
+form = "sf.functions.decompress-string"
+kind = "error_parity"
+target = "error"
+note = "An unknown method; Snowflake refuses it when compiling (SQLSTATE 42P19)."
+catches = "a fallback codec instead of an error"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; the level is ignored; returns VARCHAR"
+cases = ["cases/translation/functions/decompress-string.toml#t006_unknown_method"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decompress-string.t007"
+form = "sf.functions.decompress-string"
+kind = "type_combination"
+target = "BINARY, ZLIB/ZSTD/BZ2"
+note = "Round trips through each non-Snappy method."
+catches = "only SNAPPY supported, or COMPRESS and DECOMPRESS disagreeing on the framing of another method"
+reading = "the page: is BINARY or a string; is SNAPPY, ZLIB, ZSTD or BZ2 with an optional level, case-insensitive; an unknown method fails; the level is ignored; returns VARCHAR"
+cases = ["cases/translation/functions/decompress-string.toml#t007_other_methods"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decrypt.t001"
+form = "sf.functions.decrypt"
+kind = "type_combination"
+target = "BINARY, VARCHAR"
+note = "Text encrypted and decrypted, converted back from BINARY with utf-8."
+catches = "the bytes returned as hex text"
+reading = "the page: the value decrypted with the passphrase as BINARY; the passphrase and any AAD must match those used to encrypt; a mismatch fails"
+cases = ["cases/translation/functions/decrypt.toml#t001_round_trip"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decrypt.t002"
+form = "sf.functions.decrypt"
+kind = "error_parity"
+target = "error"
+note = "An AAD that differs from the one used to encrypt, which the page says fails."
+catches = "the value decrypted anyway"
+reading = "the page: the value decrypted with the passphrase as BINARY; the passphrase and any AAD must match those used to encrypt; a mismatch fails"
+cases = ["cases/translation/functions/decrypt.toml#t002_aad_mismatch"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decrypt.t003"
+form = "sf.functions.decrypt"
+kind = "error_parity"
+target = "error"
+note = "A passphrase other than the one used to encrypt."
+catches = "garbage bytes or NULL returned"
+reading = "the page: the value decrypted with the passphrase as BINARY; the passphrase and any AAD must match those used to encrypt; a mismatch fails"
+cases = ["cases/translation/functions/decrypt.toml#t003_wrong_passphrase"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decrypt.t004"
+form = "sf.functions.decrypt"
+kind = "null_argument"
+target = "value-to-decrypt"
+note = "A NULL value; the page does not say; the recording decides."
+catches = "an error where Snowflake returns NULL, or the reverse"
+reading = "the page: the value decrypted with the passphrase as BINARY; the passphrase and any AAD must match those used to encrypt; a mismatch fails"
+cases = ["cases/translation/functions/decrypt.toml#t004_null_value_to_decrypt"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decrypt-raw.t001"
+form = "sf.functions.decrypt-raw"
+kind = "type_combination"
+target = "BINARY ciphertext, key, IV, AAD, tag"
+note = "ENCRYPT_RAW output decrypted with its own fields."
+catches = "the ciphertext returned unchanged"
+reading = "the page: the ciphertext decrypted with the BINARY key, IV, AAD and tag used to encrypt it, as BINARY"
+cases = ["cases/translation/functions/decrypt-raw.toml#t001_round_trip"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decrypt-raw.t002"
+form = "sf.functions.decrypt-raw"
+kind = "error_parity"
+target = "error"
+note = "A different AAD, which the page says makes decryption fail."
+catches = "the bytes returned anyway"
+reading = "the page: the ciphertext decrypted with the BINARY key, IV, AAD and tag used to encrypt it, as BINARY"
+cases = ["cases/translation/functions/decrypt-raw.toml#t002_wrong_aad"]
+required = true
+
+[[obligation]]
+id = "sf.functions.decrypt-raw.t003"
+form = "sf.functions.decrypt-raw"
+kind = "null_argument"
+target = "value-to-decrypt"
+note = "A NULL ciphertext; the page does not say; the recording decides."
+catches = "an error where Snowflake returns NULL, or the reverse"
+reading = "the page: the ciphertext decrypted with the BINARY key, IV, AAD and tag used to encrypt it, as BINARY"
+cases = ["cases/translation/functions/decrypt-raw.toml#t003_null_value_to_decrypt"]
+required = true
+
+[[obligation]]
+id = "sf.functions.degrees.t001"
+form = "sf.functions.degrees"
+kind = "type_combination"
+target = "FLOAT"
+note = "DEGREES of FLOAT values."
+catches = "RADIANS computed (the reverse conversion)"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/degrees.toml#t001_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.degrees.t002"
+form = "sf.functions.degrees"
+kind = "type_combination"
+target = "NUMBER(12,4)"
+note = "DEGREES of NUMBER(12,4), converted to FLOAT."
+catches = "RADIANS computed (the reverse conversion); or the result left fixed-point"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/degrees.toml#t002_fixed"]
+required = true
+
+[[obligation]]
+id = "sf.functions.degrees.t003"
+form = "sf.functions.degrees"
+kind = "null_argument"
+target = "x"
+note = "DEGREES of NULL."
+catches = "NULL turned into 0 or NaN"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/degrees.toml#t003_null"]
+required = true
+
+[[obligation]]
+id = "sf.functions.degrees.t004"
+form = "sf.functions.degrees"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "Large radian values."
+catches = "x*180 computed before dividing by pi, overflowing where the result is finite"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/degrees.toml#t004_large"]
+required = true
+
+[[obligation]]
+id = "sf.functions.degrees.t005"
+form = "sf.functions.degrees"
+kind = "boundary"
+subkind = "magnitude"
+target = "magnitude"
+note = "A result past the double range: records what Snowflake returns there (NaN, Infinity, NULL or an error); re-kinded to error_parity after capture if Snowflake raises an error."
+catches = "Infinity versus an error"
+reading = "the page: \"The data type must be FLOAT.\" Returns FLOAT; a fixed-point argument reaches it by implicit conversion"
+cases = ["cases/translation/functions/degrees.toml#t005_overflow"]
+required = true
+
+[[obligation]]
+id = "sf.functions.dense-rank.t001"
+form = "sf.functions.dense-rank"
+kind = "type_combination"
+target = "ORDER BY ASC"
+note = "A tie at 20 and a NULL order key."
+catches = "a gap after the tie (RANK: 1,2,2,4)"
+reading = "the page: the rank within the ordered partition, with ties sharing a rank and no gaps"
+cases = ["cases/translation/functions/dense-rank.toml#t001_ties"]
+required = true
+
+[[obligation]]
+id = "sf.functions.dense-rank.t002"
+form = "sf.functions.dense-rank"
+kind = "type_combination"
+target = "ORDER BY DESC NULLS FIRST"
+note = "Descending order with NULLS FIRST."
+catches = "NULLS FIRST ignored"
+reading = "the page: the rank within the ordered partition, with ties sharing a rank and no gaps"
+cases = ["cases/translation/functions/dense-rank.toml#t002_desc_nulls_first"]
+required = true
+
+[[obligation]]
+id = "sf.functions.dense-rank.t003"
+form = "sf.functions.dense-rank"
+kind = "null_argument"
+target = "order-expr"
+note = "NULL order keys sorted first with ASC NULLS FIRST; tied NULLs share a rank."
+catches = "NULLs sorted last, or each NULL given its own rank"
+reading = "the page: the rank within the ordered partition, with ties sharing a rank and no gaps"
+cases = ["cases/translation/functions/dense-rank.toml#t003_null_order_expr"]
+required = true
+
+[[obligation]]
+id = "sf.functions.div0.t001"
+form = "sf.functions.div0"
+kind = "type_combination"
+target = "NUMBER(38,0)"
+note = "DIV0 of integers, as / divides."
+catches = "integer division (3 instead of 3.500000)"
+reading = "the page: both arguments numeric expressions; the numeric category gives integer NUMBER, NUMBER with a scale and FLOAT; DIV0 returns 0 when the divisor is 0"
+cases = ["cases/translation/functions/div0.toml#t001_int"]
+required = true
+
+[[obligation]]
+id = "sf.functions.div0.t002"
+form = "sf.functions.div0"
+kind = "type_combination"
+target = "NUMBER(10,2)"
+note = "DIV0 of NUMBER(10,2) values."
+catches = "quotient scale differing from /"
+reading = "the page: both arguments numeric expressions; the numeric category gives integer NUMBER, NUMBER with a scale and FLOAT; DIV0 returns 0 when the divisor is 0"
+cases = ["cases/translation/functions/div0.toml#t002_dec"]
+required = true
+
+[[obligation]]
+id = "sf.functions.div0.t003"
+form = "sf.functions.div0"
+kind = "type_combination"
+target = "FLOAT"
+note = "DIV0 of FLOAT values."
+catches = "infinity or NaN for a zero divisor instead of 0"
+reading = "the page: both arguments numeric expressions; the numeric category gives integer NUMBER, NUMBER with a scale and FLOAT; DIV0 returns 0 when the divisor is 0"
+cases = ["cases/translation/functions/div0.toml#t003_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.div0.t004"
+form = "sf.functions.div0"
+kind = "null_argument"
+target = "dividend"
+note = "NULL dividend."
+catches = "NULL turned into 0"
+reading = "the page: both arguments numeric expressions; the numeric category gives integer NUMBER, NUMBER with a scale and FLOAT; DIV0 returns 0 when the divisor is 0"
+cases = ["cases/translation/functions/div0.toml#t004_null_a"]
+required = true
+
+[[obligation]]
+id = "sf.functions.div0.t005"
+form = "sf.functions.div0"
+kind = "null_argument"
+target = "divisor"
+note = "NULL divisor."
+catches = "a NULL divisor returning 0 (that is DIV0NULL)"
+reading = "the page: both arguments numeric expressions; the numeric category gives integer NUMBER, NUMBER with a scale and FLOAT; DIV0 returns 0 when the divisor is 0"
+cases = ["cases/translation/functions/div0.toml#t005_null_b"]
+required = true
+
+[[obligation]]
+id = "sf.functions.div0.t006"
+form = "sf.functions.div0"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "Negative dividends over zero; Snowflake returns +0 for DIV0(-5, 0) (captured at e8691b63)."
+catches = "the sign of the dividend kept on the zero result (-0 instead of 0)"
+reading = "the page: both arguments numeric expressions; the numeric category gives integer NUMBER, NUMBER with a scale and FLOAT; DIV0 returns 0 when the divisor is 0"
+cases = ["cases/translation/functions/div0.toml#t006_neg_zero"]
+required = true
+
+[[obligation]]
+id = "sf.functions.div0null.t001"
+form = "sf.functions.div0null"
+kind = "type_combination"
+target = "NUMBER(38,0)"
+note = "DIV0NULL of integers, as / divides."
+catches = "integer division (3 instead of 3.500000)"
+reading = "the page: both arguments numeric expressions; the numeric category gives integer NUMBER, NUMBER with a scale and FLOAT; DIV0NULL returns 0 when the divisor is 0 or NULL"
+cases = ["cases/translation/functions/div0null.toml#t001_int"]
+required = true
+
+[[obligation]]
+id = "sf.functions.div0null.t002"
+form = "sf.functions.div0null"
+kind = "type_combination"
+target = "NUMBER(10,2)"
+note = "DIV0NULL of NUMBER(10,2) values."
+catches = "quotient scale differing from /"
+reading = "the page: both arguments numeric expressions; the numeric category gives integer NUMBER, NUMBER with a scale and FLOAT; DIV0NULL returns 0 when the divisor is 0 or NULL"
+cases = ["cases/translation/functions/div0null.toml#t002_dec"]
+required = true
+
+[[obligation]]
+id = "sf.functions.div0null.t003"
+form = "sf.functions.div0null"
+kind = "type_combination"
+target = "FLOAT"
+note = "DIV0NULL of FLOAT values."
+catches = "infinity or NaN for a zero divisor instead of 0"
+reading = "the page: both arguments numeric expressions; the numeric category gives integer NUMBER, NUMBER with a scale and FLOAT; DIV0NULL returns 0 when the divisor is 0 or NULL"
+cases = ["cases/translation/functions/div0null.toml#t003_float"]
+required = true
+
+[[obligation]]
+id = "sf.functions.div0null.t004"
+form = "sf.functions.div0null"
+kind = "null_argument"
+target = "dividend"
+note = "NULL dividend."
+catches = "NULL turned into 0"
+reading = "the page: both arguments numeric expressions; the numeric category gives integer NUMBER, NUMBER with a scale and FLOAT; DIV0NULL returns 0 when the divisor is 0 or NULL"
+cases = ["cases/translation/functions/div0null.toml#t004_null_a"]
+required = true
+
+[[obligation]]
+id = "sf.functions.div0null.t005"
+form = "sf.functions.div0null"
+kind = "null_argument"
+target = "divisor"
+note = "NULL divisor."
+catches = "a NULL divisor returning NULL (that is DIV0)"
+reading = "the page: both arguments numeric expressions; the numeric category gives integer NUMBER, NUMBER with a scale and FLOAT; DIV0NULL returns 0 when the divisor is 0 or NULL"
+cases = ["cases/translation/functions/div0null.toml#t005_null_b"]
+required = true
+
+[[obligation]]
+id = "sf.functions.div0null.t006"
+form = "sf.functions.div0null"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "Negative dividends over zero."
+catches = "a sign-keeping or FLOAT path giving -0 or -1e-10 where the quotient underflows the scale"
+reading = "the page: both arguments numeric expressions; the numeric category gives integer NUMBER, NUMBER with a scale and FLOAT; DIV0NULL returns 0 when the divisor is 0 or NULL"
+cases = ["cases/translation/functions/div0null.toml#t006_neg_zero"]
+required = true
+
+[[obligation]]
+id = "sf.functions.editdistance.t001"
+form = "sf.functions.editdistance"
+kind = "type_combination"
+target = "VARCHAR, VARCHAR"
+note = "Levenshtein distances that differ from Hamming and LCS distances."
+catches = "Hamming or LCS distance, a byte-based distance (2 for caf\u00e9/cafe), or transpositions counted as 1"
+reading = "the page: two strings and an optional integer ; a negative maximum gives 0; the result is LEAST(distance, max_distance)"
+cases = ["cases/translation/functions/editdistance.toml#t001_varchar"]
+required = true
+
+[[obligation]]
+id = "sf.functions.editdistance.t002"
+form = "sf.functions.editdistance"
+kind = "type_combination"
+target = "VARCHAR, VARCHAR, NUMBER"
+note = "A maximum below and above the distance."
+catches = "the maximum ignored (3 instead of 2) or applied when above the distance"
+reading = "the page: two strings and an optional integer ; a negative maximum gives 0; the result is LEAST(distance, max_distance)"
+cases = ["cases/translation/functions/editdistance.toml#t002_max_distance"]
+required = true
+
+[[obligation]]
+id = "sf.functions.editdistance.t003"
+form = "sf.functions.editdistance"
+kind = "null_argument"
+target = "string_expr1"
+note = "NULL first string."
+catches = "NULL read as an empty string (the other length)"
+reading = "the page: two strings and an optional integer ; a negative maximum gives 0; the result is LEAST(distance, max_distance)"
+cases = ["cases/translation/functions/editdistance.toml#t003_null_string_expr1"]
+required = true
+
+[[obligation]]
+id = "sf.functions.editdistance.t004"
+form = "sf.functions.editdistance"
+kind = "null_argument"
+target = "string_expr2"
+note = "NULL second string."
+catches = "NULL read as an empty string"
+reading = "the page: two strings and an optional integer ; a negative maximum gives 0; the result is LEAST(distance, max_distance)"
+cases = ["cases/translation/functions/editdistance.toml#t004_null_string_expr2"]
+required = true
+
+[[obligation]]
+id = "sf.functions.editdistance.t005"
+form = "sf.functions.editdistance"
+kind = "null_argument"
+target = "max_distance"
+note = "NULL maximum."
+catches = "NULL read as no maximum (the full distance instead of NULL)"
+reading = "the page: two strings and an optional integer ; a negative maximum gives 0; the result is LEAST(distance, max_distance)"
+cases = ["cases/translation/functions/editdistance.toml#t005_null_max_distance"]
+required = true
+
+[[obligation]]
+id = "sf.functions.editdistance.t006"
+form = "sf.functions.editdistance"
+kind = "boundary"
+subkind = "negative"
+target = "negative"
+note = "A negative maximum, which the page says gives 0."
+catches = "the negative maximum returned or ignored"
+reading = "the page: two strings and an optional integer ; a negative maximum gives 0; the result is LEAST(distance, max_distance)"
+cases = ["cases/translation/functions/editdistance.toml#t006_negative_max"]
+required = true
+
+[[obligation]]
+id = "sf.functions.editdistance.t007"
+form = "sf.functions.editdistance"
+kind = "boundary"
+subkind = "empty_string"
+target = "empty_string"
+note = "An empty string against a set one."
+catches = "NULL or 0 instead of the other length"
+reading = "the page: two strings and an optional integer