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.
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/CHANGELOG.md +17 -0
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/PKG-INFO +15 -17
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/README.md +14 -16
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/pyproject.toml +1 -1
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/__init__.py +1 -1
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/redshift.py +0 -11
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/snowflake.py +24 -13
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_core.py +35 -17
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_sql_logger.py +1 -8
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_sql_utils.py +13 -0
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_types.py +15 -0
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/LICENSE +0 -0
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/__init__.py +0 -0
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/athena.py +0 -0
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/base.py +0 -0
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/bigquery.py +0 -0
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/duckdb.py +0 -0
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/presto.py +0 -0
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/trino.py +0 -0
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_exceptions.py +0 -0
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_mock_table.py +0 -0
- {sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_pytest_plugin.py +0 -0
- {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.
|
|
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
|
|
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 (
|
|
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
|
-
- **
|
|
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
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
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
|
|
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 (
|
|
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
|
-
- **
|
|
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
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
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.
|
|
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>"]
|
|
@@ -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
|
-
#
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
469
|
-
|
|
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 =
|
|
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)
|
{sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_sql_logger.py
RENAMED
|
@@ -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
|
-
|
|
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)
|
{sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_sql_utils.py
RENAMED
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_adapters/trino.py
RENAMED
|
File without changes
|
{sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_exceptions.py
RENAMED
|
File without changes
|
{sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_mock_table.py
RENAMED
|
File without changes
|
{sql_testing_library-0.21.0 → sql_testing_library-0.22.1}/src/sql_testing_library/_pytest_plugin.py
RENAMED
|
File without changes
|
|
File without changes
|