sql-testing-library 0.9.0__tar.gz → 0.10.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 (22) hide show
  1. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/CHANGELOG.md +6 -0
  2. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/PKG-INFO +4 -5
  3. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/README.md +3 -4
  4. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/pyproject.toml +1 -1
  5. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/bigquery.py +74 -0
  6. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_sql_utils.py +9 -1
  7. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/LICENSE +0 -0
  8. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/__init__.py +0 -0
  9. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/__init__.py +0 -0
  10. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/athena.py +0 -0
  11. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/base.py +0 -0
  12. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/presto.py +0 -0
  13. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/redshift.py +0 -0
  14. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/snowflake.py +0 -0
  15. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_adapters/trino.py +0 -0
  16. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_core.py +0 -0
  17. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_exceptions.py +0 -0
  18. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_mock_table.py +0 -0
  19. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_pytest_plugin.py +0 -0
  20. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_sql_logger.py +0 -0
  21. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/_types.py +0 -0
  22. {sql_testing_library-0.9.0 → sql_testing_library-0.10.0}/src/sql_testing_library/py.typed +0 -0
@@ -5,6 +5,12 @@ 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.10.0 (2025-06-15)
9
+
10
+ ### Feat
11
+
12
+ - **bigquery**: add support for map in bigquery (#99)
13
+
8
14
  ## 0.9.0 (2025-06-10)
9
15
 
10
16
  ### Feat
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sql-testing-library
3
- Version: 0.9.0
3
+ Version: 0.10.0
4
4
  Summary: A powerful Python framework for unit testing SQL queries across BigQuery, Snowflake, Redshift, Athena, and Trino with mock data
5
5
  License: MIT
6
6
  Keywords: sql,testing,unit-testing,mock-data,database-testing,bigquery,snowflake,redshift,athena,trino,data-engineering,etl-testing,sql-validation,query-testing
@@ -136,14 +136,13 @@ The library supports different data types across database engines. All checkmark
136
136
  | **Integer Array** | `List[int]` | ✅ | ✅ | ✅ | ✅ | ✅ |
137
137
  | **Decimal Array** | `List[Decimal]` | ✅ | ✅ | ✅ | ✅ | ✅ |
138
138
  | **Optional Array** | `Optional[List[T]]` | ✅ | ✅ | ✅ | ✅ | ✅ |
139
- | **Map/Object** | `Dict[K, V]` | | ✅ | ✅ | ✅ | ❌ |
140
- | **Struct/Record** | `dict`/`dataclass` | ❌ | ❌ | ❌ | ❌ | ❌ |
139
+ | **Map/Dict** | `Dict[K, V]` | | ✅ | ✅ | ✅ | ❌ |
140
+ | **Struct/Record** | `dataclass` | ❌ | ❌ | ❌ | ❌ | ❌ |
141
141
  | **Nested Arrays** | `List[List[T]]` | ❌ | ❌ | ❌ | ❌ | ❌ |
142
- | **JSON Objects** | `JSON` | ❌ | ❌ | ❌ | ❌ | ❌ |
143
142
 
144
143
  ### Database-Specific Notes
145
144
 
146
- - **BigQuery**: NULL arrays become empty arrays `[]`; uses scientific notation for large decimals
145
+ - **BigQuery**: NULL arrays become empty arrays `[]`; uses scientific notation for large decimals; dict/map types stored as JSON strings
147
146
  - **Athena**: 256KB query size limit; supports arrays and maps using `ARRAY[]` and `MAP(ARRAY[], ARRAY[])` syntax
148
147
  - **Redshift**: Arrays and maps implemented via SUPER type (JSON parsing); 16MB query size limit
149
148
  - **Trino**: Memory catalog for testing; excellent decimal precision; supports arrays and maps
@@ -79,14 +79,13 @@ The library supports different data types across database engines. All checkmark
79
79
  | **Integer Array** | `List[int]` | ✅ | ✅ | ✅ | ✅ | ✅ |
80
80
  | **Decimal Array** | `List[Decimal]` | ✅ | ✅ | ✅ | ✅ | ✅ |
81
81
  | **Optional Array** | `Optional[List[T]]` | ✅ | ✅ | ✅ | ✅ | ✅ |
82
- | **Map/Object** | `Dict[K, V]` | | ✅ | ✅ | ✅ | ❌ |
83
- | **Struct/Record** | `dict`/`dataclass` | ❌ | ❌ | ❌ | ❌ | ❌ |
82
+ | **Map/Dict** | `Dict[K, V]` | | ✅ | ✅ | ✅ | ❌ |
83
+ | **Struct/Record** | `dataclass` | ❌ | ❌ | ❌ | ❌ | ❌ |
84
84
  | **Nested Arrays** | `List[List[T]]` | ❌ | ❌ | ❌ | ❌ | ❌ |
85
- | **JSON Objects** | `JSON` | ❌ | ❌ | ❌ | ❌ | ❌ |
86
85
 
87
86
  ### Database-Specific Notes
88
87
 
89
- - **BigQuery**: NULL arrays become empty arrays `[]`; uses scientific notation for large decimals
88
+ - **BigQuery**: NULL arrays become empty arrays `[]`; uses scientific notation for large decimals; dict/map types stored as JSON strings
90
89
  - **Athena**: 256KB query size limit; supports arrays and maps using `ARRAY[]` and `MAP(ARRAY[], ARRAY[])` syntax
91
90
  - **Redshift**: Arrays and maps implemented via SUPER type (JSON parsing); 16MB query size limit
92
91
  - **Trino**: Memory catalog for testing; excellent decimal precision; supports arrays and maps
@@ -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.9.0"
7
+ version = "0.10.0"
8
8
  description = "A powerful Python framework for unit testing SQL queries across BigQuery, Snowflake, Redshift, Athena, and Trino with mock data"
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>"]
@@ -30,6 +30,32 @@ class BigQueryTypeConverter(BaseTypeConverter):
30
30
 
31
31
  def convert(self, value: Any, target_type: Type) -> Any:
32
32
  """Convert BigQuery result value to target type."""
33
+ # Handle None/NULL values first
34
+ if value is None:
35
+ return None
36
+
37
+ # Handle Optional types
38
+ if self.is_optional_type(target_type):
39
+ if value is None:
40
+ return None
41
+ target_type = self.get_optional_inner_type(target_type)
42
+
43
+ # Handle dict/map types from BigQuery STRING columns containing JSON
44
+ if hasattr(target_type, "__origin__") and target_type.__origin__ is dict:
45
+ # BigQuery returns JSON stored as strings, so we need to parse them
46
+ if isinstance(value, str):
47
+ import json
48
+
49
+ try:
50
+ return json.loads(value)
51
+ except json.JSONDecodeError:
52
+ return {}
53
+ elif isinstance(value, dict):
54
+ # Already a dict (shouldn't happen with STRING columns, but handle it)
55
+ return value
56
+ else:
57
+ return {}
58
+
33
59
  # BigQuery typically returns proper Python types, so use base converter
34
60
  return super().convert(value, target_type)
35
61
 
@@ -82,6 +108,9 @@ class BigQueryAdapter(DatabaseAdapter):
82
108
  # Insert data
83
109
  df = mock_table.to_dataframe()
84
110
  if not df.empty:
111
+ # Convert dict columns to JSON strings for BigQuery
112
+ df = self._prepare_dataframe_for_bigquery(df, mock_table)
113
+
85
114
  job_config = bigquery.LoadJobConfig()
86
115
  job = self.client.load_table_from_dataframe(df, table, job_config=job_config)
87
116
  job.result() # Wait for job to complete
@@ -130,6 +159,9 @@ class BigQueryAdapter(DatabaseAdapter):
130
159
 
131
160
  # Insert data if any
132
161
  if not df.empty:
162
+ # Convert dict columns to JSON strings for BigQuery
163
+ df = self._prepare_dataframe_for_bigquery(df, mock_table)
164
+
133
165
  job_config = bigquery.LoadJobConfig()
134
166
  job = self.client.load_table_from_dataframe(df, table, job_config=job_config)
135
167
  job.result()
@@ -188,9 +220,51 @@ class BigQueryAdapter(DatabaseAdapter):
188
220
 
189
221
  # Create field with mode=REPEATED for arrays
190
222
  schema.append(bigquery.SchemaField(col_name, element_bq_type, mode="REPEATED"))
223
+ # Handle Dict/Map types
224
+ elif hasattr(col_type, "__origin__") and col_type.__origin__ is dict:
225
+ # BigQuery stores JSON data as STRING type
226
+ schema.append(bigquery.SchemaField(col_name, bigquery.enums.SqlTypeNames.STRING))
191
227
  else:
192
228
  # Handle scalar types
193
229
  bq_type = type_mapping.get(col_type, bigquery.enums.SqlTypeNames.STRING)
194
230
  schema.append(bigquery.SchemaField(col_name, bq_type))
195
231
 
196
232
  return schema
233
+
234
+ def _prepare_dataframe_for_bigquery(
235
+ self, df: "pd.DataFrame", mock_table: BaseMockTable
236
+ ) -> "pd.DataFrame":
237
+ """Prepare DataFrame for BigQuery by converting dict columns to JSON strings."""
238
+ import json
239
+
240
+ import pandas as pd
241
+
242
+ from .._sql_utils import DecimalEncoder
243
+
244
+ # Create a copy to avoid modifying the original
245
+ df_copy = df.copy()
246
+ column_types = mock_table.get_column_types()
247
+
248
+ for col_name, col_type in column_types.items():
249
+ # Handle Optional types
250
+ if hasattr(col_type, "__origin__") and col_type.__origin__ is Union:
251
+ # Extract the non-None type from Optional[T]
252
+ non_none_types = [arg for arg in get_args(col_type) if arg is not type(None)]
253
+ if non_none_types:
254
+ col_type = non_none_types[0]
255
+
256
+ # Check if this is a dict type
257
+ if hasattr(col_type, "__origin__") and col_type.__origin__ is dict:
258
+ # Convert dict values to JSON strings
259
+ def convert_dict_to_json(val):
260
+ if pd.isna(val) or val is None:
261
+ return None
262
+ elif isinstance(val, dict):
263
+ # Use DecimalEncoder to handle Decimal values in dicts
264
+ return json.dumps(val, cls=DecimalEncoder)
265
+ else:
266
+ return val
267
+
268
+ df_copy[col_name] = df_copy[col_name].apply(convert_dict_to_json)
269
+
270
+ return df_copy
@@ -167,6 +167,9 @@ def format_sql_value(value: Any, column_type: Type, dialect: str = "standard") -
167
167
  elif dialect == "redshift":
168
168
  # Redshift SUPER type handles NULL maps
169
169
  return "NULL::SUPER"
170
+ elif dialect == "bigquery":
171
+ # BigQuery JSON type handles NULL maps
172
+ return "NULL"
170
173
  else:
171
174
  return "NULL"
172
175
 
@@ -289,9 +292,14 @@ def format_sql_value(value: Any, column_type: Type, dialect: str = "standard") -
289
292
  # Redshift uses SUPER type with JSON-like syntax for maps
290
293
  json_str = json.dumps(value, cls=DecimalEncoder)
291
294
  return f"JSON_PARSE('{json_str}')"
295
+ elif dialect == "bigquery":
296
+ # BigQuery stores JSON as strings
297
+ json_str = json.dumps(value, cls=DecimalEncoder)
298
+ # Escape single quotes in JSON string for SQL
299
+ json_str = json_str.replace("'", "''")
300
+ return f"'{json_str}'"
292
301
  else:
293
302
  # Other databases don't have native map support yet
294
- # Could potentially use JSON for BigQuery, Snowflake
295
303
  raise NotImplementedError(f"Map type not yet supported for dialect: {dialect}")
296
304
 
297
305
  # Handle string types