sqlspec 0.17.0__py3-none-any.whl → 0.18.0__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.
- sqlspec/__init__.py +1 -1
- sqlspec/_sql.py +188 -234
- sqlspec/adapters/adbc/config.py +24 -30
- sqlspec/adapters/adbc/driver.py +42 -61
- sqlspec/adapters/aiosqlite/config.py +5 -10
- sqlspec/adapters/aiosqlite/driver.py +9 -25
- sqlspec/adapters/aiosqlite/pool.py +43 -35
- sqlspec/adapters/asyncmy/config.py +10 -7
- sqlspec/adapters/asyncmy/driver.py +18 -39
- sqlspec/adapters/asyncpg/config.py +4 -0
- sqlspec/adapters/asyncpg/driver.py +32 -79
- sqlspec/adapters/bigquery/config.py +12 -65
- sqlspec/adapters/bigquery/driver.py +39 -133
- sqlspec/adapters/duckdb/config.py +11 -15
- sqlspec/adapters/duckdb/driver.py +61 -85
- sqlspec/adapters/duckdb/pool.py +2 -5
- sqlspec/adapters/oracledb/_types.py +8 -1
- sqlspec/adapters/oracledb/config.py +55 -38
- sqlspec/adapters/oracledb/driver.py +35 -92
- sqlspec/adapters/oracledb/migrations.py +257 -0
- sqlspec/adapters/psqlpy/config.py +13 -9
- sqlspec/adapters/psqlpy/driver.py +28 -103
- sqlspec/adapters/psycopg/config.py +9 -5
- sqlspec/adapters/psycopg/driver.py +107 -175
- sqlspec/adapters/sqlite/config.py +7 -5
- sqlspec/adapters/sqlite/driver.py +37 -73
- sqlspec/adapters/sqlite/pool.py +3 -12
- sqlspec/base.py +1 -8
- sqlspec/builder/__init__.py +1 -1
- sqlspec/builder/_base.py +34 -20
- sqlspec/builder/_column.py +5 -1
- sqlspec/builder/_ddl.py +407 -183
- sqlspec/builder/_expression_wrappers.py +46 -0
- sqlspec/builder/_insert.py +2 -4
- sqlspec/builder/_update.py +5 -5
- sqlspec/builder/mixins/_insert_operations.py +26 -6
- sqlspec/builder/mixins/_merge_operations.py +1 -1
- sqlspec/builder/mixins/_order_limit_operations.py +16 -4
- sqlspec/builder/mixins/_select_operations.py +3 -7
- sqlspec/builder/mixins/_update_operations.py +4 -4
- sqlspec/config.py +32 -13
- sqlspec/core/__init__.py +89 -14
- sqlspec/core/cache.py +57 -104
- sqlspec/core/compiler.py +57 -112
- sqlspec/core/filters.py +1 -21
- sqlspec/core/hashing.py +13 -47
- sqlspec/core/parameters.py +272 -261
- sqlspec/core/result.py +12 -27
- sqlspec/core/splitter.py +17 -21
- sqlspec/core/statement.py +150 -159
- sqlspec/driver/_async.py +2 -15
- sqlspec/driver/_common.py +16 -95
- sqlspec/driver/_sync.py +2 -15
- sqlspec/driver/mixins/_result_tools.py +8 -29
- sqlspec/driver/mixins/_sql_translator.py +6 -8
- sqlspec/exceptions.py +1 -2
- sqlspec/loader.py +43 -115
- sqlspec/migrations/__init__.py +1 -1
- sqlspec/migrations/base.py +34 -45
- sqlspec/migrations/commands.py +34 -15
- sqlspec/migrations/loaders.py +1 -1
- sqlspec/migrations/runner.py +104 -19
- sqlspec/migrations/tracker.py +49 -2
- sqlspec/protocols.py +13 -6
- sqlspec/storage/__init__.py +4 -4
- sqlspec/storage/backends/fsspec.py +5 -6
- sqlspec/storage/backends/obstore.py +7 -8
- sqlspec/storage/registry.py +3 -3
- sqlspec/utils/__init__.py +2 -2
- sqlspec/utils/logging.py +6 -10
- sqlspec/utils/sync_tools.py +27 -4
- sqlspec/utils/text.py +6 -1
- {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/METADATA +1 -1
- sqlspec-0.18.0.dist-info/RECORD +138 -0
- sqlspec/builder/_ddl_utils.py +0 -103
- sqlspec-0.17.0.dist-info/RECORD +0 -137
- {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/_sql.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""SQL factory for creating SQL builders and column expressions.
|
|
2
2
|
|
|
3
|
-
Provides
|
|
3
|
+
Provides statement builders (select, insert, update, etc.) and column expressions.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import logging
|
|
@@ -33,12 +33,21 @@ from sqlspec.builder import (
|
|
|
33
33
|
Truncate,
|
|
34
34
|
Update,
|
|
35
35
|
)
|
|
36
|
+
from sqlspec.builder._expression_wrappers import (
|
|
37
|
+
AggregateExpression,
|
|
38
|
+
ConversionExpression,
|
|
39
|
+
FunctionExpression,
|
|
40
|
+
MathExpression,
|
|
41
|
+
StringExpression,
|
|
42
|
+
)
|
|
36
43
|
from sqlspec.builder.mixins._join_operations import JoinBuilder
|
|
37
44
|
from sqlspec.builder.mixins._select_operations import Case, SubqueryBuilder, WindowFunctionBuilder
|
|
45
|
+
from sqlspec.core.statement import SQL
|
|
38
46
|
from sqlspec.exceptions import SQLBuilderError
|
|
39
47
|
|
|
40
48
|
if TYPE_CHECKING:
|
|
41
|
-
from sqlspec.
|
|
49
|
+
from sqlspec.builder._expression_wrappers import ExpressionWrapper
|
|
50
|
+
|
|
42
51
|
|
|
43
52
|
__all__ = (
|
|
44
53
|
"AlterTable",
|
|
@@ -100,7 +109,7 @@ SQL_STARTERS = {
|
|
|
100
109
|
|
|
101
110
|
|
|
102
111
|
class SQLFactory:
|
|
103
|
-
"""
|
|
112
|
+
"""Factory for creating SQL builders and column expressions."""
|
|
104
113
|
|
|
105
114
|
@classmethod
|
|
106
115
|
def detect_sql_type(cls, sql: str, dialect: DialectType = None) -> str:
|
|
@@ -127,9 +136,6 @@ class SQLFactory:
|
|
|
127
136
|
"""
|
|
128
137
|
self.dialect = dialect
|
|
129
138
|
|
|
130
|
-
# ===================
|
|
131
|
-
# Callable Interface
|
|
132
|
-
# ===================
|
|
133
139
|
def __call__(self, statement: str, dialect: DialectType = None) -> "Any":
|
|
134
140
|
"""Create a SelectBuilder from a SQL string, only allowing SELECT/CTE queries.
|
|
135
141
|
|
|
@@ -175,9 +181,6 @@ class SQLFactory:
|
|
|
175
181
|
)
|
|
176
182
|
raise SQLBuilderError(msg)
|
|
177
183
|
|
|
178
|
-
# ===================
|
|
179
|
-
# Statement Builders
|
|
180
|
-
# ===================
|
|
181
184
|
def select(
|
|
182
185
|
self, *columns_or_sql: Union[str, exp.Expression, Column, "SQL", "Case"], dialect: DialectType = None
|
|
183
186
|
) -> "Select":
|
|
@@ -262,10 +265,6 @@ class SQLFactory:
|
|
|
262
265
|
return builder.into(table_or_sql)
|
|
263
266
|
return builder
|
|
264
267
|
|
|
265
|
-
# ===================
|
|
266
|
-
# DDL Statement Builders
|
|
267
|
-
# ===================
|
|
268
|
-
|
|
269
268
|
def create_table(self, table_name: str, dialect: DialectType = None) -> "CreateTable":
|
|
270
269
|
"""Create a CREATE TABLE builder.
|
|
271
270
|
|
|
@@ -276,9 +275,7 @@ class SQLFactory:
|
|
|
276
275
|
Returns:
|
|
277
276
|
CreateTable builder instance
|
|
278
277
|
"""
|
|
279
|
-
|
|
280
|
-
builder.dialect = dialect or self.dialect
|
|
281
|
-
return builder
|
|
278
|
+
return CreateTable(table_name, dialect=dialect or self.dialect)
|
|
282
279
|
|
|
283
280
|
def create_table_as_select(self, dialect: DialectType = None) -> "CreateTableAsSelect":
|
|
284
281
|
"""Create a CREATE TABLE AS SELECT builder.
|
|
@@ -289,35 +286,31 @@ class SQLFactory:
|
|
|
289
286
|
Returns:
|
|
290
287
|
CreateTableAsSelect builder instance
|
|
291
288
|
"""
|
|
292
|
-
|
|
293
|
-
builder.dialect = dialect or self.dialect
|
|
294
|
-
return builder
|
|
289
|
+
return CreateTableAsSelect(dialect=dialect or self.dialect)
|
|
295
290
|
|
|
296
|
-
def create_view(self, dialect: DialectType = None) -> "CreateView":
|
|
291
|
+
def create_view(self, view_name: str, dialect: DialectType = None) -> "CreateView":
|
|
297
292
|
"""Create a CREATE VIEW builder.
|
|
298
293
|
|
|
299
294
|
Args:
|
|
295
|
+
view_name: Name of the view to create
|
|
300
296
|
dialect: Optional SQL dialect
|
|
301
297
|
|
|
302
298
|
Returns:
|
|
303
299
|
CreateView builder instance
|
|
304
300
|
"""
|
|
305
|
-
|
|
306
|
-
builder.dialect = dialect or self.dialect
|
|
307
|
-
return builder
|
|
301
|
+
return CreateView(view_name, dialect=dialect or self.dialect)
|
|
308
302
|
|
|
309
|
-
def create_materialized_view(self, dialect: DialectType = None) -> "CreateMaterializedView":
|
|
303
|
+
def create_materialized_view(self, view_name: str, dialect: DialectType = None) -> "CreateMaterializedView":
|
|
310
304
|
"""Create a CREATE MATERIALIZED VIEW builder.
|
|
311
305
|
|
|
312
306
|
Args:
|
|
307
|
+
view_name: Name of the materialized view to create
|
|
313
308
|
dialect: Optional SQL dialect
|
|
314
309
|
|
|
315
310
|
Returns:
|
|
316
311
|
CreateMaterializedView builder instance
|
|
317
312
|
"""
|
|
318
|
-
|
|
319
|
-
builder.dialect = dialect or self.dialect
|
|
320
|
-
return builder
|
|
313
|
+
return CreateMaterializedView(view_name, dialect=dialect or self.dialect)
|
|
321
314
|
|
|
322
315
|
def create_index(self, index_name: str, dialect: DialectType = None) -> "CreateIndex":
|
|
323
316
|
"""Create a CREATE INDEX builder.
|
|
@@ -331,18 +324,17 @@ class SQLFactory:
|
|
|
331
324
|
"""
|
|
332
325
|
return CreateIndex(index_name, dialect=dialect or self.dialect)
|
|
333
326
|
|
|
334
|
-
def create_schema(self, dialect: DialectType = None) -> "CreateSchema":
|
|
327
|
+
def create_schema(self, schema_name: str, dialect: DialectType = None) -> "CreateSchema":
|
|
335
328
|
"""Create a CREATE SCHEMA builder.
|
|
336
329
|
|
|
337
330
|
Args:
|
|
331
|
+
schema_name: Name of the schema to create
|
|
338
332
|
dialect: Optional SQL dialect
|
|
339
333
|
|
|
340
334
|
Returns:
|
|
341
335
|
CreateSchema builder instance
|
|
342
336
|
"""
|
|
343
|
-
|
|
344
|
-
builder.dialect = dialect or self.dialect
|
|
345
|
-
return builder
|
|
337
|
+
return CreateSchema(schema_name, dialect=dialect or self.dialect)
|
|
346
338
|
|
|
347
339
|
def drop_table(self, table_name: str, dialect: DialectType = None) -> "DropTable":
|
|
348
340
|
"""Create a DROP TABLE builder.
|
|
@@ -356,16 +348,17 @@ class SQLFactory:
|
|
|
356
348
|
"""
|
|
357
349
|
return DropTable(table_name, dialect=dialect or self.dialect)
|
|
358
350
|
|
|
359
|
-
def drop_view(self, dialect: DialectType = None) -> "DropView":
|
|
351
|
+
def drop_view(self, view_name: str, dialect: DialectType = None) -> "DropView":
|
|
360
352
|
"""Create a DROP VIEW builder.
|
|
361
353
|
|
|
362
354
|
Args:
|
|
355
|
+
view_name: Name of the view to drop
|
|
363
356
|
dialect: Optional SQL dialect
|
|
364
357
|
|
|
365
358
|
Returns:
|
|
366
359
|
DropView builder instance
|
|
367
360
|
"""
|
|
368
|
-
return DropView(dialect=dialect or self.dialect)
|
|
361
|
+
return DropView(view_name, dialect=dialect or self.dialect)
|
|
369
362
|
|
|
370
363
|
def drop_index(self, index_name: str, dialect: DialectType = None) -> "DropIndex":
|
|
371
364
|
"""Create a DROP INDEX builder.
|
|
@@ -379,16 +372,17 @@ class SQLFactory:
|
|
|
379
372
|
"""
|
|
380
373
|
return DropIndex(index_name, dialect=dialect or self.dialect)
|
|
381
374
|
|
|
382
|
-
def drop_schema(self, dialect: DialectType = None) -> "DropSchema":
|
|
375
|
+
def drop_schema(self, schema_name: str, dialect: DialectType = None) -> "DropSchema":
|
|
383
376
|
"""Create a DROP SCHEMA builder.
|
|
384
377
|
|
|
385
378
|
Args:
|
|
379
|
+
schema_name: Name of the schema to drop
|
|
386
380
|
dialect: Optional SQL dialect
|
|
387
381
|
|
|
388
382
|
Returns:
|
|
389
383
|
DropSchema builder instance
|
|
390
384
|
"""
|
|
391
|
-
return DropSchema(dialect=dialect or self.dialect)
|
|
385
|
+
return DropSchema(schema_name, dialect=dialect or self.dialect)
|
|
392
386
|
|
|
393
387
|
def alter_table(self, table_name: str, dialect: DialectType = None) -> "AlterTable":
|
|
394
388
|
"""Create an ALTER TABLE builder.
|
|
@@ -400,22 +394,19 @@ class SQLFactory:
|
|
|
400
394
|
Returns:
|
|
401
395
|
AlterTable builder instance
|
|
402
396
|
"""
|
|
403
|
-
|
|
404
|
-
builder.dialect = dialect or self.dialect
|
|
405
|
-
return builder
|
|
397
|
+
return AlterTable(table_name, dialect=dialect or self.dialect)
|
|
406
398
|
|
|
407
|
-
def rename_table(self, dialect: DialectType = None) -> "RenameTable":
|
|
399
|
+
def rename_table(self, old_name: str, dialect: DialectType = None) -> "RenameTable":
|
|
408
400
|
"""Create a RENAME TABLE builder.
|
|
409
401
|
|
|
410
402
|
Args:
|
|
403
|
+
old_name: Current name of the table
|
|
411
404
|
dialect: Optional SQL dialect
|
|
412
405
|
|
|
413
406
|
Returns:
|
|
414
407
|
RenameTable builder instance
|
|
415
408
|
"""
|
|
416
|
-
|
|
417
|
-
builder.dialect = dialect or self.dialect
|
|
418
|
-
return builder
|
|
409
|
+
return RenameTable(old_name, dialect=dialect or self.dialect)
|
|
419
410
|
|
|
420
411
|
def comment_on(self, dialect: DialectType = None) -> "CommentOn":
|
|
421
412
|
"""Create a COMMENT ON builder.
|
|
@@ -426,17 +417,11 @@ class SQLFactory:
|
|
|
426
417
|
Returns:
|
|
427
418
|
CommentOn builder instance
|
|
428
419
|
"""
|
|
429
|
-
|
|
430
|
-
builder.dialect = dialect or self.dialect
|
|
431
|
-
return builder
|
|
432
|
-
|
|
433
|
-
# ===================
|
|
434
|
-
# SQL Analysis Helpers
|
|
435
|
-
# ===================
|
|
420
|
+
return CommentOn(dialect=dialect or self.dialect)
|
|
436
421
|
|
|
437
422
|
@staticmethod
|
|
438
423
|
def _looks_like_sql(candidate: str, expected_type: Optional[str] = None) -> bool:
|
|
439
|
-
"""
|
|
424
|
+
"""Determine if a string looks like SQL.
|
|
440
425
|
|
|
441
426
|
Args:
|
|
442
427
|
candidate: String to check
|
|
@@ -453,12 +438,7 @@ class SQLFactory:
|
|
|
453
438
|
if expected_type:
|
|
454
439
|
return candidate_upper.startswith(expected_type.upper())
|
|
455
440
|
|
|
456
|
-
# More sophisticated check for SQL vs column names
|
|
457
|
-
# Column names that start with SQL keywords are common (user_id, insert_date, etc.)
|
|
458
441
|
if any(candidate_upper.startswith(starter) for starter in SQL_STARTERS):
|
|
459
|
-
# Additional checks to distinguish real SQL from column names:
|
|
460
|
-
# 1. Real SQL typically has spaces (SELECT ... FROM, INSERT INTO, etc.)
|
|
461
|
-
# 2. Check for common SQL syntax patterns
|
|
462
442
|
return " " in candidate
|
|
463
443
|
|
|
464
444
|
return False
|
|
@@ -466,19 +446,16 @@ class SQLFactory:
|
|
|
466
446
|
def _populate_insert_from_sql(self, builder: "Insert", sql_string: str) -> "Insert":
|
|
467
447
|
"""Parse SQL string and populate INSERT builder using SQLGlot directly."""
|
|
468
448
|
try:
|
|
469
|
-
|
|
470
|
-
parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect) # type: ignore[var-annotated]
|
|
449
|
+
parsed_expr: exp.Expression = exp.maybe_parse(sql_string, dialect=self.dialect)
|
|
471
450
|
|
|
472
451
|
if isinstance(parsed_expr, exp.Insert):
|
|
473
452
|
builder._expression = parsed_expr
|
|
474
453
|
return builder
|
|
475
454
|
|
|
476
455
|
if isinstance(parsed_expr, exp.Select):
|
|
477
|
-
# The actual conversion logic can be handled by the builder itself
|
|
478
456
|
logger.info("Detected SELECT statement for INSERT - may need target table specification")
|
|
479
457
|
return builder
|
|
480
458
|
|
|
481
|
-
# For other statement types, just return the builder as-is
|
|
482
459
|
logger.warning("Cannot create INSERT from %s statement", type(parsed_expr).__name__)
|
|
483
460
|
|
|
484
461
|
except Exception as e:
|
|
@@ -488,8 +465,7 @@ class SQLFactory:
|
|
|
488
465
|
def _populate_select_from_sql(self, builder: "Select", sql_string: str) -> "Select":
|
|
489
466
|
"""Parse SQL string and populate SELECT builder using SQLGlot directly."""
|
|
490
467
|
try:
|
|
491
|
-
|
|
492
|
-
parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect) # type: ignore[var-annotated]
|
|
468
|
+
parsed_expr: exp.Expression = exp.maybe_parse(sql_string, dialect=self.dialect)
|
|
493
469
|
|
|
494
470
|
if isinstance(parsed_expr, exp.Select):
|
|
495
471
|
builder._expression = parsed_expr
|
|
@@ -504,8 +480,7 @@ class SQLFactory:
|
|
|
504
480
|
def _populate_update_from_sql(self, builder: "Update", sql_string: str) -> "Update":
|
|
505
481
|
"""Parse SQL string and populate UPDATE builder using SQLGlot directly."""
|
|
506
482
|
try:
|
|
507
|
-
|
|
508
|
-
parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect) # type: ignore[var-annotated]
|
|
483
|
+
parsed_expr: exp.Expression = exp.maybe_parse(sql_string, dialect=self.dialect)
|
|
509
484
|
|
|
510
485
|
if isinstance(parsed_expr, exp.Update):
|
|
511
486
|
builder._expression = parsed_expr
|
|
@@ -520,8 +495,7 @@ class SQLFactory:
|
|
|
520
495
|
def _populate_delete_from_sql(self, builder: "Delete", sql_string: str) -> "Delete":
|
|
521
496
|
"""Parse SQL string and populate DELETE builder using SQLGlot directly."""
|
|
522
497
|
try:
|
|
523
|
-
|
|
524
|
-
parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect) # type: ignore[var-annotated]
|
|
498
|
+
parsed_expr: exp.Expression = exp.maybe_parse(sql_string, dialect=self.dialect)
|
|
525
499
|
|
|
526
500
|
if isinstance(parsed_expr, exp.Delete):
|
|
527
501
|
builder._expression = parsed_expr
|
|
@@ -536,8 +510,7 @@ class SQLFactory:
|
|
|
536
510
|
def _populate_merge_from_sql(self, builder: "Merge", sql_string: str) -> "Merge":
|
|
537
511
|
"""Parse SQL string and populate MERGE builder using SQLGlot directly."""
|
|
538
512
|
try:
|
|
539
|
-
|
|
540
|
-
parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect) # type: ignore[var-annotated]
|
|
513
|
+
parsed_expr: exp.Expression = exp.maybe_parse(sql_string, dialect=self.dialect)
|
|
541
514
|
|
|
542
515
|
if isinstance(parsed_expr, exp.Merge):
|
|
543
516
|
builder._expression = parsed_expr
|
|
@@ -549,10 +522,6 @@ class SQLFactory:
|
|
|
549
522
|
logger.warning("Failed to parse MERGE SQL, falling back to traditional mode: %s", e)
|
|
550
523
|
return builder
|
|
551
524
|
|
|
552
|
-
# ===================
|
|
553
|
-
# Column References
|
|
554
|
-
# ===================
|
|
555
|
-
|
|
556
525
|
def column(self, name: str, table: Optional[str] = None) -> Column:
|
|
557
526
|
"""Create a column reference.
|
|
558
527
|
|
|
@@ -567,10 +536,10 @@ class SQLFactory:
|
|
|
567
536
|
|
|
568
537
|
@property
|
|
569
538
|
def case_(self) -> "Case":
|
|
570
|
-
"""Create a CASE expression builder
|
|
539
|
+
"""Create a CASE expression builder.
|
|
571
540
|
|
|
572
541
|
Returns:
|
|
573
|
-
Case builder instance for
|
|
542
|
+
Case builder instance for CASE expression building.
|
|
574
543
|
|
|
575
544
|
Example:
|
|
576
545
|
```python
|
|
@@ -669,23 +638,15 @@ class SQLFactory:
|
|
|
669
638
|
Column object for the given name.
|
|
670
639
|
|
|
671
640
|
Note:
|
|
672
|
-
Special SQL constructs like case_, row_number_, etc. are
|
|
673
|
-
handled as properties for
|
|
641
|
+
Special SQL constructs like case_, row_number_, etc. are
|
|
642
|
+
handled as properties for type safety.
|
|
674
643
|
"""
|
|
675
644
|
return Column(name)
|
|
676
645
|
|
|
677
|
-
# ===================
|
|
678
|
-
# Raw SQL Expressions
|
|
679
|
-
# ===================
|
|
680
|
-
|
|
681
646
|
@staticmethod
|
|
682
647
|
def raw(sql_fragment: str, **parameters: Any) -> "Union[exp.Expression, SQL]":
|
|
683
648
|
"""Create a raw SQL expression from a string fragment with optional parameters.
|
|
684
649
|
|
|
685
|
-
This method makes it explicit that you are passing raw SQL that should
|
|
686
|
-
be parsed and included directly in the query. Useful for complex expressions,
|
|
687
|
-
database-specific functions, or when you need precise control over the SQL.
|
|
688
|
-
|
|
689
650
|
Args:
|
|
690
651
|
sql_fragment: Raw SQL string to parse into an expression.
|
|
691
652
|
**parameters: Named parameters for parameter binding.
|
|
@@ -699,22 +660,21 @@ class SQLFactory:
|
|
|
699
660
|
|
|
700
661
|
Example:
|
|
701
662
|
```python
|
|
702
|
-
# Raw expression without parameters (current behavior)
|
|
703
663
|
expr = sql.raw("COALESCE(name, 'Unknown')")
|
|
704
664
|
|
|
705
|
-
|
|
665
|
+
|
|
706
666
|
stmt = sql.raw(
|
|
707
667
|
"LOWER(name) LIKE LOWER(:pattern)", pattern=f"%{query}%"
|
|
708
668
|
)
|
|
709
669
|
|
|
710
|
-
|
|
670
|
+
|
|
711
671
|
expr = sql.raw(
|
|
712
672
|
"price BETWEEN :min_price AND :max_price",
|
|
713
673
|
min_price=100,
|
|
714
674
|
max_price=500,
|
|
715
675
|
)
|
|
716
676
|
|
|
717
|
-
|
|
677
|
+
|
|
718
678
|
query = sql.select(
|
|
719
679
|
"name",
|
|
720
680
|
sql.raw(
|
|
@@ -724,11 +684,9 @@ class SQLFactory:
|
|
|
724
684
|
```
|
|
725
685
|
"""
|
|
726
686
|
if not parameters:
|
|
727
|
-
# Original behavior - return pure expression
|
|
728
687
|
try:
|
|
729
|
-
parsed:
|
|
730
|
-
|
|
731
|
-
return parsed
|
|
688
|
+
parsed: exp.Expression = exp.maybe_parse(sql_fragment)
|
|
689
|
+
return parsed
|
|
732
690
|
if sql_fragment.strip().replace("_", "").replace(".", "").isalnum():
|
|
733
691
|
return exp.to_identifier(sql_fragment)
|
|
734
692
|
return exp.Literal.string(sql_fragment)
|
|
@@ -736,17 +694,12 @@ class SQLFactory:
|
|
|
736
694
|
msg = f"Failed to parse raw SQL fragment '{sql_fragment}': {e}"
|
|
737
695
|
raise SQLBuilderError(msg) from e
|
|
738
696
|
|
|
739
|
-
# New behavior - return SQL statement with parameters
|
|
740
|
-
from sqlspec.core.statement import SQL
|
|
741
|
-
|
|
742
697
|
return SQL(sql_fragment, parameters)
|
|
743
698
|
|
|
744
|
-
# ===================
|
|
745
|
-
# Aggregate Functions
|
|
746
|
-
# ===================
|
|
747
|
-
|
|
748
699
|
@staticmethod
|
|
749
|
-
def count(
|
|
700
|
+
def count(
|
|
701
|
+
column: Union[str, exp.Expression, "ExpressionWrapper", "Case", "Column"] = "*", distinct: bool = False
|
|
702
|
+
) -> AggregateExpression:
|
|
750
703
|
"""Create a COUNT expression.
|
|
751
704
|
|
|
752
705
|
Args:
|
|
@@ -756,12 +709,14 @@ class SQLFactory:
|
|
|
756
709
|
Returns:
|
|
757
710
|
COUNT expression.
|
|
758
711
|
"""
|
|
759
|
-
if column == "*":
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
712
|
+
if isinstance(column, str) and column == "*":
|
|
713
|
+
expr = exp.Count(this=exp.Star(), distinct=distinct)
|
|
714
|
+
else:
|
|
715
|
+
col_expr = SQLFactory._extract_expression(column)
|
|
716
|
+
expr = exp.Count(this=col_expr, distinct=distinct)
|
|
717
|
+
return AggregateExpression(expr)
|
|
718
|
+
|
|
719
|
+
def count_distinct(self, column: Union[str, exp.Expression, "ExpressionWrapper", "Case"]) -> AggregateExpression:
|
|
765
720
|
"""Create a COUNT(DISTINCT column) expression.
|
|
766
721
|
|
|
767
722
|
Args:
|
|
@@ -773,7 +728,9 @@ class SQLFactory:
|
|
|
773
728
|
return self.count(column, distinct=True)
|
|
774
729
|
|
|
775
730
|
@staticmethod
|
|
776
|
-
def sum(
|
|
731
|
+
def sum(
|
|
732
|
+
column: Union[str, exp.Expression, "ExpressionWrapper", "Case"], distinct: bool = False
|
|
733
|
+
) -> AggregateExpression:
|
|
777
734
|
"""Create a SUM expression.
|
|
778
735
|
|
|
779
736
|
Args:
|
|
@@ -783,11 +740,11 @@ class SQLFactory:
|
|
|
783
740
|
Returns:
|
|
784
741
|
SUM expression.
|
|
785
742
|
"""
|
|
786
|
-
col_expr =
|
|
787
|
-
return exp.Sum(this=col_expr, distinct=distinct)
|
|
743
|
+
col_expr = SQLFactory._extract_expression(column)
|
|
744
|
+
return AggregateExpression(exp.Sum(this=col_expr, distinct=distinct))
|
|
788
745
|
|
|
789
746
|
@staticmethod
|
|
790
|
-
def avg(column: Union[str, exp.Expression]) ->
|
|
747
|
+
def avg(column: Union[str, exp.Expression, "ExpressionWrapper", "Case"]) -> AggregateExpression:
|
|
791
748
|
"""Create an AVG expression.
|
|
792
749
|
|
|
793
750
|
Args:
|
|
@@ -796,11 +753,11 @@ class SQLFactory:
|
|
|
796
753
|
Returns:
|
|
797
754
|
AVG expression.
|
|
798
755
|
"""
|
|
799
|
-
col_expr =
|
|
800
|
-
return exp.Avg(this=col_expr)
|
|
756
|
+
col_expr = SQLFactory._extract_expression(column)
|
|
757
|
+
return AggregateExpression(exp.Avg(this=col_expr))
|
|
801
758
|
|
|
802
759
|
@staticmethod
|
|
803
|
-
def max(column: Union[str, exp.Expression]) ->
|
|
760
|
+
def max(column: Union[str, exp.Expression, "ExpressionWrapper", "Case"]) -> AggregateExpression:
|
|
804
761
|
"""Create a MAX expression.
|
|
805
762
|
|
|
806
763
|
Args:
|
|
@@ -809,11 +766,11 @@ class SQLFactory:
|
|
|
809
766
|
Returns:
|
|
810
767
|
MAX expression.
|
|
811
768
|
"""
|
|
812
|
-
col_expr =
|
|
813
|
-
return exp.Max(this=col_expr)
|
|
769
|
+
col_expr = SQLFactory._extract_expression(column)
|
|
770
|
+
return AggregateExpression(exp.Max(this=col_expr))
|
|
814
771
|
|
|
815
772
|
@staticmethod
|
|
816
|
-
def min(column: Union[str, exp.Expression]) ->
|
|
773
|
+
def min(column: Union[str, exp.Expression, "ExpressionWrapper", "Case"]) -> AggregateExpression:
|
|
817
774
|
"""Create a MIN expression.
|
|
818
775
|
|
|
819
776
|
Args:
|
|
@@ -822,15 +779,11 @@ class SQLFactory:
|
|
|
822
779
|
Returns:
|
|
823
780
|
MIN expression.
|
|
824
781
|
"""
|
|
825
|
-
col_expr =
|
|
826
|
-
return exp.Min(this=col_expr)
|
|
827
|
-
|
|
828
|
-
# ===================
|
|
829
|
-
# Advanced SQL Operations
|
|
830
|
-
# ===================
|
|
782
|
+
col_expr = SQLFactory._extract_expression(column)
|
|
783
|
+
return AggregateExpression(exp.Min(this=col_expr))
|
|
831
784
|
|
|
832
785
|
@staticmethod
|
|
833
|
-
def rollup(*columns: Union[str, exp.Expression]) ->
|
|
786
|
+
def rollup(*columns: Union[str, exp.Expression]) -> FunctionExpression:
|
|
834
787
|
"""Create a ROLLUP expression for GROUP BY clauses.
|
|
835
788
|
|
|
836
789
|
Args:
|
|
@@ -841,7 +794,6 @@ class SQLFactory:
|
|
|
841
794
|
|
|
842
795
|
Example:
|
|
843
796
|
```python
|
|
844
|
-
# GROUP BY ROLLUP(product, region)
|
|
845
797
|
query = (
|
|
846
798
|
sql.select("product", "region", sql.sum("sales"))
|
|
847
799
|
.from_("sales_data")
|
|
@@ -850,10 +802,10 @@ class SQLFactory:
|
|
|
850
802
|
```
|
|
851
803
|
"""
|
|
852
804
|
column_exprs = [exp.column(col) if isinstance(col, str) else col for col in columns]
|
|
853
|
-
return exp.Rollup(expressions=column_exprs)
|
|
805
|
+
return FunctionExpression(exp.Rollup(expressions=column_exprs))
|
|
854
806
|
|
|
855
807
|
@staticmethod
|
|
856
|
-
def cube(*columns: Union[str, exp.Expression]) ->
|
|
808
|
+
def cube(*columns: Union[str, exp.Expression]) -> FunctionExpression:
|
|
857
809
|
"""Create a CUBE expression for GROUP BY clauses.
|
|
858
810
|
|
|
859
811
|
Args:
|
|
@@ -864,7 +816,6 @@ class SQLFactory:
|
|
|
864
816
|
|
|
865
817
|
Example:
|
|
866
818
|
```python
|
|
867
|
-
# GROUP BY CUBE(product, region)
|
|
868
819
|
query = (
|
|
869
820
|
sql.select("product", "region", sql.sum("sales"))
|
|
870
821
|
.from_("sales_data")
|
|
@@ -873,10 +824,10 @@ class SQLFactory:
|
|
|
873
824
|
```
|
|
874
825
|
"""
|
|
875
826
|
column_exprs = [exp.column(col) if isinstance(col, str) else col for col in columns]
|
|
876
|
-
return exp.Cube(expressions=column_exprs)
|
|
827
|
+
return FunctionExpression(exp.Cube(expressions=column_exprs))
|
|
877
828
|
|
|
878
829
|
@staticmethod
|
|
879
|
-
def grouping_sets(*column_sets: Union[tuple[str, ...], list[str]]) ->
|
|
830
|
+
def grouping_sets(*column_sets: Union[tuple[str, ...], list[str]]) -> FunctionExpression:
|
|
880
831
|
"""Create a GROUPING SETS expression for GROUP BY clauses.
|
|
881
832
|
|
|
882
833
|
Args:
|
|
@@ -887,7 +838,6 @@ class SQLFactory:
|
|
|
887
838
|
|
|
888
839
|
Example:
|
|
889
840
|
```python
|
|
890
|
-
# GROUP BY GROUPING SETS ((product), (region), ())
|
|
891
841
|
query = (
|
|
892
842
|
sql.select("product", "region", sql.sum("sales"))
|
|
893
843
|
.from_("sales_data")
|
|
@@ -908,10 +858,10 @@ class SQLFactory:
|
|
|
908
858
|
else:
|
|
909
859
|
set_expressions.append(exp.column(column_set))
|
|
910
860
|
|
|
911
|
-
return exp.GroupingSets(expressions=set_expressions)
|
|
861
|
+
return FunctionExpression(exp.GroupingSets(expressions=set_expressions))
|
|
912
862
|
|
|
913
863
|
@staticmethod
|
|
914
|
-
def any(values: Union[list[Any], exp.Expression, str]) ->
|
|
864
|
+
def any(values: Union[list[Any], exp.Expression, str]) -> FunctionExpression:
|
|
915
865
|
"""Create an ANY expression for use with comparison operators.
|
|
916
866
|
|
|
917
867
|
Args:
|
|
@@ -922,7 +872,6 @@ class SQLFactory:
|
|
|
922
872
|
|
|
923
873
|
Example:
|
|
924
874
|
```python
|
|
925
|
-
# WHERE id = ANY(subquery)
|
|
926
875
|
subquery = sql.select("user_id").from_("active_users")
|
|
927
876
|
query = (
|
|
928
877
|
sql.select("*")
|
|
@@ -932,18 +881,15 @@ class SQLFactory:
|
|
|
932
881
|
```
|
|
933
882
|
"""
|
|
934
883
|
if isinstance(values, list):
|
|
935
|
-
literals = [SQLFactory.
|
|
936
|
-
return exp.Any(this=exp.Array(expressions=literals))
|
|
884
|
+
literals = [SQLFactory.to_literal(v) for v in values]
|
|
885
|
+
return FunctionExpression(exp.Any(this=exp.Array(expressions=literals)))
|
|
937
886
|
if isinstance(values, str):
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
return exp.Any(this=parsed)
|
|
942
|
-
return exp.Any(this=exp.Literal.string(values))
|
|
943
|
-
return exp.Any(this=values)
|
|
887
|
+
parsed: exp.Expression = exp.maybe_parse(values)
|
|
888
|
+
return FunctionExpression(exp.Any(this=parsed))
|
|
889
|
+
return FunctionExpression(exp.Any(this=values))
|
|
944
890
|
|
|
945
891
|
@staticmethod
|
|
946
|
-
def not_any_(values: Union[list[Any], exp.Expression, str]) ->
|
|
892
|
+
def not_any_(values: Union[list[Any], exp.Expression, str]) -> FunctionExpression:
|
|
947
893
|
"""Create a NOT ANY expression for use with comparison operators.
|
|
948
894
|
|
|
949
895
|
Args:
|
|
@@ -954,7 +900,6 @@ class SQLFactory:
|
|
|
954
900
|
|
|
955
901
|
Example:
|
|
956
902
|
```python
|
|
957
|
-
# WHERE id <> ANY(subquery)
|
|
958
903
|
subquery = sql.select("user_id").from_("blocked_users")
|
|
959
904
|
query = (
|
|
960
905
|
sql.select("*")
|
|
@@ -963,14 +908,10 @@ class SQLFactory:
|
|
|
963
908
|
)
|
|
964
909
|
```
|
|
965
910
|
"""
|
|
966
|
-
return SQLFactory.any(values)
|
|
967
|
-
|
|
968
|
-
# ===================
|
|
969
|
-
# String Functions
|
|
970
|
-
# ===================
|
|
911
|
+
return SQLFactory.any(values)
|
|
971
912
|
|
|
972
913
|
@staticmethod
|
|
973
|
-
def concat(*expressions: Union[str, exp.Expression]) ->
|
|
914
|
+
def concat(*expressions: Union[str, exp.Expression]) -> StringExpression:
|
|
974
915
|
"""Create a CONCAT expression.
|
|
975
916
|
|
|
976
917
|
Args:
|
|
@@ -980,10 +921,10 @@ class SQLFactory:
|
|
|
980
921
|
CONCAT expression.
|
|
981
922
|
"""
|
|
982
923
|
exprs = [exp.column(expr) if isinstance(expr, str) else expr for expr in expressions]
|
|
983
|
-
return exp.Concat(expressions=exprs)
|
|
924
|
+
return StringExpression(exp.Concat(expressions=exprs))
|
|
984
925
|
|
|
985
926
|
@staticmethod
|
|
986
|
-
def upper(column: Union[str, exp.Expression]) ->
|
|
927
|
+
def upper(column: Union[str, exp.Expression]) -> StringExpression:
|
|
987
928
|
"""Create an UPPER expression.
|
|
988
929
|
|
|
989
930
|
Args:
|
|
@@ -993,10 +934,10 @@ class SQLFactory:
|
|
|
993
934
|
UPPER expression.
|
|
994
935
|
"""
|
|
995
936
|
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
996
|
-
return exp.Upper(this=col_expr)
|
|
937
|
+
return StringExpression(exp.Upper(this=col_expr))
|
|
997
938
|
|
|
998
939
|
@staticmethod
|
|
999
|
-
def lower(column: Union[str, exp.Expression]) ->
|
|
940
|
+
def lower(column: Union[str, exp.Expression]) -> StringExpression:
|
|
1000
941
|
"""Create a LOWER expression.
|
|
1001
942
|
|
|
1002
943
|
Args:
|
|
@@ -1006,10 +947,10 @@ class SQLFactory:
|
|
|
1006
947
|
LOWER expression.
|
|
1007
948
|
"""
|
|
1008
949
|
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
1009
|
-
return exp.Lower(this=col_expr)
|
|
950
|
+
return StringExpression(exp.Lower(this=col_expr))
|
|
1010
951
|
|
|
1011
952
|
@staticmethod
|
|
1012
|
-
def length(column: Union[str, exp.Expression]) ->
|
|
953
|
+
def length(column: Union[str, exp.Expression]) -> StringExpression:
|
|
1013
954
|
"""Create a LENGTH expression.
|
|
1014
955
|
|
|
1015
956
|
Args:
|
|
@@ -1019,14 +960,10 @@ class SQLFactory:
|
|
|
1019
960
|
LENGTH expression.
|
|
1020
961
|
"""
|
|
1021
962
|
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
1022
|
-
return exp.Length(this=col_expr)
|
|
1023
|
-
|
|
1024
|
-
# ===================
|
|
1025
|
-
# Math Functions
|
|
1026
|
-
# ===================
|
|
963
|
+
return StringExpression(exp.Length(this=col_expr))
|
|
1027
964
|
|
|
1028
965
|
@staticmethod
|
|
1029
|
-
def round(column: Union[str, exp.Expression], decimals: int = 0) ->
|
|
966
|
+
def round(column: Union[str, exp.Expression], decimals: int = 0) -> MathExpression:
|
|
1030
967
|
"""Create a ROUND expression.
|
|
1031
968
|
|
|
1032
969
|
Args:
|
|
@@ -1038,19 +975,15 @@ class SQLFactory:
|
|
|
1038
975
|
"""
|
|
1039
976
|
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
1040
977
|
if decimals == 0:
|
|
1041
|
-
return exp.Round(this=col_expr)
|
|
1042
|
-
return exp.Round(this=col_expr, expression=exp.Literal.number(decimals))
|
|
1043
|
-
|
|
1044
|
-
# ===================
|
|
1045
|
-
# Conversion Functions
|
|
1046
|
-
# ===================
|
|
978
|
+
return MathExpression(exp.Round(this=col_expr))
|
|
979
|
+
return MathExpression(exp.Round(this=col_expr, expression=exp.Literal.number(decimals)))
|
|
1047
980
|
|
|
1048
981
|
@staticmethod
|
|
1049
|
-
def
|
|
982
|
+
def to_literal(value: Any) -> FunctionExpression:
|
|
1050
983
|
"""Convert a Python value to a SQLGlot literal expression.
|
|
1051
984
|
|
|
1052
|
-
Uses SQLGlot's built-in exp.convert() function for
|
|
1053
|
-
|
|
985
|
+
Uses SQLGlot's built-in exp.convert() function for literal creation.
|
|
986
|
+
Handles all Python primitive types:
|
|
1054
987
|
- None -> exp.Null (renders as NULL)
|
|
1055
988
|
- bool -> exp.Boolean (renders as TRUE/FALSE or 1/0 based on dialect)
|
|
1056
989
|
- int/float -> exp.Literal with is_number=True
|
|
@@ -1063,12 +996,51 @@ class SQLFactory:
|
|
|
1063
996
|
Returns:
|
|
1064
997
|
SQLGlot expression representing the literal value.
|
|
1065
998
|
"""
|
|
999
|
+
if isinstance(value, exp.Expression):
|
|
1000
|
+
return FunctionExpression(value)
|
|
1001
|
+
return FunctionExpression(exp.convert(value))
|
|
1002
|
+
|
|
1003
|
+
@staticmethod
|
|
1004
|
+
def _to_expression(value: Any) -> exp.Expression:
|
|
1005
|
+
"""Convert a Python value to a raw SQLGlot expression.
|
|
1006
|
+
|
|
1007
|
+
Args:
|
|
1008
|
+
value: Python value or SQLGlot expression to convert.
|
|
1009
|
+
|
|
1010
|
+
Returns:
|
|
1011
|
+
Raw SQLGlot expression.
|
|
1012
|
+
"""
|
|
1013
|
+
if isinstance(value, exp.Expression):
|
|
1014
|
+
return value
|
|
1015
|
+
return exp.convert(value)
|
|
1016
|
+
|
|
1017
|
+
@staticmethod
|
|
1018
|
+
def _extract_expression(value: Any) -> exp.Expression:
|
|
1019
|
+
"""Extract SQLGlot expression from value, handling our wrapper types.
|
|
1020
|
+
|
|
1021
|
+
Args:
|
|
1022
|
+
value: String, SQLGlot expression, or our wrapper type.
|
|
1023
|
+
|
|
1024
|
+
Returns:
|
|
1025
|
+
Raw SQLGlot expression.
|
|
1026
|
+
"""
|
|
1027
|
+
from sqlspec.builder._expression_wrappers import ExpressionWrapper
|
|
1028
|
+
from sqlspec.builder.mixins._select_operations import Case
|
|
1029
|
+
|
|
1030
|
+
if isinstance(value, str):
|
|
1031
|
+
return exp.column(value)
|
|
1032
|
+
if isinstance(value, Column):
|
|
1033
|
+
return value._expression
|
|
1034
|
+
if isinstance(value, ExpressionWrapper):
|
|
1035
|
+
return value.expression
|
|
1036
|
+
if isinstance(value, Case):
|
|
1037
|
+
return exp.Case(ifs=value._conditions, default=value._default)
|
|
1066
1038
|
if isinstance(value, exp.Expression):
|
|
1067
1039
|
return value
|
|
1068
1040
|
return exp.convert(value)
|
|
1069
1041
|
|
|
1070
1042
|
@staticmethod
|
|
1071
|
-
def decode(column: Union[str, exp.Expression], *args: Union[str, exp.Expression, Any]) ->
|
|
1043
|
+
def decode(column: Union[str, exp.Expression], *args: Union[str, exp.Expression, Any]) -> FunctionExpression:
|
|
1072
1044
|
"""Create a DECODE expression (Oracle-style conditional logic).
|
|
1073
1045
|
|
|
1074
1046
|
DECODE compares column to each search value and returns the corresponding result.
|
|
@@ -1087,7 +1059,6 @@ class SQLFactory:
|
|
|
1087
1059
|
|
|
1088
1060
|
Example:
|
|
1089
1061
|
```python
|
|
1090
|
-
# DECODE(status, 'A', 'Active', 'I', 'Inactive', 'Unknown')
|
|
1091
1062
|
sql.decode(
|
|
1092
1063
|
"status", "A", "Active", "I", "Inactive", "Unknown"
|
|
1093
1064
|
)
|
|
@@ -1104,23 +1075,22 @@ class SQLFactory:
|
|
|
1104
1075
|
|
|
1105
1076
|
for i in range(0, len(args) - 1, 2):
|
|
1106
1077
|
if i + 1 >= len(args):
|
|
1107
|
-
|
|
1108
|
-
default = SQLFactory._to_literal(args[i])
|
|
1078
|
+
default = SQLFactory._to_expression(args[i])
|
|
1109
1079
|
break
|
|
1110
1080
|
|
|
1111
1081
|
search_val = args[i]
|
|
1112
1082
|
result_val = args[i + 1]
|
|
1113
1083
|
|
|
1114
|
-
search_expr = SQLFactory.
|
|
1115
|
-
result_expr = SQLFactory.
|
|
1084
|
+
search_expr = SQLFactory._to_expression(search_val)
|
|
1085
|
+
result_expr = SQLFactory._to_expression(result_val)
|
|
1116
1086
|
|
|
1117
1087
|
condition = exp.EQ(this=col_expr, expression=search_expr)
|
|
1118
|
-
conditions.append(exp.
|
|
1088
|
+
conditions.append(exp.If(this=condition, true=result_expr))
|
|
1119
1089
|
|
|
1120
|
-
return exp.Case(ifs=conditions, default=default)
|
|
1090
|
+
return FunctionExpression(exp.Case(ifs=conditions, default=default))
|
|
1121
1091
|
|
|
1122
1092
|
@staticmethod
|
|
1123
|
-
def cast(column: Union[str, exp.Expression], data_type: str) ->
|
|
1093
|
+
def cast(column: Union[str, exp.Expression], data_type: str) -> ConversionExpression:
|
|
1124
1094
|
"""Create a CAST expression for type conversion.
|
|
1125
1095
|
|
|
1126
1096
|
Args:
|
|
@@ -1131,10 +1101,10 @@ class SQLFactory:
|
|
|
1131
1101
|
CAST expression.
|
|
1132
1102
|
"""
|
|
1133
1103
|
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
1134
|
-
return exp.Cast(this=col_expr, to=exp.DataType.build(data_type))
|
|
1104
|
+
return ConversionExpression(exp.Cast(this=col_expr, to=exp.DataType.build(data_type)))
|
|
1135
1105
|
|
|
1136
1106
|
@staticmethod
|
|
1137
|
-
def coalesce(*expressions: Union[str, exp.Expression]) ->
|
|
1107
|
+
def coalesce(*expressions: Union[str, exp.Expression]) -> ConversionExpression:
|
|
1138
1108
|
"""Create a COALESCE expression.
|
|
1139
1109
|
|
|
1140
1110
|
Args:
|
|
@@ -1144,10 +1114,12 @@ class SQLFactory:
|
|
|
1144
1114
|
COALESCE expression.
|
|
1145
1115
|
"""
|
|
1146
1116
|
exprs = [exp.column(expr) if isinstance(expr, str) else expr for expr in expressions]
|
|
1147
|
-
return exp.Coalesce(expressions=exprs)
|
|
1117
|
+
return ConversionExpression(exp.Coalesce(expressions=exprs))
|
|
1148
1118
|
|
|
1149
1119
|
@staticmethod
|
|
1150
|
-
def nvl(
|
|
1120
|
+
def nvl(
|
|
1121
|
+
column: Union[str, exp.Expression], substitute_value: Union[str, exp.Expression, Any]
|
|
1122
|
+
) -> ConversionExpression:
|
|
1151
1123
|
"""Create an NVL (Oracle-style) expression using COALESCE.
|
|
1152
1124
|
|
|
1153
1125
|
Args:
|
|
@@ -1158,15 +1130,15 @@ class SQLFactory:
|
|
|
1158
1130
|
COALESCE expression equivalent to NVL.
|
|
1159
1131
|
"""
|
|
1160
1132
|
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
1161
|
-
sub_expr = SQLFactory.
|
|
1162
|
-
return exp.Coalesce(expressions=[col_expr, sub_expr])
|
|
1133
|
+
sub_expr = SQLFactory._to_expression(substitute_value)
|
|
1134
|
+
return ConversionExpression(exp.Coalesce(expressions=[col_expr, sub_expr]))
|
|
1163
1135
|
|
|
1164
1136
|
@staticmethod
|
|
1165
1137
|
def nvl2(
|
|
1166
1138
|
column: Union[str, exp.Expression],
|
|
1167
1139
|
value_if_not_null: Union[str, exp.Expression, Any],
|
|
1168
1140
|
value_if_null: Union[str, exp.Expression, Any],
|
|
1169
|
-
) ->
|
|
1141
|
+
) -> ConversionExpression:
|
|
1170
1142
|
"""Create an NVL2 (Oracle-style) expression using CASE.
|
|
1171
1143
|
|
|
1172
1144
|
NVL2 returns value_if_not_null if column is not NULL,
|
|
@@ -1182,31 +1154,25 @@ class SQLFactory:
|
|
|
1182
1154
|
|
|
1183
1155
|
Example:
|
|
1184
1156
|
```python
|
|
1185
|
-
# NVL2(salary, 'Has Salary', 'No Salary')
|
|
1186
1157
|
sql.nvl2("salary", "Has Salary", "No Salary")
|
|
1187
1158
|
```
|
|
1188
1159
|
"""
|
|
1189
1160
|
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
1190
|
-
not_null_expr = SQLFactory.
|
|
1191
|
-
null_expr = SQLFactory.
|
|
1161
|
+
not_null_expr = SQLFactory._to_expression(value_if_not_null)
|
|
1162
|
+
null_expr = SQLFactory._to_expression(value_if_null)
|
|
1192
1163
|
|
|
1193
|
-
# Create CASE WHEN column IS NOT NULL THEN value_if_not_null ELSE value_if_null END
|
|
1194
1164
|
is_null = exp.Is(this=col_expr, expression=exp.Null())
|
|
1195
1165
|
condition = exp.Not(this=is_null)
|
|
1196
1166
|
when_clause = exp.If(this=condition, true=not_null_expr)
|
|
1197
1167
|
|
|
1198
|
-
return exp.Case(ifs=[when_clause], default=null_expr)
|
|
1199
|
-
|
|
1200
|
-
# ===================
|
|
1201
|
-
# Bulk Operations
|
|
1202
|
-
# ===================
|
|
1168
|
+
return ConversionExpression(exp.Case(ifs=[when_clause], default=null_expr))
|
|
1203
1169
|
|
|
1204
1170
|
@staticmethod
|
|
1205
|
-
def bulk_insert(table_name: str, column_count: int, placeholder_style: str = "?") ->
|
|
1171
|
+
def bulk_insert(table_name: str, column_count: int, placeholder_style: str = "?") -> FunctionExpression:
|
|
1206
1172
|
"""Create bulk INSERT expression for executemany operations.
|
|
1207
1173
|
|
|
1208
|
-
|
|
1209
|
-
|
|
1174
|
+
For bulk loading operations like CSV ingestion where
|
|
1175
|
+
an INSERT expression with placeholders for executemany() is needed.
|
|
1210
1176
|
|
|
1211
1177
|
Args:
|
|
1212
1178
|
table_name: Name of the table to insert into
|
|
@@ -1214,36 +1180,35 @@ class SQLFactory:
|
|
|
1214
1180
|
placeholder_style: Placeholder style ("?" for SQLite/PostgreSQL, "%s" for MySQL, ":1" for Oracle)
|
|
1215
1181
|
|
|
1216
1182
|
Returns:
|
|
1217
|
-
INSERT expression with
|
|
1183
|
+
INSERT expression with placeholders for bulk operations
|
|
1218
1184
|
|
|
1219
1185
|
Example:
|
|
1220
1186
|
```python
|
|
1221
1187
|
from sqlspec import sql
|
|
1222
1188
|
|
|
1223
|
-
|
|
1189
|
+
|
|
1224
1190
|
insert_expr = sql.bulk_insert("my_table", 3)
|
|
1225
|
-
# Creates: INSERT INTO "my_table" VALUES (?, ?, ?)
|
|
1226
1191
|
|
|
1227
|
-
|
|
1192
|
+
|
|
1228
1193
|
insert_expr = sql.bulk_insert(
|
|
1229
1194
|
"my_table", 3, placeholder_style="%s"
|
|
1230
1195
|
)
|
|
1231
|
-
# Creates: INSERT INTO "my_table" VALUES (%s, %s, %s)
|
|
1232
1196
|
|
|
1233
|
-
|
|
1197
|
+
|
|
1234
1198
|
insert_expr = sql.bulk_insert(
|
|
1235
1199
|
"my_table", 3, placeholder_style=":1"
|
|
1236
1200
|
)
|
|
1237
|
-
# Creates: INSERT INTO "my_table" VALUES (:1, :2, :3)
|
|
1238
1201
|
```
|
|
1239
1202
|
"""
|
|
1240
|
-
return
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1203
|
+
return FunctionExpression(
|
|
1204
|
+
exp.Insert(
|
|
1205
|
+
this=exp.Table(this=exp.to_identifier(table_name)),
|
|
1206
|
+
expression=exp.Values(
|
|
1207
|
+
expressions=[
|
|
1208
|
+
exp.Tuple(expressions=[exp.Placeholder(this=placeholder_style) for _ in range(column_count)])
|
|
1209
|
+
]
|
|
1210
|
+
),
|
|
1211
|
+
)
|
|
1247
1212
|
)
|
|
1248
1213
|
|
|
1249
1214
|
def truncate(self, table_name: str) -> "Truncate":
|
|
@@ -1259,10 +1224,10 @@ class SQLFactory:
|
|
|
1259
1224
|
```python
|
|
1260
1225
|
from sqlspec import sql
|
|
1261
1226
|
|
|
1262
|
-
|
|
1227
|
+
|
|
1263
1228
|
truncate_sql = sql.truncate_table("my_table").build().sql
|
|
1264
1229
|
|
|
1265
|
-
|
|
1230
|
+
|
|
1266
1231
|
truncate_sql = (
|
|
1267
1232
|
sql.truncate_table("my_table")
|
|
1268
1233
|
.cascade()
|
|
@@ -1272,13 +1237,7 @@ class SQLFactory:
|
|
|
1272
1237
|
)
|
|
1273
1238
|
```
|
|
1274
1239
|
"""
|
|
1275
|
-
|
|
1276
|
-
builder._table_name = table_name
|
|
1277
|
-
return builder
|
|
1278
|
-
|
|
1279
|
-
# ===================
|
|
1280
|
-
# Case Expressions
|
|
1281
|
-
# ===================
|
|
1240
|
+
return Truncate(table_name, dialect=self.dialect)
|
|
1282
1241
|
|
|
1283
1242
|
@staticmethod
|
|
1284
1243
|
def case() -> "Case":
|
|
@@ -1289,15 +1248,11 @@ class SQLFactory:
|
|
|
1289
1248
|
"""
|
|
1290
1249
|
return Case()
|
|
1291
1250
|
|
|
1292
|
-
# ===================
|
|
1293
|
-
# Window Functions
|
|
1294
|
-
# ===================
|
|
1295
|
-
|
|
1296
1251
|
def row_number(
|
|
1297
1252
|
self,
|
|
1298
1253
|
partition_by: Optional[Union[str, list[str], exp.Expression]] = None,
|
|
1299
1254
|
order_by: Optional[Union[str, list[str], exp.Expression]] = None,
|
|
1300
|
-
) ->
|
|
1255
|
+
) -> FunctionExpression:
|
|
1301
1256
|
"""Create a ROW_NUMBER() window function.
|
|
1302
1257
|
|
|
1303
1258
|
Args:
|
|
@@ -1313,7 +1268,7 @@ class SQLFactory:
|
|
|
1313
1268
|
self,
|
|
1314
1269
|
partition_by: Optional[Union[str, list[str], exp.Expression]] = None,
|
|
1315
1270
|
order_by: Optional[Union[str, list[str], exp.Expression]] = None,
|
|
1316
|
-
) ->
|
|
1271
|
+
) -> FunctionExpression:
|
|
1317
1272
|
"""Create a RANK() window function.
|
|
1318
1273
|
|
|
1319
1274
|
Args:
|
|
@@ -1329,7 +1284,7 @@ class SQLFactory:
|
|
|
1329
1284
|
self,
|
|
1330
1285
|
partition_by: Optional[Union[str, list[str], exp.Expression]] = None,
|
|
1331
1286
|
order_by: Optional[Union[str, list[str], exp.Expression]] = None,
|
|
1332
|
-
) ->
|
|
1287
|
+
) -> FunctionExpression:
|
|
1333
1288
|
"""Create a DENSE_RANK() window function.
|
|
1334
1289
|
|
|
1335
1290
|
Args:
|
|
@@ -1347,7 +1302,7 @@ class SQLFactory:
|
|
|
1347
1302
|
func_args: list[exp.Expression],
|
|
1348
1303
|
partition_by: Optional[Union[str, list[str], exp.Expression]] = None,
|
|
1349
1304
|
order_by: Optional[Union[str, list[str], exp.Expression]] = None,
|
|
1350
|
-
) ->
|
|
1305
|
+
) -> FunctionExpression:
|
|
1351
1306
|
"""Helper to create window function expressions.
|
|
1352
1307
|
|
|
1353
1308
|
Args:
|
|
@@ -1373,14 +1328,13 @@ class SQLFactory:
|
|
|
1373
1328
|
|
|
1374
1329
|
if order_by:
|
|
1375
1330
|
if isinstance(order_by, str):
|
|
1376
|
-
over_args["order"] = [exp.column(order_by).asc()]
|
|
1331
|
+
over_args["order"] = exp.Order(expressions=[exp.column(order_by).asc()])
|
|
1377
1332
|
elif isinstance(order_by, list):
|
|
1378
|
-
over_args["order"] = [exp.column(col).asc() for col in order_by]
|
|
1333
|
+
over_args["order"] = exp.Order(expressions=[exp.column(col).asc() for col in order_by])
|
|
1379
1334
|
elif isinstance(order_by, exp.Expression):
|
|
1380
|
-
over_args["order"] = [order_by]
|
|
1335
|
+
over_args["order"] = exp.Order(expressions=[order_by])
|
|
1381
1336
|
|
|
1382
|
-
return exp.Window(this=func_expr, **over_args)
|
|
1337
|
+
return FunctionExpression(exp.Window(this=func_expr, **over_args))
|
|
1383
1338
|
|
|
1384
1339
|
|
|
1385
|
-
# Create a default SQL factory instance
|
|
1386
1340
|
sql = SQLFactory()
|