From 881965fccff79a7072a50075a5d54ba8fb899e49 Mon Sep 17 00:00:00 2001 From: logicoffee Date: Fri, 23 Jan 2026 12:42:35 +0900 Subject: [PATCH 1/2] Change the format of the output of analyze --- README.md | 21 ++- .../references_analyzer.py | 21 ++- tests/test_analyze_bigquery.py | 147 ++++++++++++++--- tests/test_analyze_redshift.py | 153 +++++++++++++++--- 4 files changed, 297 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 53d62e4..6dd3cda 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,26 @@ from references = analyze(dialects.BigQuery, sql, schema, "production") print(references) -# {"production.shop.orders": {"id", "user_id", "payment.amount"}} +# [ +# { +# "database": "production", +# "schema": "shop", +# "table": "orders", +# "column": "id", +# }, +# { +# "database": "production", +# "schema": "shop", +# "table": "orders", +# "column": "payment.amount", +# }, +# { +# "database": "production", +# "schema": "shop", +# "table": "orders", +# "column": "user_id", +# }, +# ] ``` ### analyze_timespan diff --git a/src/analytics_query_analyzer/references_analyzer.py b/src/analytics_query_analyzer/references_analyzer.py index f2debc2..45c7c99 100644 --- a/src/analytics_query_analyzer/references_analyzer.py +++ b/src/analytics_query_analyzer/references_analyzer.py @@ -9,7 +9,7 @@ def __init__(self, schema: dict, default_catalog: str): self.schema = schema self.default_catalog = default_catalog - def analyze(self, expression: exp.Expression) -> dict: + def analyze(self, expression: exp.Expression) -> list[dict[str, str]]: references: dict[str, set[str]] = {} for scope in traverse_scope(expression): @@ -27,7 +27,7 @@ def analyze(self, expression: exp.Expression) -> dict: table_path = ".".join(full_path.split(".", 3)[:3]) _add(references, table_path, column_path) - return references + return _flatten_references(references) def _add(references: dict[str, set[str]], table: str, column: str): if table in references: @@ -36,6 +36,23 @@ def _add(references: dict[str, set[str]], table: str, column: str): references[table] = {column} +def _flatten_references(references: dict[str, set[str]]) -> list[dict[str, str]]: + rows: list[dict[str, str]] = [] + for table_path, columns in references.items(): + database, schema, table = table_path.split(".", 2) + for column in columns: + rows.append( + { + "database": database, + "schema": schema, + "table": table, + "column": column, + } + ) + rows.sort(key=lambda row: (row["database"], row["schema"], row["table"], row["column"])) + return rows + + def _extract_column(expr: exp.Expression) -> exp.Column | None: if isinstance(expr, exp.Column): return expr diff --git a/tests/test_analyze_bigquery.py b/tests/test_analyze_bigquery.py index 7c1307e..0a302d6 100644 --- a/tests/test_analyze_bigquery.py +++ b/tests/test_analyze_bigquery.py @@ -32,61 +32,125 @@ test_cases = [ - {"name": "not referencing a table", "sql": "select 1", "expected": {}}, + {"name": "not referencing a table", "sql": "select 1", "expected": []}, { "name": "referencing a table but not columns", "sql": "select count(1) from shop.orders", - "expected": {}, + "expected": [], }, { "name": "simple column reference", "sql": "select user_id from shop.orders", - "expected": {"production.shop.orders": {"user_id"}}, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "user_id", + } + ], }, { "name": "qualifying a table with a project", "sql": "select user_id from production.shop.orders", - "expected": {"production.shop.orders": {"user_id"}}, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "user_id", + } + ], }, { "name": "qualifying a table with a non-default project", "sql": "select id from development.shop.users", - "expected": {"development.shop.users": {"id"}}, + "expected": [ + { + "database": "development", + "schema": "shop", + "table": "users", + "column": "id", + } + ], }, { "name": "referencing a column in a where clause", "sql": "select count(1) from shop.orders where ordered_at >= '2026-01-01'", - "expected": {"production.shop.orders": {"ordered_at"}}, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + } + ], }, { "name": "referencing a column in a join", "sql": "select count(1) from shop.orders join shop.users on orders.user_id = users.id", - "expected": { - "production.shop.orders": {"user_id"}, - "production.shop.users": {"id"}, - }, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "user_id", + }, + { + "database": "production", + "schema": "shop", + "table": "users", + "column": "id", + }, + ], }, { "name": "referencing a column in an ORDER BY clause", "sql": "select user_id from shop.orders order by payment_amount desc", - "expected": {"production.shop.orders": {"user_id", "payment_amount"}}, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "payment_amount", + }, + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "user_id", + }, + ], }, { "name": "wildcard pattern", "sql": "select * from shop.users", - "expected": { - "production.shop.users": set(schema["production"]["shop"]["users"].keys()) - }, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "users", + "column": column, + } + for column in schema["production"]["shop"]["users"].keys() + ], }, { "name": "using a wildcard with COUNT", "sql": "select count(*) from shop.users", - "expected": {}, + "expected": [], }, { "name": "SELECT EXCEPT pattern", "sql": "select * except (name) from shop.users", - "expected": {"production.shop.users": {"id"}}, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "users", + "column": "id", + } + ], }, { "name": "CTE pattern", @@ -105,17 +169,44 @@ from amount_by_method """, - "expected": {"production.shop.orders": {"payment_method", "payment_amount"}}, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "payment_amount", + }, + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "payment_method", + }, + ], }, { "name": "referencing a field of a struct", "sql": "select brand.category from shop.items", - "expected": {"production.shop.items": {"brand.category"}}, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "items", + "column": "brand.category", + } + ], }, { "name": "referencing fields of a struct with a wildcard", "sql": "select brand.* from shop.items", - "expected": {"production.shop.items": {"brand.*"}}, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "items", + "column": "brand.*", + } + ], }, { "name": "referencing a field in an unnested struct", @@ -127,7 +218,14 @@ where exists (select 1 from unnest(items) where amount > 1) """, - "expected": {"production.shop.orders": {"items"}}, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "items", + } + ], }, ] @@ -139,4 +237,11 @@ ) def test_analyze_case(sql, expected): result = analyze(dialects.BigQuery, sql, schema, "production") - assert result == expected + assert _sorted_rows(result) == _sorted_rows(expected) + + +def _sorted_rows(rows: list[dict[str, str]]) -> list[dict[str, str]]: + return sorted( + rows, + key=lambda row: (row["database"], row["schema"], row["table"], row["column"]), + ) diff --git a/tests/test_analyze_redshift.py b/tests/test_analyze_redshift.py index 0889d1f..8035dd3 100644 --- a/tests/test_analyze_redshift.py +++ b/tests/test_analyze_redshift.py @@ -32,61 +32,131 @@ test_cases = [ - {"name": "not referencing a table", "sql": "select 1", "expected": {}}, + {"name": "not referencing a table", "sql": "select 1", "expected": []}, { "name": "referencing a table but not columns", "sql": "select count(1) from shop.orders", - "expected": {}, + "expected": [], }, { "name": "simple column reference", "sql": "select user_id from shop.orders", - "expected": {"production.shop.orders": {"user_id"}}, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "user_id", + } + ], }, { "name": "qualifying a table with a project", "sql": "select user_id from production.shop.orders", - "expected": {"production.shop.orders": {"user_id"}}, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "user_id", + } + ], }, { "name": "qualifying a table with a non-default project", "sql": "select id from development.shop.users", - "expected": {"development.shop.users": {"id"}}, + "expected": [ + { + "database": "development", + "schema": "shop", + "table": "users", + "column": "id", + } + ], }, { "name": "where clause reference", "sql": "select count(1) from shop.orders where ordered_at >= '2026-01-01'", - "expected": {"production.shop.orders": {"ordered_at"}}, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + } + ], }, { "name": "join reference", "sql": "select count(1) from shop.orders join shop.users on orders.user_id = users.id", - "expected": { - "production.shop.orders": {"user_id"}, - "production.shop.users": {"id"}, - }, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "user_id", + }, + { + "database": "production", + "schema": "shop", + "table": "users", + "column": "id", + }, + ], }, { "name": "referencing a column in an ORDER BY clause", "sql": "select user_id from shop.orders order by payment_amount desc", - "expected": {"production.shop.orders": {"user_id", "payment_amount"}}, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "payment_amount", + }, + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "user_id", + }, + ], }, { "name": "wildcard pattern", "sql": "select * from shop.users", - "expected": { - "production.shop.users": set(schema["production"]["shop"]["users"].keys()) - }, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "users", + "column": column, + } + for column in schema["production"]["shop"]["users"].keys() + ], }, { "name": "using a wildcard with COUNT", "sql": "select count(*) from shop.users", - "expected": {}, + "expected": [], }, { "name": "selecting multiple columns", "sql": "select id, name from shop.users", - "expected": {"production.shop.users": {"id", "name"}}, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "users", + "column": "id", + }, + { + "database": "production", + "schema": "shop", + "table": "users", + "column": "name", + }, + ], }, { "name": "CTE pattern", @@ -105,17 +175,44 @@ from amount_by_method """, - "expected": {"production.shop.orders": {"payment_method", "payment_amount"}}, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "payment_amount", + }, + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "payment_method", + }, + ], }, { "name": "referencing a field of a super column", "sql": "select brand['category'] from shop.items", - "expected": {"production.shop.items": {"brand"}}, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "items", + "column": "brand", + } + ], }, { "name": "referencing multiple fields of a super column", "sql": "select brand['category'], brand['name'] from shop.items", - "expected": {"production.shop.items": {"brand"}}, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "items", + "column": "brand", + } + ], }, { "name": "referencing a field in a super column filter", @@ -127,7 +224,14 @@ where json_extract_path_text(items, 'amount') is not null """, - "expected": {"production.shop.orders": {"items"}}, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "items", + } + ], }, ] @@ -139,4 +243,11 @@ ) def test_analyze_case(sql, expected): result = analyze(dialects.Redshift, sql, schema, "production") - assert result == expected + assert _sorted_rows(result) == _sorted_rows(expected) + + +def _sorted_rows(rows: list[dict[str, str]]) -> list[dict[str, str]]: + return sorted( + rows, + key=lambda row: (row["database"], row["schema"], row["table"], row["column"]), + ) From 5896d8419b2d55e6971ae0ade61d3d12d127c841 Mon Sep 17 00:00:00 2001 From: logicoffee Date: Fri, 23 Jan 2026 12:52:35 +0900 Subject: [PATCH 2/2] Change the format of the output of analyze_timespan --- README.md | 12 +- .../timespan_analyzer.py | 31 +- tests/test_analyze_timespan_bigquery.py | 769 ++++++++---- tests/test_analyze_timespan_redshift.py | 1094 ++++++++--------- 4 files changed, 1111 insertions(+), 795 deletions(-) diff --git a/README.md b/README.md index 6dd3cda..dc57ad8 100644 --- a/README.md +++ b/README.md @@ -119,12 +119,16 @@ where timespans = analyze_timespan(dialects.BigQuery, sql, schema, "production") print(timespans) -# { -# "production.shop.orders.ordered_at": { +# [ +# { +# "database": "production", +# "schema": "shop", +# "table": "orders", +# "column": "ordered_at", # "lower": "2025-01-01", -# "upper": "2026-01-01" +# "upper": "2026-01-01", # } -# } +# ] ``` To make `current_date()` deterministic, pass a provider: diff --git a/src/analytics_query_analyzer/timespan_analyzer.py b/src/analytics_query_analyzer/timespan_analyzer.py index 9937649..007e671 100644 --- a/src/analytics_query_analyzer/timespan_analyzer.py +++ b/src/analytics_query_analyzer/timespan_analyzer.py @@ -27,8 +27,8 @@ def __init__( self.default_catalog = default_catalog self.current_date_provider = current_date_provider or date.today - def analyze(self, expression: exp.Expression) -> dict[str, dict[str, str | None]]: - return _stringify_results(self._analyze_internal(expression)) + def analyze(self, expression: exp.Expression) -> list[dict[str, str | None]]: + return _flatten_timespans(_stringify_results(self._analyze_internal(expression))) def _analyze_internal(self, expression: exp.Expression) -> TimespanResults: if isinstance(expression, exp.Union): @@ -632,3 +632,30 @@ def _stringify_results(results: TimespanResults) -> dict[str, dict[str, str | No "upper": bounds["upper"].isoformat() if bounds["upper"] else None, } return output + + +def _flatten_timespans( + results: dict[str, dict[str, str | None]], +) -> list[dict[str, str | None]]: + rows: list[dict[str, str | None]] = [] + for full_path, bounds in results.items(): + database, schema, table, column = full_path.split(".", 3) + rows.append( + { + "database": database, + "schema": schema, + "table": table, + "column": column, + "lower": bounds.get("lower"), + "upper": bounds.get("upper"), + } + ) + rows.sort( + key=lambda row: ( + row["database"] or "", + row["schema"] or "", + row["table"] or "", + row["column"] or "", + ) + ) + return rows diff --git a/tests/test_analyze_timespan_bigquery.py b/tests/test_analyze_timespan_bigquery.py index 3ecd28d..53ed120 100644 --- a/tests/test_analyze_timespan_bigquery.py +++ b/tests/test_analyze_timespan_bigquery.py @@ -6,532 +6,828 @@ schema = { "production": { "shop": { - "orders": { - "id": "int64", - "ordered_at": "datetime", - "user_id": "int64", - }, - "events": { - "id": "int64", - "event_at": "datetime", - "user_id": "int64", - }, + "orders": {"id": "int64", "ordered_at": "datetime", "user_id": "int64"}, + "events": {"id": "int64", "event_at": "datetime", "user_id": "int64"}, "users": {"id": "int64", "name": "string"}, } - }, + } } test_cases = [ { "name": "without condition", "sql": "select ordered_at from shop.orders", - "expected": {}, + "expected": [], }, { "name": "lower bound (>)", "sql": "select * from shop.orders where ordered_at > '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": "2026-01-01", "upper": None} - }, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": "2026-01-01", + "upper": None, + } + ], }, { "name": "lower bound (>=)", "sql": "select * from shop.orders where ordered_at >= '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": "2026-01-01", "upper": None} - }, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": "2026-01-01", + "upper": None, + } + ], }, { "name": "upper bound (<)", "sql": "select * from shop.orders where ordered_at < '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": None, "upper": "2026-01-01"} - }, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": None, + "upper": "2026-01-01", + } + ], }, { "name": "upper bound (<=)", "sql": "select * from shop.orders where ordered_at <= '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": None, "upper": "2026-01-01"} - }, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": None, + "upper": "2026-01-01", + } + ], }, { "name": "equality", "sql": "select * from shop.orders where ordered_at = '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": { + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": "2026-01-01", } - }, + ], }, { "name": "in clause", "sql": "select * from shop.orders where ordered_at in ('2026-01-01')", - "expected": { - "production.shop.orders.ordered_at": { + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": "2026-01-01", } - }, + ], }, { "name": "between", - "sql": "select * from shop.orders where ordered_at between '2026-01-01' and '2026-01-02'", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where ordered_at between '2026-01-01' and " + "'2026-01-02'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": "2026-01-02", } - }, + ], }, { "name": "trunc column", - "sql": "select * from shop.orders where date_trunc(ordered_at, month) >= '2026-02-10'", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where date_trunc(ordered_at, month) >= " + "'2026-02-10'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-02-01", "upper": None, } - }, + ], }, { "name": "date function string", "sql": "select * from shop.orders where ordered_at >= date('2026-01-01')", - "expected": { - "production.shop.orders.ordered_at": { + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "date from parts", "sql": "select * from shop.orders where ordered_at >= date(2026, 1, 1)", - "expected": { - "production.shop.orders.ordered_at": { + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "date literal", "sql": "select * from shop.orders where ordered_at >= date '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": { + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "datetime function string", - "sql": "select * from shop.orders where ordered_at >= datetime('2026-01-01 09:00:00')", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where ordered_at >= datetime('2026-01-01 " + "09:00:00')", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "datetime from parts", "sql": "select * from shop.orders where ordered_at >= datetime(2026, 1, 1, 9, 0, 0)", - "expected": { - "production.shop.orders.ordered_at": { + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "datetime literal", "sql": "select * from shop.orders where ordered_at >= datetime '2026-01-01 09:00:00'", - "expected": { - "production.shop.orders.ordered_at": { + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "timestamp function string", - "sql": "select * from shop.orders where ordered_at >= timestamp('2026-01-01 09:00:00')", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where ordered_at >= timestamp('2026-01-01 " + "09:00:00')", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "timestamp function string with offset", - "sql": "select * from shop.orders where ordered_at >= timestamp('2026-01-01 00:00:00+09:00')", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where ordered_at >= timestamp('2026-01-01 " + "00:00:00+09:00')", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "timestamp function zone", - "sql": "select * from shop.orders where ordered_at >= timestamp('2026-01-01 00:00:00', 'Asia/Tokyo')", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where ordered_at >= timestamp('2026-01-01 " + "00:00:00', 'Asia/Tokyo')", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "timestamp literal", - "sql": "select * from shop.orders where ordered_at >= timestamp '2026-01-01 09:00:00'", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where ordered_at >= timestamp '2026-01-01 " + "09:00:00'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "timestamp from date", "sql": "select * from shop.orders where ordered_at >= timestamp(date '2026-01-01')", - "expected": { - "production.shop.orders.ordered_at": { + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "date sub day", - "sql": "select * from shop.orders where ordered_at > date_sub(date('2026-01-04'), interval 3 day)", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where ordered_at > date_sub(date('2026-01-04'), " + "interval 3 day)", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "date add day", - "sql": "select * from shop.orders where ordered_at >= date_add(date('2026-01-04'), interval 2 day)", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where ordered_at >= date_add(date('2026-01-04'), " + "interval 2 day)", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-06", "upper": None, } - }, + ], }, { "name": "date add month", - "sql": "select * from shop.orders where ordered_at >= date_add(date('2026-01-31'), interval 1 month)", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where ordered_at >= date_add(date('2026-01-31'), " + "interval 1 month)", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-02-28", "upper": None, } - }, + ], }, { "name": "trunc literal", - "sql": "select * from shop.orders where ordered_at >= date_trunc('2026-02-10', month)", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where ordered_at >= date_trunc('2026-02-10', " + "month)", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-02-01", "upper": None, } - }, + ], }, { "name": "cast date", "sql": "select * from shop.orders where ordered_at >= cast('2026-01-01' as date)", - "expected": { - "production.shop.orders.ordered_at": { + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "cast datetime", - "sql": "select * from shop.orders where ordered_at >= cast('2026-01-01 09:00:00' as datetime)", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where ordered_at >= cast('2026-01-01 09:00:00' as " + "datetime)", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "cast timestamp", - "sql": "select * from shop.orders where ordered_at >= cast('2026-01-01 09:00:00' as timestamp)", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where ordered_at >= cast('2026-01-01 09:00:00' as " + "timestamp)", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "timestamp seconds", "sql": "select * from shop.orders where ordered_at >= timestamp_seconds(1767229200)", - "expected": { - "production.shop.orders.ordered_at": { + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "timestamp millis", - "sql": "select * from shop.orders where ordered_at >= timestamp_millis(1767229200000)", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where ordered_at >= " + "timestamp_millis(1767229200000)", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "parse date", - "sql": "select * from shop.orders where ordered_at >= parse_date('%Y-%m-%d', '2026-01-01')", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where ordered_at >= parse_date('%Y-%m-%d', " + "'2026-01-01')", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "parse datetime", - "sql": "select * from shop.orders where ordered_at >= parse_datetime('%Y-%m-%d %H:%M:%S', '2026-01-01 09:00:00')", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where ordered_at >= parse_datetime('%Y-%m-%d " + "%H:%M:%S', '2026-01-01 09:00:00')", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "parse timestamp", - "sql": "select * from shop.orders where ordered_at >= parse_timestamp('%Y-%m-%d %H:%M:%S', '2026-01-01 09:00:00')", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where ordered_at >= parse_timestamp('%Y-%m-%d " + "%H:%M:%S', '2026-01-01 09:00:00')", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "differently formatted literals", - "sql": "select * from shop.orders where ordered_at >= '2026-01-01' and ordered_at > '2026-01-02 09:00:00'", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where ordered_at >= '2026-01-01' and ordered_at > " + "'2026-01-02 09:00:00'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-02", "upper": None, } - }, + ], }, { "name": "non-literal", - "sql": "select * from shop.orders where ordered_at > date_sub('2026-01-01', interval 1 month)", - "expected": { - "production.shop.orders.ordered_at": {"lower": "2025-12-01", "upper": None} - }, + "sql": "select * from shop.orders where ordered_at > date_sub('2026-01-01', interval " + "1 month)", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": "2025-12-01", + "upper": None, + } + ], }, { "name": "not equal", "sql": "select * from shop.orders where ordered_at != '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": None, "upper": None} - }, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": None, + "upper": None, + } + ], }, { "name": "not equal (alt)", "sql": "select * from shop.orders where ordered_at <> '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": None, "upper": None} - }, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": None, + "upper": None, + } + ], }, { "name": "negation", "sql": "select * from shop.orders where not ordered_at > '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": None, "upper": "2026-01-01"} - }, + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": None, + "upper": "2026-01-01", + } + ], }, { "name": "multiple conditions (and)", - "sql": "select * from orders where ordered_at > '2026-01-01' and ordered_at > '2026-01-02'", - "expected": { - "production.shop.orders.ordered_at": {"lower": "2026-01-02", "upper": None} - }, + "sql": "select * from orders where ordered_at > '2026-01-01' and ordered_at > " + "'2026-01-02'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": "2026-01-02", + "upper": None, + } + ], }, { "name": "multiple conditions (or)", - "sql": "select * from shop.orders where ordered_at > '2026-01-01' or ordered_at > '2026-01-02'", - "expected": { - "production.shop.orders.ordered_at": {"lower": "2026-01-01", "upper": None} - }, + "sql": "select * from shop.orders where ordered_at > '2026-01-01' or ordered_at > " + "'2026-01-02'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": "2026-01-01", + "upper": None, + } + ], }, { "name": "lower > upper", - "sql": "select * from shop.orders where ordered_at > '2026-01-02' and ordered_at < '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": None, "upper": None} - }, + "sql": "select * from shop.orders where ordered_at > '2026-01-02' and ordered_at < " + "'2026-01-01'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": None, + "upper": None, + } + ], }, { "name": "lower > upper between", - "sql": "select * from shop.orders where ordered_at between '2026-01-02' and '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": None, "upper": None} - }, + "sql": "select * from shop.orders where ordered_at between '2026-01-02' and " + "'2026-01-01'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": None, + "upper": None, + } + ], }, { "name": "function wrapped column", "sql": "select * from shop.orders where date(ordered_at) >= '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": { + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "function wrapped mixed conditions", - "sql": "select * from shop.orders where date(ordered_at) >= '2026-01-01' and ordered_at < '2026-01-02'", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.orders where date(ordered_at) >= '2026-01-01' and " + "ordered_at < '2026-01-02'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": "2026-01-02", } - }, + ], }, { "name": "join condition", - "sql": "select * from shop.users join shop.orders on users.id = orders.user_id and orders.ordered_at > '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": "2026-01-01", "upper": None} - }, + "sql": "select * from shop.users join shop.orders on users.id = orders.user_id and " + "orders.ordered_at > '2026-01-01'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": "2026-01-01", + "upper": None, + } + ], }, { "name": "multiple tables", - "sql": "select * from shop.orders join shop.events on orders.user_id = events.user_id where orders.ordered_at >= '2026-01-01' and events.event_at > '2026-01-02'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", + "sql": "select * from shop.orders join shop.events on orders.user_id = " + "events.user_id where orders.ordered_at >= '2026-01-01' and events.event_at > " + "'2026-01-02'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "events", + "column": "event_at", + "lower": "2026-01-02", "upper": None, }, - "production.shop.events.event_at": { - "lower": "2026-01-02", + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": "2026-01-01", "upper": None, }, - }, + ], }, { "name": "cte", - "sql": "with base as (select ordered_at from shop.orders) select * from base where ordered_at > '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": "2026-01-01", "upper": None} - }, + "sql": "with base as (select ordered_at from shop.orders) select * from base where " + "ordered_at > '2026-01-01'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": "2026-01-01", + "upper": None, + } + ], }, { "name": "cte inner and outer", - "sql": "with base as (select ordered_at from shop.orders where ordered_at >= '2026-01-01') select * from base where ordered_at < '2026-01-02'", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "with base as (select ordered_at from shop.orders where ordered_at >= " + "'2026-01-01') select * from base where ordered_at < '2026-01-02'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": "2026-01-02", } - }, + ], }, { "name": "multi cte", - "sql": "with base as (select ordered_at from shop.orders), filtered as (select ordered_at from base where ordered_at >= '2026-01-01') select * from filtered where ordered_at < '2026-01-02'", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "with base as (select ordered_at from shop.orders), filtered as (select " + "ordered_at from base where ordered_at >= '2026-01-01') select * from " + "filtered where ordered_at < '2026-01-02'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": "2026-01-02", } - }, + ], }, { "name": "subquery from", - "sql": "select * from (select ordered_at from shop.orders) t where t.ordered_at >= '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from (select ordered_at from shop.orders) t where t.ordered_at >= " + "'2026-01-01'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "join subquery", - "sql": "select * from shop.users u join (select user_id, ordered_at from shop.orders) o on u.id = o.user_id and o.ordered_at < '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select * from shop.users u join (select user_id, ordered_at from " + "shop.orders) o on u.id = o.user_id and o.ordered_at < '2026-01-01'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": None, "upper": "2026-01-01", } - }, + ], }, { "name": "qualify clause", - "sql": "select min(ordered_at) over (partition by user_id) as first_ordered_at from shop.orders qualify first_ordered_at > '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select min(ordered_at) over (partition by user_id) as first_ordered_at from " + "shop.orders qualify first_ordered_at > '2026-01-01'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "having clause", - "sql": "select min(ordered_at) as first_ordered_at from shop.orders having first_ordered_at > '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select min(ordered_at) as first_ordered_at from shop.orders having " + "first_ordered_at > '2026-01-01'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "union both filtered", - "sql": "select ordered_at from shop.orders where ordered_at >= '2026-01-01' union all select ordered_at from shop.orders where ordered_at >= '2026-01-02'", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select ordered_at from shop.orders where ordered_at >= '2026-01-01' union " + "all select ordered_at from shop.orders where ordered_at >= '2026-01-02'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "union one filtered", - "sql": "select ordered_at from shop.orders where ordered_at >= '2026-01-01' union all select ordered_at from shop.orders", - "expected": { - "production.shop.orders.ordered_at": { + "sql": "select ordered_at from shop.orders where ordered_at >= '2026-01-01' union " + "all select ordered_at from shop.orders", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", "lower": "2026-01-01", "upper": None, } - }, + ], }, { "name": "union different tables", - "sql": "select ordered_at from shop.orders where ordered_at >= '2026-01-01' union all select event_at from shop.events where event_at < '2026-01-02'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - }, - "production.shop.events.event_at": { + "sql": "select ordered_at from shop.orders where ordered_at >= '2026-01-01' union " + "all select event_at from shop.events where event_at < '2026-01-02'", + "expected": [ + { + "database": "production", + "schema": "shop", + "table": "events", + "column": "event_at", "lower": None, "upper": "2026-01-02", }, - }, + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": "2026-01-01", + "upper": None, + }, + ], }, ] @@ -555,9 +851,16 @@ def test_current_date_provider(): "production", current_date_provider=lambda: "2026-01-01", ) - assert result == { - "production.shop.orders.ordered_at": {"lower": "2026-01-01", "upper": None} - } + assert result == [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": "2026-01-01", + "upper": None, + } + ] def test_current_datetime_provider(): @@ -569,9 +872,16 @@ def test_current_datetime_provider(): "production", current_date_provider=lambda: "2026-01-01", ) - assert result == { - "production.shop.orders.ordered_at": {"lower": "2026-01-01", "upper": None} - } + assert result == [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": "2026-01-01", + "upper": None, + } + ] def test_current_timestamp_provider(): @@ -583,6 +893,13 @@ def test_current_timestamp_provider(): "production", current_date_provider=lambda: "2026-01-01", ) - assert result == { - "production.shop.orders.ordered_at": {"lower": "2026-01-01", "upper": None} - } + assert result == [ + { + "database": "production", + "schema": "shop", + "table": "orders", + "column": "ordered_at", + "lower": "2026-01-01", + "upper": None, + } + ] diff --git a/tests/test_analyze_timespan_redshift.py b/tests/test_analyze_timespan_redshift.py index e45a451..f116846 100644 --- a/tests/test_analyze_timespan_redshift.py +++ b/tests/test_analyze_timespan_redshift.py @@ -3,558 +3,519 @@ from analytics_query_analyzer.analyzer import analyze_timespan -schema = { - "production": { - "shop": { - "orders": { - "id": "int64", - "ordered_at": "timestamp", - "user_id": "int64", - }, - "events": { - "id": "int64", - "event_at": "timestamp", - "user_id": "int64", - }, - "users": {"id": "int64", "name": "string"}, - } - }, -} - -test_cases = [ - { - "name": "without condition", - "sql": "select ordered_at from shop.orders", - "expected": {}, - }, - { - "name": "lower bound (>)", - "sql": "select * from shop.orders where ordered_at > '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": "2026-01-01", "upper": None} - }, - }, - { - "name": "lower bound (>=)", - "sql": "select * from shop.orders where ordered_at >= '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": "2026-01-01", "upper": None} - }, - }, - { - "name": "upper bound (<)", - "sql": "select * from shop.orders where ordered_at < '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": None, "upper": "2026-01-01"} - }, - }, - { - "name": "upper bound (<=)", - "sql": "select * from shop.orders where ordered_at <= '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": None, "upper": "2026-01-01"} - }, - }, - { - "name": "equality", - "sql": "select * from shop.orders where ordered_at = '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": "2026-01-01", - } - }, - }, - { - "name": "in clause", - "sql": "select * from shop.orders where ordered_at in ('2026-01-01')", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": "2026-01-01", - } - }, - }, - { - "name": "between", - "sql": "select * from shop.orders where ordered_at between '2026-01-01' and '2026-01-02'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": "2026-01-02", - } - }, - }, - { - "name": "trunc column", - "sql": "select * from shop.orders where date_trunc('month', ordered_at) >= '2026-02-10'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-02-01", - "upper": None, - } - }, - }, - { - "name": "date function string", - "sql": "select * from shop.orders where ordered_at >= to_date('2026-01-01', 'YYYY-MM-DD')", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "date from parts", - "sql": "select * from shop.orders where ordered_at >= date_from_parts(2026, 1, 1)", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "date literal", - "sql": "select * from shop.orders where ordered_at >= date '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "datetime function string", - "sql": "select * from shop.orders where ordered_at >= to_timestamp('2026-01-01 09:00:00', 'YYYY-MM-DD HH24:MI:SS')", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "datetime from parts", - "sql": "select * from shop.orders where ordered_at >= make_timestamp(2026, 1, 1, 9, 0, 0)", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "datetime literal", - "sql": "select * from shop.orders where ordered_at >= cast('2026-01-01 09:00:00' as timestamp)", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "timestamp function string", - "sql": "select * from shop.orders where ordered_at >= to_timestamp('2026-01-01 09:00:00', 'YYYY-MM-DD HH24:MI:SS')", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "timestamp function string with offset", - "sql": "select * from shop.orders where ordered_at >= to_timestamp('2026-01-01 00:00:00+09:00', 'YYYY-MM-DD HH24:MI:SSOF')", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "timestamp function zone", - "sql": "select * from shop.orders where ordered_at >= timestamp '2026-01-01 00:00:00+09:00'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "timestamp literal", - "sql": "select * from shop.orders where ordered_at >= timestamp '2026-01-01 09:00:00'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "timestamp from date", - "sql": "select * from shop.orders where ordered_at >= timestamp(date '2026-01-01')", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "date sub day", - "sql": "select * from shop.orders where ordered_at > dateadd(day, -3, date '2026-01-04')", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "interval sub day", - "sql": "select * from shop.orders where ordered_at > (date '2026-01-04' - interval '3 day')", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "date add day", - "sql": "select * from shop.orders where ordered_at >= dateadd(day, 2, date '2026-01-04')", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-06", - "upper": None, - } - }, - }, - { - "name": "interval add day", - "sql": "select * from shop.orders where ordered_at >= (date '2026-01-04' + interval '2 day')", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-06", - "upper": None, - } - }, - }, - { - "name": "date add month", - "sql": "select * from shop.orders where ordered_at >= dateadd(month, 1, date '2026-01-31')", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-02-28", - "upper": None, - } - }, - }, - { - "name": "trunc literal", - "sql": "select * from shop.orders where ordered_at >= date_trunc('month', '2026-02-10')", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-02-01", - "upper": None, - } - }, - }, - { - "name": "cast date", - "sql": "select * from shop.orders where ordered_at >= cast('2026-01-01' as date)", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "cast datetime", - "sql": "select * from shop.orders where ordered_at >= cast('2026-01-01 09:00:00' as timestamp)", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "cast timestamp", - "sql": "select * from shop.orders where ordered_at >= cast('2026-01-01 09:00:00' as timestamptz)", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "timestamp seconds", - "sql": "select * from shop.orders where ordered_at >= to_timestamp(1767229200)", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "timestamp millis", - "sql": "select * from shop.orders where ordered_at >= to_timestamp(1767229200.0)", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "parse date", - "sql": "select * from shop.orders where ordered_at >= to_date('2026-01-01', 'YYYY-MM-DD')", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "parse datetime", - "sql": "select * from shop.orders where ordered_at >= to_timestamp('2026-01-01 09:00:00', 'YYYY-MM-DD HH24:MI:SS')", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "parse timestamp", - "sql": "select * from shop.orders where ordered_at >= to_timestamp('2026-01-01 09:00:00+09:00', 'YYYY-MM-DD HH24:MI:SSOF')", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "differently formatted literals", - "sql": "select * from shop.orders where ordered_at >= '2026-01-01' and ordered_at > '2026-01-02 09:00:00'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-02", - "upper": None, - } - }, - }, - { - "name": "non-literal", - "sql": "select * from shop.orders where ordered_at > dateadd(month, -1, to_date('2026-01-01', 'YYYY-MM-DD'))", - "expected": { - "production.shop.orders.ordered_at": {"lower": "2025-12-01", "upper": None} - }, - }, - { - "name": "not equal", - "sql": "select * from shop.orders where ordered_at != '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": None, "upper": None} - }, - }, - { - "name": "not equal (alt)", - "sql": "select * from shop.orders where ordered_at <> '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": None, "upper": None} - }, - }, - { - "name": "negation", - "sql": "select * from shop.orders where not ordered_at > '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": None, "upper": "2026-01-01"} - }, - }, - { - "name": "multiple conditions (and)", - "sql": "select * from orders where ordered_at > '2026-01-01' and ordered_at > '2026-01-02'", - "expected": { - "production.shop.orders.ordered_at": {"lower": "2026-01-02", "upper": None} - }, - }, - { - "name": "multiple conditions (or)", - "sql": "select * from shop.orders where ordered_at > '2026-01-01' or ordered_at > '2026-01-02'", - "expected": { - "production.shop.orders.ordered_at": {"lower": "2026-01-01", "upper": None} - }, - }, - { - "name": "lower > upper", - "sql": "select * from shop.orders where ordered_at > '2026-01-02' and ordered_at < '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": None, "upper": None} - }, - }, - { - "name": "lower > upper between", - "sql": "select * from shop.orders where ordered_at between '2026-01-02' and '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": None, "upper": None} - }, - }, - { - "name": "function wrapped column", - "sql": "select * from shop.orders where date(ordered_at) >= '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "function wrapped mixed conditions", - "sql": "select * from shop.orders where date(ordered_at) >= '2026-01-01' and ordered_at < '2026-01-02'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": "2026-01-02", - } - }, - }, - { - "name": "join condition", - "sql": "select * from shop.users join shop.orders on users.id = orders.user_id and orders.ordered_at > '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": "2026-01-01", "upper": None} - }, - }, - { - "name": "multiple tables", - "sql": "select * from shop.orders join shop.events on orders.user_id = events.user_id where orders.ordered_at >= '2026-01-01' and events.event_at > '2026-01-02'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - }, - "production.shop.events.event_at": { - "lower": "2026-01-02", - "upper": None, - }, - }, - }, - { - "name": "cte", - "sql": "with base as (select ordered_at from shop.orders) select * from base where ordered_at > '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": {"lower": "2026-01-01", "upper": None} - }, - }, - { - "name": "cte inner and outer", - "sql": "with base as (select ordered_at from shop.orders where ordered_at >= '2026-01-01') select * from base where ordered_at < '2026-01-02'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": "2026-01-02", - } - }, - }, - { - "name": "multi cte", - "sql": "with base as (select ordered_at from shop.orders), filtered as (select ordered_at from base where ordered_at >= '2026-01-01') select * from filtered where ordered_at < '2026-01-02'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": "2026-01-02", - } - }, - }, - { - "name": "subquery from", - "sql": "select * from (select ordered_at from shop.orders) t where t.ordered_at >= '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "join subquery", - "sql": "select * from shop.users u join (select user_id, ordered_at from shop.orders) o on u.id = o.user_id and o.ordered_at < '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": None, - "upper": "2026-01-01", - } - }, - }, - { - "name": "qualify clause", - "sql": "select min(ordered_at) over (partition by user_id) as first_ordered_at from shop.orders qualify first_ordered_at > '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "having clause", - "sql": "select min(ordered_at) as first_ordered_at from shop.orders having first_ordered_at > '2026-01-01'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "union both filtered", - "sql": "select ordered_at from shop.orders where ordered_at >= '2026-01-01' union all select ordered_at from shop.orders where ordered_at >= '2026-01-02'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "union one filtered", - "sql": "select ordered_at from shop.orders where ordered_at >= '2026-01-01' union all select ordered_at from shop.orders", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - } - }, - }, - { - "name": "union different tables", - "sql": "select ordered_at from shop.orders where ordered_at >= '2026-01-01' union all select event_at from shop.events where event_at < '2026-01-02'", - "expected": { - "production.shop.orders.ordered_at": { - "lower": "2026-01-01", - "upper": None, - }, - "production.shop.events.event_at": { - "lower": None, - "upper": "2026-01-02", - }, - }, - }, -] +schema = {'production': {'shop': {'orders': {'id': 'int64', + 'ordered_at': 'timestamp', + 'user_id': 'int64'}, + 'events': {'id': 'int64', + 'event_at': 'timestamp', + 'user_id': 'int64'}, + 'users': {'id': 'int64', 'name': 'string'}}}} +test_cases = [{'name': 'without condition', + 'sql': 'select ordered_at from shop.orders', + 'expected': []}, + {'name': 'lower bound (>)', + 'sql': "select * from shop.orders where ordered_at > '2026-01-01'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'lower bound (>=)', + 'sql': "select * from shop.orders where ordered_at >= '2026-01-01'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'upper bound (<)', + 'sql': "select * from shop.orders where ordered_at < '2026-01-01'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': None, + 'upper': '2026-01-01'}]}, + {'name': 'upper bound (<=)', + 'sql': "select * from shop.orders where ordered_at <= '2026-01-01'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': None, + 'upper': '2026-01-01'}]}, + {'name': 'equality', + 'sql': "select * from shop.orders where ordered_at = '2026-01-01'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': '2026-01-01'}]}, + {'name': 'in clause', + 'sql': "select * from shop.orders where ordered_at in ('2026-01-01')", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': '2026-01-01'}]}, + {'name': 'between', + 'sql': "select * from shop.orders where ordered_at between '2026-01-01' and " + "'2026-01-02'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': '2026-01-02'}]}, + {'name': 'trunc column', + 'sql': "select * from shop.orders where date_trunc('month', ordered_at) >= " + "'2026-02-10'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-02-01', + 'upper': None}]}, + {'name': 'date function string', + 'sql': "select * from shop.orders where ordered_at >= to_date('2026-01-01', " + "'YYYY-MM-DD')", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'date from parts', + 'sql': 'select * from shop.orders where ordered_at >= date_from_parts(2026, 1, 1)', + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'date literal', + 'sql': "select * from shop.orders where ordered_at >= date '2026-01-01'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'datetime function string', + 'sql': "select * from shop.orders where ordered_at >= to_timestamp('2026-01-01 " + "09:00:00', 'YYYY-MM-DD HH24:MI:SS')", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'datetime from parts', + 'sql': 'select * from shop.orders where ordered_at >= make_timestamp(2026, 1, 1, 9, ' + '0, 0)', + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'datetime literal', + 'sql': "select * from shop.orders where ordered_at >= cast('2026-01-01 09:00:00' as " + 'timestamp)', + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'timestamp function string', + 'sql': "select * from shop.orders where ordered_at >= to_timestamp('2026-01-01 " + "09:00:00', 'YYYY-MM-DD HH24:MI:SS')", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'timestamp function string with offset', + 'sql': "select * from shop.orders where ordered_at >= to_timestamp('2026-01-01 " + "00:00:00+09:00', 'YYYY-MM-DD HH24:MI:SSOF')", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'timestamp function zone', + 'sql': "select * from shop.orders where ordered_at >= timestamp '2026-01-01 " + "00:00:00+09:00'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'timestamp literal', + 'sql': "select * from shop.orders where ordered_at >= timestamp '2026-01-01 " + "09:00:00'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'timestamp from date', + 'sql': "select * from shop.orders where ordered_at >= timestamp(date '2026-01-01')", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'date sub day', + 'sql': 'select * from shop.orders where ordered_at > dateadd(day, -3, date ' + "'2026-01-04')", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'interval sub day', + 'sql': "select * from shop.orders where ordered_at > (date '2026-01-04' - interval " + "'3 day')", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'date add day', + 'sql': 'select * from shop.orders where ordered_at >= dateadd(day, 2, date ' + "'2026-01-04')", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-06', + 'upper': None}]}, + {'name': 'interval add day', + 'sql': "select * from shop.orders where ordered_at >= (date '2026-01-04' + interval " + "'2 day')", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-06', + 'upper': None}]}, + {'name': 'date add month', + 'sql': 'select * from shop.orders where ordered_at >= dateadd(month, 1, date ' + "'2026-01-31')", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-02-28', + 'upper': None}]}, + {'name': 'trunc literal', + 'sql': "select * from shop.orders where ordered_at >= date_trunc('month', " + "'2026-02-10')", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-02-01', + 'upper': None}]}, + {'name': 'cast date', + 'sql': "select * from shop.orders where ordered_at >= cast('2026-01-01' as date)", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'cast datetime', + 'sql': "select * from shop.orders where ordered_at >= cast('2026-01-01 09:00:00' as " + 'timestamp)', + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'cast timestamp', + 'sql': "select * from shop.orders where ordered_at >= cast('2026-01-01 09:00:00' as " + 'timestamptz)', + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'timestamp seconds', + 'sql': 'select * from shop.orders where ordered_at >= to_timestamp(1767229200)', + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'timestamp millis', + 'sql': 'select * from shop.orders where ordered_at >= to_timestamp(1767229200.0)', + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'parse date', + 'sql': "select * from shop.orders where ordered_at >= to_date('2026-01-01', " + "'YYYY-MM-DD')", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'parse datetime', + 'sql': "select * from shop.orders where ordered_at >= to_timestamp('2026-01-01 " + "09:00:00', 'YYYY-MM-DD HH24:MI:SS')", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'parse timestamp', + 'sql': "select * from shop.orders where ordered_at >= to_timestamp('2026-01-01 " + "09:00:00+09:00', 'YYYY-MM-DD HH24:MI:SSOF')", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'differently formatted literals', + 'sql': "select * from shop.orders where ordered_at >= '2026-01-01' and ordered_at > " + "'2026-01-02 09:00:00'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-02', + 'upper': None}]}, + {'name': 'non-literal', + 'sql': 'select * from shop.orders where ordered_at > dateadd(month, -1, ' + "to_date('2026-01-01', 'YYYY-MM-DD'))", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2025-12-01', + 'upper': None}]}, + {'name': 'not equal', + 'sql': "select * from shop.orders where ordered_at != '2026-01-01'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': None, + 'upper': None}]}, + {'name': 'not equal (alt)', + 'sql': "select * from shop.orders where ordered_at <> '2026-01-01'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': None, + 'upper': None}]}, + {'name': 'negation', + 'sql': "select * from shop.orders where not ordered_at > '2026-01-01'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': None, + 'upper': '2026-01-01'}]}, + {'name': 'multiple conditions (and)', + 'sql': "select * from orders where ordered_at > '2026-01-01' and ordered_at > " + "'2026-01-02'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-02', + 'upper': None}]}, + {'name': 'multiple conditions (or)', + 'sql': "select * from shop.orders where ordered_at > '2026-01-01' or ordered_at > " + "'2026-01-02'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'lower > upper', + 'sql': "select * from shop.orders where ordered_at > '2026-01-02' and ordered_at < " + "'2026-01-01'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': None, + 'upper': None}]}, + {'name': 'lower > upper between', + 'sql': "select * from shop.orders where ordered_at between '2026-01-02' and " + "'2026-01-01'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': None, + 'upper': None}]}, + {'name': 'function wrapped column', + 'sql': "select * from shop.orders where date(ordered_at) >= '2026-01-01'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'function wrapped mixed conditions', + 'sql': "select * from shop.orders where date(ordered_at) >= '2026-01-01' and " + "ordered_at < '2026-01-02'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': '2026-01-02'}]}, + {'name': 'join condition', + 'sql': 'select * from shop.users join shop.orders on users.id = orders.user_id and ' + "orders.ordered_at > '2026-01-01'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'multiple tables', + 'sql': 'select * from shop.orders join shop.events on orders.user_id = ' + "events.user_id where orders.ordered_at >= '2026-01-01' and events.event_at > " + "'2026-01-02'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'events', + 'column': 'event_at', + 'lower': '2026-01-02', + 'upper': None}, + {'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'cte', + 'sql': 'with base as (select ordered_at from shop.orders) select * from base where ' + "ordered_at > '2026-01-01'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'cte inner and outer', + 'sql': 'with base as (select ordered_at from shop.orders where ordered_at >= ' + "'2026-01-01') select * from base where ordered_at < '2026-01-02'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': '2026-01-02'}]}, + {'name': 'multi cte', + 'sql': 'with base as (select ordered_at from shop.orders), filtered as (select ' + "ordered_at from base where ordered_at >= '2026-01-01') select * from " + "filtered where ordered_at < '2026-01-02'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': '2026-01-02'}]}, + {'name': 'subquery from', + 'sql': 'select * from (select ordered_at from shop.orders) t where t.ordered_at >= ' + "'2026-01-01'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'join subquery', + 'sql': 'select * from shop.users u join (select user_id, ordered_at from ' + "shop.orders) o on u.id = o.user_id and o.ordered_at < '2026-01-01'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': None, + 'upper': '2026-01-01'}]}, + {'name': 'qualify clause', + 'sql': 'select min(ordered_at) over (partition by user_id) as first_ordered_at from ' + "shop.orders qualify first_ordered_at > '2026-01-01'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'having clause', + 'sql': 'select min(ordered_at) as first_ordered_at from shop.orders having ' + "first_ordered_at > '2026-01-01'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'union both filtered', + 'sql': "select ordered_at from shop.orders where ordered_at >= '2026-01-01' union " + "all select ordered_at from shop.orders where ordered_at >= '2026-01-02'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'union one filtered', + 'sql': "select ordered_at from shop.orders where ordered_at >= '2026-01-01' union " + 'all select ordered_at from shop.orders', + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}, + {'name': 'union different tables', + 'sql': "select ordered_at from shop.orders where ordered_at >= '2026-01-01' union " + "all select event_at from shop.events where event_at < '2026-01-02'", + 'expected': [{'database': 'production', + 'schema': 'shop', + 'table': 'events', + 'column': 'event_at', + 'lower': None, + 'upper': '2026-01-02'}, + {'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}]}] @pytest.mark.parametrize( ("sql", "expected"), @@ -565,7 +526,6 @@ def test_analyze_case(sql, expected): result = analyze_timespan(dialects.Redshift, sql, schema, "production") assert result == expected - def test_current_date_provider(): sql = "select * from shop.orders where ordered_at >= current_date()" result = analyze_timespan( @@ -575,10 +535,12 @@ def test_current_date_provider(): "production", current_date_provider=lambda: "2026-01-01", ) - assert result == { - "production.shop.orders.ordered_at": {"lower": "2026-01-01", "upper": None} - } - + assert result == [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}] def test_current_datetime_provider(): sql = "select * from shop.orders where ordered_at >= current_timestamp()" @@ -589,10 +551,12 @@ def test_current_datetime_provider(): "production", current_date_provider=lambda: "2026-01-01", ) - assert result == { - "production.shop.orders.ordered_at": {"lower": "2026-01-01", "upper": None} - } - + assert result == [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}] def test_current_timestamp_provider(): sql = "select * from shop.orders where ordered_at >= current_timestamp()" @@ -603,6 +567,10 @@ def test_current_timestamp_provider(): "production", current_date_provider=lambda: "2026-01-01", ) - assert result == { - "production.shop.orders.ordered_at": {"lower": "2026-01-01", "upper": None} - } + assert result == [{'database': 'production', + 'schema': 'shop', + 'table': 'orders', + 'column': 'ordered_at', + 'lower': '2026-01-01', + 'upper': None}] +