sql-testing-library 0.21.0__tar.gz → 0.22.0__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.0}/CHANGELOG.md +10 -0
  2. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/PKG-INFO +15 -17
  3. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/README.md +14 -16
  4. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/pyproject.toml +1 -1
  5. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/src/sql_testing_library/__init__.py +1 -1
  6. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/src/sql_testing_library/_adapters/redshift.py +0 -11
  7. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/src/sql_testing_library/_adapters/snowflake.py +24 -13
  8. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/src/sql_testing_library/_sql_utils.py +13 -0
  9. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/src/sql_testing_library/_types.py +15 -0
  10. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/LICENSE +0 -0
  11. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/src/sql_testing_library/_adapters/__init__.py +0 -0
  12. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/src/sql_testing_library/_adapters/athena.py +0 -0
  13. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/src/sql_testing_library/_adapters/base.py +0 -0
  14. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/src/sql_testing_library/_adapters/bigquery.py +0 -0
  15. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/src/sql_testing_library/_adapters/duckdb.py +0 -0
  16. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/src/sql_testing_library/_adapters/presto.py +0 -0
  17. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/src/sql_testing_library/_adapters/trino.py +0 -0
  18. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/src/sql_testing_library/_core.py +0 -0
  19. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/src/sql_testing_library/_exceptions.py +0 -0
  20. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/src/sql_testing_library/_mock_table.py +0 -0
  21. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/src/sql_testing_library/_pytest_plugin.py +0 -0
  22. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/src/sql_testing_library/_sql_logger.py +0 -0
  23. {sql_testing_library-0.21.0 → sql_testing_library-0.22.0}/src/sql_testing_library/py.typed +0 -0
@@ -5,6 +5,16 @@ 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.0 (2026-01-05)
9
+
10
+ ### Feat
11
+
12
+ - add Snowflake support for deeply nested complex types via OBJECT
13
+
14
+ ### Fix
15
+
16
+ - update Snowflake unit test to use JSON format
17
+
8
18
  ## 0.21.0 (2026-01-03)
9
19
 
10
20
  ### 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.0
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.0"
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.0"
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)
@@ -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