sqlspec 0.12.1__py3-none-any.whl → 0.13.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/_sql.py +21 -180
- sqlspec/adapters/adbc/config.py +10 -12
- sqlspec/adapters/adbc/driver.py +120 -118
- sqlspec/adapters/aiosqlite/config.py +3 -3
- sqlspec/adapters/aiosqlite/driver.py +116 -141
- sqlspec/adapters/asyncmy/config.py +3 -4
- sqlspec/adapters/asyncmy/driver.py +123 -135
- sqlspec/adapters/asyncpg/config.py +3 -7
- sqlspec/adapters/asyncpg/driver.py +98 -140
- sqlspec/adapters/bigquery/config.py +4 -5
- sqlspec/adapters/bigquery/driver.py +231 -181
- sqlspec/adapters/duckdb/config.py +3 -6
- sqlspec/adapters/duckdb/driver.py +132 -124
- sqlspec/adapters/oracledb/config.py +6 -5
- sqlspec/adapters/oracledb/driver.py +242 -259
- sqlspec/adapters/psqlpy/config.py +3 -7
- sqlspec/adapters/psqlpy/driver.py +118 -93
- sqlspec/adapters/psycopg/config.py +34 -30
- sqlspec/adapters/psycopg/driver.py +342 -214
- sqlspec/adapters/sqlite/config.py +3 -3
- sqlspec/adapters/sqlite/driver.py +150 -104
- sqlspec/config.py +0 -4
- sqlspec/driver/_async.py +89 -98
- sqlspec/driver/_common.py +52 -17
- sqlspec/driver/_sync.py +81 -105
- sqlspec/driver/connection.py +207 -0
- sqlspec/driver/mixins/_csv_writer.py +91 -0
- sqlspec/driver/mixins/_pipeline.py +38 -49
- sqlspec/driver/mixins/_result_utils.py +27 -9
- sqlspec/driver/mixins/_storage.py +149 -216
- sqlspec/driver/mixins/_type_coercion.py +3 -4
- sqlspec/driver/parameters.py +138 -0
- sqlspec/exceptions.py +10 -2
- sqlspec/extensions/aiosql/adapter.py +0 -10
- sqlspec/extensions/litestar/handlers.py +0 -1
- sqlspec/extensions/litestar/plugin.py +0 -3
- sqlspec/extensions/litestar/providers.py +0 -14
- sqlspec/loader.py +31 -118
- sqlspec/protocols.py +542 -0
- sqlspec/service/__init__.py +3 -2
- sqlspec/service/_util.py +147 -0
- sqlspec/service/base.py +1116 -9
- sqlspec/statement/builder/__init__.py +42 -32
- sqlspec/statement/builder/_ddl_utils.py +0 -10
- sqlspec/statement/builder/_parsing_utils.py +10 -4
- sqlspec/statement/builder/base.py +70 -23
- sqlspec/statement/builder/column.py +283 -0
- sqlspec/statement/builder/ddl.py +102 -65
- sqlspec/statement/builder/delete.py +23 -7
- sqlspec/statement/builder/insert.py +29 -15
- sqlspec/statement/builder/merge.py +4 -4
- sqlspec/statement/builder/mixins/_aggregate_functions.py +113 -14
- sqlspec/statement/builder/mixins/_common_table_expr.py +0 -1
- sqlspec/statement/builder/mixins/_delete_from.py +1 -1
- sqlspec/statement/builder/mixins/_from.py +10 -8
- sqlspec/statement/builder/mixins/_group_by.py +0 -1
- sqlspec/statement/builder/mixins/_insert_from_select.py +0 -1
- sqlspec/statement/builder/mixins/_insert_values.py +0 -2
- sqlspec/statement/builder/mixins/_join.py +20 -13
- sqlspec/statement/builder/mixins/_limit_offset.py +3 -3
- sqlspec/statement/builder/mixins/_merge_clauses.py +3 -4
- sqlspec/statement/builder/mixins/_order_by.py +2 -2
- sqlspec/statement/builder/mixins/_pivot.py +4 -7
- sqlspec/statement/builder/mixins/_select_columns.py +6 -5
- sqlspec/statement/builder/mixins/_unpivot.py +6 -9
- sqlspec/statement/builder/mixins/_update_from.py +2 -1
- sqlspec/statement/builder/mixins/_update_set.py +11 -8
- sqlspec/statement/builder/mixins/_where.py +61 -34
- sqlspec/statement/builder/select.py +32 -17
- sqlspec/statement/builder/update.py +25 -11
- sqlspec/statement/filters.py +39 -14
- sqlspec/statement/parameter_manager.py +220 -0
- sqlspec/statement/parameters.py +210 -79
- sqlspec/statement/pipelines/__init__.py +166 -23
- sqlspec/statement/pipelines/analyzers/_analyzer.py +22 -25
- sqlspec/statement/pipelines/context.py +35 -39
- sqlspec/statement/pipelines/transformers/__init__.py +2 -3
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +19 -187
- sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +667 -43
- sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +76 -0
- sqlspec/statement/pipelines/validators/_dml_safety.py +33 -18
- sqlspec/statement/pipelines/validators/_parameter_style.py +87 -14
- sqlspec/statement/pipelines/validators/_performance.py +38 -23
- sqlspec/statement/pipelines/validators/_security.py +39 -62
- sqlspec/statement/result.py +37 -129
- sqlspec/statement/splitter.py +0 -12
- sqlspec/statement/sql.py +885 -379
- sqlspec/statement/sql_compiler.py +140 -0
- sqlspec/storage/__init__.py +10 -2
- sqlspec/storage/backends/fsspec.py +82 -35
- sqlspec/storage/backends/obstore.py +66 -49
- sqlspec/storage/capabilities.py +101 -0
- sqlspec/storage/registry.py +56 -83
- sqlspec/typing.py +6 -434
- sqlspec/utils/cached_property.py +25 -0
- sqlspec/utils/correlation.py +0 -2
- sqlspec/utils/logging.py +0 -6
- sqlspec/utils/sync_tools.py +0 -4
- sqlspec/utils/text.py +0 -5
- sqlspec/utils/type_guards.py +892 -0
- {sqlspec-0.12.1.dist-info → sqlspec-0.13.0.dist-info}/METADATA +1 -1
- sqlspec-0.13.0.dist-info/RECORD +150 -0
- sqlspec/statement/builder/protocols.py +0 -20
- sqlspec/statement/pipelines/base.py +0 -315
- sqlspec/statement/pipelines/result_types.py +0 -41
- sqlspec/statement/pipelines/transformers/_remove_comments.py +0 -66
- sqlspec/statement/pipelines/transformers/_remove_hints.py +0 -81
- sqlspec/statement/pipelines/validators/base.py +0 -67
- sqlspec/storage/protocol.py +0 -170
- sqlspec-0.12.1.dist-info/RECORD +0 -145
- {sqlspec-0.12.1.dist-info → sqlspec-0.13.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.12.1.dist-info → sqlspec-0.13.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.12.1.dist-info → sqlspec-0.13.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/statement/builder/ddl.py
CHANGED
|
@@ -12,25 +12,28 @@ from sqlspec.statement.builder.base import QueryBuilder, SafeQuery
|
|
|
12
12
|
from sqlspec.statement.result import SQLResult
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
|
+
from sqlspec.statement.builder.column import ColumnExpression
|
|
15
16
|
from sqlspec.statement.sql import SQL, SQLConfig
|
|
16
17
|
|
|
17
18
|
__all__ = (
|
|
18
19
|
"AlterOperation",
|
|
19
|
-
"
|
|
20
|
+
"AlterTable",
|
|
20
21
|
"ColumnDefinition",
|
|
21
|
-
"
|
|
22
|
+
"CommentOn",
|
|
22
23
|
"ConstraintDefinition",
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
24
|
+
"CreateIndex",
|
|
25
|
+
"CreateMaterializedView",
|
|
26
|
+
"CreateSchema",
|
|
27
|
+
"CreateTable",
|
|
28
|
+
"CreateTableAsSelect",
|
|
29
|
+
"CreateView",
|
|
27
30
|
"DDLBuilder",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
31
|
+
"DropIndex",
|
|
32
|
+
"DropSchema",
|
|
33
|
+
"DropTable",
|
|
34
|
+
"DropView",
|
|
35
|
+
"RenameTable",
|
|
36
|
+
"TruncateTable",
|
|
34
37
|
)
|
|
35
38
|
|
|
36
39
|
|
|
@@ -99,12 +102,12 @@ class ConstraintDefinition:
|
|
|
99
102
|
|
|
100
103
|
# --- CREATE TABLE ---
|
|
101
104
|
@dataclass
|
|
102
|
-
class
|
|
105
|
+
class CreateTable(DDLBuilder):
|
|
103
106
|
"""Builder for CREATE TABLE statements with columns and constraints.
|
|
104
107
|
|
|
105
108
|
Example:
|
|
106
109
|
builder = (
|
|
107
|
-
|
|
110
|
+
CreateTable("users")
|
|
108
111
|
.column("id", "SERIAL", primary_key=True)
|
|
109
112
|
.column("email", "VARCHAR(255)", not_null=True, unique=True)
|
|
110
113
|
.column("created_at", "TIMESTAMP", default="CURRENT_TIMESTAMP")
|
|
@@ -179,11 +182,9 @@ class CreateTableBuilder(DDLBuilder):
|
|
|
179
182
|
if not dtype:
|
|
180
183
|
self._raise_sql_builder_error("Column type must be a non-empty string")
|
|
181
184
|
|
|
182
|
-
# Check for duplicate column names
|
|
183
185
|
if any(col.name == name for col in self._columns):
|
|
184
186
|
self._raise_sql_builder_error(f"Column '{name}' already defined")
|
|
185
187
|
|
|
186
|
-
# Create column definition
|
|
187
188
|
column_def = ColumnDefinition(
|
|
188
189
|
name=name,
|
|
189
190
|
dtype=dtype,
|
|
@@ -215,15 +216,12 @@ class CreateTableBuilder(DDLBuilder):
|
|
|
215
216
|
if not col_list:
|
|
216
217
|
self._raise_sql_builder_error("Primary key must include at least one column")
|
|
217
218
|
|
|
218
|
-
# Check if primary key already exists
|
|
219
219
|
existing_pk = next((c for c in self._constraints if c.constraint_type == "PRIMARY KEY"), None)
|
|
220
220
|
if existing_pk:
|
|
221
|
-
# Update existing primary key to include new columns
|
|
222
221
|
for col in col_list:
|
|
223
222
|
if col not in existing_pk.columns:
|
|
224
223
|
existing_pk.columns.append(col)
|
|
225
224
|
else:
|
|
226
|
-
# Create new primary key constraint
|
|
227
225
|
constraint = ConstraintDefinition(constraint_type="PRIMARY KEY", name=name, columns=col_list)
|
|
228
226
|
self._constraints.append(constraint)
|
|
229
227
|
|
|
@@ -250,7 +248,6 @@ class CreateTableBuilder(DDLBuilder):
|
|
|
250
248
|
if len(col_list) != len(ref_col_list):
|
|
251
249
|
self._raise_sql_builder_error("Foreign key columns and referenced columns must have same length")
|
|
252
250
|
|
|
253
|
-
# Validate actions
|
|
254
251
|
valid_actions = {"CASCADE", "SET NULL", "SET DEFAULT", "RESTRICT", "NO ACTION", None}
|
|
255
252
|
if on_delete and on_delete.upper() not in valid_actions:
|
|
256
253
|
self._raise_sql_builder_error(f"Invalid ON DELETE action: {on_delete}")
|
|
@@ -285,12 +282,21 @@ class CreateTableBuilder(DDLBuilder):
|
|
|
285
282
|
self._constraints.append(constraint)
|
|
286
283
|
return self
|
|
287
284
|
|
|
288
|
-
def check_constraint(self, condition: str, name: "Optional[str]" = None) -> "Self":
|
|
285
|
+
def check_constraint(self, condition: Union[str, "ColumnExpression"], name: "Optional[str]" = None) -> "Self":
|
|
289
286
|
"""Add a check constraint."""
|
|
290
287
|
if not condition:
|
|
291
288
|
self._raise_sql_builder_error("Check constraint must have a condition")
|
|
292
289
|
|
|
293
|
-
|
|
290
|
+
condition_str: str
|
|
291
|
+
if hasattr(condition, "sqlglot_expression"):
|
|
292
|
+
# This is a ColumnExpression - render as raw SQL for DDL (no parameters)
|
|
293
|
+
sqlglot_expr = getattr(condition, "sqlglot_expression", None)
|
|
294
|
+
condition_str = sqlglot_expr.sql(dialect=self.dialect) if sqlglot_expr else str(condition)
|
|
295
|
+
else:
|
|
296
|
+
# String condition - use as-is
|
|
297
|
+
condition_str = str(condition)
|
|
298
|
+
|
|
299
|
+
constraint = ConstraintDefinition(constraint_type="CHECK", name=name, condition=condition_str)
|
|
294
300
|
|
|
295
301
|
self._constraints.append(constraint)
|
|
296
302
|
return self
|
|
@@ -325,19 +331,16 @@ class CreateTableBuilder(DDLBuilder):
|
|
|
325
331
|
if not self._columns and not self._like_table:
|
|
326
332
|
self._raise_sql_builder_error("Table must have at least one column or use LIKE clause")
|
|
327
333
|
|
|
328
|
-
# Build table identifier with schema if provided
|
|
329
334
|
if self._schema:
|
|
330
335
|
table = exp.Table(this=exp.to_identifier(self._table_name), db=exp.to_identifier(self._schema))
|
|
331
336
|
else:
|
|
332
337
|
table = exp.to_table(self._table_name)
|
|
333
338
|
|
|
334
|
-
# Build column expressions
|
|
335
339
|
column_defs: list[exp.Expression] = []
|
|
336
340
|
for col in self._columns:
|
|
337
341
|
col_expr = build_column_expression(col)
|
|
338
342
|
column_defs.append(col_expr)
|
|
339
343
|
|
|
340
|
-
# Build constraint expressions
|
|
341
344
|
for constraint in self._constraints:
|
|
342
345
|
# Skip PRIMARY KEY constraints that are already defined on columns
|
|
343
346
|
if constraint.constraint_type == "PRIMARY KEY" and len(constraint.columns) == 1:
|
|
@@ -349,7 +352,6 @@ class CreateTableBuilder(DDLBuilder):
|
|
|
349
352
|
if constraint_expr:
|
|
350
353
|
column_defs.append(constraint_expr)
|
|
351
354
|
|
|
352
|
-
# Build properties for table options
|
|
353
355
|
props: list[exp.Property] = []
|
|
354
356
|
if self._table_options.get("engine"):
|
|
355
357
|
props.append(
|
|
@@ -364,7 +366,6 @@ class CreateTableBuilder(DDLBuilder):
|
|
|
364
366
|
exp.Property(this=exp.to_identifier("PARTITION BY"), value=exp.Literal.string(self._partition_by))
|
|
365
367
|
)
|
|
366
368
|
|
|
367
|
-
# Add other table options
|
|
368
369
|
for key, value in self._table_options.items():
|
|
369
370
|
if key != "engine": # Skip already handled options
|
|
370
371
|
if isinstance(value, str):
|
|
@@ -374,15 +375,12 @@ class CreateTableBuilder(DDLBuilder):
|
|
|
374
375
|
|
|
375
376
|
properties_node = exp.Properties(expressions=props) if props else None
|
|
376
377
|
|
|
377
|
-
# Build schema expression
|
|
378
378
|
schema_expr = exp.Schema(expressions=column_defs) if column_defs else None
|
|
379
379
|
|
|
380
|
-
# Handle LIKE clause
|
|
381
380
|
like_expr = None
|
|
382
381
|
if self._like_table:
|
|
383
382
|
like_expr = exp.to_table(self._like_table)
|
|
384
383
|
|
|
385
|
-
# Create the CREATE expression
|
|
386
384
|
return exp.Create(
|
|
387
385
|
kind="TABLE",
|
|
388
386
|
this=table,
|
|
@@ -406,13 +404,23 @@ class CreateTableBuilder(DDLBuilder):
|
|
|
406
404
|
|
|
407
405
|
# --- DROP TABLE ---
|
|
408
406
|
@dataclass
|
|
409
|
-
class
|
|
407
|
+
class DropTable(DDLBuilder):
|
|
410
408
|
"""Builder for DROP TABLE [IF EXISTS] ... [CASCADE|RESTRICT]."""
|
|
411
409
|
|
|
412
410
|
_table_name: Optional[str] = None
|
|
413
411
|
_if_exists: bool = False
|
|
414
412
|
_cascade: Optional[bool] = None # True: CASCADE, False: RESTRICT, None: not set
|
|
415
413
|
|
|
414
|
+
def __init__(self, table_name: str, **kwargs: Any) -> None:
|
|
415
|
+
"""Initialize DROP TABLE with table name.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
table_name: Name of the table to drop
|
|
419
|
+
**kwargs: Additional DDLBuilder arguments
|
|
420
|
+
"""
|
|
421
|
+
super().__init__(**kwargs)
|
|
422
|
+
self._table_name = table_name
|
|
423
|
+
|
|
416
424
|
def table(self, name: str) -> Self:
|
|
417
425
|
self._table_name = name
|
|
418
426
|
return self
|
|
@@ -439,7 +447,7 @@ class DropTableBuilder(DDLBuilder):
|
|
|
439
447
|
|
|
440
448
|
# --- DROP INDEX ---
|
|
441
449
|
@dataclass
|
|
442
|
-
class
|
|
450
|
+
class DropIndex(DDLBuilder):
|
|
443
451
|
"""Builder for DROP INDEX [IF EXISTS] ... [ON table] [CASCADE|RESTRICT]."""
|
|
444
452
|
|
|
445
453
|
_index_name: Optional[str] = None
|
|
@@ -447,6 +455,16 @@ class DropIndexBuilder(DDLBuilder):
|
|
|
447
455
|
_if_exists: bool = False
|
|
448
456
|
_cascade: Optional[bool] = None
|
|
449
457
|
|
|
458
|
+
def __init__(self, index_name: str, **kwargs: Any) -> None:
|
|
459
|
+
"""Initialize DROP INDEX with index name.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
index_name: Name of the index to drop
|
|
463
|
+
**kwargs: Additional DDLBuilder arguments
|
|
464
|
+
"""
|
|
465
|
+
super().__init__(**kwargs)
|
|
466
|
+
self._index_name = index_name
|
|
467
|
+
|
|
450
468
|
def name(self, index_name: str) -> Self:
|
|
451
469
|
self._index_name = index_name
|
|
452
470
|
return self
|
|
@@ -481,7 +499,7 @@ class DropIndexBuilder(DDLBuilder):
|
|
|
481
499
|
|
|
482
500
|
# --- DROP VIEW ---
|
|
483
501
|
@dataclass
|
|
484
|
-
class
|
|
502
|
+
class DropView(DDLBuilder):
|
|
485
503
|
"""Builder for DROP VIEW [IF EXISTS] ... [CASCADE|RESTRICT]."""
|
|
486
504
|
|
|
487
505
|
_view_name: Optional[str] = None
|
|
@@ -514,7 +532,7 @@ class DropViewBuilder(DDLBuilder):
|
|
|
514
532
|
|
|
515
533
|
# --- DROP SCHEMA ---
|
|
516
534
|
@dataclass
|
|
517
|
-
class
|
|
535
|
+
class DropSchema(DDLBuilder):
|
|
518
536
|
"""Builder for DROP SCHEMA [IF EXISTS] ... [CASCADE|RESTRICT]."""
|
|
519
537
|
|
|
520
538
|
_schema_name: Optional[str] = None
|
|
@@ -547,7 +565,7 @@ class DropSchemaBuilder(DDLBuilder):
|
|
|
547
565
|
|
|
548
566
|
# --- CREATE INDEX ---
|
|
549
567
|
@dataclass
|
|
550
|
-
class
|
|
568
|
+
class CreateIndex(DDLBuilder):
|
|
551
569
|
"""Builder for CREATE [UNIQUE] INDEX [IF NOT EXISTS] ... ON ... (...).
|
|
552
570
|
|
|
553
571
|
Supports columns, expressions, ordering, using, and where.
|
|
@@ -561,6 +579,19 @@ class CreateIndexBuilder(DDLBuilder):
|
|
|
561
579
|
_using: Optional[str] = None
|
|
562
580
|
_where: Optional[Union[str, exp.Expression]] = None
|
|
563
581
|
|
|
582
|
+
def __init__(self, index_name: str, **kwargs: Any) -> None:
|
|
583
|
+
"""Initialize CREATE INDEX with index name.
|
|
584
|
+
|
|
585
|
+
Args:
|
|
586
|
+
index_name: Name of the index to create
|
|
587
|
+
**kwargs: Additional DDLBuilder arguments
|
|
588
|
+
"""
|
|
589
|
+
super().__init__(**kwargs)
|
|
590
|
+
self._index_name = index_name
|
|
591
|
+
# Initialize dataclass fields that may not be set by super().__init__
|
|
592
|
+
if not hasattr(self, "_columns"):
|
|
593
|
+
self._columns = []
|
|
594
|
+
|
|
564
595
|
def name(self, index_name: str) -> Self:
|
|
565
596
|
self._index_name = index_name
|
|
566
597
|
return self
|
|
@@ -620,7 +651,7 @@ class CreateIndexBuilder(DDLBuilder):
|
|
|
620
651
|
|
|
621
652
|
# --- TRUNCATE TABLE ---
|
|
622
653
|
@dataclass
|
|
623
|
-
class
|
|
654
|
+
class TruncateTable(DDLBuilder):
|
|
624
655
|
"""Builder for TRUNCATE TABLE ... [CASCADE|RESTRICT] [RESTART IDENTITY|CONTINUE IDENTITY]."""
|
|
625
656
|
|
|
626
657
|
_table_name: Optional[str] = None
|
|
@@ -673,7 +704,7 @@ class AlterOperation:
|
|
|
673
704
|
|
|
674
705
|
# --- CREATE SCHEMA ---
|
|
675
706
|
@dataclass
|
|
676
|
-
class
|
|
707
|
+
class CreateSchema(DDLBuilder):
|
|
677
708
|
"""Builder for CREATE SCHEMA [IF NOT EXISTS] schema_name [AUTHORIZATION user_name]."""
|
|
678
709
|
|
|
679
710
|
_schema_name: Optional[str] = None
|
|
@@ -710,7 +741,7 @@ class CreateSchemaBuilder(DDLBuilder):
|
|
|
710
741
|
|
|
711
742
|
|
|
712
743
|
@dataclass
|
|
713
|
-
class
|
|
744
|
+
class CreateTableAsSelect(DDLBuilder):
|
|
714
745
|
"""Builder for CREATE TABLE [IF NOT EXISTS] ... AS SELECT ... (CTAS).
|
|
715
746
|
|
|
716
747
|
Supports optional column list and parameterized SELECT sources.
|
|
@@ -759,24 +790,33 @@ class CreateTableAsSelectBuilder(DDLBuilder):
|
|
|
759
790
|
if self._select_query is None:
|
|
760
791
|
self._raise_sql_builder_error("SELECT query must be set for CREATE TABLE AS SELECT.")
|
|
761
792
|
|
|
762
|
-
# Determine the SELECT expression and parameters
|
|
763
793
|
select_expr = None
|
|
764
794
|
select_params = None
|
|
765
|
-
from sqlspec.statement.builder.select import
|
|
795
|
+
from sqlspec.statement.builder.select import Select
|
|
766
796
|
from sqlspec.statement.sql import SQL
|
|
767
797
|
|
|
768
798
|
if isinstance(self._select_query, SQL):
|
|
769
799
|
select_expr = self._select_query.expression
|
|
770
800
|
select_params = getattr(self._select_query, "parameters", None)
|
|
771
|
-
elif isinstance(self._select_query,
|
|
801
|
+
elif isinstance(self._select_query, Select):
|
|
772
802
|
select_expr = getattr(self._select_query, "_expression", None)
|
|
773
803
|
select_params = getattr(self._select_query, "_parameters", None)
|
|
804
|
+
|
|
805
|
+
with_ctes = getattr(self._select_query, "_with_ctes", {})
|
|
806
|
+
if with_ctes and select_expr and isinstance(select_expr, exp.Select):
|
|
807
|
+
for alias, cte in with_ctes.items():
|
|
808
|
+
if hasattr(select_expr, "with_"):
|
|
809
|
+
select_expr = select_expr.with_(
|
|
810
|
+
cte.this, # The CTE's SELECT expression
|
|
811
|
+
as_=alias,
|
|
812
|
+
copy=False,
|
|
813
|
+
)
|
|
774
814
|
elif isinstance(self._select_query, str):
|
|
775
815
|
select_expr = exp.maybe_parse(self._select_query)
|
|
776
816
|
select_params = None
|
|
777
817
|
else:
|
|
778
818
|
self._raise_sql_builder_error("Unsupported type for SELECT query in CTAS.")
|
|
779
|
-
if select_expr is None
|
|
819
|
+
if select_expr is None:
|
|
780
820
|
self._raise_sql_builder_error("SELECT query must be a valid SELECT expression.")
|
|
781
821
|
|
|
782
822
|
# Merge parameters from SELECT if present
|
|
@@ -786,7 +826,6 @@ class CreateTableAsSelectBuilder(DDLBuilder):
|
|
|
786
826
|
# The SELECT query already has unique parameter names
|
|
787
827
|
self._parameters[p_name] = p_value
|
|
788
828
|
|
|
789
|
-
# Build schema/column list if provided
|
|
790
829
|
schema_expr = None
|
|
791
830
|
if self._columns:
|
|
792
831
|
schema_expr = exp.Schema(expressions=[exp.column(c) for c in self._columns])
|
|
@@ -801,7 +840,7 @@ class CreateTableAsSelectBuilder(DDLBuilder):
|
|
|
801
840
|
|
|
802
841
|
|
|
803
842
|
@dataclass
|
|
804
|
-
class
|
|
843
|
+
class CreateMaterializedView(DDLBuilder):
|
|
805
844
|
"""Builder for CREATE MATERIALIZED VIEW [IF NOT EXISTS] ... AS SELECT ...
|
|
806
845
|
|
|
807
846
|
Supports optional column list, parameterized SELECT sources, and dialect-specific options.
|
|
@@ -868,16 +907,15 @@ class CreateMaterializedViewBuilder(DDLBuilder):
|
|
|
868
907
|
if self._select_query is None:
|
|
869
908
|
self._raise_sql_builder_error("SELECT query must be set for CREATE MATERIALIZED VIEW.")
|
|
870
909
|
|
|
871
|
-
# Determine the SELECT expression and parameters
|
|
872
910
|
select_expr = None
|
|
873
911
|
select_params = None
|
|
874
|
-
from sqlspec.statement.builder.select import
|
|
912
|
+
from sqlspec.statement.builder.select import Select
|
|
875
913
|
from sqlspec.statement.sql import SQL
|
|
876
914
|
|
|
877
915
|
if isinstance(self._select_query, SQL):
|
|
878
916
|
select_expr = self._select_query.expression
|
|
879
917
|
select_params = getattr(self._select_query, "parameters", None)
|
|
880
|
-
elif isinstance(self._select_query,
|
|
918
|
+
elif isinstance(self._select_query, Select):
|
|
881
919
|
select_expr = getattr(self._select_query, "_expression", None)
|
|
882
920
|
select_params = getattr(self._select_query, "_parameters", None)
|
|
883
921
|
elif isinstance(self._select_query, str):
|
|
@@ -895,12 +933,10 @@ class CreateMaterializedViewBuilder(DDLBuilder):
|
|
|
895
933
|
# The SELECT query already has unique parameter names
|
|
896
934
|
self._parameters[p_name] = p_value
|
|
897
935
|
|
|
898
|
-
# Build schema/column list if provided
|
|
899
936
|
schema_expr = None
|
|
900
937
|
if self._columns:
|
|
901
938
|
schema_expr = exp.Schema(expressions=[exp.column(c) for c in self._columns])
|
|
902
939
|
|
|
903
|
-
# Build properties for dialect-specific options
|
|
904
940
|
props: list[exp.Property] = []
|
|
905
941
|
if self._refresh_mode:
|
|
906
942
|
props.append(
|
|
@@ -932,7 +968,7 @@ class CreateMaterializedViewBuilder(DDLBuilder):
|
|
|
932
968
|
|
|
933
969
|
|
|
934
970
|
@dataclass
|
|
935
|
-
class
|
|
971
|
+
class CreateView(DDLBuilder):
|
|
936
972
|
"""Builder for CREATE VIEW [IF NOT EXISTS] ... AS SELECT ...
|
|
937
973
|
|
|
938
974
|
Supports optional column list, parameterized SELECT sources, and hints.
|
|
@@ -970,16 +1006,15 @@ class CreateViewBuilder(DDLBuilder):
|
|
|
970
1006
|
if self._select_query is None:
|
|
971
1007
|
self._raise_sql_builder_error("SELECT query must be set for CREATE VIEW.")
|
|
972
1008
|
|
|
973
|
-
# Determine the SELECT expression and parameters
|
|
974
1009
|
select_expr = None
|
|
975
1010
|
select_params = None
|
|
976
|
-
from sqlspec.statement.builder.select import
|
|
1011
|
+
from sqlspec.statement.builder.select import Select
|
|
977
1012
|
from sqlspec.statement.sql import SQL
|
|
978
1013
|
|
|
979
1014
|
if isinstance(self._select_query, SQL):
|
|
980
1015
|
select_expr = self._select_query.expression
|
|
981
1016
|
select_params = getattr(self._select_query, "parameters", None)
|
|
982
|
-
elif isinstance(self._select_query,
|
|
1017
|
+
elif isinstance(self._select_query, Select):
|
|
983
1018
|
select_expr = getattr(self._select_query, "_expression", None)
|
|
984
1019
|
select_params = getattr(self._select_query, "_parameters", None)
|
|
985
1020
|
elif isinstance(self._select_query, str):
|
|
@@ -997,12 +1032,10 @@ class CreateViewBuilder(DDLBuilder):
|
|
|
997
1032
|
# The SELECT query already has unique parameter names
|
|
998
1033
|
self._parameters[p_name] = p_value
|
|
999
1034
|
|
|
1000
|
-
# Build schema/column list if provided
|
|
1001
1035
|
schema_expr = None
|
|
1002
1036
|
if self._columns:
|
|
1003
1037
|
schema_expr = exp.Schema(expressions=[exp.column(c) for c in self._columns])
|
|
1004
1038
|
|
|
1005
|
-
# Build properties for hints
|
|
1006
1039
|
props: list[exp.Property] = [
|
|
1007
1040
|
exp.Property(this=exp.to_identifier("HINT"), value=exp.Literal.string(h)) for h in self._hints
|
|
1008
1041
|
]
|
|
@@ -1019,7 +1052,7 @@ class CreateViewBuilder(DDLBuilder):
|
|
|
1019
1052
|
|
|
1020
1053
|
|
|
1021
1054
|
@dataclass
|
|
1022
|
-
class
|
|
1055
|
+
class AlterTable(DDLBuilder):
|
|
1023
1056
|
"""Builder for ALTER TABLE with granular operations.
|
|
1024
1057
|
|
|
1025
1058
|
Supports column operations (add, drop, alter type, rename) and constraint operations.
|
|
@@ -1124,7 +1157,7 @@ class AlterTableBuilder(DDLBuilder):
|
|
|
1124
1157
|
name: "Optional[str]" = None,
|
|
1125
1158
|
references_table: "Optional[str]" = None,
|
|
1126
1159
|
references_columns: "Optional[Union[str, list[str]]]" = None,
|
|
1127
|
-
condition: "Optional[str]" = None,
|
|
1160
|
+
condition: "Optional[Union[str, ColumnExpression]]" = None,
|
|
1128
1161
|
on_delete: "Optional[str]" = None,
|
|
1129
1162
|
on_update: "Optional[str]" = None,
|
|
1130
1163
|
) -> "Self":
|
|
@@ -1154,13 +1187,22 @@ class AlterTableBuilder(DDLBuilder):
|
|
|
1154
1187
|
if references_columns is not None:
|
|
1155
1188
|
ref_col_list = [references_columns] if isinstance(references_columns, str) else list(references_columns)
|
|
1156
1189
|
|
|
1190
|
+
# Handle ColumnExpression for CHECK constraints
|
|
1191
|
+
condition_str: Optional[str] = None
|
|
1192
|
+
if condition is not None:
|
|
1193
|
+
if hasattr(condition, "sqlglot_expression"):
|
|
1194
|
+
sqlglot_expr = getattr(condition, "sqlglot_expression", None)
|
|
1195
|
+
condition_str = sqlglot_expr.sql(dialect=self.dialect) if sqlglot_expr else str(condition)
|
|
1196
|
+
else:
|
|
1197
|
+
condition_str = str(condition)
|
|
1198
|
+
|
|
1157
1199
|
constraint_def = ConstraintDefinition(
|
|
1158
1200
|
constraint_type=constraint_type.upper(),
|
|
1159
1201
|
name=name,
|
|
1160
1202
|
columns=col_list or [],
|
|
1161
1203
|
references_table=references_table,
|
|
1162
1204
|
references_columns=ref_col_list or [],
|
|
1163
|
-
condition=
|
|
1205
|
+
condition=condition_str,
|
|
1164
1206
|
on_delete=on_delete,
|
|
1165
1207
|
on_update=on_update,
|
|
1166
1208
|
)
|
|
@@ -1280,10 +1322,8 @@ class AlterTableBuilder(DDLBuilder):
|
|
|
1280
1322
|
if not op.column_definition or op.column_definition.default is None:
|
|
1281
1323
|
self._raise_sql_builder_error("Default value required for SET DEFAULT")
|
|
1282
1324
|
default_val = op.column_definition.default
|
|
1283
|
-
# Handle different default value types
|
|
1284
1325
|
default_expr: Optional[exp.Expression]
|
|
1285
1326
|
if isinstance(default_val, str):
|
|
1286
|
-
# Check if it's a function/expression or a literal string
|
|
1287
1327
|
if default_val.upper() in {"CURRENT_TIMESTAMP", "CURRENT_DATE", "CURRENT_TIME"} or "(" in default_val:
|
|
1288
1328
|
default_expr = exp.maybe_parse(default_val)
|
|
1289
1329
|
else:
|
|
@@ -1306,7 +1346,7 @@ class AlterTableBuilder(DDLBuilder):
|
|
|
1306
1346
|
|
|
1307
1347
|
|
|
1308
1348
|
@dataclass
|
|
1309
|
-
class
|
|
1349
|
+
class CommentOn(DDLBuilder):
|
|
1310
1350
|
"""Builder for COMMENT ON ... IS ... statements.
|
|
1311
1351
|
|
|
1312
1352
|
Supports COMMENT ON TABLE and COMMENT ON COLUMN.
|
|
@@ -1335,12 +1375,10 @@ class CommentOnBuilder(DDLBuilder):
|
|
|
1335
1375
|
|
|
1336
1376
|
def _create_base_expression(self) -> exp.Expression:
|
|
1337
1377
|
if self._target_type == "TABLE" and self._table and self._comment is not None:
|
|
1338
|
-
# Create a proper Comment expression
|
|
1339
1378
|
return exp.Comment(
|
|
1340
1379
|
this=exp.to_table(self._table), kind="TABLE", expression=exp.Literal.string(self._comment)
|
|
1341
1380
|
)
|
|
1342
1381
|
if self._target_type == "COLUMN" and self._table and self._column and self._comment is not None:
|
|
1343
|
-
# Create a proper Comment expression for column
|
|
1344
1382
|
return exp.Comment(
|
|
1345
1383
|
this=exp.Column(table=self._table, this=self._column),
|
|
1346
1384
|
kind="COLUMN",
|
|
@@ -1351,7 +1389,7 @@ class CommentOnBuilder(DDLBuilder):
|
|
|
1351
1389
|
|
|
1352
1390
|
|
|
1353
1391
|
@dataclass
|
|
1354
|
-
class
|
|
1392
|
+
class RenameTable(DDLBuilder):
|
|
1355
1393
|
"""Builder for ALTER TABLE ... RENAME TO ... statements.
|
|
1356
1394
|
|
|
1357
1395
|
Supports renaming a table.
|
|
@@ -1371,7 +1409,6 @@ class RenameTableBuilder(DDLBuilder):
|
|
|
1371
1409
|
def _create_base_expression(self) -> exp.Expression:
|
|
1372
1410
|
if not self._old_name or not self._new_name:
|
|
1373
1411
|
self._raise_sql_builder_error("Both old and new table names must be set for RENAME TABLE.")
|
|
1374
|
-
# Create ALTER TABLE with RENAME TO action
|
|
1375
1412
|
return exp.Alter(
|
|
1376
1413
|
this=exp.to_table(self._old_name),
|
|
1377
1414
|
kind="TABLE",
|
|
@@ -5,7 +5,7 @@ with automatic parameter binding and validation.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from dataclasses import dataclass, field
|
|
8
|
-
from typing import Optional
|
|
8
|
+
from typing import Any, Optional
|
|
9
9
|
|
|
10
10
|
from sqlglot import exp
|
|
11
11
|
|
|
@@ -14,11 +14,11 @@ from sqlspec.statement.builder.mixins import DeleteFromClauseMixin, ReturningCla
|
|
|
14
14
|
from sqlspec.statement.result import SQLResult
|
|
15
15
|
from sqlspec.typing import RowT
|
|
16
16
|
|
|
17
|
-
__all__ = ("
|
|
17
|
+
__all__ = ("Delete",)
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
@dataclass(unsafe_hash=True)
|
|
21
|
-
class
|
|
21
|
+
class Delete(QueryBuilder[RowT], WhereClauseMixin, ReturningClauseMixin, DeleteFromClauseMixin):
|
|
22
22
|
"""Builder for DELETE statements.
|
|
23
23
|
|
|
24
24
|
This builder provides a fluent interface for constructing SQL DELETE statements
|
|
@@ -28,13 +28,14 @@ class DeleteBuilder(QueryBuilder[RowT], WhereClauseMixin, ReturningClauseMixin,
|
|
|
28
28
|
Example:
|
|
29
29
|
```python
|
|
30
30
|
# Basic DELETE
|
|
31
|
-
delete_query = (
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
delete_query = Delete().from_("users").where("age < 18")
|
|
32
|
+
|
|
33
|
+
# Even more concise with constructor
|
|
34
|
+
delete_query = Delete("users").where("age < 18")
|
|
34
35
|
|
|
35
36
|
# DELETE with parameterized conditions
|
|
36
37
|
delete_query = (
|
|
37
|
-
|
|
38
|
+
Delete()
|
|
38
39
|
.from_("users")
|
|
39
40
|
.where_eq("status", "inactive")
|
|
40
41
|
.where_in("category", ["test", "demo"])
|
|
@@ -44,6 +45,21 @@ class DeleteBuilder(QueryBuilder[RowT], WhereClauseMixin, ReturningClauseMixin,
|
|
|
44
45
|
|
|
45
46
|
_table: "Optional[str]" = field(default=None, init=False)
|
|
46
47
|
|
|
48
|
+
def __init__(self, table: Optional[str] = None, **kwargs: Any) -> None:
|
|
49
|
+
"""Initialize DELETE with optional table.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
table: Target table name
|
|
53
|
+
**kwargs: Additional QueryBuilder arguments
|
|
54
|
+
"""
|
|
55
|
+
super().__init__(**kwargs)
|
|
56
|
+
|
|
57
|
+
# Initialize fields from dataclass
|
|
58
|
+
self._table = None
|
|
59
|
+
|
|
60
|
+
if table:
|
|
61
|
+
self.from_(table)
|
|
62
|
+
|
|
47
63
|
@property
|
|
48
64
|
def _expected_result_type(self) -> "type[SQLResult[RowT]]":
|
|
49
65
|
"""Get the expected result type for DELETE operations.
|