sqlspec 0.25.0__py3-none-any.whl → 0.27.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 +7 -15
- sqlspec/_serialization.py +256 -24
- sqlspec/_typing.py +71 -52
- sqlspec/adapters/adbc/_types.py +1 -1
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +870 -0
- sqlspec/adapters/adbc/config.py +69 -12
- sqlspec/adapters/adbc/data_dictionary.py +340 -0
- sqlspec/adapters/adbc/driver.py +266 -58
- sqlspec/adapters/adbc/litestar/__init__.py +5 -0
- sqlspec/adapters/adbc/litestar/store.py +504 -0
- sqlspec/adapters/adbc/type_converter.py +153 -0
- sqlspec/adapters/aiosqlite/_types.py +1 -1
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +527 -0
- sqlspec/adapters/aiosqlite/config.py +88 -15
- sqlspec/adapters/aiosqlite/data_dictionary.py +149 -0
- sqlspec/adapters/aiosqlite/driver.py +143 -40
- sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
- sqlspec/adapters/aiosqlite/pool.py +7 -7
- sqlspec/adapters/asyncmy/__init__.py +7 -1
- sqlspec/adapters/asyncmy/_types.py +2 -2
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +493 -0
- sqlspec/adapters/asyncmy/config.py +68 -23
- sqlspec/adapters/asyncmy/data_dictionary.py +161 -0
- sqlspec/adapters/asyncmy/driver.py +313 -58
- sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncmy/litestar/store.py +296 -0
- sqlspec/adapters/asyncpg/__init__.py +2 -1
- sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
- sqlspec/adapters/asyncpg/_types.py +11 -7
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +450 -0
- sqlspec/adapters/asyncpg/config.py +59 -35
- sqlspec/adapters/asyncpg/data_dictionary.py +173 -0
- sqlspec/adapters/asyncpg/driver.py +170 -25
- sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncpg/litestar/store.py +253 -0
- sqlspec/adapters/bigquery/_types.py +1 -1
- sqlspec/adapters/bigquery/adk/__init__.py +5 -0
- sqlspec/adapters/bigquery/adk/store.py +576 -0
- sqlspec/adapters/bigquery/config.py +27 -10
- sqlspec/adapters/bigquery/data_dictionary.py +149 -0
- sqlspec/adapters/bigquery/driver.py +368 -142
- sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
- sqlspec/adapters/bigquery/litestar/store.py +327 -0
- sqlspec/adapters/bigquery/type_converter.py +125 -0
- sqlspec/adapters/duckdb/_types.py +1 -1
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +553 -0
- sqlspec/adapters/duckdb/config.py +80 -20
- sqlspec/adapters/duckdb/data_dictionary.py +163 -0
- sqlspec/adapters/duckdb/driver.py +167 -45
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +332 -0
- sqlspec/adapters/duckdb/pool.py +4 -4
- sqlspec/adapters/duckdb/type_converter.py +133 -0
- sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
- sqlspec/adapters/oracledb/_types.py +20 -2
- sqlspec/adapters/oracledb/adk/__init__.py +5 -0
- sqlspec/adapters/oracledb/adk/store.py +1745 -0
- sqlspec/adapters/oracledb/config.py +122 -32
- sqlspec/adapters/oracledb/data_dictionary.py +509 -0
- sqlspec/adapters/oracledb/driver.py +353 -91
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +767 -0
- sqlspec/adapters/oracledb/migrations.py +348 -73
- sqlspec/adapters/oracledb/type_converter.py +207 -0
- sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
- sqlspec/adapters/psqlpy/_types.py +2 -1
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +482 -0
- sqlspec/adapters/psqlpy/config.py +46 -17
- sqlspec/adapters/psqlpy/data_dictionary.py +172 -0
- sqlspec/adapters/psqlpy/driver.py +123 -209
- sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
- sqlspec/adapters/psqlpy/litestar/store.py +272 -0
- sqlspec/adapters/psqlpy/type_converter.py +102 -0
- sqlspec/adapters/psycopg/_type_handlers.py +80 -0
- sqlspec/adapters/psycopg/_types.py +2 -1
- sqlspec/adapters/psycopg/adk/__init__.py +5 -0
- sqlspec/adapters/psycopg/adk/store.py +944 -0
- sqlspec/adapters/psycopg/config.py +69 -35
- sqlspec/adapters/psycopg/data_dictionary.py +331 -0
- sqlspec/adapters/psycopg/driver.py +238 -81
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +554 -0
- sqlspec/adapters/sqlite/__init__.py +2 -1
- sqlspec/adapters/sqlite/_type_handlers.py +86 -0
- sqlspec/adapters/sqlite/_types.py +1 -1
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +572 -0
- sqlspec/adapters/sqlite/config.py +87 -15
- sqlspec/adapters/sqlite/data_dictionary.py +149 -0
- sqlspec/adapters/sqlite/driver.py +137 -54
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +318 -0
- sqlspec/adapters/sqlite/pool.py +18 -9
- sqlspec/base.py +45 -26
- sqlspec/builder/__init__.py +73 -4
- sqlspec/builder/_base.py +162 -89
- sqlspec/builder/_column.py +62 -29
- sqlspec/builder/_ddl.py +180 -121
- sqlspec/builder/_delete.py +5 -4
- sqlspec/builder/_dml.py +388 -0
- sqlspec/{_sql.py → builder/_factory.py} +53 -94
- sqlspec/builder/_insert.py +32 -131
- sqlspec/builder/_join.py +375 -0
- sqlspec/builder/_merge.py +446 -11
- sqlspec/builder/_parsing_utils.py +111 -17
- sqlspec/builder/_select.py +1457 -24
- sqlspec/builder/_update.py +11 -42
- sqlspec/cli.py +307 -194
- sqlspec/config.py +252 -67
- sqlspec/core/__init__.py +5 -4
- sqlspec/core/cache.py +17 -17
- sqlspec/core/compiler.py +62 -9
- sqlspec/core/filters.py +37 -37
- sqlspec/core/hashing.py +9 -9
- sqlspec/core/parameters.py +83 -48
- sqlspec/core/result.py +102 -46
- sqlspec/core/splitter.py +16 -17
- sqlspec/core/statement.py +36 -30
- sqlspec/core/type_conversion.py +235 -0
- sqlspec/driver/__init__.py +7 -6
- sqlspec/driver/_async.py +188 -151
- sqlspec/driver/_common.py +285 -80
- sqlspec/driver/_sync.py +188 -152
- sqlspec/driver/mixins/_result_tools.py +20 -236
- sqlspec/driver/mixins/_sql_translator.py +4 -4
- sqlspec/exceptions.py +75 -7
- sqlspec/extensions/adk/__init__.py +53 -0
- sqlspec/extensions/adk/_types.py +51 -0
- sqlspec/extensions/adk/converters.py +172 -0
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
- sqlspec/extensions/adk/migrations/__init__.py +0 -0
- sqlspec/extensions/adk/service.py +181 -0
- sqlspec/extensions/adk/store.py +536 -0
- sqlspec/extensions/aiosql/adapter.py +73 -53
- sqlspec/extensions/litestar/__init__.py +21 -4
- sqlspec/extensions/litestar/cli.py +54 -10
- sqlspec/extensions/litestar/config.py +59 -266
- sqlspec/extensions/litestar/handlers.py +46 -17
- sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
- sqlspec/extensions/litestar/migrations/__init__.py +3 -0
- sqlspec/extensions/litestar/plugin.py +324 -223
- sqlspec/extensions/litestar/providers.py +25 -25
- sqlspec/extensions/litestar/store.py +265 -0
- sqlspec/loader.py +30 -49
- sqlspec/migrations/__init__.py +4 -3
- sqlspec/migrations/base.py +302 -39
- sqlspec/migrations/commands.py +611 -144
- sqlspec/migrations/context.py +142 -0
- sqlspec/migrations/fix.py +199 -0
- sqlspec/migrations/loaders.py +68 -23
- sqlspec/migrations/runner.py +543 -107
- sqlspec/migrations/tracker.py +237 -21
- sqlspec/migrations/utils.py +51 -3
- sqlspec/migrations/validation.py +177 -0
- sqlspec/protocols.py +66 -36
- sqlspec/storage/_utils.py +98 -0
- sqlspec/storage/backends/fsspec.py +134 -106
- sqlspec/storage/backends/local.py +78 -51
- sqlspec/storage/backends/obstore.py +278 -162
- sqlspec/storage/registry.py +75 -39
- sqlspec/typing.py +16 -84
- sqlspec/utils/config_resolver.py +153 -0
- sqlspec/utils/correlation.py +4 -5
- sqlspec/utils/data_transformation.py +3 -2
- sqlspec/utils/deprecation.py +9 -8
- sqlspec/utils/fixtures.py +4 -4
- sqlspec/utils/logging.py +46 -6
- sqlspec/utils/module_loader.py +2 -2
- sqlspec/utils/schema.py +288 -0
- sqlspec/utils/serializers.py +50 -2
- sqlspec/utils/sync_tools.py +21 -17
- sqlspec/utils/text.py +1 -2
- sqlspec/utils/type_guards.py +111 -20
- sqlspec/utils/version.py +433 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
- sqlspec-0.27.0.dist-info/RECORD +207 -0
- sqlspec/builder/mixins/__init__.py +0 -55
- sqlspec/builder/mixins/_cte_and_set_ops.py +0 -254
- sqlspec/builder/mixins/_delete_operations.py +0 -50
- sqlspec/builder/mixins/_insert_operations.py +0 -282
- sqlspec/builder/mixins/_join_operations.py +0 -389
- sqlspec/builder/mixins/_merge_operations.py +0 -592
- sqlspec/builder/mixins/_order_limit_operations.py +0 -152
- sqlspec/builder/mixins/_pivot_operations.py +0 -157
- sqlspec/builder/mixins/_select_operations.py +0 -936
- sqlspec/builder/mixins/_update_operations.py +0 -218
- sqlspec/builder/mixins/_where_clause.py +0 -1304
- sqlspec-0.25.0.dist-info/RECORD +0 -139
- sqlspec-0.25.0.dist-info/licenses/NOTICE +0 -29
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,16 +4,16 @@ Provides statement builders (select, insert, update, etc.) and column expression
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import logging
|
|
7
|
-
from typing import TYPE_CHECKING, Any,
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Union
|
|
8
8
|
|
|
9
9
|
import sqlglot
|
|
10
10
|
from sqlglot import exp
|
|
11
11
|
from sqlglot.dialects.dialect import DialectType
|
|
12
12
|
from sqlglot.errors import ParseError as SQLGlotParseError
|
|
13
13
|
|
|
14
|
-
from sqlspec.builder import
|
|
14
|
+
from sqlspec.builder._column import Column
|
|
15
|
+
from sqlspec.builder._ddl import (
|
|
15
16
|
AlterTable,
|
|
16
|
-
Column,
|
|
17
17
|
CommentOn,
|
|
18
18
|
CreateIndex,
|
|
19
19
|
CreateMaterializedView,
|
|
@@ -21,18 +21,14 @@ from sqlspec.builder import (
|
|
|
21
21
|
CreateTable,
|
|
22
22
|
CreateTableAsSelect,
|
|
23
23
|
CreateView,
|
|
24
|
-
Delete,
|
|
25
24
|
DropIndex,
|
|
26
25
|
DropSchema,
|
|
27
26
|
DropTable,
|
|
28
27
|
DropView,
|
|
29
|
-
Insert,
|
|
30
|
-
Merge,
|
|
31
28
|
RenameTable,
|
|
32
|
-
Select,
|
|
33
29
|
Truncate,
|
|
34
|
-
Update,
|
|
35
30
|
)
|
|
31
|
+
from sqlspec.builder._delete import Delete
|
|
36
32
|
from sqlspec.builder._expression_wrappers import (
|
|
37
33
|
AggregateExpression,
|
|
38
34
|
ConversionExpression,
|
|
@@ -40,8 +36,12 @@ from sqlspec.builder._expression_wrappers import (
|
|
|
40
36
|
MathExpression,
|
|
41
37
|
StringExpression,
|
|
42
38
|
)
|
|
43
|
-
from sqlspec.builder.
|
|
44
|
-
from sqlspec.builder.
|
|
39
|
+
from sqlspec.builder._insert import Insert
|
|
40
|
+
from sqlspec.builder._join import JoinBuilder
|
|
41
|
+
from sqlspec.builder._merge import Merge
|
|
42
|
+
from sqlspec.builder._parsing_utils import extract_expression, to_expression
|
|
43
|
+
from sqlspec.builder._select import Case, Select, SubqueryBuilder, WindowFunctionBuilder
|
|
44
|
+
from sqlspec.builder._update import Update
|
|
45
45
|
from sqlspec.core.statement import SQL
|
|
46
46
|
from sqlspec.exceptions import SQLBuilderError
|
|
47
47
|
|
|
@@ -204,7 +204,7 @@ class SQLFactory:
|
|
|
204
204
|
select_builder.select(*columns_or_sql)
|
|
205
205
|
return select_builder
|
|
206
206
|
|
|
207
|
-
def insert(self, table_or_sql:
|
|
207
|
+
def insert(self, table_or_sql: str | None = None, dialect: DialectType = None) -> "Insert":
|
|
208
208
|
builder_dialect = dialect or self.dialect
|
|
209
209
|
builder = Insert(dialect=builder_dialect)
|
|
210
210
|
if table_or_sql:
|
|
@@ -221,7 +221,7 @@ class SQLFactory:
|
|
|
221
221
|
return builder.into(table_or_sql)
|
|
222
222
|
return builder
|
|
223
223
|
|
|
224
|
-
def update(self, table_or_sql:
|
|
224
|
+
def update(self, table_or_sql: str | None = None, dialect: DialectType = None) -> "Update":
|
|
225
225
|
builder_dialect = dialect or self.dialect
|
|
226
226
|
builder = Update(dialect=builder_dialect)
|
|
227
227
|
if table_or_sql:
|
|
@@ -237,7 +237,7 @@ class SQLFactory:
|
|
|
237
237
|
return builder.table(table_or_sql)
|
|
238
238
|
return builder
|
|
239
239
|
|
|
240
|
-
def delete(self, table_or_sql:
|
|
240
|
+
def delete(self, table_or_sql: str | None = None, dialect: DialectType = None) -> "Delete":
|
|
241
241
|
builder_dialect = dialect or self.dialect
|
|
242
242
|
builder = Delete(dialect=builder_dialect)
|
|
243
243
|
if table_or_sql and self._looks_like_sql(table_or_sql):
|
|
@@ -251,7 +251,7 @@ class SQLFactory:
|
|
|
251
251
|
return self._populate_delete_from_sql(builder, table_or_sql)
|
|
252
252
|
return builder
|
|
253
253
|
|
|
254
|
-
def merge(self, table_or_sql:
|
|
254
|
+
def merge(self, table_or_sql: str | None = None, dialect: DialectType = None) -> "Merge":
|
|
255
255
|
builder_dialect = dialect or self.dialect
|
|
256
256
|
builder = Merge(dialect=builder_dialect)
|
|
257
257
|
if table_or_sql:
|
|
@@ -422,7 +422,7 @@ class SQLFactory:
|
|
|
422
422
|
return CommentOn(dialect=dialect or self.dialect)
|
|
423
423
|
|
|
424
424
|
@staticmethod
|
|
425
|
-
def _looks_like_sql(candidate: str, expected_type:
|
|
425
|
+
def _looks_like_sql(candidate: str, expected_type: str | None = None) -> bool:
|
|
426
426
|
"""Determine if a string looks like SQL.
|
|
427
427
|
|
|
428
428
|
Args:
|
|
@@ -524,7 +524,7 @@ class SQLFactory:
|
|
|
524
524
|
logger.warning("Failed to parse MERGE SQL, falling back to traditional mode: %s", e)
|
|
525
525
|
return builder
|
|
526
526
|
|
|
527
|
-
def column(self, name: str, table:
|
|
527
|
+
def column(self, name: str, table: str | None = None) -> Column:
|
|
528
528
|
"""Create a column reference.
|
|
529
529
|
|
|
530
530
|
Args:
|
|
@@ -682,7 +682,7 @@ class SQLFactory:
|
|
|
682
682
|
return Column(name)
|
|
683
683
|
|
|
684
684
|
@staticmethod
|
|
685
|
-
def raw(sql_fragment: str, **parameters: Any) -> "
|
|
685
|
+
def raw(sql_fragment: str, **parameters: Any) -> "exp.Expression | SQL":
|
|
686
686
|
"""Create a raw SQL expression from a string fragment with optional parameters.
|
|
687
687
|
|
|
688
688
|
Args:
|
|
@@ -746,7 +746,7 @@ class SQLFactory:
|
|
|
746
746
|
if isinstance(column, str) and column == "*":
|
|
747
747
|
expr = exp.Count(this=exp.Star(), distinct=distinct)
|
|
748
748
|
else:
|
|
749
|
-
col_expr =
|
|
749
|
+
col_expr = extract_expression(column)
|
|
750
750
|
expr = exp.Count(this=col_expr, distinct=distinct)
|
|
751
751
|
return AggregateExpression(expr)
|
|
752
752
|
|
|
@@ -774,7 +774,7 @@ class SQLFactory:
|
|
|
774
774
|
Returns:
|
|
775
775
|
SUM expression.
|
|
776
776
|
"""
|
|
777
|
-
col_expr =
|
|
777
|
+
col_expr = extract_expression(column)
|
|
778
778
|
return AggregateExpression(exp.Sum(this=col_expr, distinct=distinct))
|
|
779
779
|
|
|
780
780
|
@staticmethod
|
|
@@ -787,7 +787,7 @@ class SQLFactory:
|
|
|
787
787
|
Returns:
|
|
788
788
|
AVG expression.
|
|
789
789
|
"""
|
|
790
|
-
col_expr =
|
|
790
|
+
col_expr = extract_expression(column)
|
|
791
791
|
return AggregateExpression(exp.Avg(this=col_expr))
|
|
792
792
|
|
|
793
793
|
@staticmethod
|
|
@@ -800,7 +800,7 @@ class SQLFactory:
|
|
|
800
800
|
Returns:
|
|
801
801
|
MAX expression.
|
|
802
802
|
"""
|
|
803
|
-
col_expr =
|
|
803
|
+
col_expr = extract_expression(column)
|
|
804
804
|
return AggregateExpression(exp.Max(this=col_expr))
|
|
805
805
|
|
|
806
806
|
@staticmethod
|
|
@@ -813,11 +813,11 @@ class SQLFactory:
|
|
|
813
813
|
Returns:
|
|
814
814
|
MIN expression.
|
|
815
815
|
"""
|
|
816
|
-
col_expr =
|
|
816
|
+
col_expr = extract_expression(column)
|
|
817
817
|
return AggregateExpression(exp.Min(this=col_expr))
|
|
818
818
|
|
|
819
819
|
@staticmethod
|
|
820
|
-
def rollup(*columns:
|
|
820
|
+
def rollup(*columns: str | exp.Expression) -> FunctionExpression:
|
|
821
821
|
"""Create a ROLLUP expression for GROUP BY clauses.
|
|
822
822
|
|
|
823
823
|
Args:
|
|
@@ -839,7 +839,7 @@ class SQLFactory:
|
|
|
839
839
|
return FunctionExpression(exp.Rollup(expressions=column_exprs))
|
|
840
840
|
|
|
841
841
|
@staticmethod
|
|
842
|
-
def cube(*columns:
|
|
842
|
+
def cube(*columns: str | exp.Expression) -> FunctionExpression:
|
|
843
843
|
"""Create a CUBE expression for GROUP BY clauses.
|
|
844
844
|
|
|
845
845
|
Args:
|
|
@@ -861,7 +861,7 @@ class SQLFactory:
|
|
|
861
861
|
return FunctionExpression(exp.Cube(expressions=column_exprs))
|
|
862
862
|
|
|
863
863
|
@staticmethod
|
|
864
|
-
def grouping_sets(*column_sets:
|
|
864
|
+
def grouping_sets(*column_sets: tuple[str, ...] | list[str]) -> FunctionExpression:
|
|
865
865
|
"""Create a GROUPING SETS expression for GROUP BY clauses.
|
|
866
866
|
|
|
867
867
|
Args:
|
|
@@ -895,7 +895,7 @@ class SQLFactory:
|
|
|
895
895
|
return FunctionExpression(exp.GroupingSets(expressions=set_expressions))
|
|
896
896
|
|
|
897
897
|
@staticmethod
|
|
898
|
-
def any(values:
|
|
898
|
+
def any(values: list[Any] | exp.Expression | str) -> FunctionExpression:
|
|
899
899
|
"""Create an ANY expression for use with comparison operators.
|
|
900
900
|
|
|
901
901
|
Args:
|
|
@@ -923,7 +923,7 @@ class SQLFactory:
|
|
|
923
923
|
return FunctionExpression(exp.Any(this=values))
|
|
924
924
|
|
|
925
925
|
@staticmethod
|
|
926
|
-
def not_any_(values:
|
|
926
|
+
def not_any_(values: list[Any] | exp.Expression | str) -> FunctionExpression:
|
|
927
927
|
"""Create a NOT ANY expression for use with comparison operators.
|
|
928
928
|
|
|
929
929
|
Args:
|
|
@@ -945,7 +945,7 @@ class SQLFactory:
|
|
|
945
945
|
return SQLFactory.any(values)
|
|
946
946
|
|
|
947
947
|
@staticmethod
|
|
948
|
-
def concat(*expressions:
|
|
948
|
+
def concat(*expressions: str | exp.Expression) -> StringExpression:
|
|
949
949
|
"""Create a CONCAT expression.
|
|
950
950
|
|
|
951
951
|
Args:
|
|
@@ -958,7 +958,7 @@ class SQLFactory:
|
|
|
958
958
|
return StringExpression(exp.Concat(expressions=exprs))
|
|
959
959
|
|
|
960
960
|
@staticmethod
|
|
961
|
-
def upper(column:
|
|
961
|
+
def upper(column: str | exp.Expression) -> StringExpression:
|
|
962
962
|
"""Create an UPPER expression.
|
|
963
963
|
|
|
964
964
|
Args:
|
|
@@ -971,7 +971,7 @@ class SQLFactory:
|
|
|
971
971
|
return StringExpression(exp.Upper(this=col_expr))
|
|
972
972
|
|
|
973
973
|
@staticmethod
|
|
974
|
-
def lower(column:
|
|
974
|
+
def lower(column: str | exp.Expression) -> StringExpression:
|
|
975
975
|
"""Create a LOWER expression.
|
|
976
976
|
|
|
977
977
|
Args:
|
|
@@ -984,7 +984,7 @@ class SQLFactory:
|
|
|
984
984
|
return StringExpression(exp.Lower(this=col_expr))
|
|
985
985
|
|
|
986
986
|
@staticmethod
|
|
987
|
-
def length(column:
|
|
987
|
+
def length(column: str | exp.Expression) -> StringExpression:
|
|
988
988
|
"""Create a LENGTH expression.
|
|
989
989
|
|
|
990
990
|
Args:
|
|
@@ -997,7 +997,7 @@ class SQLFactory:
|
|
|
997
997
|
return StringExpression(exp.Length(this=col_expr))
|
|
998
998
|
|
|
999
999
|
@staticmethod
|
|
1000
|
-
def round(column:
|
|
1000
|
+
def round(column: str | exp.Expression, decimals: int = 0) -> MathExpression:
|
|
1001
1001
|
"""Create a ROUND expression.
|
|
1002
1002
|
|
|
1003
1003
|
Args:
|
|
@@ -1035,46 +1035,7 @@ class SQLFactory:
|
|
|
1035
1035
|
return FunctionExpression(exp.convert(value))
|
|
1036
1036
|
|
|
1037
1037
|
@staticmethod
|
|
1038
|
-
def
|
|
1039
|
-
"""Convert a Python value to a raw SQLGlot expression.
|
|
1040
|
-
|
|
1041
|
-
Args:
|
|
1042
|
-
value: Python value or SQLGlot expression to convert.
|
|
1043
|
-
|
|
1044
|
-
Returns:
|
|
1045
|
-
Raw SQLGlot expression.
|
|
1046
|
-
"""
|
|
1047
|
-
if isinstance(value, exp.Expression):
|
|
1048
|
-
return value
|
|
1049
|
-
return exp.convert(value)
|
|
1050
|
-
|
|
1051
|
-
@staticmethod
|
|
1052
|
-
def _extract_expression(value: Any) -> exp.Expression:
|
|
1053
|
-
"""Extract SQLGlot expression from value, handling our wrapper types.
|
|
1054
|
-
|
|
1055
|
-
Args:
|
|
1056
|
-
value: String, SQLGlot expression, or our wrapper type.
|
|
1057
|
-
|
|
1058
|
-
Returns:
|
|
1059
|
-
Raw SQLGlot expression.
|
|
1060
|
-
"""
|
|
1061
|
-
from sqlspec.builder._expression_wrappers import ExpressionWrapper
|
|
1062
|
-
from sqlspec.builder.mixins._select_operations import Case
|
|
1063
|
-
|
|
1064
|
-
if isinstance(value, str):
|
|
1065
|
-
return exp.column(value)
|
|
1066
|
-
if isinstance(value, Column):
|
|
1067
|
-
return value.sqlglot_expression
|
|
1068
|
-
if isinstance(value, ExpressionWrapper):
|
|
1069
|
-
return value.expression
|
|
1070
|
-
if isinstance(value, Case):
|
|
1071
|
-
return exp.Case(ifs=value.conditions, default=value.default)
|
|
1072
|
-
if isinstance(value, exp.Expression):
|
|
1073
|
-
return value
|
|
1074
|
-
return exp.convert(value)
|
|
1075
|
-
|
|
1076
|
-
@staticmethod
|
|
1077
|
-
def decode(column: Union[str, exp.Expression], *args: Union[str, exp.Expression, Any]) -> FunctionExpression:
|
|
1038
|
+
def decode(column: str | exp.Expression, *args: str | exp.Expression | Any) -> FunctionExpression:
|
|
1078
1039
|
"""Create a DECODE expression (Oracle-style conditional logic).
|
|
1079
1040
|
|
|
1080
1041
|
DECODE compares column to each search value and returns the corresponding result.
|
|
@@ -1109,14 +1070,14 @@ class SQLFactory:
|
|
|
1109
1070
|
|
|
1110
1071
|
for i in range(0, len(args) - 1, 2):
|
|
1111
1072
|
if i + 1 >= len(args):
|
|
1112
|
-
default =
|
|
1073
|
+
default = to_expression(args[i])
|
|
1113
1074
|
break
|
|
1114
1075
|
|
|
1115
1076
|
search_val = args[i]
|
|
1116
1077
|
result_val = args[i + 1]
|
|
1117
1078
|
|
|
1118
|
-
search_expr =
|
|
1119
|
-
result_expr =
|
|
1079
|
+
search_expr = to_expression(search_val)
|
|
1080
|
+
result_expr = to_expression(result_val)
|
|
1120
1081
|
|
|
1121
1082
|
condition = exp.EQ(this=col_expr, expression=search_expr)
|
|
1122
1083
|
conditions.append(exp.If(this=condition, true=result_expr))
|
|
@@ -1124,7 +1085,7 @@ class SQLFactory:
|
|
|
1124
1085
|
return FunctionExpression(exp.Case(ifs=conditions, default=default))
|
|
1125
1086
|
|
|
1126
1087
|
@staticmethod
|
|
1127
|
-
def cast(column:
|
|
1088
|
+
def cast(column: str | exp.Expression, data_type: str) -> ConversionExpression:
|
|
1128
1089
|
"""Create a CAST expression for type conversion.
|
|
1129
1090
|
|
|
1130
1091
|
Args:
|
|
@@ -1138,7 +1099,7 @@ class SQLFactory:
|
|
|
1138
1099
|
return ConversionExpression(exp.Cast(this=col_expr, to=exp.DataType.build(data_type)))
|
|
1139
1100
|
|
|
1140
1101
|
@staticmethod
|
|
1141
|
-
def coalesce(*expressions:
|
|
1102
|
+
def coalesce(*expressions: str | exp.Expression) -> ConversionExpression:
|
|
1142
1103
|
"""Create a COALESCE expression.
|
|
1143
1104
|
|
|
1144
1105
|
Args:
|
|
@@ -1151,9 +1112,7 @@ class SQLFactory:
|
|
|
1151
1112
|
return ConversionExpression(exp.Coalesce(expressions=exprs))
|
|
1152
1113
|
|
|
1153
1114
|
@staticmethod
|
|
1154
|
-
def nvl(
|
|
1155
|
-
column: Union[str, exp.Expression], substitute_value: Union[str, exp.Expression, Any]
|
|
1156
|
-
) -> ConversionExpression:
|
|
1115
|
+
def nvl(column: str | exp.Expression, substitute_value: str | exp.Expression | Any) -> ConversionExpression:
|
|
1157
1116
|
"""Create an NVL (Oracle-style) expression using COALESCE.
|
|
1158
1117
|
|
|
1159
1118
|
Args:
|
|
@@ -1164,14 +1123,14 @@ class SQLFactory:
|
|
|
1164
1123
|
COALESCE expression equivalent to NVL.
|
|
1165
1124
|
"""
|
|
1166
1125
|
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
1167
|
-
sub_expr =
|
|
1126
|
+
sub_expr = to_expression(substitute_value)
|
|
1168
1127
|
return ConversionExpression(exp.Coalesce(expressions=[col_expr, sub_expr]))
|
|
1169
1128
|
|
|
1170
1129
|
@staticmethod
|
|
1171
1130
|
def nvl2(
|
|
1172
|
-
column:
|
|
1173
|
-
value_if_not_null:
|
|
1174
|
-
value_if_null:
|
|
1131
|
+
column: str | exp.Expression,
|
|
1132
|
+
value_if_not_null: str | exp.Expression | Any,
|
|
1133
|
+
value_if_null: str | exp.Expression | Any,
|
|
1175
1134
|
) -> ConversionExpression:
|
|
1176
1135
|
"""Create an NVL2 (Oracle-style) expression using CASE.
|
|
1177
1136
|
|
|
@@ -1192,8 +1151,8 @@ class SQLFactory:
|
|
|
1192
1151
|
```
|
|
1193
1152
|
"""
|
|
1194
1153
|
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
1195
|
-
not_null_expr =
|
|
1196
|
-
null_expr =
|
|
1154
|
+
not_null_expr = to_expression(value_if_not_null)
|
|
1155
|
+
null_expr = to_expression(value_if_null)
|
|
1197
1156
|
|
|
1198
1157
|
is_null = exp.Is(this=col_expr, expression=exp.Null())
|
|
1199
1158
|
condition = exp.Not(this=is_null)
|
|
@@ -1284,8 +1243,8 @@ class SQLFactory:
|
|
|
1284
1243
|
|
|
1285
1244
|
def row_number(
|
|
1286
1245
|
self,
|
|
1287
|
-
partition_by:
|
|
1288
|
-
order_by:
|
|
1246
|
+
partition_by: str | list[str] | exp.Expression | None = None,
|
|
1247
|
+
order_by: str | list[str] | exp.Expression | None = None,
|
|
1289
1248
|
) -> FunctionExpression:
|
|
1290
1249
|
"""Create a ROW_NUMBER() window function.
|
|
1291
1250
|
|
|
@@ -1300,8 +1259,8 @@ class SQLFactory:
|
|
|
1300
1259
|
|
|
1301
1260
|
def rank(
|
|
1302
1261
|
self,
|
|
1303
|
-
partition_by:
|
|
1304
|
-
order_by:
|
|
1262
|
+
partition_by: str | list[str] | exp.Expression | None = None,
|
|
1263
|
+
order_by: str | list[str] | exp.Expression | None = None,
|
|
1305
1264
|
) -> FunctionExpression:
|
|
1306
1265
|
"""Create a RANK() window function.
|
|
1307
1266
|
|
|
@@ -1316,8 +1275,8 @@ class SQLFactory:
|
|
|
1316
1275
|
|
|
1317
1276
|
def dense_rank(
|
|
1318
1277
|
self,
|
|
1319
|
-
partition_by:
|
|
1320
|
-
order_by:
|
|
1278
|
+
partition_by: str | list[str] | exp.Expression | None = None,
|
|
1279
|
+
order_by: str | list[str] | exp.Expression | None = None,
|
|
1321
1280
|
) -> FunctionExpression:
|
|
1322
1281
|
"""Create a DENSE_RANK() window function.
|
|
1323
1282
|
|
|
@@ -1334,8 +1293,8 @@ class SQLFactory:
|
|
|
1334
1293
|
def _create_window_function(
|
|
1335
1294
|
func_name: str,
|
|
1336
1295
|
func_args: list[exp.Expression],
|
|
1337
|
-
partition_by:
|
|
1338
|
-
order_by:
|
|
1296
|
+
partition_by: str | list[str] | exp.Expression | None = None,
|
|
1297
|
+
order_by: str | list[str] | exp.Expression | None = None,
|
|
1339
1298
|
) -> FunctionExpression:
|
|
1340
1299
|
"""Helper to create window function expressions.
|
|
1341
1300
|
|
sqlspec/builder/_insert.py
CHANGED
|
@@ -4,13 +4,15 @@ Provides a fluent interface for building SQL INSERT queries with
|
|
|
4
4
|
parameter binding and validation.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from typing import TYPE_CHECKING, Any, Final
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Final
|
|
8
8
|
|
|
9
9
|
from sqlglot import exp
|
|
10
10
|
from typing_extensions import Self
|
|
11
11
|
|
|
12
12
|
from sqlspec.builder._base import QueryBuilder
|
|
13
|
-
from sqlspec.builder.
|
|
13
|
+
from sqlspec.builder._dml import InsertFromSelectMixin, InsertIntoClauseMixin, InsertValuesMixin
|
|
14
|
+
from sqlspec.builder._parsing_utils import extract_sql_object_expression
|
|
15
|
+
from sqlspec.builder._select import ReturningClauseMixin
|
|
14
16
|
from sqlspec.core.result import SQLResult
|
|
15
17
|
from sqlspec.exceptions import SQLBuilderError
|
|
16
18
|
from sqlspec.utils.type_guards import has_expression_and_sql
|
|
@@ -22,9 +24,6 @@ if TYPE_CHECKING:
|
|
|
22
24
|
__all__ = ("Insert",)
|
|
23
25
|
|
|
24
26
|
ERR_MSG_TABLE_NOT_SET: Final[str] = "The target table must be set using .into() before adding values."
|
|
25
|
-
ERR_MSG_VALUES_COLUMNS_MISMATCH: Final[str] = (
|
|
26
|
-
"Number of values ({values_len}) does not match the number of specified columns ({columns_len})."
|
|
27
|
-
)
|
|
28
27
|
ERR_MSG_INTERNAL_EXPRESSION_TYPE: Final[str] = "Internal error: expression is not an Insert instance as expected."
|
|
29
28
|
ERR_MSG_EXPRESSION_NOT_INITIALIZED: Final[str] = "Internal error: base expression not initialized."
|
|
30
29
|
|
|
@@ -37,7 +36,7 @@ class Insert(QueryBuilder, ReturningClauseMixin, InsertValuesMixin, InsertFromSe
|
|
|
37
36
|
|
|
38
37
|
__slots__ = ("_columns", "_table", "_values_added_count")
|
|
39
38
|
|
|
40
|
-
def __init__(self, table:
|
|
39
|
+
def __init__(self, table: str | None = None, **kwargs: Any) -> None:
|
|
41
40
|
"""Initialize INSERT with optional table.
|
|
42
41
|
|
|
43
42
|
Args:
|
|
@@ -46,8 +45,7 @@ class Insert(QueryBuilder, ReturningClauseMixin, InsertValuesMixin, InsertFromSe
|
|
|
46
45
|
"""
|
|
47
46
|
super().__init__(**kwargs)
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
self._table: Optional[str] = None
|
|
48
|
+
self._table: str | None = None
|
|
51
49
|
self._columns: list[str] = []
|
|
52
50
|
self._values_added_count: int = 0
|
|
53
51
|
|
|
@@ -94,95 +92,6 @@ class Insert(QueryBuilder, ReturningClauseMixin, InsertValuesMixin, InsertFromSe
|
|
|
94
92
|
"""Get the insert expression (public API)."""
|
|
95
93
|
return self._get_insert_expression()
|
|
96
94
|
|
|
97
|
-
def values(self, *values: Any, **kwargs: Any) -> "Self":
|
|
98
|
-
"""Adds a row of values to the INSERT statement.
|
|
99
|
-
|
|
100
|
-
This method can be called multiple times to insert multiple rows,
|
|
101
|
-
resulting in a multi-row INSERT statement like `VALUES (...), (...)`.
|
|
102
|
-
|
|
103
|
-
Supports:
|
|
104
|
-
- values(val1, val2, val3)
|
|
105
|
-
- values(col1=val1, col2=val2)
|
|
106
|
-
- values(mapping)
|
|
107
|
-
|
|
108
|
-
Args:
|
|
109
|
-
*values: The values for the row to be inserted. The number of values
|
|
110
|
-
must match the number of columns set by `columns()`, if `columns()` was called
|
|
111
|
-
and specified any non-empty list of columns.
|
|
112
|
-
**kwargs: Column-value pairs for named values.
|
|
113
|
-
|
|
114
|
-
Returns:
|
|
115
|
-
The current builder instance for method chaining.
|
|
116
|
-
|
|
117
|
-
Raises:
|
|
118
|
-
SQLBuilderError: If `into()` has not been called to set the table,
|
|
119
|
-
or if `columns()` was called with a non-empty list of columns
|
|
120
|
-
and the number of values does not match the number of specified columns.
|
|
121
|
-
"""
|
|
122
|
-
if not self._table:
|
|
123
|
-
raise SQLBuilderError(ERR_MSG_TABLE_NOT_SET)
|
|
124
|
-
|
|
125
|
-
if kwargs:
|
|
126
|
-
if values:
|
|
127
|
-
msg = "Cannot mix positional values with keyword values."
|
|
128
|
-
raise SQLBuilderError(msg)
|
|
129
|
-
return self.values_from_dict(kwargs)
|
|
130
|
-
|
|
131
|
-
if len(values) == 1:
|
|
132
|
-
values_0 = values[0]
|
|
133
|
-
if hasattr(values_0, "items") and hasattr(values_0, "keys"):
|
|
134
|
-
return self.values_from_dict(values_0)
|
|
135
|
-
|
|
136
|
-
insert_expr = self.get_insert_expression()
|
|
137
|
-
|
|
138
|
-
if self._columns and len(values) != len(self._columns):
|
|
139
|
-
msg = ERR_MSG_VALUES_COLUMNS_MISMATCH.format(values_len=len(values), columns_len=len(self._columns))
|
|
140
|
-
raise SQLBuilderError(msg)
|
|
141
|
-
|
|
142
|
-
value_placeholders: list[exp.Expression] = []
|
|
143
|
-
for i, value in enumerate(values):
|
|
144
|
-
if isinstance(value, exp.Expression):
|
|
145
|
-
value_placeholders.append(value)
|
|
146
|
-
elif has_expression_and_sql(value):
|
|
147
|
-
# Handle SQL objects (from sql.raw with parameters)
|
|
148
|
-
expression = getattr(value, "expression", None)
|
|
149
|
-
if expression is not None and isinstance(expression, exp.Expression):
|
|
150
|
-
# Merge parameters from SQL object into builder
|
|
151
|
-
self._merge_sql_object_parameters(value)
|
|
152
|
-
value_placeholders.append(expression)
|
|
153
|
-
else:
|
|
154
|
-
# If expression is None, fall back to parsing the raw SQL
|
|
155
|
-
sql_text = getattr(value, "sql", "")
|
|
156
|
-
# Merge parameters even when parsing raw SQL
|
|
157
|
-
self._merge_sql_object_parameters(value)
|
|
158
|
-
# Check if sql_text is callable (like Expression.sql method)
|
|
159
|
-
if callable(sql_text):
|
|
160
|
-
sql_text = str(value)
|
|
161
|
-
value_expr = exp.maybe_parse(sql_text) or exp.convert(str(sql_text))
|
|
162
|
-
value_placeholders.append(value_expr)
|
|
163
|
-
else:
|
|
164
|
-
if self._columns and i < len(self._columns):
|
|
165
|
-
column_str = str(self._columns[i])
|
|
166
|
-
column_name = column_str.rsplit(".", maxsplit=1)[-1] if "." in column_str else column_str
|
|
167
|
-
param_name = self.generate_unique_parameter_name(column_name)
|
|
168
|
-
else:
|
|
169
|
-
param_name = self.generate_unique_parameter_name(f"value_{i + 1}")
|
|
170
|
-
_, param_name = self.add_parameter(value, name=param_name)
|
|
171
|
-
value_placeholders.append(exp.Placeholder(this=param_name))
|
|
172
|
-
|
|
173
|
-
tuple_expr = exp.Tuple(expressions=value_placeholders)
|
|
174
|
-
if self._values_added_count == 0:
|
|
175
|
-
insert_expr.set("expression", exp.Values(expressions=[tuple_expr]))
|
|
176
|
-
else:
|
|
177
|
-
current_values = insert_expr.args.get("expression")
|
|
178
|
-
if isinstance(current_values, exp.Values):
|
|
179
|
-
current_values.expressions.append(tuple_expr)
|
|
180
|
-
else:
|
|
181
|
-
insert_expr.set("expression", exp.Values(expressions=[tuple_expr]))
|
|
182
|
-
|
|
183
|
-
self._values_added_count += 1
|
|
184
|
-
return self
|
|
185
|
-
|
|
186
95
|
def values_from_dict(self, data: "Mapping[str, Any]") -> "Self":
|
|
187
96
|
"""Adds a row of values from a dictionary.
|
|
188
97
|
|
|
@@ -258,17 +167,14 @@ class Insert(QueryBuilder, ReturningClauseMixin, InsertValuesMixin, InsertFromSe
|
|
|
258
167
|
|
|
259
168
|
Example:
|
|
260
169
|
```python
|
|
261
|
-
# ON CONFLICT (id) DO NOTHING
|
|
262
170
|
sql.insert("users").values(id=1, name="John").on_conflict(
|
|
263
171
|
"id"
|
|
264
172
|
).do_nothing()
|
|
265
173
|
|
|
266
|
-
# ON CONFLICT (email, username) DO UPDATE SET updated_at = NOW()
|
|
267
174
|
sql.insert("users").values(...).on_conflict(
|
|
268
175
|
"email", "username"
|
|
269
176
|
).do_update(updated_at=sql.raw("NOW()"))
|
|
270
177
|
|
|
271
|
-
# ON CONFLICT DO NOTHING (catches all conflicts)
|
|
272
178
|
sql.insert("users").values(...).on_conflict().do_nothing()
|
|
273
179
|
```
|
|
274
180
|
"""
|
|
@@ -290,22 +196,41 @@ class Insert(QueryBuilder, ReturningClauseMixin, InsertValuesMixin, InsertFromSe
|
|
|
290
196
|
return self.on_conflict(*columns).do_nothing()
|
|
291
197
|
|
|
292
198
|
def on_duplicate_key_update(self, **kwargs: Any) -> "Insert":
|
|
293
|
-
"""Adds
|
|
199
|
+
"""Adds MySQL-style ON DUPLICATE KEY UPDATE clause.
|
|
294
200
|
|
|
295
201
|
Args:
|
|
296
|
-
**kwargs: Column-value pairs to update on
|
|
202
|
+
**kwargs: Column-value pairs to update on duplicate key.
|
|
297
203
|
|
|
298
204
|
Returns:
|
|
299
205
|
The current builder instance for method chaining.
|
|
300
206
|
|
|
301
207
|
Note:
|
|
302
|
-
This method
|
|
303
|
-
|
|
304
|
-
ON DUPLICATE KEY UPDATE, etc.).
|
|
208
|
+
This method creates MySQL-specific ON DUPLICATE KEY UPDATE syntax.
|
|
209
|
+
For PostgreSQL, use on_conflict() instead.
|
|
305
210
|
"""
|
|
306
211
|
if not kwargs:
|
|
307
212
|
return self
|
|
308
|
-
|
|
213
|
+
|
|
214
|
+
insert_expr = self._get_insert_expression()
|
|
215
|
+
|
|
216
|
+
set_expressions = []
|
|
217
|
+
for col, val in kwargs.items():
|
|
218
|
+
if has_expression_and_sql(val):
|
|
219
|
+
value_expr = extract_sql_object_expression(val, builder=self)
|
|
220
|
+
elif isinstance(val, exp.Expression):
|
|
221
|
+
value_expr = val
|
|
222
|
+
else:
|
|
223
|
+
param_name = self.generate_unique_parameter_name(col)
|
|
224
|
+
_, param_name = self.add_parameter(val, name=param_name)
|
|
225
|
+
value_expr = exp.Placeholder(this=param_name)
|
|
226
|
+
|
|
227
|
+
set_expressions.append(exp.EQ(this=exp.column(col), expression=value_expr))
|
|
228
|
+
|
|
229
|
+
on_conflict = exp.OnConflict(duplicate=True, action=exp.var("UPDATE"), expressions=set_expressions or None)
|
|
230
|
+
|
|
231
|
+
insert_expr.set("conflict", on_conflict)
|
|
232
|
+
|
|
233
|
+
return self
|
|
309
234
|
|
|
310
235
|
|
|
311
236
|
class ConflictBuilder:
|
|
@@ -342,7 +267,6 @@ class ConflictBuilder:
|
|
|
342
267
|
"""
|
|
343
268
|
insert_expr = self._insert_builder.get_insert_expression()
|
|
344
269
|
|
|
345
|
-
# Create ON CONFLICT with proper structure
|
|
346
270
|
conflict_keys = [exp.to_identifier(col) for col in self._columns] if self._columns else None
|
|
347
271
|
on_conflict = exp.OnConflict(conflict_keys=conflict_keys, action=exp.var("DO NOTHING"))
|
|
348
272
|
|
|
@@ -369,42 +293,19 @@ class ConflictBuilder:
|
|
|
369
293
|
"""
|
|
370
294
|
insert_expr = self._insert_builder.get_insert_expression()
|
|
371
295
|
|
|
372
|
-
# Create SET expressions for the UPDATE
|
|
373
296
|
set_expressions = []
|
|
374
297
|
for col, val in kwargs.items():
|
|
375
298
|
if has_expression_and_sql(val):
|
|
376
|
-
|
|
377
|
-
expression = getattr(val, "expression", None)
|
|
378
|
-
if expression is not None and isinstance(expression, exp.Expression):
|
|
379
|
-
# Merge parameters from SQL object into builder
|
|
380
|
-
if hasattr(val, "parameters"):
|
|
381
|
-
sql_parameters = getattr(val, "parameters", {})
|
|
382
|
-
for param_name, param_value in sql_parameters.items():
|
|
383
|
-
self._insert_builder.add_parameter(param_value, name=param_name)
|
|
384
|
-
value_expr = expression
|
|
385
|
-
else:
|
|
386
|
-
# If expression is None, fall back to parsing the raw SQL
|
|
387
|
-
sql_text = getattr(val, "sql", "")
|
|
388
|
-
# Merge parameters even when parsing raw SQL
|
|
389
|
-
if hasattr(val, "parameters"):
|
|
390
|
-
sql_parameters = getattr(val, "parameters", {})
|
|
391
|
-
for param_name, param_value in sql_parameters.items():
|
|
392
|
-
self._insert_builder.add_parameter(param_value, name=param_name)
|
|
393
|
-
# Check if sql_text is callable (like Expression.sql method)
|
|
394
|
-
if callable(sql_text):
|
|
395
|
-
sql_text = str(val)
|
|
396
|
-
value_expr = exp.maybe_parse(sql_text) or exp.convert(str(sql_text))
|
|
299
|
+
value_expr = extract_sql_object_expression(val, builder=self._insert_builder)
|
|
397
300
|
elif isinstance(val, exp.Expression):
|
|
398
301
|
value_expr = val
|
|
399
302
|
else:
|
|
400
|
-
# Create parameter for regular values
|
|
401
303
|
param_name = self._insert_builder.generate_unique_parameter_name(col)
|
|
402
304
|
_, param_name = self._insert_builder.add_parameter(val, name=param_name)
|
|
403
305
|
value_expr = exp.Placeholder(this=param_name)
|
|
404
306
|
|
|
405
307
|
set_expressions.append(exp.EQ(this=exp.column(col), expression=value_expr))
|
|
406
308
|
|
|
407
|
-
# Create ON CONFLICT with proper structure
|
|
408
309
|
conflict_keys = [exp.to_identifier(col) for col in self._columns] if self._columns else None
|
|
409
310
|
on_conflict = exp.OnConflict(
|
|
410
311
|
conflict_keys=conflict_keys, action=exp.var("DO UPDATE"), expressions=set_expressions or None
|