sql-testing-library 0.21.0__tar.gz → 0.22.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/CHANGELOG.md +17 -0
  2. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/PKG-INFO +15 -17
  3. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/README.md +14 -16
  4. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/pyproject.toml +1 -1
  5. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/__init__.py +1 -1
  6. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/redshift.py +0 -11
  7. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/snowflake.py +24 -13
  8. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_core.py +35 -17
  9. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_sql_logger.py +1 -8
  10. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_sql_utils.py +13 -0
  11. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_types.py +15 -0
  12. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/LICENSE +0 -0
  13. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/__init__.py +0 -0
  14. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/athena.py +0 -0
  15. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/base.py +0 -0
  16. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/bigquery.py +0 -0
  17. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/duckdb.py +0 -0
  18. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/presto.py +0 -0
  19. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/trino.py +0 -0
  20. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_exceptions.py +0 -0
  21. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_mock_table.py +0 -0
  22. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_pytest_plugin.py +0 -0
  23. {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/py.typed +0 -0
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## 0.22.1 (2026-03-02)
9
+
10
+ ### Fix
11
+
12
+ - skip CTE alias check for qualified table names and make default_namespace optional (#143)
13
+ - remove worker log suffix and handle leading SQL comments before WITH clause (#142)
14
+
15
+ ## 0.22.0 (2026-01-05)
16
+
17
+ ### Feat
18
+
19
+ - add Snowflake support for deeply nested complex types via OBJECT
20
+
21
+ ### Fix
22
+
23
+ - update Snowflake unit test to use JSON format
24
+
8
25
  ## 0.21.0 (2026-01-03)
9
26
 
10
27
  ### Feat
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sql-testing-library
3
- Version: 0.21.0
3
+ Version: 0.22.1
4
4
  Summary: SQL Testing Framework for Python: Unit test SQL queries with mock data injection for BigQuery, Snowflake, Redshift, Athena, Trino, and DuckDB. Simplify data engineering ETL testing and analytics validation.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -145,11 +145,11 @@ The library supports different data types across database engines. All checkmark
145
145
  | **Decimal Array** | `List[Decimal]` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
146
146
  | **Optional Array** | `Optional[List[T]]` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
147
147
  | **Map/Dict** | `Dict[K, V]` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
148
- | **Struct/Record** | `dataclass` | ✅ | ✅ | ✅ | ✅ | | ✅ |
149
- | **Nested Arrays** | `List[List[T]]` | ❌ | ✅ | ✅ | ✅ | 🚧 | ✅ |
150
- | **Arrays of Structs** | `List[dataclass]` | ✅ | ✅ | ✅ | ✅ | 🚧 | ✅ |
151
- | **3D Arrays** | `List[List[List[T]]]` | ❌ | ✅ | ✅ | ✅ | 🚧 | ✅ |
152
- | **Arrays of Arrays of Structs** | `List[List[dataclass]]` | ❌ | ✅ | ✅ | ✅ | 🚧 | ✅ |
148
+ | **Struct/Record** | `dataclass` | ✅ | ✅ | ✅ | ✅ | | ✅ |
149
+ | **Nested Arrays** | `List[List[T]]` | ❌ | ✅ | ✅ | ✅ | | ✅ |
150
+ | **Arrays of Structs** | `List[dataclass]` | ✅ | ✅ | ✅ | ✅ | | ✅ |
151
+ | **3D Arrays** | `List[List[List[T]]]` | ❌ | ✅ | ✅ | ✅ | | ✅ |
152
+ | **Arrays of Arrays of Structs** | `List[List[dataclass]]` | ❌ | ✅ | ✅ | ✅ | | ✅ |
153
153
 
154
154
  ### Database-Specific Notes
155
155
 
@@ -157,7 +157,7 @@ The library supports different data types across database engines. All checkmark
157
157
  - **Athena**: 256KB query size limit; supports arrays and maps using `ARRAY[]` and `MAP(ARRAY[], ARRAY[])` syntax; supports struct types using `ROW` with named fields (dataclasses and Pydantic models); **full support for deeply nested types** including nested arrays, arrays of structs, and 3D arrays
158
158
  - **Redshift**: Arrays and maps implemented via SUPER type (JSON parsing); 16MB query size limit; **full support for deeply nested types** including struct types using SUPER + JSON_PARSE, nested arrays (2D, 3D+), arrays of structs, and arrays of arrays of structs
159
159
  - **Trino**: Memory catalog for testing; excellent decimal precision; supports arrays, maps, and struct types using `ROW` with named fields (dataclasses and Pydantic models); **full support for deeply nested types** including nested arrays, arrays of structs, and 3D arrays
160
- - **Snowflake**: Column names normalized to lowercase; 1MB query size limit; dict/map types implemented via VARIANT type (JSON parsing); struct types not yet supported (🚧 TODO); nested arrays not yet supported (🚧 TODO)
160
+ - **Snowflake**: Column names normalized to lowercase; 1MB query size limit; dict/map types implemented via VARIANT type (JSON parsing); **full support for deeply nested types** including struct types using OBJECT + PARSE_JSON, nested arrays (2D, 3D+), arrays of structs, and arrays of arrays of structs
161
161
  - **DuckDB**: Fast embedded analytics database; excellent SQL standards compliance; supports arrays, maps, and struct types using `STRUCT` syntax with named fields (dataclasses and Pydantic models); **full support for deeply nested types** including nested arrays (2D, 3D+), arrays of structs, and arrays of arrays of structs
162
162
 
163
163
  ## Execution Modes Support
@@ -1434,25 +1434,23 @@ The library has a few known limitations that are planned to be addressed in futu
1434
1434
 
1435
1435
  ### Deeply Nested Complex Types Support
1436
1436
 
1437
- **✅ Fully Supported (Athena, Trino, DuckDB & Redshift):**
1437
+ **✅ Fully Supported (All Major Adapters):**
1438
1438
  - Nested arrays (2D, 3D+): `List[List[int]]`, `List[List[List[int]]]`
1439
1439
  - Arrays of structs: `List[Address]` where Address is a dataclass
1440
1440
  - Arrays of arrays of structs: `List[List[OrderItem]]`
1441
1441
  - Maps with complex values: `Dict[str, str]`, `Dict[str, int]`
1442
1442
  - See `tests/integration/test_deeply_nested_types_integration.py` for comprehensive examples
1443
- - **16 tests passing** across Athena, Trino, DuckDB, and Redshift (both CTE and physical tables modes)
1443
+ - **20 tests passing** across Athena, Trino, DuckDB, Redshift, and Snowflake (both CTE and physical tables modes)
1444
1444
 
1445
1445
  **Implementation Details:**
1446
1446
  - **Athena/Trino**: Use ROW types with named fields
1447
1447
  - **DuckDB**: Use STRUCT types with dictionary-style syntax
1448
- - **Redshift**: Use SUPER type with JSON_PARSE for serialization, recursive JSON parsing for deserialization
1449
-
1450
- **🚧 TODO - Implementation Needed:**
1451
- - **BigQuery**: Does not support nested arrays - this is a **database limitation**, not a library limitation. BigQuery's type system doesn't allow `ARRAY<ARRAY<T>>` constructs
1452
- - **Snowflake**:
1453
- - Struct type support not implemented (see `Struct type not yet supported for dialect: snowflake`)
1454
- - Nested arrays not yet supported
1455
- - TODO: Implement struct/nested type support using VARIANT/OBJECT types
1448
+ - **Redshift**: Use SUPER type with JSON_PARSE, recursive JSON parsing
1449
+ - **Snowflake**: Use OBJECT type with PARSE_JSON, recursive JSON parsing
1450
+ - **Shared Helper**: `_parse_json_if_string()` in BaseTypeConverter for Redshift/Snowflake
1451
+
1452
+ **🚧 Database Limitations:**
1453
+ - **BigQuery**: Does not support nested arrays - this is a **database limitation**, not a library limitation. BigQuery's type system doesn't allow `ARRAY<ARRAY<T>>` constructs. Struct types and arrays of structs work fine.
1456
1454
 
1457
1455
  ### Database-Specific Limitations
1458
1456
  - **BigQuery**: Does not support nested arrays (arrays of arrays). This is a BigQuery database limitation, not a library limitation.
@@ -85,11 +85,11 @@ The library supports different data types across database engines. All checkmark
85
85
  | **Decimal Array** | `List[Decimal]` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
86
86
  | **Optional Array** | `Optional[List[T]]` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
87
87
  | **Map/Dict** | `Dict[K, V]` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
88
- | **Struct/Record** | `dataclass` | ✅ | ✅ | ✅ | ✅ | | ✅ |
89
- | **Nested Arrays** | `List[List[T]]` | ❌ | ✅ | ✅ | ✅ | 🚧 | ✅ |
90
- | **Arrays of Structs** | `List[dataclass]` | ✅ | ✅ | ✅ | ✅ | 🚧 | ✅ |
91
- | **3D Arrays** | `List[List[List[T]]]` | ❌ | ✅ | ✅ | ✅ | 🚧 | ✅ |
92
- | **Arrays of Arrays of Structs** | `List[List[dataclass]]` | ❌ | ✅ | ✅ | ✅ | 🚧 | ✅ |
88
+ | **Struct/Record** | `dataclass` | ✅ | ✅ | ✅ | ✅ | | ✅ |
89
+ | **Nested Arrays** | `List[List[T]]` | ❌ | ✅ | ✅ | ✅ | | ✅ |
90
+ | **Arrays of Structs** | `List[dataclass]` | ✅ | ✅ | ✅ | ✅ | | ✅ |
91
+ | **3D Arrays** | `List[List[List[T]]]` | ❌ | ✅ | ✅ | ✅ | | ✅ |
92
+ | **Arrays of Arrays of Structs** | `List[List[dataclass]]` | ❌ | ✅ | ✅ | ✅ | | ✅ |
93
93
 
94
94
  ### Database-Specific Notes
95
95
 
@@ -97,7 +97,7 @@ The library supports different data types across database engines. All checkmark
97
97
  - **Athena**: 256KB query size limit; supports arrays and maps using `ARRAY[]` and `MAP(ARRAY[], ARRAY[])` syntax; supports struct types using `ROW` with named fields (dataclasses and Pydantic models); **full support for deeply nested types** including nested arrays, arrays of structs, and 3D arrays
98
98
  - **Redshift**: Arrays and maps implemented via SUPER type (JSON parsing); 16MB query size limit; **full support for deeply nested types** including struct types using SUPER + JSON_PARSE, nested arrays (2D, 3D+), arrays of structs, and arrays of arrays of structs
99
99
  - **Trino**: Memory catalog for testing; excellent decimal precision; supports arrays, maps, and struct types using `ROW` with named fields (dataclasses and Pydantic models); **full support for deeply nested types** including nested arrays, arrays of structs, and 3D arrays
100
- - **Snowflake**: Column names normalized to lowercase; 1MB query size limit; dict/map types implemented via VARIANT type (JSON parsing); struct types not yet supported (🚧 TODO); nested arrays not yet supported (🚧 TODO)
100
+ - **Snowflake**: Column names normalized to lowercase; 1MB query size limit; dict/map types implemented via VARIANT type (JSON parsing); **full support for deeply nested types** including struct types using OBJECT + PARSE_JSON, nested arrays (2D, 3D+), arrays of structs, and arrays of arrays of structs
101
101
  - **DuckDB**: Fast embedded analytics database; excellent SQL standards compliance; supports arrays, maps, and struct types using `STRUCT` syntax with named fields (dataclasses and Pydantic models); **full support for deeply nested types** including nested arrays (2D, 3D+), arrays of structs, and arrays of arrays of structs
102
102
 
103
103
  ## Execution Modes Support
@@ -1374,25 +1374,23 @@ The library has a few known limitations that are planned to be addressed in futu
1374
1374
 
1375
1375
  ### Deeply Nested Complex Types Support
1376
1376
 
1377
- **✅ Fully Supported (Athena, Trino, DuckDB & Redshift):**
1377
+ **✅ Fully Supported (All Major Adapters):**
1378
1378
  - Nested arrays (2D, 3D+): `List[List[int]]`, `List[List[List[int]]]`
1379
1379
  - Arrays of structs: `List[Address]` where Address is a dataclass
1380
1380
  - Arrays of arrays of structs: `List[List[OrderItem]]`
1381
1381
  - Maps with complex values: `Dict[str, str]`, `Dict[str, int]`
1382
1382
  - See `tests/integration/test_deeply_nested_types_integration.py` for comprehensive examples
1383
- - **16 tests passing** across Athena, Trino, DuckDB, and Redshift (both CTE and physical tables modes)
1383
+ - **20 tests passing** across Athena, Trino, DuckDB, Redshift, and Snowflake (both CTE and physical tables modes)
1384
1384
 
1385
1385
  **Implementation Details:**
1386
1386
  - **Athena/Trino**: Use ROW types with named fields
1387
1387
  - **DuckDB**: Use STRUCT types with dictionary-style syntax
1388
- - **Redshift**: Use SUPER type with JSON_PARSE for serialization, recursive JSON parsing for deserialization
1389
-
1390
- **🚧 TODO - Implementation Needed:**
1391
- - **BigQuery**: Does not support nested arrays - this is a **database limitation**, not a library limitation. BigQuery's type system doesn't allow `ARRAY<ARRAY<T>>` constructs
1392
- - **Snowflake**:
1393
- - Struct type support not implemented (see `Struct type not yet supported for dialect: snowflake`)
1394
- - Nested arrays not yet supported
1395
- - TODO: Implement struct/nested type support using VARIANT/OBJECT types
1388
+ - **Redshift**: Use SUPER type with JSON_PARSE, recursive JSON parsing
1389
+ - **Snowflake**: Use OBJECT type with PARSE_JSON, recursive JSON parsing
1390
+ - **Shared Helper**: `_parse_json_if_string()` in BaseTypeConverter for Redshift/Snowflake
1391
+
1392
+ **🚧 Database Limitations:**
1393
+ - **BigQuery**: Does not support nested arrays - this is a **database limitation**, not a library limitation. BigQuery's type system doesn't allow `ARRAY<ARRAY<T>>` constructs. Struct types and arrays of structs work fine.
1396
1394
 
1397
1395
  ### Database-Specific Limitations
1398
1396
  - **BigQuery**: Does not support nested arrays (arrays of arrays). This is a BigQuery database limitation, not a library limitation.
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "sql-testing-library"
7
- version = "0.21.0"
7
+ version = "0.22.1"
8
8
  description = "SQL Testing Framework for Python: Unit test SQL queries with mock data injection for BigQuery, Snowflake, Redshift, Athena, Trino, and DuckDB. Simplify data engineering ETL testing and analytics validation."
9
9
  authors = ["Gurmeet Saran <gurmeetx@gmail.com>", "Kushal Thakkar <kushal.thakkar@gmail.com>"]
10
10
  maintainers = ["Gurmeet Saran <gurmeetx@gmail.com>", "Kushal Thakkar <kushal.thakkar@gmail.com>"]
@@ -27,7 +27,7 @@ try:
27
27
  except ImportError:
28
28
  __all__ = []
29
29
 
30
- __version__ = "0.21.0"
30
+ __version__ = "0.22.1"
31
31
  __all__.extend(
32
32
  [
33
33
  "SQLTestFramework",
@@ -29,17 +29,6 @@ except ImportError:
29
29
  class RedshiftTypeConverter(BaseTypeConverter):
30
30
  """Redshift-specific type converter."""
31
31
 
32
- def _parse_json_if_string(self, value: Any) -> Any:
33
- """Helper to parse JSON strings from SUPER columns."""
34
- if isinstance(value, str):
35
- import json
36
-
37
- try:
38
- return json.loads(value)
39
- except json.JSONDecodeError:
40
- return value
41
- return value
42
-
43
32
  def convert(self, value: Any, target_type: Type) -> Any:
44
33
  """Convert Redshift result value to target type."""
45
34
  # Handle None/NULL values first
@@ -50,21 +50,32 @@ class SnowflakeTypeConverter(BaseTypeConverter):
50
50
  return None
51
51
  target_type = self.get_optional_inner_type(target_type)
52
52
 
53
+ # Handle struct types from Snowflake OBJECT/VARIANT columns
54
+ from .._types import is_struct_type
55
+
56
+ if is_struct_type(target_type):
57
+ # Parse JSON string if needed (using base class helper)
58
+ parsed_value = self._parse_json_if_string(value)
59
+ # Delegate to base converter (handles dict → struct)
60
+ return super().convert(parsed_value, target_type)
61
+
62
+ # Handle list/array types from Snowflake ARRAY/VARIANT columns
63
+ if hasattr(target_type, "__origin__") and target_type.__origin__ is list:
64
+ # Parse JSON string if needed (using base class helper)
65
+ parsed_value = self._parse_json_if_string(value)
66
+
67
+ if isinstance(parsed_value, list):
68
+ # Recursively convert each element with proper typing
69
+ element_type = get_args(target_type)[0] if get_args(target_type) else str
70
+ return [self.convert(elem, element_type) for elem in parsed_value]
71
+ else:
72
+ return []
73
+
53
74
  # Handle dict/map types from Snowflake VARIANT columns
54
75
  if hasattr(target_type, "__origin__") and target_type.__origin__ is dict:
55
- # Snowflake returns VARIANT types as Python dicts already
56
- if isinstance(value, dict):
57
- return value
58
- # If it's a string, parse it
59
- elif isinstance(value, str):
60
- import json
61
-
62
- try:
63
- return json.loads(value)
64
- except json.JSONDecodeError:
65
- return {}
66
- else:
67
- return {}
76
+ # Parse JSON string if needed (using base class helper)
77
+ parsed_value = self._parse_json_if_string(value)
78
+ return parsed_value if isinstance(parsed_value, dict) else {}
68
79
 
69
80
  # Snowflake returns proper Python types in most cases, so use base converter
70
81
  return super().convert(value, target_type)
@@ -1,6 +1,7 @@
1
1
  """Core SQL testing framework."""
2
2
 
3
3
  import os
4
+ import re
4
5
  from concurrent.futures import ThreadPoolExecutor, as_completed
5
6
  from dataclasses import dataclass
6
7
  from typing import (
@@ -35,6 +36,14 @@ from ._sql_logger import SQLLogger
35
36
  # Type for adapter types
36
37
  AdapterType = Literal["bigquery", "athena", "redshift", "trino", "snowflake", "duckdb"]
37
38
 
39
+
40
+ # Matches an optional run of SQL comments (-- … and /* … */) followed by the WITH
41
+ # keyword. Group 1 captures everything before WITH so it can be preserved as a prefix.
42
+ _WITH_AFTER_COMMENTS_RE = re.compile(
43
+ r"\A(\s*(?:(?:/\*.*?\*/|--[^\n]*)\s*)*)WITH\b",
44
+ re.DOTALL | re.IGNORECASE,
45
+ )
46
+
38
47
  T = TypeVar("T")
39
48
 
40
49
  # Global storage for SQL execution data (used by pytest plugin)
@@ -84,12 +93,8 @@ class SQLTestCase(Generic[T]):
84
93
  stacklevel=2,
85
94
  )
86
95
  self.default_namespace = self.execution_database
87
- elif self.default_namespace is None and self.execution_database is None:
88
- # Neither provided - this is an error
89
- raise ValueError(
90
- "Must provide either 'default_namespace' (preferred) or 'execution_database' "
91
- "(deprecated) parameter"
92
- )
96
+ # Both None is fine queries using only fully-qualified table names
97
+ # don't need a default_namespace.
93
98
 
94
99
 
95
100
  class SQLTestFramework:
@@ -138,9 +143,8 @@ class SQLTestFramework:
138
143
  # Parse SQL to find table references
139
144
  referenced_tables = self._parse_sql_tables(test_case.query)
140
145
 
141
- # Resolve unqualified table names
142
- # default_namespace is guaranteed to be set by __post_init__
143
- assert test_case.default_namespace is not None
146
+ # Resolve unqualified table names (default_namespace may be None if
147
+ # all table references in the query are already fully qualified)
144
148
  resolved_tables = self._resolve_table_names(
145
149
  referenced_tables, test_case.default_namespace
146
150
  )
@@ -354,8 +358,11 @@ class SQLTestFramework:
354
358
  # Find all real tables (excluding the CTEs)
355
359
  tables = []
356
360
  for table in parsed.find_all(exp.Table):
357
- # Skip tables that are actually CTE references
358
- if str(table.name) in cte_aliases:
361
+ # Skip tables that are actually CTE references.
362
+ # Only unqualified names (no db/catalog) can be CTE references;
363
+ # a fully-qualified name like project.dataset.table is always a
364
+ # real table even when its unqualified name matches a CTE alias.
365
+ if not table.db and not table.catalog and str(table.name) in cte_aliases:
359
366
  continue
360
367
 
361
368
  # Get the fully qualified name including catalog/schema if present
@@ -374,7 +381,7 @@ class SQLTestFramework:
374
381
  raise SQLParseError(query, str(e)) # noqa: B904
375
382
 
376
383
  def _resolve_table_names(
377
- self, referenced_tables: List[str], default_namespace: str
384
+ self, referenced_tables: List[str], default_namespace: Optional[str]
378
385
  ) -> Dict[str, str]:
379
386
  """
380
387
  Resolve unqualified table names using default namespace context.
@@ -388,7 +395,13 @@ class SQLTestFramework:
388
395
  # Already qualified
389
396
  resolved[table_name] = table_name
390
397
  else:
391
- # Add namespace prefix
398
+ if default_namespace is None:
399
+ raise ValueError(
400
+ f"Table '{table_name}' is not fully qualified and no "
401
+ f"default_namespace was provided. Either use a fully qualified "
402
+ f"table name (e.g. 'project.dataset.{table_name}') or set "
403
+ f"default_namespace in your TestCase."
404
+ )
392
405
  qualified_name = f"{default_namespace}.{table_name}"
393
406
  resolved[table_name] = qualified_name
394
407
 
@@ -463,12 +476,17 @@ class SQLTestFramework:
463
476
 
464
477
  # Combine CTEs with original query
465
478
  if ctes:
466
- # Check if modified query already starts with WITH
467
479
  modified_query_stripped = modified_query.strip()
468
- if modified_query_stripped.upper().startswith("WITH"):
469
- # Query already has WITH clause, so append our CTEs with comma
480
+ m = _WITH_AFTER_COMMENTS_RE.match(modified_query_stripped)
481
+ if m:
482
+ # Query already has a WITH clause (possibly after leading comments).
483
+ # Preserve any leading comments and inject mock-table CTEs right
484
+ # before the existing WITH keyword.
485
+ prefix = m.group(1)
470
486
  cte_block = ",\n".join(ctes)
471
- final_query = f"WITH {cte_block},\n{modified_query_stripped[4:].strip()}"
487
+ final_query = (
488
+ f"{prefix}WITH {cte_block},\n{modified_query_stripped[m.end() :].strip()}"
489
+ )
472
490
  else:
473
491
  # Query doesn't have WITH clause, add it
474
492
  cte_block = "WITH " + ",\n".join(ctes)
@@ -96,14 +96,7 @@ class SQLLogger:
96
96
  # Generate run ID with timestamp
97
97
  timestamp = datetime.now().strftime("%Y%m%dT%H%M%S")
98
98
 
99
- # Check if running in parallel mode
100
- worker_id = self._get_worker_id()
101
- if worker_id:
102
- # Include worker ID in run directory name for parallel execution
103
- SQLLogger._run_id = f"runid_{timestamp}_{worker_id}"
104
- else:
105
- # Serial execution - use standard run ID
106
- SQLLogger._run_id = f"runid_{timestamp}"
99
+ SQLLogger._run_id = f"runid_{timestamp}"
107
100
 
108
101
  SQLLogger._run_directory = self.log_dir / SQLLogger._run_id
109
102
  SQLLogger._run_directory.mkdir(parents=True, exist_ok=True)
@@ -676,6 +676,19 @@ def _format_struct_value(value: Any, struct_type: Type, dialect: str) -> str:
676
676
  json_str = json.dumps(json_obj, cls=DecimalEncoder)
677
677
  return f"JSON_PARSE('{json_str}')"
678
678
 
679
+ # For Snowflake (using OBJECT type with JSON)
680
+ elif dialect == "snowflake":
681
+ # Handle NULL struct values
682
+ if value is None:
683
+ return "NULL"
684
+
685
+ # Use helper function to convert struct to JSON-serializable dict
686
+ json_obj = _convert_to_json_serializable(value, struct_type)
687
+ json_str = json.dumps(json_obj, cls=DecimalEncoder)
688
+ # Escape single quotes for SQL
689
+ json_str = json_str.replace("'", "''")
690
+ return f"PARSE_JSON('{json_str}')"
691
+
679
692
  # For other databases, struct support would need to be implemented
680
693
  else:
681
694
  raise NotImplementedError(f"Struct type not yet supported for dialect: {dialect}")
@@ -224,6 +224,21 @@ class BaseTypeConverter:
224
224
  inner_type: Type = next(arg for arg in args if arg is not type(None))
225
225
  return inner_type
226
226
 
227
+ @staticmethod
228
+ def _parse_json_if_string(value: Any) -> Any:
229
+ """Helper to parse JSON strings from VARIANT/SUPER/OBJECT columns.
230
+
231
+ Used by adapters that store complex types as JSON (Redshift, Snowflake).
232
+ """
233
+ if isinstance(value, str):
234
+ import json
235
+
236
+ try:
237
+ return json.loads(value)
238
+ except json.JSONDecodeError:
239
+ return value
240
+ return value
241
+
227
242
  def convert(self, value: Any, target_type: Type) -> Any:
228
243
  """Convert value to target type."""
229
244
  # Handle None/NULL values