sqlspec 0.12.2__py3-none-any.whl → 0.13.1__py3-none-any.whl

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.

Potentially problematic release.


This version of sqlspec might be problematic. Click here for more details.

Files changed (113) hide show
  1. sqlspec/_sql.py +21 -180
  2. sqlspec/adapters/adbc/config.py +10 -12
  3. sqlspec/adapters/adbc/driver.py +120 -118
  4. sqlspec/adapters/aiosqlite/config.py +16 -3
  5. sqlspec/adapters/aiosqlite/driver.py +100 -130
  6. sqlspec/adapters/asyncmy/config.py +17 -4
  7. sqlspec/adapters/asyncmy/driver.py +123 -135
  8. sqlspec/adapters/asyncpg/config.py +17 -29
  9. sqlspec/adapters/asyncpg/driver.py +98 -140
  10. sqlspec/adapters/bigquery/config.py +4 -5
  11. sqlspec/adapters/bigquery/driver.py +125 -167
  12. sqlspec/adapters/duckdb/config.py +3 -6
  13. sqlspec/adapters/duckdb/driver.py +114 -111
  14. sqlspec/adapters/oracledb/config.py +32 -5
  15. sqlspec/adapters/oracledb/driver.py +242 -259
  16. sqlspec/adapters/psqlpy/config.py +18 -9
  17. sqlspec/adapters/psqlpy/driver.py +118 -93
  18. sqlspec/adapters/psycopg/config.py +44 -31
  19. sqlspec/adapters/psycopg/driver.py +283 -236
  20. sqlspec/adapters/sqlite/config.py +3 -3
  21. sqlspec/adapters/sqlite/driver.py +103 -97
  22. sqlspec/config.py +0 -4
  23. sqlspec/driver/_async.py +89 -98
  24. sqlspec/driver/_common.py +52 -17
  25. sqlspec/driver/_sync.py +81 -105
  26. sqlspec/driver/connection.py +207 -0
  27. sqlspec/driver/mixins/_csv_writer.py +91 -0
  28. sqlspec/driver/mixins/_pipeline.py +38 -49
  29. sqlspec/driver/mixins/_result_utils.py +27 -9
  30. sqlspec/driver/mixins/_storage.py +67 -181
  31. sqlspec/driver/mixins/_type_coercion.py +3 -4
  32. sqlspec/driver/parameters.py +138 -0
  33. sqlspec/exceptions.py +10 -2
  34. sqlspec/extensions/aiosql/adapter.py +0 -10
  35. sqlspec/extensions/litestar/handlers.py +0 -1
  36. sqlspec/extensions/litestar/plugin.py +0 -3
  37. sqlspec/extensions/litestar/providers.py +0 -14
  38. sqlspec/loader.py +25 -90
  39. sqlspec/protocols.py +542 -0
  40. sqlspec/service/__init__.py +3 -2
  41. sqlspec/service/_util.py +147 -0
  42. sqlspec/service/base.py +1116 -9
  43. sqlspec/statement/builder/__init__.py +42 -32
  44. sqlspec/statement/builder/_ddl_utils.py +0 -10
  45. sqlspec/statement/builder/_parsing_utils.py +10 -4
  46. sqlspec/statement/builder/base.py +67 -22
  47. sqlspec/statement/builder/column.py +283 -0
  48. sqlspec/statement/builder/ddl.py +91 -67
  49. sqlspec/statement/builder/delete.py +23 -7
  50. sqlspec/statement/builder/insert.py +29 -15
  51. sqlspec/statement/builder/merge.py +4 -4
  52. sqlspec/statement/builder/mixins/_aggregate_functions.py +113 -14
  53. sqlspec/statement/builder/mixins/_common_table_expr.py +0 -1
  54. sqlspec/statement/builder/mixins/_delete_from.py +1 -1
  55. sqlspec/statement/builder/mixins/_from.py +10 -8
  56. sqlspec/statement/builder/mixins/_group_by.py +0 -1
  57. sqlspec/statement/builder/mixins/_insert_from_select.py +0 -1
  58. sqlspec/statement/builder/mixins/_insert_values.py +0 -2
  59. sqlspec/statement/builder/mixins/_join.py +20 -13
  60. sqlspec/statement/builder/mixins/_limit_offset.py +3 -3
  61. sqlspec/statement/builder/mixins/_merge_clauses.py +3 -4
  62. sqlspec/statement/builder/mixins/_order_by.py +2 -2
  63. sqlspec/statement/builder/mixins/_pivot.py +4 -7
  64. sqlspec/statement/builder/mixins/_select_columns.py +6 -5
  65. sqlspec/statement/builder/mixins/_unpivot.py +6 -9
  66. sqlspec/statement/builder/mixins/_update_from.py +2 -1
  67. sqlspec/statement/builder/mixins/_update_set.py +11 -8
  68. sqlspec/statement/builder/mixins/_where.py +61 -34
  69. sqlspec/statement/builder/select.py +32 -17
  70. sqlspec/statement/builder/update.py +25 -11
  71. sqlspec/statement/filters.py +39 -14
  72. sqlspec/statement/parameter_manager.py +220 -0
  73. sqlspec/statement/parameters.py +210 -79
  74. sqlspec/statement/pipelines/__init__.py +166 -23
  75. sqlspec/statement/pipelines/analyzers/_analyzer.py +21 -20
  76. sqlspec/statement/pipelines/context.py +35 -39
  77. sqlspec/statement/pipelines/transformers/__init__.py +2 -3
  78. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +19 -187
  79. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +628 -58
  80. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +76 -0
  81. sqlspec/statement/pipelines/validators/_dml_safety.py +33 -18
  82. sqlspec/statement/pipelines/validators/_parameter_style.py +87 -14
  83. sqlspec/statement/pipelines/validators/_performance.py +38 -23
  84. sqlspec/statement/pipelines/validators/_security.py +39 -62
  85. sqlspec/statement/result.py +37 -129
  86. sqlspec/statement/splitter.py +0 -12
  87. sqlspec/statement/sql.py +863 -391
  88. sqlspec/statement/sql_compiler.py +140 -0
  89. sqlspec/storage/__init__.py +10 -2
  90. sqlspec/storage/backends/fsspec.py +53 -8
  91. sqlspec/storage/backends/obstore.py +15 -19
  92. sqlspec/storage/capabilities.py +101 -0
  93. sqlspec/storage/registry.py +56 -83
  94. sqlspec/typing.py +6 -434
  95. sqlspec/utils/cached_property.py +25 -0
  96. sqlspec/utils/correlation.py +0 -2
  97. sqlspec/utils/logging.py +0 -6
  98. sqlspec/utils/sync_tools.py +0 -4
  99. sqlspec/utils/text.py +0 -5
  100. sqlspec/utils/type_guards.py +892 -0
  101. {sqlspec-0.12.2.dist-info → sqlspec-0.13.1.dist-info}/METADATA +1 -1
  102. sqlspec-0.13.1.dist-info/RECORD +150 -0
  103. sqlspec/statement/builder/protocols.py +0 -20
  104. sqlspec/statement/pipelines/base.py +0 -315
  105. sqlspec/statement/pipelines/result_types.py +0 -41
  106. sqlspec/statement/pipelines/transformers/_remove_comments.py +0 -66
  107. sqlspec/statement/pipelines/transformers/_remove_hints.py +0 -81
  108. sqlspec/statement/pipelines/validators/base.py +0 -67
  109. sqlspec/storage/protocol.py +0 -173
  110. sqlspec-0.12.2.dist-info/RECORD +0 -145
  111. {sqlspec-0.12.2.dist-info → sqlspec-0.13.1.dist-info}/WHEEL +0 -0
  112. {sqlspec-0.12.2.dist-info → sqlspec-0.13.1.dist-info}/licenses/LICENSE +0 -0
  113. {sqlspec-0.12.2.dist-info → sqlspec-0.13.1.dist-info}/licenses/NOTICE +0 -0
