sqlspec 0.12.2__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 +100 -130
- 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 +125 -167
- sqlspec/adapters/duckdb/config.py +3 -6
- sqlspec/adapters/duckdb/driver.py +114 -111
- 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 +18 -31
- sqlspec/adapters/psycopg/driver.py +283 -236
- sqlspec/adapters/sqlite/config.py +3 -3
- sqlspec/adapters/sqlite/driver.py +103 -97
- 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 +67 -181
- 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 +25 -90
- 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 +67 -22
- sqlspec/statement/builder/column.py +283 -0
- sqlspec/statement/builder/ddl.py +91 -67
- 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 +21 -20
- 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 +628 -58
- 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 +863 -391
- sqlspec/statement/sql_compiler.py +140 -0
- sqlspec/storage/__init__.py +10 -2
- sqlspec/storage/backends/fsspec.py +53 -8
- sqlspec/storage/backends/obstore.py +15 -19
- 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.2.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 -173
- sqlspec-0.12.2.dist-info/RECORD +0 -145
- {sqlspec-0.12.2.dist-info → sqlspec-0.13.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.12.2.dist-info → sqlspec-0.13.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.12.2.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,20 @@ 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,
|
|
772
|
-
# Get the expression and parameters directly
|
|
801
|
+
elif isinstance(self._select_query, Select):
|
|
773
802
|
select_expr = getattr(self._select_query, "_expression", None)
|
|
774
803
|
select_params = getattr(self._select_query, "_parameters", None)
|
|
775
804
|
|
|
776
|
-
# Apply CTEs if present
|
|
777
805
|
with_ctes = getattr(self._select_query, "_with_ctes", {})
|
|
778
806
|
if with_ctes and select_expr and isinstance(select_expr, exp.Select):
|
|
779
|
-
# Apply CTEs directly to the SELECT expression using sqlglot's with_ method
|
|
780
807
|
for alias, cte in with_ctes.items():
|
|
781
808
|
if hasattr(select_expr, "with_"):
|
|
782
809
|
select_expr = select_expr.with_(
|
|
@@ -799,7 +826,6 @@ class CreateTableAsSelectBuilder(DDLBuilder):
|
|
|
799
826
|
# The SELECT query already has unique parameter names
|
|
800
827
|
self._parameters[p_name] = p_value
|
|
801
828
|
|
|
802
|
-
# Build schema/column list if provided
|
|
803
829
|
schema_expr = None
|
|
804
830
|
if self._columns:
|
|
805
831
|
schema_expr = exp.Schema(expressions=[exp.column(c) for c in self._columns])
|
|
@@ -814,7 +840,7 @@ class CreateTableAsSelectBuilder(DDLBuilder):
|
|
|
814
840
|
|
|
815
841
|
|
|
816
842
|
@dataclass
|
|
817
|
-
class
|
|
843
|
+
class CreateMaterializedView(DDLBuilder):
|
|
818
844
|
"""Builder for CREATE MATERIALIZED VIEW [IF NOT EXISTS] ... AS SELECT ...
|
|
819
845
|
|
|
820
846
|
Supports optional column list, parameterized SELECT sources, and dialect-specific options.
|
|
@@ -881,16 +907,15 @@ class CreateMaterializedViewBuilder(DDLBuilder):
|
|
|
881
907
|
if self._select_query is None:
|
|
882
908
|
self._raise_sql_builder_error("SELECT query must be set for CREATE MATERIALIZED VIEW.")
|
|
883
909
|
|
|
884
|
-
# Determine the SELECT expression and parameters
|
|
885
910
|
select_expr = None
|
|
886
911
|
select_params = None
|
|
887
|
-
from sqlspec.statement.builder.select import
|
|
912
|
+
from sqlspec.statement.builder.select import Select
|
|
888
913
|
from sqlspec.statement.sql import SQL
|
|
889
914
|
|
|
890
915
|
if isinstance(self._select_query, SQL):
|
|
891
916
|
select_expr = self._select_query.expression
|
|
892
917
|
select_params = getattr(self._select_query, "parameters", None)
|
|
893
|
-
elif isinstance(self._select_query,
|
|
918
|
+
elif isinstance(self._select_query, Select):
|
|
894
919
|
select_expr = getattr(self._select_query, "_expression", None)
|
|
895
920
|
select_params = getattr(self._select_query, "_parameters", None)
|
|
896
921
|
elif isinstance(self._select_query, str):
|
|
@@ -908,12 +933,10 @@ class CreateMaterializedViewBuilder(DDLBuilder):
|
|
|
908
933
|
# The SELECT query already has unique parameter names
|
|
909
934
|
self._parameters[p_name] = p_value
|
|
910
935
|
|
|
911
|
-
# Build schema/column list if provided
|
|
912
936
|
schema_expr = None
|
|
913
937
|
if self._columns:
|
|
914
938
|
schema_expr = exp.Schema(expressions=[exp.column(c) for c in self._columns])
|
|
915
939
|
|
|
916
|
-
# Build properties for dialect-specific options
|
|
917
940
|
props: list[exp.Property] = []
|
|
918
941
|
if self._refresh_mode:
|
|
919
942
|
props.append(
|
|
@@ -945,7 +968,7 @@ class CreateMaterializedViewBuilder(DDLBuilder):
|
|
|
945
968
|
|
|
946
969
|
|
|
947
970
|
@dataclass
|
|
948
|
-
class
|
|
971
|
+
class CreateView(DDLBuilder):
|
|
949
972
|
"""Builder for CREATE VIEW [IF NOT EXISTS] ... AS SELECT ...
|
|
950
973
|
|
|
951
974
|
Supports optional column list, parameterized SELECT sources, and hints.
|
|
@@ -983,16 +1006,15 @@ class CreateViewBuilder(DDLBuilder):
|
|
|
983
1006
|
if self._select_query is None:
|
|
984
1007
|
self._raise_sql_builder_error("SELECT query must be set for CREATE VIEW.")
|
|
985
1008
|
|
|
986
|
-
# Determine the SELECT expression and parameters
|
|
987
1009
|
select_expr = None
|
|
988
1010
|
select_params = None
|
|
989
|
-
from sqlspec.statement.builder.select import
|
|
1011
|
+
from sqlspec.statement.builder.select import Select
|
|
990
1012
|
from sqlspec.statement.sql import SQL
|
|
991
1013
|
|
|
992
1014
|
if isinstance(self._select_query, SQL):
|
|
993
1015
|
select_expr = self._select_query.expression
|
|
994
1016
|
select_params = getattr(self._select_query, "parameters", None)
|
|
995
|
-
elif isinstance(self._select_query,
|
|
1017
|
+
elif isinstance(self._select_query, Select):
|
|
996
1018
|
select_expr = getattr(self._select_query, "_expression", None)
|
|
997
1019
|
select_params = getattr(self._select_query, "_parameters", None)
|
|
998
1020
|
elif isinstance(self._select_query, str):
|
|
@@ -1010,12 +1032,10 @@ class CreateViewBuilder(DDLBuilder):
|
|
|
1010
1032
|
# The SELECT query already has unique parameter names
|
|
1011
1033
|
self._parameters[p_name] = p_value
|
|
1012
1034
|
|
|
1013
|
-
# Build schema/column list if provided
|
|
1014
1035
|
schema_expr = None
|
|
1015
1036
|
if self._columns:
|
|
1016
1037
|
schema_expr = exp.Schema(expressions=[exp.column(c) for c in self._columns])
|
|
1017
1038
|
|
|
1018
|
-
# Build properties for hints
|
|
1019
1039
|
props: list[exp.Property] = [
|
|
1020
1040
|
exp.Property(this=exp.to_identifier("HINT"), value=exp.Literal.string(h)) for h in self._hints
|
|
1021
1041
|
]
|
|
@@ -1032,7 +1052,7 @@ class CreateViewBuilder(DDLBuilder):
|
|
|
1032
1052
|
|
|
1033
1053
|
|
|
1034
1054
|
@dataclass
|
|
1035
|
-
class
|
|
1055
|
+
class AlterTable(DDLBuilder):
|
|
1036
1056
|
"""Builder for ALTER TABLE with granular operations.
|
|
1037
1057
|
|
|
1038
1058
|
Supports column operations (add, drop, alter type, rename) and constraint operations.
|
|
@@ -1137,7 +1157,7 @@ class AlterTableBuilder(DDLBuilder):
|
|
|
1137
1157
|
name: "Optional[str]" = None,
|
|
1138
1158
|
references_table: "Optional[str]" = None,
|
|
1139
1159
|
references_columns: "Optional[Union[str, list[str]]]" = None,
|
|
1140
|
-
condition: "Optional[str]" = None,
|
|
1160
|
+
condition: "Optional[Union[str, ColumnExpression]]" = None,
|
|
1141
1161
|
on_delete: "Optional[str]" = None,
|
|
1142
1162
|
on_update: "Optional[str]" = None,
|
|
1143
1163
|
) -> "Self":
|
|
@@ -1167,13 +1187,22 @@ class AlterTableBuilder(DDLBuilder):
|
|
|
1167
1187
|
if references_columns is not None:
|
|
1168
1188
|
ref_col_list = [references_columns] if isinstance(references_columns, str) else list(references_columns)
|
|
1169
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
|
+
|
|
1170
1199
|
constraint_def = ConstraintDefinition(
|
|
1171
1200
|
constraint_type=constraint_type.upper(),
|
|
1172
1201
|
name=name,
|
|
1173
1202
|
columns=col_list or [],
|
|
1174
1203
|
references_table=references_table,
|
|
1175
1204
|
references_columns=ref_col_list or [],
|
|
1176
|
-
condition=
|
|
1205
|
+
condition=condition_str,
|
|
1177
1206
|
on_delete=on_delete,
|
|
1178
1207
|
on_update=on_update,
|
|
1179
1208
|
)
|
|
@@ -1293,10 +1322,8 @@ class AlterTableBuilder(DDLBuilder):
|
|
|
1293
1322
|
if not op.column_definition or op.column_definition.default is None:
|
|
1294
1323
|
self._raise_sql_builder_error("Default value required for SET DEFAULT")
|
|
1295
1324
|
default_val = op.column_definition.default
|
|
1296
|
-
# Handle different default value types
|
|
1297
1325
|
default_expr: Optional[exp.Expression]
|
|
1298
1326
|
if isinstance(default_val, str):
|
|
1299
|
-
# Check if it's a function/expression or a literal string
|
|
1300
1327
|
if default_val.upper() in {"CURRENT_TIMESTAMP", "CURRENT_DATE", "CURRENT_TIME"} or "(" in default_val:
|
|
1301
1328
|
default_expr = exp.maybe_parse(default_val)
|
|
1302
1329
|
else:
|
|
@@ -1319,7 +1346,7 @@ class AlterTableBuilder(DDLBuilder):
|
|
|
1319
1346
|
|
|
1320
1347
|
|
|
1321
1348
|
@dataclass
|
|
1322
|
-
class
|
|
1349
|
+
class CommentOn(DDLBuilder):
|
|
1323
1350
|
"""Builder for COMMENT ON ... IS ... statements.
|
|
1324
1351
|
|
|
1325
1352
|
Supports COMMENT ON TABLE and COMMENT ON COLUMN.
|
|
@@ -1348,12 +1375,10 @@ class CommentOnBuilder(DDLBuilder):
|
|
|
1348
1375
|
|
|
1349
1376
|
def _create_base_expression(self) -> exp.Expression:
|
|
1350
1377
|
if self._target_type == "TABLE" and self._table and self._comment is not None:
|
|
1351
|
-
# Create a proper Comment expression
|
|
1352
1378
|
return exp.Comment(
|
|
1353
1379
|
this=exp.to_table(self._table), kind="TABLE", expression=exp.Literal.string(self._comment)
|
|
1354
1380
|
)
|
|
1355
1381
|
if self._target_type == "COLUMN" and self._table and self._column and self._comment is not None:
|
|
1356
|
-
# Create a proper Comment expression for column
|
|
1357
1382
|
return exp.Comment(
|
|
1358
1383
|
this=exp.Column(table=self._table, this=self._column),
|
|
1359
1384
|
kind="COLUMN",
|
|
@@ -1364,7 +1389,7 @@ class CommentOnBuilder(DDLBuilder):
|
|
|
1364
1389
|
|
|
1365
1390
|
|
|
1366
1391
|
@dataclass
|
|
1367
|
-
class
|
|
1392
|
+
class RenameTable(DDLBuilder):
|
|
1368
1393
|
"""Builder for ALTER TABLE ... RENAME TO ... statements.
|
|
1369
1394
|
|
|
1370
1395
|
Supports renaming a table.
|
|
@@ -1384,7 +1409,6 @@ class RenameTableBuilder(DDLBuilder):
|
|
|
1384
1409
|
def _create_base_expression(self) -> exp.Expression:
|
|
1385
1410
|
if not self._old_name or not self._new_name:
|
|
1386
1411
|
self._raise_sql_builder_error("Both old and new table names must be set for RENAME TABLE.")
|
|
1387
|
-
# Create ALTER TABLE with RENAME TO action
|
|
1388
1412
|
return exp.Alter(
|
|
1389
1413
|
this=exp.to_table(self._old_name),
|
|
1390
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.
|
|
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
|
|
25
25
|
from collections.abc import Mapping, Sequence
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
__all__ = ("
|
|
28
|
+
__all__ = ("Insert",)
|
|
29
29
|
|
|
30
30
|
ERR_MSG_TABLE_NOT_SET = "The target table must be set using .into() before adding values."
|
|
31
31
|
ERR_MSG_VALUES_COLUMNS_MISMATCH = (
|
|
@@ -36,9 +36,7 @@ ERR_MSG_EXPRESSION_NOT_INITIALIZED = "Internal error: base expression not initia
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
@dataclass(unsafe_hash=True)
|
|
39
|
-
class
|
|
40
|
-
QueryBuilder[RowT], ReturningClauseMixin, InsertValuesMixin, InsertFromSelectMixin, InsertIntoClauseMixin
|
|
41
|
-
):
|
|
39
|
+
class Insert(QueryBuilder[RowT], ReturningClauseMixin, InsertValuesMixin, InsertFromSelectMixin, InsertIntoClauseMixin):
|
|
42
40
|
"""Builder for INSERT statements.
|
|
43
41
|
|
|
44
42
|
This builder facilitates the construction of SQL INSERT queries
|
|
@@ -48,15 +46,20 @@ class InsertBuilder(
|
|
|
48
46
|
```python
|
|
49
47
|
# Basic INSERT with values
|
|
50
48
|
insert_query = (
|
|
51
|
-
|
|
49
|
+
Insert()
|
|
52
50
|
.into("users")
|
|
53
51
|
.columns("name", "email", "age")
|
|
54
52
|
.values("John Doe", "john@example.com", 30)
|
|
55
53
|
)
|
|
56
54
|
|
|
55
|
+
# Even more concise with constructor
|
|
56
|
+
insert_query = Insert("users").values(
|
|
57
|
+
{"name": "John", "age": 30}
|
|
58
|
+
)
|
|
59
|
+
|
|
57
60
|
# Multi-row INSERT
|
|
58
61
|
insert_query = (
|
|
59
|
-
|
|
62
|
+
Insert()
|
|
60
63
|
.into("users")
|
|
61
64
|
.columns("name", "email")
|
|
62
65
|
.values("John", "john@example.com")
|
|
@@ -65,7 +68,7 @@ class InsertBuilder(
|
|
|
65
68
|
|
|
66
69
|
# INSERT from dictionary
|
|
67
70
|
insert_query = (
|
|
68
|
-
|
|
71
|
+
Insert()
|
|
69
72
|
.into("users")
|
|
70
73
|
.values_from_dict(
|
|
71
74
|
{"name": "John", "email": "john@example.com"}
|
|
@@ -74,10 +77,10 @@ class InsertBuilder(
|
|
|
74
77
|
|
|
75
78
|
# INSERT from SELECT
|
|
76
79
|
insert_query = (
|
|
77
|
-
|
|
80
|
+
Insert()
|
|
78
81
|
.into("users_backup")
|
|
79
82
|
.from_select(
|
|
80
|
-
|
|
83
|
+
Select()
|
|
81
84
|
.select("name", "email")
|
|
82
85
|
.from_("users")
|
|
83
86
|
.where("active = true")
|
|
@@ -90,6 +93,23 @@ class InsertBuilder(
|
|
|
90
93
|
_columns: list[str] = field(default_factory=list, init=False)
|
|
91
94
|
_values_added_count: int = field(default=0, init=False)
|
|
92
95
|
|
|
96
|
+
def __init__(self, table: Optional[str] = None, **kwargs: Any) -> None:
|
|
97
|
+
"""Initialize INSERT with optional table.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
table: Target table name
|
|
101
|
+
**kwargs: Additional QueryBuilder arguments
|
|
102
|
+
"""
|
|
103
|
+
super().__init__(**kwargs)
|
|
104
|
+
|
|
105
|
+
# Initialize fields from dataclass
|
|
106
|
+
self._table = None
|
|
107
|
+
self._columns = []
|
|
108
|
+
self._values_added_count = 0
|
|
109
|
+
|
|
110
|
+
if table:
|
|
111
|
+
self.into(table)
|
|
112
|
+
|
|
93
113
|
def _create_base_expression(self) -> exp.Insert:
|
|
94
114
|
"""Create a base INSERT expression.
|
|
95
115
|
|
|
@@ -165,7 +185,6 @@ class InsertBuilder(
|
|
|
165
185
|
else:
|
|
166
186
|
# This case should ideally not be reached if logic is correct:
|
|
167
187
|
# means _values_added_count > 0 but expression is not exp.Values.
|
|
168
|
-
# Fallback to creating a new Values node, though this might indicate an issue.
|
|
169
188
|
new_values_node = exp.Values(expressions=[exp.Tuple(expressions=list(value_placeholders))])
|
|
170
189
|
insert_expr.set("expression", new_values_node)
|
|
171
190
|
|
|
@@ -191,14 +210,12 @@ class InsertBuilder(
|
|
|
191
210
|
raise SQLBuilderError(ERR_MSG_TABLE_NOT_SET)
|
|
192
211
|
|
|
193
212
|
if not self._columns:
|
|
194
|
-
# Set columns from dictionary keys if not already set
|
|
195
213
|
self.columns(*data.keys())
|
|
196
214
|
elif set(self._columns) != set(data.keys()):
|
|
197
215
|
# Verify that dictionary keys match existing columns
|
|
198
216
|
msg = f"Dictionary keys {set(data.keys())} do not match existing columns {set(self._columns)}."
|
|
199
217
|
raise SQLBuilderError(msg)
|
|
200
218
|
|
|
201
|
-
# Add values in the same order as columns
|
|
202
219
|
return self.values(*[data[col] for col in self._columns])
|
|
203
220
|
|
|
204
221
|
def values_from_dicts(self, data: "Sequence[Mapping[str, Any]]") -> "Self":
|
|
@@ -219,12 +236,10 @@ class InsertBuilder(
|
|
|
219
236
|
if not data:
|
|
220
237
|
return self
|
|
221
238
|
|
|
222
|
-
# Use the first dictionary to establish columns
|
|
223
239
|
first_dict = data[0]
|
|
224
240
|
if not self._columns:
|
|
225
241
|
self.columns(*first_dict.keys())
|
|
226
242
|
|
|
227
|
-
# Validate that all dictionaries have the same keys
|
|
228
243
|
expected_keys = set(self._columns)
|
|
229
244
|
for i, row_dict in enumerate(data):
|
|
230
245
|
if set(row_dict.keys()) != expected_keys:
|
|
@@ -234,7 +249,6 @@ class InsertBuilder(
|
|
|
234
249
|
)
|
|
235
250
|
raise SQLBuilderError(msg)
|
|
236
251
|
|
|
237
|
-
# Add each row
|
|
238
252
|
for row_dict in data:
|
|
239
253
|
self.values(*[row_dict[col] for col in self._columns])
|
|
240
254
|
|