sqlspec 0.14.1__py3-none-any.whl → 0.16.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 +50 -25
- sqlspec/__main__.py +1 -1
- sqlspec/__metadata__.py +1 -3
- sqlspec/_serialization.py +1 -2
- sqlspec/_sql.py +480 -121
- sqlspec/_typing.py +278 -142
- sqlspec/adapters/adbc/__init__.py +4 -3
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/config.py +115 -260
- sqlspec/adapters/adbc/driver.py +462 -367
- sqlspec/adapters/aiosqlite/__init__.py +18 -3
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/config.py +199 -129
- sqlspec/adapters/aiosqlite/driver.py +230 -269
- sqlspec/adapters/asyncmy/__init__.py +18 -3
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/config.py +80 -168
- sqlspec/adapters/asyncmy/driver.py +260 -225
- sqlspec/adapters/asyncpg/__init__.py +19 -4
- sqlspec/adapters/asyncpg/_types.py +17 -0
- sqlspec/adapters/asyncpg/config.py +82 -181
- sqlspec/adapters/asyncpg/driver.py +285 -383
- sqlspec/adapters/bigquery/__init__.py +17 -3
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/config.py +191 -258
- sqlspec/adapters/bigquery/driver.py +474 -646
- sqlspec/adapters/duckdb/__init__.py +14 -3
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/config.py +415 -351
- sqlspec/adapters/duckdb/driver.py +343 -413
- sqlspec/adapters/oracledb/__init__.py +19 -5
- sqlspec/adapters/oracledb/_types.py +14 -0
- sqlspec/adapters/oracledb/config.py +123 -379
- sqlspec/adapters/oracledb/driver.py +507 -560
- sqlspec/adapters/psqlpy/__init__.py +13 -3
- sqlspec/adapters/psqlpy/_types.py +11 -0
- sqlspec/adapters/psqlpy/config.py +93 -254
- sqlspec/adapters/psqlpy/driver.py +505 -234
- sqlspec/adapters/psycopg/__init__.py +19 -5
- sqlspec/adapters/psycopg/_types.py +17 -0
- sqlspec/adapters/psycopg/config.py +143 -403
- sqlspec/adapters/psycopg/driver.py +706 -872
- sqlspec/adapters/sqlite/__init__.py +14 -3
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/config.py +202 -118
- sqlspec/adapters/sqlite/driver.py +264 -303
- sqlspec/base.py +105 -9
- sqlspec/{statement/builder → builder}/__init__.py +12 -14
- sqlspec/{statement/builder → builder}/_base.py +120 -55
- sqlspec/{statement/builder → builder}/_column.py +17 -6
- sqlspec/{statement/builder → builder}/_ddl.py +46 -79
- sqlspec/{statement/builder → builder}/_ddl_utils.py +5 -10
- sqlspec/{statement/builder → builder}/_delete.py +6 -25
- sqlspec/{statement/builder → builder}/_insert.py +18 -65
- sqlspec/builder/_merge.py +56 -0
- sqlspec/{statement/builder → builder}/_parsing_utils.py +8 -11
- sqlspec/{statement/builder → builder}/_select.py +11 -56
- sqlspec/{statement/builder → builder}/_update.py +12 -18
- sqlspec/{statement/builder → builder}/mixins/__init__.py +10 -14
- sqlspec/{statement/builder → builder}/mixins/_cte_and_set_ops.py +48 -59
- sqlspec/{statement/builder → builder}/mixins/_insert_operations.py +34 -18
- sqlspec/{statement/builder → builder}/mixins/_join_operations.py +1 -3
- sqlspec/{statement/builder → builder}/mixins/_merge_operations.py +19 -9
- sqlspec/{statement/builder → builder}/mixins/_order_limit_operations.py +3 -3
- sqlspec/{statement/builder → builder}/mixins/_pivot_operations.py +4 -8
- sqlspec/{statement/builder → builder}/mixins/_select_operations.py +25 -38
- sqlspec/{statement/builder → builder}/mixins/_update_operations.py +15 -16
- sqlspec/{statement/builder → builder}/mixins/_where_clause.py +210 -137
- sqlspec/cli.py +4 -5
- sqlspec/config.py +180 -133
- sqlspec/core/__init__.py +63 -0
- sqlspec/core/cache.py +873 -0
- sqlspec/core/compiler.py +396 -0
- sqlspec/core/filters.py +830 -0
- sqlspec/core/hashing.py +310 -0
- sqlspec/core/parameters.py +1209 -0
- sqlspec/core/result.py +664 -0
- sqlspec/{statement → core}/splitter.py +321 -191
- sqlspec/core/statement.py +666 -0
- sqlspec/driver/__init__.py +7 -10
- sqlspec/driver/_async.py +387 -176
- sqlspec/driver/_common.py +527 -289
- sqlspec/driver/_sync.py +390 -172
- sqlspec/driver/mixins/__init__.py +2 -19
- sqlspec/driver/mixins/_result_tools.py +164 -0
- sqlspec/driver/mixins/_sql_translator.py +6 -3
- sqlspec/exceptions.py +5 -252
- sqlspec/extensions/aiosql/adapter.py +93 -96
- sqlspec/extensions/litestar/cli.py +1 -1
- sqlspec/extensions/litestar/config.py +0 -1
- sqlspec/extensions/litestar/handlers.py +15 -26
- sqlspec/extensions/litestar/plugin.py +18 -16
- sqlspec/extensions/litestar/providers.py +17 -52
- sqlspec/loader.py +424 -105
- sqlspec/migrations/__init__.py +12 -0
- sqlspec/migrations/base.py +92 -68
- sqlspec/migrations/commands.py +24 -106
- sqlspec/migrations/loaders.py +402 -0
- sqlspec/migrations/runner.py +49 -51
- sqlspec/migrations/tracker.py +31 -44
- sqlspec/migrations/utils.py +64 -24
- sqlspec/protocols.py +7 -183
- sqlspec/storage/__init__.py +1 -1
- sqlspec/storage/backends/base.py +37 -40
- sqlspec/storage/backends/fsspec.py +136 -112
- sqlspec/storage/backends/obstore.py +138 -160
- sqlspec/storage/capabilities.py +5 -4
- sqlspec/storage/registry.py +57 -106
- sqlspec/typing.py +136 -115
- sqlspec/utils/__init__.py +2 -3
- sqlspec/utils/correlation.py +0 -3
- sqlspec/utils/deprecation.py +6 -6
- sqlspec/utils/fixtures.py +6 -6
- sqlspec/utils/logging.py +0 -2
- sqlspec/utils/module_loader.py +7 -12
- sqlspec/utils/singleton.py +0 -1
- sqlspec/utils/sync_tools.py +17 -38
- sqlspec/utils/text.py +12 -51
- sqlspec/utils/type_guards.py +443 -232
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/METADATA +7 -2
- sqlspec-0.16.0.dist-info/RECORD +134 -0
- sqlspec/adapters/adbc/transformers.py +0 -108
- sqlspec/driver/connection.py +0 -207
- sqlspec/driver/mixins/_cache.py +0 -114
- sqlspec/driver/mixins/_csv_writer.py +0 -91
- sqlspec/driver/mixins/_pipeline.py +0 -508
- sqlspec/driver/mixins/_query_tools.py +0 -796
- sqlspec/driver/mixins/_result_utils.py +0 -138
- sqlspec/driver/mixins/_storage.py +0 -912
- sqlspec/driver/mixins/_type_coercion.py +0 -128
- sqlspec/driver/parameters.py +0 -138
- sqlspec/statement/__init__.py +0 -21
- sqlspec/statement/builder/_merge.py +0 -95
- sqlspec/statement/cache.py +0 -50
- sqlspec/statement/filters.py +0 -625
- sqlspec/statement/parameters.py +0 -956
- sqlspec/statement/pipelines/__init__.py +0 -210
- sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
- sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
- sqlspec/statement/pipelines/context.py +0 -109
- sqlspec/statement/pipelines/transformers/__init__.py +0 -7
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
- sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
- sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
- sqlspec/statement/pipelines/validators/__init__.py +0 -23
- sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
- sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
- sqlspec/statement/pipelines/validators/_performance.py +0 -714
- sqlspec/statement/pipelines/validators/_security.py +0 -967
- sqlspec/statement/result.py +0 -435
- sqlspec/statement/sql.py +0 -1774
- sqlspec/utils/cached_property.py +0 -25
- sqlspec/utils/statement_hashing.py +0 -203
- sqlspec-0.14.1.dist-info/RECORD +0 -145
- /sqlspec/{statement/builder → builder}/mixins/_delete_operations.py +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/_sql.py
CHANGED
|
@@ -1,21 +1,68 @@
|
|
|
1
1
|
"""Unified SQL factory for creating SQL builders and column expressions with a clean API.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
- `sql` provides both statement builders (select, insert, update, etc.) and column expressions
|
|
3
|
+
Provides both statement builders (select, insert, update, etc.) and column expressions.
|
|
5
4
|
"""
|
|
6
5
|
|
|
7
6
|
import logging
|
|
8
|
-
from typing import Any, Optional, Union
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
9
8
|
|
|
10
9
|
import sqlglot
|
|
11
10
|
from sqlglot import exp
|
|
12
11
|
from sqlglot.dialects.dialect import DialectType
|
|
13
12
|
from sqlglot.errors import ParseError as SQLGlotParseError
|
|
14
13
|
|
|
14
|
+
from sqlspec.builder import (
|
|
15
|
+
AlterTable,
|
|
16
|
+
Column,
|
|
17
|
+
CommentOn,
|
|
18
|
+
CreateIndex,
|
|
19
|
+
CreateMaterializedView,
|
|
20
|
+
CreateSchema,
|
|
21
|
+
CreateTable,
|
|
22
|
+
CreateTableAsSelect,
|
|
23
|
+
CreateView,
|
|
24
|
+
Delete,
|
|
25
|
+
DropIndex,
|
|
26
|
+
DropSchema,
|
|
27
|
+
DropTable,
|
|
28
|
+
DropView,
|
|
29
|
+
Insert,
|
|
30
|
+
Merge,
|
|
31
|
+
RenameTable,
|
|
32
|
+
Select,
|
|
33
|
+
Truncate,
|
|
34
|
+
Update,
|
|
35
|
+
)
|
|
15
36
|
from sqlspec.exceptions import SQLBuilderError
|
|
16
|
-
from sqlspec.statement.builder import Column, Delete, Insert, Merge, Select, Update
|
|
17
37
|
|
|
18
|
-
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from sqlspec.core.statement import SQL
|
|
40
|
+
|
|
41
|
+
__all__ = (
|
|
42
|
+
"AlterTable",
|
|
43
|
+
"Case",
|
|
44
|
+
"Column",
|
|
45
|
+
"CommentOn",
|
|
46
|
+
"CreateIndex",
|
|
47
|
+
"CreateMaterializedView",
|
|
48
|
+
"CreateSchema",
|
|
49
|
+
"CreateTable",
|
|
50
|
+
"CreateTableAsSelect",
|
|
51
|
+
"CreateView",
|
|
52
|
+
"Delete",
|
|
53
|
+
"DropIndex",
|
|
54
|
+
"DropSchema",
|
|
55
|
+
"DropTable",
|
|
56
|
+
"DropView",
|
|
57
|
+
"Insert",
|
|
58
|
+
"Merge",
|
|
59
|
+
"RenameTable",
|
|
60
|
+
"SQLFactory",
|
|
61
|
+
"Select",
|
|
62
|
+
"Truncate",
|
|
63
|
+
"Update",
|
|
64
|
+
"sql",
|
|
65
|
+
)
|
|
19
66
|
|
|
20
67
|
logger = logging.getLogger("sqlspec")
|
|
21
68
|
|
|
@@ -50,58 +97,18 @@ SQL_STARTERS = {
|
|
|
50
97
|
|
|
51
98
|
|
|
52
99
|
class SQLFactory:
|
|
53
|
-
"""Unified factory for creating SQL builders and column expressions with a fluent API.
|
|
54
|
-
|
|
55
|
-
Provides both statement builders and column expressions through a single, clean interface.
|
|
56
|
-
Now supports parsing raw SQL strings into appropriate builders for enhanced flexibility.
|
|
57
|
-
|
|
58
|
-
Example:
|
|
59
|
-
```python
|
|
60
|
-
from sqlspec import sql
|
|
61
|
-
|
|
62
|
-
# Traditional builder usage (unchanged)
|
|
63
|
-
query = (
|
|
64
|
-
sql.select(sql.id, sql.name)
|
|
65
|
-
.from_("users")
|
|
66
|
-
.where("age > 18")
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
# New: Raw SQL parsing
|
|
70
|
-
insert_sql = sql.insert(
|
|
71
|
-
"INSERT INTO users (name, email) VALUES ('John', 'john@example.com')"
|
|
72
|
-
)
|
|
73
|
-
select_sql = sql.select(
|
|
74
|
-
"SELECT * FROM users WHERE active = 1"
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
# RETURNING clause detection
|
|
78
|
-
returning_insert = sql.insert(
|
|
79
|
-
"INSERT INTO users (name) VALUES ('John') RETURNING id"
|
|
80
|
-
)
|
|
81
|
-
# → When executed, will return SelectResult instead of ExecuteResult
|
|
82
|
-
|
|
83
|
-
# Smart INSERT FROM SELECT
|
|
84
|
-
insert_from_select = sql.insert(
|
|
85
|
-
"SELECT id, name FROM source WHERE active = 1"
|
|
86
|
-
)
|
|
87
|
-
# → Will prompt for target table or convert to INSERT FROM SELECT pattern
|
|
88
|
-
```
|
|
89
|
-
"""
|
|
100
|
+
"""Unified factory for creating SQL builders and column expressions with a fluent API."""
|
|
90
101
|
|
|
91
102
|
@classmethod
|
|
92
103
|
def detect_sql_type(cls, sql: str, dialect: DialectType = None) -> str:
|
|
93
104
|
try:
|
|
94
|
-
# Minimal parsing just to get the command type
|
|
95
105
|
parsed_expr = sqlglot.parse_one(sql, read=dialect)
|
|
96
106
|
if parsed_expr and parsed_expr.key:
|
|
97
107
|
return parsed_expr.key.upper()
|
|
98
|
-
# Fallback for expressions that might not have a direct 'key'
|
|
99
|
-
# or where key is None (e.g. some DDL without explicit command like SET)
|
|
100
108
|
if parsed_expr:
|
|
101
|
-
# Attempt to get the class name as a fallback, e.g., "Set", "Command"
|
|
102
109
|
command_type = type(parsed_expr).__name__.upper()
|
|
103
110
|
if command_type == "COMMAND" and parsed_expr.this:
|
|
104
|
-
return str(parsed_expr.this).upper()
|
|
111
|
+
return str(parsed_expr.this).upper()
|
|
105
112
|
return command_type
|
|
106
113
|
except SQLGlotParseError:
|
|
107
114
|
logger.debug("Failed to parse SQL for type detection: %s", sql[:100])
|
|
@@ -120,15 +127,7 @@ class SQLFactory:
|
|
|
120
127
|
# ===================
|
|
121
128
|
# Callable Interface
|
|
122
129
|
# ===================
|
|
123
|
-
def __call__(
|
|
124
|
-
self,
|
|
125
|
-
statement: str,
|
|
126
|
-
parameters: Optional[Any] = None,
|
|
127
|
-
*filters: Any,
|
|
128
|
-
config: Optional[Any] = None,
|
|
129
|
-
dialect: DialectType = None,
|
|
130
|
-
**kwargs: Any,
|
|
131
|
-
) -> "Any":
|
|
130
|
+
def __call__(self, statement: str, dialect: DialectType = None) -> "Any":
|
|
132
131
|
"""Create a SelectBuilder from a SQL string, only allowing SELECT/CTE queries.
|
|
133
132
|
|
|
134
133
|
Args:
|
|
@@ -152,7 +151,6 @@ class SQLFactory:
|
|
|
152
151
|
msg = f"Failed to parse SQL: {e}"
|
|
153
152
|
raise SQLBuilderError(msg) from e
|
|
154
153
|
actual_type = type(parsed_expr).__name__.upper()
|
|
155
|
-
# Map sqlglot expression class to type string
|
|
156
154
|
expr_type_map = {
|
|
157
155
|
"SELECT": "SELECT",
|
|
158
156
|
"INSERT": "INSERT",
|
|
@@ -262,6 +260,174 @@ class SQLFactory:
|
|
|
262
260
|
return builder.into(table_or_sql)
|
|
263
261
|
return builder
|
|
264
262
|
|
|
263
|
+
# ===================
|
|
264
|
+
# DDL Statement Builders
|
|
265
|
+
# ===================
|
|
266
|
+
|
|
267
|
+
def create_table(self, table_name: str, dialect: DialectType = None) -> "CreateTable":
|
|
268
|
+
"""Create a CREATE TABLE builder.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
table_name: Name of the table to create
|
|
272
|
+
dialect: Optional SQL dialect
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
CreateTable builder instance
|
|
276
|
+
"""
|
|
277
|
+
builder = CreateTable(table_name)
|
|
278
|
+
builder.dialect = dialect or self.dialect
|
|
279
|
+
return builder
|
|
280
|
+
|
|
281
|
+
def create_table_as_select(self, dialect: DialectType = None) -> "CreateTableAsSelect":
|
|
282
|
+
"""Create a CREATE TABLE AS SELECT builder.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
dialect: Optional SQL dialect
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
CreateTableAsSelect builder instance
|
|
289
|
+
"""
|
|
290
|
+
builder = CreateTableAsSelect()
|
|
291
|
+
builder.dialect = dialect or self.dialect
|
|
292
|
+
return builder
|
|
293
|
+
|
|
294
|
+
def create_view(self, dialect: DialectType = None) -> "CreateView":
|
|
295
|
+
"""Create a CREATE VIEW builder.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
dialect: Optional SQL dialect
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
CreateView builder instance
|
|
302
|
+
"""
|
|
303
|
+
builder = CreateView()
|
|
304
|
+
builder.dialect = dialect or self.dialect
|
|
305
|
+
return builder
|
|
306
|
+
|
|
307
|
+
def create_materialized_view(self, dialect: DialectType = None) -> "CreateMaterializedView":
|
|
308
|
+
"""Create a CREATE MATERIALIZED VIEW builder.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
dialect: Optional SQL dialect
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
CreateMaterializedView builder instance
|
|
315
|
+
"""
|
|
316
|
+
builder = CreateMaterializedView()
|
|
317
|
+
builder.dialect = dialect or self.dialect
|
|
318
|
+
return builder
|
|
319
|
+
|
|
320
|
+
def create_index(self, index_name: str, dialect: DialectType = None) -> "CreateIndex":
|
|
321
|
+
"""Create a CREATE INDEX builder.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
index_name: Name of the index to create
|
|
325
|
+
dialect: Optional SQL dialect
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
CreateIndex builder instance
|
|
329
|
+
"""
|
|
330
|
+
return CreateIndex(index_name, dialect=dialect or self.dialect)
|
|
331
|
+
|
|
332
|
+
def create_schema(self, dialect: DialectType = None) -> "CreateSchema":
|
|
333
|
+
"""Create a CREATE SCHEMA builder.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
dialect: Optional SQL dialect
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
CreateSchema builder instance
|
|
340
|
+
"""
|
|
341
|
+
builder = CreateSchema()
|
|
342
|
+
builder.dialect = dialect or self.dialect
|
|
343
|
+
return builder
|
|
344
|
+
|
|
345
|
+
def drop_table(self, table_name: str, dialect: DialectType = None) -> "DropTable":
|
|
346
|
+
"""Create a DROP TABLE builder.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
table_name: Name of the table to drop
|
|
350
|
+
dialect: Optional SQL dialect
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
DropTable builder instance
|
|
354
|
+
"""
|
|
355
|
+
return DropTable(table_name, dialect=dialect or self.dialect)
|
|
356
|
+
|
|
357
|
+
def drop_view(self, dialect: DialectType = None) -> "DropView":
|
|
358
|
+
"""Create a DROP VIEW builder.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
dialect: Optional SQL dialect
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
DropView builder instance
|
|
365
|
+
"""
|
|
366
|
+
return DropView(dialect=dialect or self.dialect)
|
|
367
|
+
|
|
368
|
+
def drop_index(self, index_name: str, dialect: DialectType = None) -> "DropIndex":
|
|
369
|
+
"""Create a DROP INDEX builder.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
index_name: Name of the index to drop
|
|
373
|
+
dialect: Optional SQL dialect
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
DropIndex builder instance
|
|
377
|
+
"""
|
|
378
|
+
return DropIndex(index_name, dialect=dialect or self.dialect)
|
|
379
|
+
|
|
380
|
+
def drop_schema(self, dialect: DialectType = None) -> "DropSchema":
|
|
381
|
+
"""Create a DROP SCHEMA builder.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
dialect: Optional SQL dialect
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
DropSchema builder instance
|
|
388
|
+
"""
|
|
389
|
+
return DropSchema(dialect=dialect or self.dialect)
|
|
390
|
+
|
|
391
|
+
def alter_table(self, table_name: str, dialect: DialectType = None) -> "AlterTable":
|
|
392
|
+
"""Create an ALTER TABLE builder.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
table_name: Name of the table to alter
|
|
396
|
+
dialect: Optional SQL dialect
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
AlterTable builder instance
|
|
400
|
+
"""
|
|
401
|
+
builder = AlterTable(table_name)
|
|
402
|
+
builder.dialect = dialect or self.dialect
|
|
403
|
+
return builder
|
|
404
|
+
|
|
405
|
+
def rename_table(self, dialect: DialectType = None) -> "RenameTable":
|
|
406
|
+
"""Create a RENAME TABLE builder.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
dialect: Optional SQL dialect
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
RenameTable builder instance
|
|
413
|
+
"""
|
|
414
|
+
builder = RenameTable()
|
|
415
|
+
builder.dialect = dialect or self.dialect
|
|
416
|
+
return builder
|
|
417
|
+
|
|
418
|
+
def comment_on(self, dialect: DialectType = None) -> "CommentOn":
|
|
419
|
+
"""Create a COMMENT ON builder.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
dialect: Optional SQL dialect
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
CommentOn builder instance
|
|
426
|
+
"""
|
|
427
|
+
builder = CommentOn()
|
|
428
|
+
builder.dialect = dialect or self.dialect
|
|
429
|
+
return builder
|
|
430
|
+
|
|
265
431
|
# ===================
|
|
266
432
|
# SQL Analysis Helpers
|
|
267
433
|
# ===================
|
|
@@ -300,8 +466,6 @@ class SQLFactory:
|
|
|
300
466
|
try:
|
|
301
467
|
# Use SQLGlot directly for parsing - no validation here
|
|
302
468
|
parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect) # type: ignore[var-annotated]
|
|
303
|
-
if parsed_expr is None:
|
|
304
|
-
parsed_expr = sqlglot.parse_one(sql_string, read=self.dialect)
|
|
305
469
|
|
|
306
470
|
if isinstance(parsed_expr, exp.Insert):
|
|
307
471
|
builder._expression = parsed_expr
|
|
@@ -324,8 +488,6 @@ class SQLFactory:
|
|
|
324
488
|
try:
|
|
325
489
|
# Use SQLGlot directly for parsing - no validation here
|
|
326
490
|
parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect) # type: ignore[var-annotated]
|
|
327
|
-
if parsed_expr is None:
|
|
328
|
-
parsed_expr = sqlglot.parse_one(sql_string, read=self.dialect)
|
|
329
491
|
|
|
330
492
|
if isinstance(parsed_expr, exp.Select):
|
|
331
493
|
builder._expression = parsed_expr
|
|
@@ -342,8 +504,6 @@ class SQLFactory:
|
|
|
342
504
|
try:
|
|
343
505
|
# Use SQLGlot directly for parsing - no validation here
|
|
344
506
|
parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect) # type: ignore[var-annotated]
|
|
345
|
-
if parsed_expr is None:
|
|
346
|
-
parsed_expr = sqlglot.parse_one(sql_string, read=self.dialect)
|
|
347
507
|
|
|
348
508
|
if isinstance(parsed_expr, exp.Update):
|
|
349
509
|
builder._expression = parsed_expr
|
|
@@ -360,8 +520,6 @@ class SQLFactory:
|
|
|
360
520
|
try:
|
|
361
521
|
# Use SQLGlot directly for parsing - no validation here
|
|
362
522
|
parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect) # type: ignore[var-annotated]
|
|
363
|
-
if parsed_expr is None:
|
|
364
|
-
parsed_expr = sqlglot.parse_one(sql_string, read=self.dialect)
|
|
365
523
|
|
|
366
524
|
if isinstance(parsed_expr, exp.Delete):
|
|
367
525
|
builder._expression = parsed_expr
|
|
@@ -378,8 +536,6 @@ class SQLFactory:
|
|
|
378
536
|
try:
|
|
379
537
|
# Use SQLGlot directly for parsing - no validation here
|
|
380
538
|
parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect) # type: ignore[var-annotated]
|
|
381
|
-
if parsed_expr is None:
|
|
382
|
-
parsed_expr = sqlglot.parse_one(sql_string, read=self.dialect)
|
|
383
539
|
|
|
384
540
|
if isinstance(parsed_expr, exp.Merge):
|
|
385
541
|
builder._expression = parsed_expr
|
|
@@ -395,6 +551,18 @@ class SQLFactory:
|
|
|
395
551
|
# Column References
|
|
396
552
|
# ===================
|
|
397
553
|
|
|
554
|
+
def column(self, name: str, table: Optional[str] = None) -> Column:
|
|
555
|
+
"""Create a column reference.
|
|
556
|
+
|
|
557
|
+
Args:
|
|
558
|
+
name: Column name.
|
|
559
|
+
table: Optional table name.
|
|
560
|
+
|
|
561
|
+
Returns:
|
|
562
|
+
Column object that supports method chaining and operator overloading.
|
|
563
|
+
"""
|
|
564
|
+
return Column(name, table)
|
|
565
|
+
|
|
398
566
|
def __getattr__(self, name: str) -> Column:
|
|
399
567
|
"""Dynamically create column references.
|
|
400
568
|
|
|
@@ -406,6 +574,73 @@ class SQLFactory:
|
|
|
406
574
|
"""
|
|
407
575
|
return Column(name)
|
|
408
576
|
|
|
577
|
+
# ===================
|
|
578
|
+
# Raw SQL Expressions
|
|
579
|
+
# ===================
|
|
580
|
+
|
|
581
|
+
@staticmethod
|
|
582
|
+
def raw(sql_fragment: str, **parameters: Any) -> "Union[exp.Expression, SQL]":
|
|
583
|
+
"""Create a raw SQL expression from a string fragment with optional parameters.
|
|
584
|
+
|
|
585
|
+
This method makes it explicit that you are passing raw SQL that should
|
|
586
|
+
be parsed and included directly in the query. Useful for complex expressions,
|
|
587
|
+
database-specific functions, or when you need precise control over the SQL.
|
|
588
|
+
|
|
589
|
+
Args:
|
|
590
|
+
sql_fragment: Raw SQL string to parse into an expression.
|
|
591
|
+
**parameters: Named parameters for parameter binding.
|
|
592
|
+
|
|
593
|
+
Returns:
|
|
594
|
+
SQLGlot expression from the parsed SQL fragment (if no parameters).
|
|
595
|
+
SQL statement object (if parameters provided).
|
|
596
|
+
|
|
597
|
+
Raises:
|
|
598
|
+
SQLBuilderError: If the SQL fragment cannot be parsed.
|
|
599
|
+
|
|
600
|
+
Example:
|
|
601
|
+
```python
|
|
602
|
+
# Raw expression without parameters (current behavior)
|
|
603
|
+
expr = sql.raw("COALESCE(name, 'Unknown')")
|
|
604
|
+
|
|
605
|
+
# Raw SQL with named parameters (new functionality)
|
|
606
|
+
stmt = sql.raw(
|
|
607
|
+
"LOWER(name) LIKE LOWER(:pattern)", pattern=f"%{query}%"
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
# Raw complex expression with parameters
|
|
611
|
+
expr = sql.raw(
|
|
612
|
+
"price BETWEEN :min_price AND :max_price",
|
|
613
|
+
min_price=100,
|
|
614
|
+
max_price=500,
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
# Raw window function
|
|
618
|
+
query = sql.select(
|
|
619
|
+
"name",
|
|
620
|
+
sql.raw(
|
|
621
|
+
"ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC)"
|
|
622
|
+
),
|
|
623
|
+
).from_("employees")
|
|
624
|
+
```
|
|
625
|
+
"""
|
|
626
|
+
if not parameters:
|
|
627
|
+
# Original behavior - return pure expression
|
|
628
|
+
try:
|
|
629
|
+
parsed: Optional[exp.Expression] = exp.maybe_parse(sql_fragment)
|
|
630
|
+
if parsed is not None:
|
|
631
|
+
return parsed
|
|
632
|
+
if sql_fragment.strip().replace("_", "").replace(".", "").isalnum():
|
|
633
|
+
return exp.to_identifier(sql_fragment)
|
|
634
|
+
return exp.Literal.string(sql_fragment)
|
|
635
|
+
except Exception as e:
|
|
636
|
+
msg = f"Failed to parse raw SQL fragment '{sql_fragment}': {e}"
|
|
637
|
+
raise SQLBuilderError(msg) from e
|
|
638
|
+
|
|
639
|
+
# New behavior - return SQL statement with parameters
|
|
640
|
+
from sqlspec.core.statement import SQL
|
|
641
|
+
|
|
642
|
+
return SQL(sql_fragment, parameters)
|
|
643
|
+
|
|
409
644
|
# ===================
|
|
410
645
|
# Aggregate Functions
|
|
411
646
|
# ===================
|
|
@@ -597,7 +832,7 @@ class SQLFactory:
|
|
|
597
832
|
```
|
|
598
833
|
"""
|
|
599
834
|
if isinstance(values, list):
|
|
600
|
-
literals = [
|
|
835
|
+
literals = [SQLFactory._to_literal(v) for v in values]
|
|
601
836
|
return exp.Any(this=exp.Array(expressions=literals))
|
|
602
837
|
if isinstance(values, str):
|
|
603
838
|
# Parse as SQL
|
|
@@ -607,6 +842,29 @@ class SQLFactory:
|
|
|
607
842
|
return exp.Any(this=exp.Literal.string(values))
|
|
608
843
|
return exp.Any(this=values)
|
|
609
844
|
|
|
845
|
+
@staticmethod
|
|
846
|
+
def not_any_(values: Union[list[Any], exp.Expression, str]) -> exp.Expression:
|
|
847
|
+
"""Create a NOT ANY expression for use with comparison operators.
|
|
848
|
+
|
|
849
|
+
Args:
|
|
850
|
+
values: Values, expression, or subquery for the NOT ANY clause.
|
|
851
|
+
|
|
852
|
+
Returns:
|
|
853
|
+
NOT ANY expression.
|
|
854
|
+
|
|
855
|
+
Example:
|
|
856
|
+
```python
|
|
857
|
+
# WHERE id <> ANY(subquery)
|
|
858
|
+
subquery = sql.select("user_id").from_("blocked_users")
|
|
859
|
+
query = (
|
|
860
|
+
sql.select("*")
|
|
861
|
+
.from_("users")
|
|
862
|
+
.where(sql.id.neq(sql.not_any(subquery)))
|
|
863
|
+
)
|
|
864
|
+
```
|
|
865
|
+
"""
|
|
866
|
+
return SQLFactory.any(values) # NOT ANY is handled by the comparison operator
|
|
867
|
+
|
|
610
868
|
# ===================
|
|
611
869
|
# String Functions
|
|
612
870
|
# ===================
|
|
@@ -687,6 +945,28 @@ class SQLFactory:
|
|
|
687
945
|
# Conversion Functions
|
|
688
946
|
# ===================
|
|
689
947
|
|
|
948
|
+
@staticmethod
|
|
949
|
+
def _to_literal(value: Any) -> exp.Expression:
|
|
950
|
+
"""Convert a Python value to a SQLGlot literal expression.
|
|
951
|
+
|
|
952
|
+
Uses SQLGlot's built-in exp.convert() function for optimal dialect-agnostic
|
|
953
|
+
literal creation. Handles all Python primitive types correctly:
|
|
954
|
+
- None -> exp.Null (renders as NULL)
|
|
955
|
+
- bool -> exp.Boolean (renders as TRUE/FALSE or 1/0 based on dialect)
|
|
956
|
+
- int/float -> exp.Literal with is_number=True
|
|
957
|
+
- str -> exp.Literal with is_string=True
|
|
958
|
+
- exp.Expression -> returned as-is (passthrough)
|
|
959
|
+
|
|
960
|
+
Args:
|
|
961
|
+
value: Python value or SQLGlot expression to convert.
|
|
962
|
+
|
|
963
|
+
Returns:
|
|
964
|
+
SQLGlot expression representing the literal value.
|
|
965
|
+
"""
|
|
966
|
+
if isinstance(value, exp.Expression):
|
|
967
|
+
return value
|
|
968
|
+
return exp.convert(value)
|
|
969
|
+
|
|
690
970
|
@staticmethod
|
|
691
971
|
def decode(column: Union[str, exp.Expression], *args: Union[str, exp.Expression, Any]) -> exp.Expression:
|
|
692
972
|
"""Create a DECODE expression (Oracle-style conditional logic).
|
|
@@ -725,29 +1005,14 @@ class SQLFactory:
|
|
|
725
1005
|
for i in range(0, len(args) - 1, 2):
|
|
726
1006
|
if i + 1 >= len(args):
|
|
727
1007
|
# Odd number of args means last one is default
|
|
728
|
-
default =
|
|
1008
|
+
default = SQLFactory._to_literal(args[i])
|
|
729
1009
|
break
|
|
730
1010
|
|
|
731
1011
|
search_val = args[i]
|
|
732
1012
|
result_val = args[i + 1]
|
|
733
1013
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
elif isinstance(search_val, (int, float)):
|
|
737
|
-
search_expr = exp.Literal.number(search_val)
|
|
738
|
-
elif isinstance(search_val, exp.Expression):
|
|
739
|
-
search_expr = search_val # type: ignore[assignment]
|
|
740
|
-
else:
|
|
741
|
-
search_expr = exp.Literal.string(str(search_val))
|
|
742
|
-
|
|
743
|
-
if isinstance(result_val, str):
|
|
744
|
-
result_expr = exp.Literal.string(result_val)
|
|
745
|
-
elif isinstance(result_val, (int, float)):
|
|
746
|
-
result_expr = exp.Literal.number(result_val)
|
|
747
|
-
elif isinstance(result_val, exp.Expression):
|
|
748
|
-
result_expr = result_val # type: ignore[assignment]
|
|
749
|
-
else:
|
|
750
|
-
result_expr = exp.Literal.string(str(result_val))
|
|
1014
|
+
search_expr = SQLFactory._to_literal(search_val)
|
|
1015
|
+
result_expr = SQLFactory._to_literal(result_val)
|
|
751
1016
|
|
|
752
1017
|
condition = exp.EQ(this=col_expr, expression=search_expr)
|
|
753
1018
|
conditions.append(exp.When(this=condition, then=result_expr))
|
|
@@ -793,30 +1058,136 @@ class SQLFactory:
|
|
|
793
1058
|
COALESCE expression equivalent to NVL.
|
|
794
1059
|
"""
|
|
795
1060
|
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
1061
|
+
sub_expr = SQLFactory._to_literal(substitute_value)
|
|
1062
|
+
return exp.Coalesce(expressions=[col_expr, sub_expr])
|
|
796
1063
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
sub_expr = exp.Literal.string(str(substitute_value))
|
|
1064
|
+
@staticmethod
|
|
1065
|
+
def nvl2(
|
|
1066
|
+
column: Union[str, exp.Expression],
|
|
1067
|
+
value_if_not_null: Union[str, exp.Expression, Any],
|
|
1068
|
+
value_if_null: Union[str, exp.Expression, Any],
|
|
1069
|
+
) -> exp.Expression:
|
|
1070
|
+
"""Create an NVL2 (Oracle-style) expression using CASE.
|
|
805
1071
|
|
|
806
|
-
|
|
1072
|
+
NVL2 returns value_if_not_null if column is not NULL,
|
|
1073
|
+
otherwise returns value_if_null.
|
|
1074
|
+
|
|
1075
|
+
Args:
|
|
1076
|
+
column: Column to check for NULL.
|
|
1077
|
+
value_if_not_null: Value to use if column is NOT NULL.
|
|
1078
|
+
value_if_null: Value to use if column is NULL.
|
|
1079
|
+
|
|
1080
|
+
Returns:
|
|
1081
|
+
CASE expression equivalent to NVL2.
|
|
1082
|
+
|
|
1083
|
+
Example:
|
|
1084
|
+
```python
|
|
1085
|
+
# NVL2(salary, 'Has Salary', 'No Salary')
|
|
1086
|
+
sql.nvl2("salary", "Has Salary", "No Salary")
|
|
1087
|
+
```
|
|
1088
|
+
"""
|
|
1089
|
+
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
1090
|
+
not_null_expr = SQLFactory._to_literal(value_if_not_null)
|
|
1091
|
+
null_expr = SQLFactory._to_literal(value_if_null)
|
|
1092
|
+
|
|
1093
|
+
# Create CASE WHEN column IS NOT NULL THEN value_if_not_null ELSE value_if_null END
|
|
1094
|
+
is_null = exp.Is(this=col_expr, expression=exp.Null())
|
|
1095
|
+
condition = exp.Not(this=is_null)
|
|
1096
|
+
when_clause = exp.If(this=condition, true=not_null_expr)
|
|
1097
|
+
|
|
1098
|
+
return exp.Case(ifs=[when_clause], default=null_expr)
|
|
1099
|
+
|
|
1100
|
+
# ===================
|
|
1101
|
+
# Bulk Operations
|
|
1102
|
+
# ===================
|
|
1103
|
+
|
|
1104
|
+
@staticmethod
|
|
1105
|
+
def bulk_insert(table_name: str, column_count: int, placeholder_style: str = "?") -> exp.Expression:
|
|
1106
|
+
"""Create bulk INSERT expression for executemany operations.
|
|
1107
|
+
|
|
1108
|
+
This is specifically for bulk loading operations like CSV ingestion where
|
|
1109
|
+
we need an INSERT expression with placeholders for executemany().
|
|
1110
|
+
|
|
1111
|
+
Args:
|
|
1112
|
+
table_name: Name of the table to insert into
|
|
1113
|
+
column_count: Number of columns (for placeholder generation)
|
|
1114
|
+
placeholder_style: Placeholder style ("?" for SQLite/PostgreSQL, "%s" for MySQL, ":1" for Oracle)
|
|
1115
|
+
|
|
1116
|
+
Returns:
|
|
1117
|
+
INSERT expression with proper placeholders for bulk operations
|
|
1118
|
+
|
|
1119
|
+
Example:
|
|
1120
|
+
```python
|
|
1121
|
+
from sqlspec import sql
|
|
1122
|
+
|
|
1123
|
+
# SQLite/PostgreSQL style
|
|
1124
|
+
insert_expr = sql.bulk_insert("my_table", 3)
|
|
1125
|
+
# Creates: INSERT INTO "my_table" VALUES (?, ?, ?)
|
|
1126
|
+
|
|
1127
|
+
# MySQL style
|
|
1128
|
+
insert_expr = sql.bulk_insert(
|
|
1129
|
+
"my_table", 3, placeholder_style="%s"
|
|
1130
|
+
)
|
|
1131
|
+
# Creates: INSERT INTO "my_table" VALUES (%s, %s, %s)
|
|
1132
|
+
|
|
1133
|
+
# Oracle style
|
|
1134
|
+
insert_expr = sql.bulk_insert(
|
|
1135
|
+
"my_table", 3, placeholder_style=":1"
|
|
1136
|
+
)
|
|
1137
|
+
# Creates: INSERT INTO "my_table" VALUES (:1, :2, :3)
|
|
1138
|
+
```
|
|
1139
|
+
"""
|
|
1140
|
+
return exp.Insert(
|
|
1141
|
+
this=exp.Table(this=exp.to_identifier(table_name)),
|
|
1142
|
+
expression=exp.Values(
|
|
1143
|
+
expressions=[
|
|
1144
|
+
exp.Tuple(expressions=[exp.Placeholder(this=placeholder_style) for _ in range(column_count)])
|
|
1145
|
+
]
|
|
1146
|
+
),
|
|
1147
|
+
)
|
|
1148
|
+
|
|
1149
|
+
def truncate(self, table_name: str) -> "Truncate":
|
|
1150
|
+
"""Create a TRUNCATE TABLE builder.
|
|
1151
|
+
|
|
1152
|
+
Args:
|
|
1153
|
+
table_name: Name of the table to truncate
|
|
1154
|
+
|
|
1155
|
+
Returns:
|
|
1156
|
+
TruncateTable builder instance
|
|
1157
|
+
|
|
1158
|
+
Example:
|
|
1159
|
+
```python
|
|
1160
|
+
from sqlspec import sql
|
|
1161
|
+
|
|
1162
|
+
# Simple truncate
|
|
1163
|
+
truncate_sql = sql.truncate_table("my_table").build().sql
|
|
1164
|
+
|
|
1165
|
+
# Truncate with options
|
|
1166
|
+
truncate_sql = (
|
|
1167
|
+
sql.truncate_table("my_table")
|
|
1168
|
+
.cascade()
|
|
1169
|
+
.restart_identity()
|
|
1170
|
+
.build()
|
|
1171
|
+
.sql
|
|
1172
|
+
)
|
|
1173
|
+
```
|
|
1174
|
+
"""
|
|
1175
|
+
builder = Truncate(dialect=self.dialect)
|
|
1176
|
+
builder._table_name = table_name
|
|
1177
|
+
return builder
|
|
807
1178
|
|
|
808
1179
|
# ===================
|
|
809
1180
|
# Case Expressions
|
|
810
1181
|
# ===================
|
|
811
1182
|
|
|
812
1183
|
@staticmethod
|
|
813
|
-
def case() -> "
|
|
1184
|
+
def case() -> "Case":
|
|
814
1185
|
"""Create a CASE expression builder.
|
|
815
1186
|
|
|
816
1187
|
Returns:
|
|
817
1188
|
CaseExpressionBuilder for building CASE expressions.
|
|
818
1189
|
"""
|
|
819
|
-
return
|
|
1190
|
+
return Case()
|
|
820
1191
|
|
|
821
1192
|
# ===================
|
|
822
1193
|
# Window Functions
|
|
@@ -911,7 +1282,7 @@ class SQLFactory:
|
|
|
911
1282
|
return exp.Window(this=func_expr, **over_args)
|
|
912
1283
|
|
|
913
1284
|
|
|
914
|
-
class
|
|
1285
|
+
class Case:
|
|
915
1286
|
"""Builder for CASE expressions using the SQL factory.
|
|
916
1287
|
|
|
917
1288
|
Example:
|
|
@@ -930,12 +1301,10 @@ class CaseExpressionBuilder:
|
|
|
930
1301
|
|
|
931
1302
|
def __init__(self) -> None:
|
|
932
1303
|
"""Initialize the CASE expression builder."""
|
|
933
|
-
self._conditions: list[exp.
|
|
1304
|
+
self._conditions: list[exp.If] = []
|
|
934
1305
|
self._default: Optional[exp.Expression] = None
|
|
935
1306
|
|
|
936
|
-
def when(
|
|
937
|
-
self, condition: Union[str, exp.Expression], value: Union[str, exp.Expression, Any]
|
|
938
|
-
) -> "CaseExpressionBuilder":
|
|
1307
|
+
def when(self, condition: Union[str, exp.Expression], value: Union[str, exp.Expression, Any]) -> "Case":
|
|
939
1308
|
"""Add a WHEN clause.
|
|
940
1309
|
|
|
941
1310
|
Args:
|
|
@@ -946,21 +1315,14 @@ class CaseExpressionBuilder:
|
|
|
946
1315
|
Self for method chaining.
|
|
947
1316
|
"""
|
|
948
1317
|
cond_expr = exp.maybe_parse(condition) or exp.column(condition) if isinstance(condition, str) else condition
|
|
1318
|
+
val_expr = SQLFactory._to_literal(value)
|
|
949
1319
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
elif isinstance(value, (int, float)):
|
|
953
|
-
val_expr = exp.Literal.number(value)
|
|
954
|
-
elif isinstance(value, exp.Expression):
|
|
955
|
-
val_expr = value # type: ignore[assignment]
|
|
956
|
-
else:
|
|
957
|
-
val_expr = exp.Literal.string(str(value))
|
|
958
|
-
|
|
959
|
-
when_clause = exp.When(this=cond_expr, then=val_expr)
|
|
1320
|
+
# SQLGlot uses exp.If for CASE WHEN clauses, not exp.When
|
|
1321
|
+
when_clause = exp.If(this=cond_expr, true=val_expr)
|
|
960
1322
|
self._conditions.append(when_clause)
|
|
961
1323
|
return self
|
|
962
1324
|
|
|
963
|
-
def else_(self, value: Union[str, exp.Expression, Any]) -> "
|
|
1325
|
+
def else_(self, value: Union[str, exp.Expression, Any]) -> "Case":
|
|
964
1326
|
"""Add an ELSE clause.
|
|
965
1327
|
|
|
966
1328
|
Args:
|
|
@@ -969,14 +1331,7 @@ class CaseExpressionBuilder:
|
|
|
969
1331
|
Returns:
|
|
970
1332
|
Self for method chaining.
|
|
971
1333
|
"""
|
|
972
|
-
|
|
973
|
-
self._default = exp.Literal.string(value)
|
|
974
|
-
elif isinstance(value, (int, float)):
|
|
975
|
-
self._default = exp.Literal.number(value)
|
|
976
|
-
elif isinstance(value, exp.Expression):
|
|
977
|
-
self._default = value
|
|
978
|
-
else:
|
|
979
|
-
self._default = exp.Literal.string(str(value))
|
|
1334
|
+
self._default = SQLFactory._to_literal(value)
|
|
980
1335
|
return self
|
|
981
1336
|
|
|
982
1337
|
def end(self) -> exp.Expression:
|
|
@@ -986,3 +1341,7 @@ class CaseExpressionBuilder:
|
|
|
986
1341
|
Complete CASE expression.
|
|
987
1342
|
"""
|
|
988
1343
|
return exp.Case(ifs=self._conditions, default=self._default)
|
|
1344
|
+
|
|
1345
|
+
|
|
1346
|
+
# Create a default SQL factory instance
|
|
1347
|
+
sql = SQLFactory()
|