sqlspec/_sql.py CHANGED
@@ -13,7 +13,7 @@ from sqlglot.dialects.dialect import DialectType
13
13
  from sqlglot.errors import ParseError as SQLGlotParseError
14
14
 
15
15
  from sqlspec.exceptions import SQLBuilderError
16
- from sqlspec.statement.builder import DeleteBuilder, InsertBuilder, MergeBuilder, SelectBuilder, UpdateBuilder
16
+ from sqlspec.statement.builder import Column, Delete, Insert, Merge, Select, Update
17
17
 
18
18
  __all__ = ("SQLFactory",)
19
19
 
@@ -100,7 +100,6 @@ class SQLFactory:
100
100
  if parsed_expr:
101
101
  # Attempt to get the class name as a fallback, e.g., "Set", "Command"
102
102
  command_type = type(parsed_expr).__name__.upper()
103
- # Handle specific cases like "COMMAND" which might be too generic
104
103
  if command_type == "COMMAND" and parsed_expr.this:
105
104
  return str(parsed_expr.this).upper() # e.g. "SET", "ALTER"
106
105
  return command_type
@@ -163,14 +162,12 @@ class SQLFactory:
163
162
  "WITH": "WITH",
164
163
  }
165
164
  actual_type_str = expr_type_map.get(actual_type, actual_type)
166
- # Only allow SELECT or WITH (if WITH wraps SELECT)
167
165
  if actual_type_str == "SELECT" or (
168
166
  actual_type_str == "WITH" and parsed_expr.this and isinstance(parsed_expr.this, exp.Select)
169
167
  ):
170
- builder = SelectBuilder(dialect=dialect or self.dialect)
168
+ builder = Select(dialect=dialect or self.dialect)
171
169
  builder._expression = parsed_expr
172
170
  return builder
173
- # If not SELECT, raise with helpful message
174
171
  msg = (
175
172
  f"sql(...) only supports SELECT statements. Detected type: {actual_type_str}. "
176
173
  f"Use sql.{actual_type_str.lower()}() instead."
@@ -180,13 +177,11 @@ class SQLFactory:
180
177
  # ===================
181
178
  # Statement Builders
182
179
  # ===================
183
- def select(self, *columns_or_sql: Union[str, exp.Expression], dialect: DialectType = None) -> "SelectBuilder":
180
+ def select(self, *columns_or_sql: Union[str, exp.Expression, Column], dialect: DialectType = None) -> "Select":
184
181
  builder_dialect = dialect or self.dialect
185
182
  if len(columns_or_sql) == 1 and isinstance(columns_or_sql[0], str):
186
183
  sql_candidate = columns_or_sql[0].strip()
187
- # Check if it actually looks like SQL before parsing
188
184
  if self._looks_like_sql(sql_candidate):
189
- # Validate type
190
185
  detected = self.detect_sql_type(sql_candidate, dialect=builder_dialect)
191
186
  if detected not in {"SELECT", "WITH"}:
192
187
  msg = (
@@ -194,21 +189,20 @@ class SQLFactory:
194
189
  f"Use sql.{detected.lower()}() if a dedicated builder exists, or ensure the SQL is SELECT/WITH."
195
190
  )
196
191
  raise SQLBuilderError(msg)
197
- select_builder = SelectBuilder(dialect=builder_dialect)
192
+ select_builder = Select(dialect=builder_dialect)
198
193
  if select_builder._expression is None:
199
194
  select_builder.__post_init__()
200
195
  return self._populate_select_from_sql(select_builder, sql_candidate)
201
- # Otherwise treat as column name and fall through to normal column handling
202
- select_builder = SelectBuilder(dialect=builder_dialect)
196
+ select_builder = Select(dialect=builder_dialect)
203
197
  if select_builder._expression is None:
204
198
  select_builder.__post_init__()
205
199
  if columns_or_sql:
206
200
  select_builder.select(*columns_or_sql)
207
201
  return select_builder
208
202
 
209
- def insert(self, table_or_sql: Optional[str] = None, dialect: DialectType = None) -> "InsertBuilder":
203
+ def insert(self, table_or_sql: Optional[str] = None, dialect: DialectType = None) -> "Insert":
210
204
  builder_dialect = dialect or self.dialect
211
- builder = InsertBuilder(dialect=builder_dialect)
205
+ builder = Insert(dialect=builder_dialect)
212
206
  if builder._expression is None:
213
207
  builder.__post_init__()
214
208
  if table_or_sql:
@@ -225,9 +219,9 @@ class SQLFactory:
225
219
  return builder.into(table_or_sql)
226
220
  return builder
227
221
 
228
- def update(self, table_or_sql: Optional[str] = None, dialect: DialectType = None) -> "UpdateBuilder":
222
+ def update(self, table_or_sql: Optional[str] = None, dialect: DialectType = None) -> "Update":
229
223
  builder_dialect = dialect or self.dialect
230
- builder = UpdateBuilder(dialect=builder_dialect)
224
+ builder = Update(dialect=builder_dialect)
231
225
  if builder._expression is None:
232
226
  builder.__post_init__()
233
227
  if table_or_sql:
@@ -240,9 +234,9 @@ class SQLFactory:
240
234
  return builder.table(table_or_sql)
241
235
  return builder
242
236
 
243
- def delete(self, table_or_sql: Optional[str] = None, dialect: DialectType = None) -> "DeleteBuilder":
237
+ def delete(self, table_or_sql: Optional[str] = None, dialect: DialectType = None) -> "Delete":
244
238
  builder_dialect = dialect or self.dialect
245
- builder = DeleteBuilder(dialect=builder_dialect)
239
+ builder = Delete(dialect=builder_dialect)
246
240
  if builder._expression is None:
247
241
  builder.__post_init__()
248
242
  if table_or_sql and self._looks_like_sql(table_or_sql):
@@ -253,9 +247,9 @@ class SQLFactory:
253
247
  return self._populate_delete_from_sql(builder, table_or_sql)
254
248
  return builder
255
249
 
256
- def merge(self, table_or_sql: Optional[str] = None, dialect: DialectType = None) -> "MergeBuilder":
250
+ def merge(self, table_or_sql: Optional[str] = None, dialect: DialectType = None) -> "Merge":
257
251
  builder_dialect = dialect or self.dialect
258
- builder = MergeBuilder(dialect=builder_dialect)
252
+ builder = Merge(dialect=builder_dialect)
259
253
  if builder._expression is None:
260
254
  builder.__post_init__()
261
255
  if table_or_sql:
@@ -288,7 +282,6 @@ class SQLFactory:
288
282
 
289
283
  candidate_upper = candidate.strip().upper()
290
284
 
291
- # Check for SQL keywords at the beginning
292
285
  if expected_type:
293
286
  return candidate_upper.startswith(expected_type.upper())
294
287
 
@@ -302,7 +295,7 @@ class SQLFactory:
302
295
 
303
296
  return False
304
297
 
305
- def _populate_insert_from_sql(self, builder: "InsertBuilder", sql_string: str) -> "InsertBuilder":
298
+ def _populate_insert_from_sql(self, builder: "Insert", sql_string: str) -> "Insert":
306
299
  """Parse SQL string and populate INSERT builder using SQLGlot directly."""
307
300
  try:
308
301
  # Use SQLGlot directly for parsing - no validation here
@@ -311,12 +304,10 @@ class SQLFactory:
311
304
  parsed_expr = sqlglot.parse_one(sql_string, read=self.dialect)
312
305
 
313
306
  if isinstance(parsed_expr, exp.Insert):
314
- # Set the internal expression to the parsed one
315
307
  builder._expression = parsed_expr
316
308
  return builder
317
309
 
318
310
  if isinstance(parsed_expr, exp.Select):
319
- # Handle INSERT FROM SELECT case - just return builder for now
320
311
  # The actual conversion logic can be handled by the builder itself
321
312
  logger.info("Detected SELECT statement for INSERT - may need target table specification")
322
313
  return builder
@@ -328,7 +319,7 @@ class SQLFactory:
328
319
  logger.warning("Failed to parse INSERT SQL, falling back to traditional mode: %s", e)
329
320
  return builder
330
321
 
331
- def _populate_select_from_sql(self, builder: "SelectBuilder", sql_string: str) -> "SelectBuilder":
322
+ def _populate_select_from_sql(self, builder: "Select", sql_string: str) -> "Select":
332
323
  """Parse SQL string and populate SELECT builder using SQLGlot directly."""
333
324
  try:
334
325
  # Use SQLGlot directly for parsing - no validation here
@@ -337,7 +328,6 @@ class SQLFactory:
337
328
  parsed_expr = sqlglot.parse_one(sql_string, read=self.dialect)
338
329
 
339
330
  if isinstance(parsed_expr, exp.Select):
340
- # Set the internal expression to the parsed one
341
331
  builder._expression = parsed_expr
342
332
  return builder
343
333
 
@@ -347,7 +337,7 @@ class SQLFactory:
347
337
  logger.warning("Failed to parse SELECT SQL, falling back to traditional mode: %s", e)
348
338
  return builder
349
339
 
350
- def _populate_update_from_sql(self, builder: "UpdateBuilder", sql_string: str) -> "UpdateBuilder":
340
+ def _populate_update_from_sql(self, builder: "Update", sql_string: str) -> "Update":
351
341
  """Parse SQL string and populate UPDATE builder using SQLGlot directly."""
352
342
  try:
353
343
  # Use SQLGlot directly for parsing - no validation here
@@ -356,7 +346,6 @@ class SQLFactory:
356
346
  parsed_expr = sqlglot.parse_one(sql_string, read=self.dialect)
357
347
 
358
348
  if isinstance(parsed_expr, exp.Update):
359
- # Set the internal expression to the parsed one
360
349
  builder._expression = parsed_expr
361
350
  return builder
362
351
 
@@ -366,7 +355,7 @@ class SQLFactory:
366
355
  logger.warning("Failed to parse UPDATE SQL, falling back to traditional mode: %s", e)
367
356
  return builder
368
357
 
369
- def _populate_delete_from_sql(self, builder: "DeleteBuilder", sql_string: str) -> "DeleteBuilder":
358
+ def _populate_delete_from_sql(self, builder: "Delete", sql_string: str) -> "Delete":
370
359
  """Parse SQL string and populate DELETE builder using SQLGlot directly."""
371
360
  try:
372
361
  # Use SQLGlot directly for parsing - no validation here
@@ -375,7 +364,6 @@ class SQLFactory:
375
364
  parsed_expr = sqlglot.parse_one(sql_string, read=self.dialect)
376
365
 
377
366
  if isinstance(parsed_expr, exp.Delete):
378
- # Set the internal expression to the parsed one
379
367
  builder._expression = parsed_expr
380
368
  return builder
381
369
 
@@ -385,7 +373,7 @@ class SQLFactory:
385
373
  logger.warning("Failed to parse DELETE SQL, falling back to traditional mode: %s", e)
386
374
  return builder
387
375
 
388
- def _populate_merge_from_sql(self, builder: "MergeBuilder", sql_string: str) -> "MergeBuilder":
376
+ def _populate_merge_from_sql(self, builder: "Merge", sql_string: str) -> "Merge":
389
377
  """Parse SQL string and populate MERGE builder using SQLGlot directly."""
390
378
  try:
391
379
  # Use SQLGlot directly for parsing - no validation here
@@ -394,7 +382,6 @@ class SQLFactory:
394
382
  parsed_expr = sqlglot.parse_one(sql_string, read=self.dialect)
395
383
 
396
384
  if isinstance(parsed_expr, exp.Merge):
397
- # Set the internal expression to the parsed one
398
385
  builder._expression = parsed_expr
399
386
  return builder
400
387
 
@@ -408,16 +395,16 @@ class SQLFactory:
408
395
  # Column References
409
396
  # ===================
410
397
 
411
- def __getattr__(self, name: str) -> exp.Column:
398
+ def __getattr__(self, name: str) -> Column:
412
399
  """Dynamically create column references.
413
400
 
414
401
  Args:
415
402
  name: Column name.
416
403
 
417
404
  Returns:
418
- Column expression for the specified column name.
405
+ Column object that supports method chaining and operator overloading.
419
406
  """
420
- return exp.column(name)
407
+ return Column(name)
421
408
 
422
409
  # ===================
423
410
  # Aggregate Functions
@@ -579,7 +566,6 @@ class SQLFactory:
579
566
  for column_set in column_sets:
580
567
  if isinstance(column_set, (tuple, list)):
581
568
  if len(column_set) == 0:
582
- # Empty set for grand total
583
569
  set_expressions.append(exp.Tuple(expressions=[]))
584
570
  else:
585
571
  columns = [exp.column(col) for col in column_set]
@@ -611,7 +597,6 @@ class SQLFactory:
611
597
  ```
612
598
  """
613
599
  if isinstance(values, list):
614
- # Convert list to array literal
615
600
  literals = [exp.Literal.string(str(v)) if isinstance(v, str) else exp.Literal.number(v) for v in values]
616
601
  return exp.Any(this=exp.Array(expressions=literals))
617
602
  if isinstance(values, str):
@@ -734,11 +719,9 @@ class SQLFactory:
734
719
  msg = "DECODE requires at least one search/result pair"
735
720
  raise ValueError(msg)
736
721
 
737
- # Build CASE expression
738
722
  conditions = []
739
723
  default = None
740
724
 
741
- # Process search/result pairs
742
725
  for i in range(0, len(args) - 1, 2):
743
726
  if i + 1 >= len(args):
744
727
  # Odd number of args means last one is default
@@ -748,7 +731,6 @@ class SQLFactory:
748
731
  search_val = args[i]
749
732
  result_val = args[i + 1]
750
733
 
751
- # Create search expression
752
734
  if isinstance(search_val, str):
753
735
  search_expr = exp.Literal.string(search_val)
754
736
  elif isinstance(search_val, (int, float)):
@@ -758,7 +740,6 @@ class SQLFactory:
758
740
  else:
759
741
  search_expr = exp.Literal.string(str(search_val))
760
742
 
761
- # Create result expression
762
743
  if isinstance(result_val, str):
763
744
  result_expr = exp.Literal.string(result_val)
764
745
  elif isinstance(result_val, (int, float)):
@@ -768,80 +749,11 @@ class SQLFactory:
768
749
  else:
769
750
  result_expr = exp.Literal.string(str(result_val))
770
751
 
771
- # Create WHEN condition
772
752
  condition = exp.EQ(this=col_expr, expression=search_expr)
773
753
  conditions.append(exp.When(this=condition, then=result_expr))
774
754
 
775
755
  return exp.Case(ifs=conditions, default=default)
776
756
 
777
- @staticmethod
778
- def to_date(date_string: Union[str, exp.Expression], format_mask: Optional[str] = None) -> exp.Expression:
779
- """Create a TO_DATE expression for converting strings to dates.
780
-
781
- Args:
782
- date_string: String or expression containing the date to convert.
783
- format_mask: Optional format mask (e.g., 'YYYY-MM-DD', 'DD/MM/YYYY').
784
-
785
- Returns:
786
- TO_DATE function expression.
787
- """
788
- date_expr = exp.column(date_string) if isinstance(date_string, str) else date_string
789
-
790
- if format_mask:
791
- format_expr = exp.Literal.string(format_mask)
792
- return exp.Anonymous(this="TO_DATE", expressions=[date_expr, format_expr])
793
- return exp.Anonymous(this="TO_DATE", expressions=[date_expr])
794
-
795
- @staticmethod
796
- def to_char(column: Union[str, exp.Expression], format_mask: Optional[str] = None) -> exp.Expression:
797
- """Create a TO_CHAR expression for converting values to strings.
798
-
799
- Args:
800
- column: Column or expression to convert to string.
801
- format_mask: Optional format mask for dates/numbers.
802
-
803
- Returns:
804
- TO_CHAR function expression.
805
- """
806
- col_expr = exp.column(column) if isinstance(column, str) else column
807
-
808
- if format_mask:
809
- format_expr = exp.Literal.string(format_mask)
810
- return exp.Anonymous(this="TO_CHAR", expressions=[col_expr, format_expr])
811
- return exp.Anonymous(this="TO_CHAR", expressions=[col_expr])
812
-
813
- @staticmethod
814
- def to_string(column: Union[str, exp.Expression]) -> exp.Expression:
815
- """Create a TO_STRING expression for converting values to strings.
816
-
817
- Args:
818
- column: Column or expression to convert to string.
819
-
820
- Returns:
821
- TO_STRING or CAST AS STRING expression.
822
- """
823
- col_expr = exp.column(column) if isinstance(column, str) else column
824
- # Use CAST for broader compatibility
825
- return exp.Cast(this=col_expr, to=exp.DataType.build("STRING"))
826
-
827
- @staticmethod
828
- def to_number(column: Union[str, exp.Expression], format_mask: Optional[str] = None) -> exp.Expression:
829
- """Create a TO_NUMBER expression for converting strings to numbers.
830
-
831
- Args:
832
- column: Column or expression to convert to number.
833
- format_mask: Optional format mask for the conversion.
834
-
835
- Returns:
836
- TO_NUMBER function expression.
837
- """
838
- col_expr = exp.column(column) if isinstance(column, str) else column
839
-
840
- if format_mask:
841
- format_expr = exp.Literal.string(format_mask)
842
- return exp.Anonymous(this="TO_NUMBER", expressions=[col_expr, format_expr])
843
- return exp.Anonymous(this="TO_NUMBER", expressions=[col_expr])
844
-
845
757
  @staticmethod
846
758
  def cast(column: Union[str, exp.Expression], data_type: str) -> exp.Expression:
847
759
  """Create a CAST expression for type conversion.
@@ -856,75 +768,6 @@ class SQLFactory:
856
768
  col_expr = exp.column(column) if isinstance(column, str) else column
857
769
  return exp.Cast(this=col_expr, to=exp.DataType.build(data_type))
858
770
 
859
- # ===================
860
- # JSON Functions
861
- # ===================
862
-
863
- @staticmethod
864
- def to_json(column: Union[str, exp.Expression]) -> exp.Expression:
865
- """Create a TO_JSON expression for converting values to JSON.
866
-
867
- Args:
868
- column: Column or expression to convert to JSON.
869
-
870
- Returns:
871
- TO_JSON function expression.
872
- """
873
- col_expr = exp.column(column) if isinstance(column, str) else column
874
- return exp.Anonymous(this="TO_JSON", expressions=[col_expr])
875
-
876
- @staticmethod
877
- def from_json(json_column: Union[str, exp.Expression], schema: Optional[str] = None) -> exp.Expression:
878
- """Create a FROM_JSON expression for parsing JSON strings.
879
-
880
- Args:
881
- json_column: Column or expression containing JSON string.
882
- schema: Optional schema specification for the JSON structure.
883
-
884
- Returns:
885
- FROM_JSON function expression.
886
- """
887
- json_expr = exp.column(json_column) if isinstance(json_column, str) else json_column
888
-
889
- if schema:
890
- schema_expr = exp.Literal.string(schema)
891
- return exp.Anonymous(this="FROM_JSON", expressions=[json_expr, schema_expr])
892
- return exp.Anonymous(this="FROM_JSON", expressions=[json_expr])
893
-
894
- @staticmethod
895
- def json_extract(json_column: Union[str, exp.Expression], path: str) -> exp.Expression:
896
- """Create a JSON_EXTRACT expression for extracting values from JSON.
897
-
898
- Args:
899
- json_column: Column or expression containing JSON.
900
- path: JSON path to extract (e.g., '$.field', '$.array[0]').
901
-
902
- Returns:
903
- JSON_EXTRACT function expression.
904
- """
905
- json_expr = exp.column(json_column) if isinstance(json_column, str) else json_column
906
- path_expr = exp.Literal.string(path)
907
- return exp.Anonymous(this="JSON_EXTRACT", expressions=[json_expr, path_expr])
908
-
909
- @staticmethod
910
- def json_value(json_column: Union[str, exp.Expression], path: str) -> exp.Expression:
911
- """Create a JSON_VALUE expression for extracting scalar values from JSON.
912
-
913
- Args:
914
- json_column: Column or expression containing JSON.
915
- path: JSON path to extract scalar value.
916
-
917
- Returns:
918
- JSON_VALUE function expression.
919
- """
920
- json_expr = exp.column(json_column) if isinstance(json_column, str) else json_column
921
- path_expr = exp.Literal.string(path)
922
- return exp.Anonymous(this="JSON_VALUE", expressions=[json_expr, path_expr])
923
-
924
- # ===================
925
- # NULL Functions
926
- # ===================
927
-
928
771
  @staticmethod
929
772
  def coalesce(*expressions: Union[str, exp.Expression]) -> exp.Expression:
930
773
  """Create a COALESCE expression.
@@ -1045,10 +888,8 @@ class SQLFactory:
1045
888
  Returns:
1046
889
  Window function expression.
1047
890
  """
1048
- # Create the function call
1049
891
  func_expr = exp.Anonymous(this=func_name, expressions=func_args)
1050
892
 
1051
- # Build OVER clause
1052
893
  over_args: dict[str, Any] = {}
1053
894
 
1054
895
  if partition_by:
@@ -270,7 +270,6 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
270
270
 
271
271
  # If explicit driver path is provided, normalize it
272
272
  if isinstance(driver_name, str):
273
- # Handle convenience aliases
274
273
  driver_aliases = {
275
274
  "sqlite": "adbc_driver_sqlite.dbapi.connect",
276
275
  "sqlite3": "adbc_driver_sqlite.dbapi.connect",
@@ -294,7 +293,6 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
294
293
 
295
294
  resolved_driver = driver_aliases.get(driver_name, driver_name)
296
295
 
297
- # Ensure it ends with .dbapi.connect
298
296
  if not resolved_driver.endswith(".dbapi.connect"):
299
297
  resolved_driver = f"{resolved_driver}.dbapi.connect"
300
298
 
@@ -458,17 +456,19 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
458
456
  @contextmanager
459
457
  def session_manager() -> "Generator[AdbcDriver, None, None]":
460
458
  with self.provide_connection(*args, **kwargs) as connection:
461
- # Get parameter styles based on the actual driver
462
459
  supported_styles, preferred_style = self._get_parameter_styles()
463
460
 
464
- # Create statement config with parameter style info if not already set
465
461
  statement_config = self.statement_config
466
- if statement_config.allowed_parameter_styles is None:
467
- statement_config = replace(
468
- statement_config,
469
- allowed_parameter_styles=supported_styles,
470
- target_parameter_style=preferred_style,
471
- )
462
+ if statement_config is not None:
463
+ if statement_config.dialect is None:
464
+ statement_config = replace(statement_config, dialect=self._get_dialect())
465
+
466
+ if statement_config.allowed_parameter_styles is None:
467
+ statement_config = replace(
468
+ statement_config,
469
+ allowed_parameter_styles=supported_styles,
470
+ target_parameter_style=preferred_style,
471
+ )
472
472
 
473
473
  driver = self.driver_type(connection=connection, config=statement_config)
474
474
  yield driver
@@ -492,7 +492,6 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
492
492
  # Merge extras parameters
493
493
  config.update(self.extras)
494
494
 
495
- # Process URI based on driver type
496
495
  if "driver_name" in config:
497
496
  driver_name = config["driver_name"]
498
497
 
@@ -526,7 +525,6 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
526
525
  if isinstance(db_kwargs, dict):
527
526
  config.update(db_kwargs)
528
527
 
529
- # Remove driver_name from config as it's not a connection parameter
530
528
  config.pop("driver_name", None)
531
529
 
532
530
  return config