sqlspec 0.16.1__py3-none-any.whl → 0.17.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 +11 -1
- sqlspec/_sql.py +18 -412
- sqlspec/adapters/aiosqlite/__init__.py +11 -1
- sqlspec/adapters/aiosqlite/config.py +137 -165
- sqlspec/adapters/aiosqlite/driver.py +21 -10
- sqlspec/adapters/aiosqlite/pool.py +492 -0
- sqlspec/adapters/duckdb/__init__.py +2 -0
- sqlspec/adapters/duckdb/config.py +11 -235
- sqlspec/adapters/duckdb/pool.py +243 -0
- sqlspec/adapters/sqlite/__init__.py +2 -0
- sqlspec/adapters/sqlite/config.py +4 -115
- sqlspec/adapters/sqlite/pool.py +140 -0
- sqlspec/base.py +147 -26
- sqlspec/builder/__init__.py +6 -0
- sqlspec/builder/_insert.py +177 -12
- sqlspec/builder/_parsing_utils.py +53 -2
- sqlspec/builder/mixins/_join_operations.py +148 -7
- sqlspec/builder/mixins/_merge_operations.py +102 -16
- sqlspec/builder/mixins/_select_operations.py +311 -6
- sqlspec/builder/mixins/_update_operations.py +49 -34
- sqlspec/builder/mixins/_where_clause.py +85 -13
- sqlspec/core/compiler.py +7 -5
- sqlspec/driver/_common.py +9 -1
- sqlspec/loader.py +27 -54
- sqlspec/storage/registry.py +2 -2
- sqlspec/typing.py +53 -99
- {sqlspec-0.16.1.dist-info → sqlspec-0.17.0.dist-info}/METADATA +1 -1
- {sqlspec-0.16.1.dist-info → sqlspec-0.17.0.dist-info}/RECORD +32 -29
- {sqlspec-0.16.1.dist-info → sqlspec-0.17.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.16.1.dist-info → sqlspec-0.17.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.16.1.dist-info → sqlspec-0.17.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.16.1.dist-info → sqlspec-0.17.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/__init__.py
CHANGED
|
@@ -34,7 +34,16 @@ from sqlspec.core import (
|
|
|
34
34
|
from sqlspec.core import filters as filters
|
|
35
35
|
from sqlspec.driver import AsyncDriverAdapterBase, ExecutionResult, SyncDriverAdapterBase
|
|
36
36
|
from sqlspec.loader import SQLFile, SQLFileLoader
|
|
37
|
-
from sqlspec.typing import
|
|
37
|
+
from sqlspec.typing import (
|
|
38
|
+
ConnectionT,
|
|
39
|
+
DictRow,
|
|
40
|
+
ModelDTOT,
|
|
41
|
+
ModelT,
|
|
42
|
+
PoolT,
|
|
43
|
+
RowT,
|
|
44
|
+
StatementParameters,
|
|
45
|
+
SupportedSchemaModel,
|
|
46
|
+
)
|
|
38
47
|
|
|
39
48
|
__all__ = (
|
|
40
49
|
"SQL",
|
|
@@ -60,6 +69,7 @@ __all__ = (
|
|
|
60
69
|
"ParameterProcessor",
|
|
61
70
|
"ParameterStyle",
|
|
62
71
|
"ParameterStyleConfig",
|
|
72
|
+
"PoolT",
|
|
63
73
|
"QueryBuilder",
|
|
64
74
|
"RowT",
|
|
65
75
|
"SQLFactory",
|
sqlspec/_sql.py
CHANGED
|
@@ -4,10 +4,9 @@ Provides both statement builders (select, insert, update, etc.) and column expre
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import logging
|
|
7
|
-
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
8
8
|
|
|
9
9
|
import sqlglot
|
|
10
|
-
from mypy_extensions import trait
|
|
11
10
|
from sqlglot import exp
|
|
12
11
|
from sqlglot.dialects.dialect import DialectType
|
|
13
12
|
from sqlglot.errors import ParseError as SQLGlotParseError
|
|
@@ -34,10 +33,11 @@ from sqlspec.builder import (
|
|
|
34
33
|
Truncate,
|
|
35
34
|
Update,
|
|
36
35
|
)
|
|
36
|
+
from sqlspec.builder.mixins._join_operations import JoinBuilder
|
|
37
|
+
from sqlspec.builder.mixins._select_operations import Case, SubqueryBuilder, WindowFunctionBuilder
|
|
37
38
|
from sqlspec.exceptions import SQLBuilderError
|
|
38
39
|
|
|
39
40
|
if TYPE_CHECKING:
|
|
40
|
-
from sqlspec.builder._column import ColumnExpression
|
|
41
41
|
from sqlspec.core.statement import SQL
|
|
42
42
|
|
|
43
43
|
__all__ = (
|
|
@@ -178,7 +178,9 @@ class SQLFactory:
|
|
|
178
178
|
# ===================
|
|
179
179
|
# Statement Builders
|
|
180
180
|
# ===================
|
|
181
|
-
def select(
|
|
181
|
+
def select(
|
|
182
|
+
self, *columns_or_sql: Union[str, exp.Expression, Column, "SQL", "Case"], dialect: DialectType = None
|
|
183
|
+
) -> "Select":
|
|
182
184
|
builder_dialect = dialect or self.dialect
|
|
183
185
|
if len(columns_or_sql) == 1 and isinstance(columns_or_sql[0], str):
|
|
184
186
|
sql_candidate = columns_or_sql[0].strip()
|
|
@@ -221,7 +223,10 @@ class SQLFactory:
|
|
|
221
223
|
if self._looks_like_sql(table_or_sql):
|
|
222
224
|
detected = self.detect_sql_type(table_or_sql, dialect=builder_dialect)
|
|
223
225
|
if detected != "UPDATE":
|
|
224
|
-
msg =
|
|
226
|
+
msg = (
|
|
227
|
+
f"sql.update() expects UPDATE statement, got {detected}. "
|
|
228
|
+
f"Use sql.{detected.lower()}() if a dedicated builder exists."
|
|
229
|
+
)
|
|
225
230
|
raise SQLBuilderError(msg)
|
|
226
231
|
return self._populate_update_from_sql(builder, table_or_sql)
|
|
227
232
|
return builder.table(table_or_sql)
|
|
@@ -233,7 +238,10 @@ class SQLFactory:
|
|
|
233
238
|
if table_or_sql and self._looks_like_sql(table_or_sql):
|
|
234
239
|
detected = self.detect_sql_type(table_or_sql, dialect=builder_dialect)
|
|
235
240
|
if detected != "DELETE":
|
|
236
|
-
msg =
|
|
241
|
+
msg = (
|
|
242
|
+
f"sql.delete() expects DELETE statement, got {detected}. "
|
|
243
|
+
f"Use sql.{detected.lower()}() if a dedicated builder exists."
|
|
244
|
+
)
|
|
237
245
|
raise SQLBuilderError(msg)
|
|
238
246
|
return self._populate_delete_from_sql(builder, table_or_sql)
|
|
239
247
|
return builder
|
|
@@ -245,7 +253,10 @@ class SQLFactory:
|
|
|
245
253
|
if self._looks_like_sql(table_or_sql):
|
|
246
254
|
detected = self.detect_sql_type(table_or_sql, dialect=builder_dialect)
|
|
247
255
|
if detected != "MERGE":
|
|
248
|
-
msg =
|
|
256
|
+
msg = (
|
|
257
|
+
f"sql.merge() expects MERGE statement, got {detected}. "
|
|
258
|
+
f"Use sql.{detected.lower()}() if a dedicated builder exists."
|
|
259
|
+
)
|
|
249
260
|
raise SQLBuilderError(msg)
|
|
250
261
|
return self._populate_merge_from_sql(builder, table_or_sql)
|
|
251
262
|
return builder.into(table_or_sql)
|
|
@@ -1371,410 +1382,5 @@ class SQLFactory:
|
|
|
1371
1382
|
return exp.Window(this=func_expr, **over_args)
|
|
1372
1383
|
|
|
1373
1384
|
|
|
1374
|
-
@trait
|
|
1375
|
-
class Case:
|
|
1376
|
-
"""Builder for CASE expressions using the SQL factory.
|
|
1377
|
-
|
|
1378
|
-
Example:
|
|
1379
|
-
```python
|
|
1380
|
-
from sqlspec import sql
|
|
1381
|
-
|
|
1382
|
-
case_expr = (
|
|
1383
|
-
sql.case()
|
|
1384
|
-
.when(sql.age < 18, "Minor")
|
|
1385
|
-
.when(sql.age < 65, "Adult")
|
|
1386
|
-
.else_("Senior")
|
|
1387
|
-
.end()
|
|
1388
|
-
)
|
|
1389
|
-
```
|
|
1390
|
-
"""
|
|
1391
|
-
|
|
1392
|
-
def __init__(self) -> None:
|
|
1393
|
-
"""Initialize the CASE expression builder."""
|
|
1394
|
-
self._conditions: list[exp.If] = []
|
|
1395
|
-
self._default: Optional[exp.Expression] = None
|
|
1396
|
-
|
|
1397
|
-
def __eq__(self, other: object) -> "ColumnExpression": # type: ignore[override]
|
|
1398
|
-
"""Equal to (==) - convert to expression then compare."""
|
|
1399
|
-
from sqlspec.builder._column import ColumnExpression
|
|
1400
|
-
|
|
1401
|
-
case_expr = exp.Case(ifs=self._conditions, default=self._default)
|
|
1402
|
-
if other is None:
|
|
1403
|
-
return ColumnExpression(exp.Is(this=case_expr, expression=exp.Null()))
|
|
1404
|
-
return ColumnExpression(exp.EQ(this=case_expr, expression=exp.convert(other)))
|
|
1405
|
-
|
|
1406
|
-
def __hash__(self) -> int:
|
|
1407
|
-
"""Make Case hashable."""
|
|
1408
|
-
return hash(id(self))
|
|
1409
|
-
|
|
1410
|
-
def when(self, condition: Union[str, exp.Expression], value: Union[str, exp.Expression, Any]) -> "Case":
|
|
1411
|
-
"""Add a WHEN clause.
|
|
1412
|
-
|
|
1413
|
-
Args:
|
|
1414
|
-
condition: Condition to test.
|
|
1415
|
-
value: Value to return if condition is true.
|
|
1416
|
-
|
|
1417
|
-
Returns:
|
|
1418
|
-
Self for method chaining.
|
|
1419
|
-
"""
|
|
1420
|
-
cond_expr = exp.maybe_parse(condition) or exp.column(condition) if isinstance(condition, str) else condition
|
|
1421
|
-
val_expr = SQLFactory._to_literal(value)
|
|
1422
|
-
|
|
1423
|
-
# SQLGlot uses exp.If for CASE WHEN clauses, not exp.When
|
|
1424
|
-
when_clause = exp.If(this=cond_expr, true=val_expr)
|
|
1425
|
-
self._conditions.append(when_clause)
|
|
1426
|
-
return self
|
|
1427
|
-
|
|
1428
|
-
def else_(self, value: Union[str, exp.Expression, Any]) -> "Case":
|
|
1429
|
-
"""Add an ELSE clause.
|
|
1430
|
-
|
|
1431
|
-
Args:
|
|
1432
|
-
value: Default value to return.
|
|
1433
|
-
|
|
1434
|
-
Returns:
|
|
1435
|
-
Self for method chaining.
|
|
1436
|
-
"""
|
|
1437
|
-
self._default = SQLFactory._to_literal(value)
|
|
1438
|
-
return self
|
|
1439
|
-
|
|
1440
|
-
def end(self) -> exp.Expression:
|
|
1441
|
-
"""Complete the CASE expression.
|
|
1442
|
-
|
|
1443
|
-
Returns:
|
|
1444
|
-
Complete CASE expression.
|
|
1445
|
-
"""
|
|
1446
|
-
return exp.Case(ifs=self._conditions, default=self._default)
|
|
1447
|
-
|
|
1448
|
-
def as_(self, alias: str) -> exp.Alias:
|
|
1449
|
-
"""Complete the CASE expression with an alias.
|
|
1450
|
-
|
|
1451
|
-
Args:
|
|
1452
|
-
alias: Alias name for the CASE expression.
|
|
1453
|
-
|
|
1454
|
-
Returns:
|
|
1455
|
-
Aliased CASE expression.
|
|
1456
|
-
"""
|
|
1457
|
-
case_expr = exp.Case(ifs=self._conditions, default=self._default)
|
|
1458
|
-
return cast("exp.Alias", exp.alias_(case_expr, alias))
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
@trait
|
|
1462
|
-
class WindowFunctionBuilder:
|
|
1463
|
-
"""Builder for window functions with fluent syntax.
|
|
1464
|
-
|
|
1465
|
-
Example:
|
|
1466
|
-
```python
|
|
1467
|
-
from sqlspec import sql
|
|
1468
|
-
|
|
1469
|
-
# sql.row_number_.partition_by("department").order_by("salary")
|
|
1470
|
-
window_func = (
|
|
1471
|
-
sql.row_number_.partition_by("department")
|
|
1472
|
-
.order_by("salary")
|
|
1473
|
-
.as_("row_num")
|
|
1474
|
-
)
|
|
1475
|
-
```
|
|
1476
|
-
"""
|
|
1477
|
-
|
|
1478
|
-
def __init__(self, function_name: str) -> None:
|
|
1479
|
-
"""Initialize the window function builder.
|
|
1480
|
-
|
|
1481
|
-
Args:
|
|
1482
|
-
function_name: Name of the window function (row_number, rank, etc.)
|
|
1483
|
-
"""
|
|
1484
|
-
self._function_name = function_name
|
|
1485
|
-
self._partition_by_cols: list[exp.Expression] = []
|
|
1486
|
-
self._order_by_cols: list[exp.Expression] = []
|
|
1487
|
-
self._alias: Optional[str] = None
|
|
1488
|
-
|
|
1489
|
-
def __eq__(self, other: object) -> "ColumnExpression": # type: ignore[override]
|
|
1490
|
-
"""Equal to (==) - convert to expression then compare."""
|
|
1491
|
-
from sqlspec.builder._column import ColumnExpression
|
|
1492
|
-
|
|
1493
|
-
window_expr = self._build_expression()
|
|
1494
|
-
if other is None:
|
|
1495
|
-
return ColumnExpression(exp.Is(this=window_expr, expression=exp.Null()))
|
|
1496
|
-
return ColumnExpression(exp.EQ(this=window_expr, expression=exp.convert(other)))
|
|
1497
|
-
|
|
1498
|
-
def __hash__(self) -> int:
|
|
1499
|
-
"""Make WindowFunctionBuilder hashable."""
|
|
1500
|
-
return hash(id(self))
|
|
1501
|
-
|
|
1502
|
-
def partition_by(self, *columns: Union[str, exp.Expression]) -> "WindowFunctionBuilder":
|
|
1503
|
-
"""Add PARTITION BY clause.
|
|
1504
|
-
|
|
1505
|
-
Args:
|
|
1506
|
-
*columns: Columns to partition by.
|
|
1507
|
-
|
|
1508
|
-
Returns:
|
|
1509
|
-
Self for method chaining.
|
|
1510
|
-
"""
|
|
1511
|
-
for col in columns:
|
|
1512
|
-
col_expr = exp.column(col) if isinstance(col, str) else col
|
|
1513
|
-
self._partition_by_cols.append(col_expr)
|
|
1514
|
-
return self
|
|
1515
|
-
|
|
1516
|
-
def order_by(self, *columns: Union[str, exp.Expression]) -> "WindowFunctionBuilder":
|
|
1517
|
-
"""Add ORDER BY clause.
|
|
1518
|
-
|
|
1519
|
-
Args:
|
|
1520
|
-
*columns: Columns to order by.
|
|
1521
|
-
|
|
1522
|
-
Returns:
|
|
1523
|
-
Self for method chaining.
|
|
1524
|
-
"""
|
|
1525
|
-
for col in columns:
|
|
1526
|
-
if isinstance(col, str):
|
|
1527
|
-
col_expr = exp.column(col).asc()
|
|
1528
|
-
self._order_by_cols.append(col_expr)
|
|
1529
|
-
else:
|
|
1530
|
-
# Convert to ordered expression
|
|
1531
|
-
self._order_by_cols.append(exp.Ordered(this=col, desc=False))
|
|
1532
|
-
return self
|
|
1533
|
-
|
|
1534
|
-
def as_(self, alias: str) -> exp.Expression:
|
|
1535
|
-
"""Complete the window function with an alias.
|
|
1536
|
-
|
|
1537
|
-
Args:
|
|
1538
|
-
alias: Alias name for the window function.
|
|
1539
|
-
|
|
1540
|
-
Returns:
|
|
1541
|
-
Aliased window function expression.
|
|
1542
|
-
"""
|
|
1543
|
-
window_expr = self._build_expression()
|
|
1544
|
-
return cast("exp.Alias", exp.alias_(window_expr, alias))
|
|
1545
|
-
|
|
1546
|
-
def build(self) -> exp.Expression:
|
|
1547
|
-
"""Complete the window function without an alias.
|
|
1548
|
-
|
|
1549
|
-
Returns:
|
|
1550
|
-
Window function expression.
|
|
1551
|
-
"""
|
|
1552
|
-
return self._build_expression()
|
|
1553
|
-
|
|
1554
|
-
def _build_expression(self) -> exp.Expression:
|
|
1555
|
-
"""Build the complete window function expression."""
|
|
1556
|
-
# Create the function expression
|
|
1557
|
-
func_expr = exp.Anonymous(this=self._function_name.upper(), expressions=[])
|
|
1558
|
-
|
|
1559
|
-
# Build the OVER clause arguments
|
|
1560
|
-
over_args: dict[str, Any] = {}
|
|
1561
|
-
|
|
1562
|
-
if self._partition_by_cols:
|
|
1563
|
-
over_args["partition_by"] = self._partition_by_cols
|
|
1564
|
-
|
|
1565
|
-
if self._order_by_cols:
|
|
1566
|
-
over_args["order"] = exp.Order(expressions=self._order_by_cols)
|
|
1567
|
-
|
|
1568
|
-
return exp.Window(this=func_expr, **over_args)
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
@trait
|
|
1572
|
-
class SubqueryBuilder:
|
|
1573
|
-
"""Builder for subquery operations with fluent syntax.
|
|
1574
|
-
|
|
1575
|
-
Example:
|
|
1576
|
-
```python
|
|
1577
|
-
from sqlspec import sql
|
|
1578
|
-
|
|
1579
|
-
# sql.exists_(subquery)
|
|
1580
|
-
exists_check = sql.exists_(
|
|
1581
|
-
sql.select("1")
|
|
1582
|
-
.from_("orders")
|
|
1583
|
-
.where_eq("user_id", sql.users.id)
|
|
1584
|
-
)
|
|
1585
|
-
|
|
1586
|
-
# sql.in_(subquery)
|
|
1587
|
-
in_check = sql.in_(
|
|
1588
|
-
sql.select("category_id")
|
|
1589
|
-
.from_("categories")
|
|
1590
|
-
.where_eq("active", True)
|
|
1591
|
-
)
|
|
1592
|
-
```
|
|
1593
|
-
"""
|
|
1594
|
-
|
|
1595
|
-
def __init__(self, operation: str) -> None:
|
|
1596
|
-
"""Initialize the subquery builder.
|
|
1597
|
-
|
|
1598
|
-
Args:
|
|
1599
|
-
operation: Type of subquery operation (exists, in, any, all)
|
|
1600
|
-
"""
|
|
1601
|
-
self._operation = operation
|
|
1602
|
-
|
|
1603
|
-
def __eq__(self, other: object) -> "ColumnExpression": # type: ignore[override]
|
|
1604
|
-
"""Equal to (==) - not typically used but needed for type consistency."""
|
|
1605
|
-
from sqlspec.builder._column import ColumnExpression
|
|
1606
|
-
|
|
1607
|
-
# SubqueryBuilder doesn't have a direct expression, so this is a placeholder
|
|
1608
|
-
# In practice, this shouldn't be called as subqueries are used differently
|
|
1609
|
-
placeholder_expr = exp.Literal.string(f"subquery_{self._operation}")
|
|
1610
|
-
if other is None:
|
|
1611
|
-
return ColumnExpression(exp.Is(this=placeholder_expr, expression=exp.Null()))
|
|
1612
|
-
return ColumnExpression(exp.EQ(this=placeholder_expr, expression=exp.convert(other)))
|
|
1613
|
-
|
|
1614
|
-
def __hash__(self) -> int:
|
|
1615
|
-
"""Make SubqueryBuilder hashable."""
|
|
1616
|
-
return hash(id(self))
|
|
1617
|
-
|
|
1618
|
-
def __call__(self, subquery: Union[str, exp.Expression, Any]) -> exp.Expression:
|
|
1619
|
-
"""Build the subquery expression.
|
|
1620
|
-
|
|
1621
|
-
Args:
|
|
1622
|
-
subquery: The subquery - can be a SQL string, SelectBuilder, or expression
|
|
1623
|
-
|
|
1624
|
-
Returns:
|
|
1625
|
-
The subquery expression (EXISTS, IN, ANY, ALL, etc.)
|
|
1626
|
-
"""
|
|
1627
|
-
subquery_expr: exp.Expression
|
|
1628
|
-
if isinstance(subquery, str):
|
|
1629
|
-
# Parse as SQL
|
|
1630
|
-
parsed: Optional[exp.Expression] = exp.maybe_parse(subquery)
|
|
1631
|
-
if not parsed:
|
|
1632
|
-
msg = f"Could not parse subquery SQL: {subquery}"
|
|
1633
|
-
raise SQLBuilderError(msg)
|
|
1634
|
-
subquery_expr = parsed
|
|
1635
|
-
elif hasattr(subquery, "build") and callable(getattr(subquery, "build", None)):
|
|
1636
|
-
# It's a query builder - build it to get the SQL and parse
|
|
1637
|
-
built_query = subquery.build() # pyright: ignore[reportAttributeAccessIssue]
|
|
1638
|
-
subquery_expr = exp.maybe_parse(built_query.sql)
|
|
1639
|
-
if not subquery_expr:
|
|
1640
|
-
msg = f"Could not parse built query: {built_query.sql}"
|
|
1641
|
-
raise SQLBuilderError(msg)
|
|
1642
|
-
elif isinstance(subquery, exp.Expression):
|
|
1643
|
-
subquery_expr = subquery
|
|
1644
|
-
else:
|
|
1645
|
-
# Try to convert to expression
|
|
1646
|
-
parsed = exp.maybe_parse(str(subquery))
|
|
1647
|
-
if not parsed:
|
|
1648
|
-
msg = f"Could not convert subquery to expression: {subquery}"
|
|
1649
|
-
raise SQLBuilderError(msg)
|
|
1650
|
-
subquery_expr = parsed
|
|
1651
|
-
|
|
1652
|
-
# Build the appropriate expression based on operation
|
|
1653
|
-
if self._operation == "exists":
|
|
1654
|
-
return exp.Exists(this=subquery_expr)
|
|
1655
|
-
if self._operation == "in":
|
|
1656
|
-
# For IN, we create a subquery that can be used with WHERE column IN (subquery)
|
|
1657
|
-
return exp.In(expressions=[subquery_expr])
|
|
1658
|
-
if self._operation == "any":
|
|
1659
|
-
return exp.Any(this=subquery_expr)
|
|
1660
|
-
if self._operation == "all":
|
|
1661
|
-
return exp.All(this=subquery_expr)
|
|
1662
|
-
msg = f"Unknown subquery operation: {self._operation}"
|
|
1663
|
-
raise SQLBuilderError(msg)
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
@trait
|
|
1667
|
-
class JoinBuilder:
|
|
1668
|
-
"""Builder for JOIN operations with fluent syntax.
|
|
1669
|
-
|
|
1670
|
-
Example:
|
|
1671
|
-
```python
|
|
1672
|
-
from sqlspec import sql
|
|
1673
|
-
|
|
1674
|
-
# sql.left_join_("posts").on("users.id = posts.user_id")
|
|
1675
|
-
join_clause = sql.left_join_("posts").on(
|
|
1676
|
-
"users.id = posts.user_id"
|
|
1677
|
-
)
|
|
1678
|
-
|
|
1679
|
-
# Or with query builder
|
|
1680
|
-
query = (
|
|
1681
|
-
sql.select("users.name", "posts.title")
|
|
1682
|
-
.from_("users")
|
|
1683
|
-
.join(
|
|
1684
|
-
sql.left_join_("posts").on(
|
|
1685
|
-
"users.id = posts.user_id"
|
|
1686
|
-
)
|
|
1687
|
-
)
|
|
1688
|
-
)
|
|
1689
|
-
```
|
|
1690
|
-
"""
|
|
1691
|
-
|
|
1692
|
-
def __init__(self, join_type: str) -> None:
|
|
1693
|
-
"""Initialize the join builder.
|
|
1694
|
-
|
|
1695
|
-
Args:
|
|
1696
|
-
join_type: Type of join (inner, left, right, full, cross)
|
|
1697
|
-
"""
|
|
1698
|
-
self._join_type = join_type.upper()
|
|
1699
|
-
self._table: Optional[Union[str, exp.Expression]] = None
|
|
1700
|
-
self._condition: Optional[exp.Expression] = None
|
|
1701
|
-
self._alias: Optional[str] = None
|
|
1702
|
-
|
|
1703
|
-
def __eq__(self, other: object) -> "ColumnExpression": # type: ignore[override]
|
|
1704
|
-
"""Equal to (==) - not typically used but needed for type consistency."""
|
|
1705
|
-
from sqlspec.builder._column import ColumnExpression
|
|
1706
|
-
|
|
1707
|
-
# JoinBuilder doesn't have a direct expression, so this is a placeholder
|
|
1708
|
-
# In practice, this shouldn't be called as joins are used differently
|
|
1709
|
-
placeholder_expr = exp.Literal.string(f"join_{self._join_type.lower()}")
|
|
1710
|
-
if other is None:
|
|
1711
|
-
return ColumnExpression(exp.Is(this=placeholder_expr, expression=exp.Null()))
|
|
1712
|
-
return ColumnExpression(exp.EQ(this=placeholder_expr, expression=exp.convert(other)))
|
|
1713
|
-
|
|
1714
|
-
def __hash__(self) -> int:
|
|
1715
|
-
"""Make JoinBuilder hashable."""
|
|
1716
|
-
return hash(id(self))
|
|
1717
|
-
|
|
1718
|
-
def __call__(self, table: Union[str, exp.Expression], alias: Optional[str] = None) -> "JoinBuilder":
|
|
1719
|
-
"""Set the table to join.
|
|
1720
|
-
|
|
1721
|
-
Args:
|
|
1722
|
-
table: Table name or expression to join
|
|
1723
|
-
alias: Optional alias for the table
|
|
1724
|
-
|
|
1725
|
-
Returns:
|
|
1726
|
-
Self for method chaining
|
|
1727
|
-
"""
|
|
1728
|
-
self._table = table
|
|
1729
|
-
self._alias = alias
|
|
1730
|
-
return self
|
|
1731
|
-
|
|
1732
|
-
def on(self, condition: Union[str, exp.Expression]) -> exp.Expression:
|
|
1733
|
-
"""Set the join condition and build the JOIN expression.
|
|
1734
|
-
|
|
1735
|
-
Args:
|
|
1736
|
-
condition: JOIN condition (e.g., "users.id = posts.user_id")
|
|
1737
|
-
|
|
1738
|
-
Returns:
|
|
1739
|
-
Complete JOIN expression
|
|
1740
|
-
"""
|
|
1741
|
-
if not self._table:
|
|
1742
|
-
msg = "Table must be set before calling .on()"
|
|
1743
|
-
raise SQLBuilderError(msg)
|
|
1744
|
-
|
|
1745
|
-
# Parse the condition
|
|
1746
|
-
condition_expr: exp.Expression
|
|
1747
|
-
if isinstance(condition, str):
|
|
1748
|
-
parsed: Optional[exp.Expression] = exp.maybe_parse(condition)
|
|
1749
|
-
condition_expr = parsed or exp.condition(condition)
|
|
1750
|
-
else:
|
|
1751
|
-
condition_expr = condition
|
|
1752
|
-
|
|
1753
|
-
# Build table expression
|
|
1754
|
-
table_expr: exp.Expression
|
|
1755
|
-
if isinstance(self._table, str):
|
|
1756
|
-
table_expr = exp.to_table(self._table)
|
|
1757
|
-
if self._alias:
|
|
1758
|
-
table_expr = cast("exp.Expression", exp.alias_(table_expr, self._alias))
|
|
1759
|
-
else:
|
|
1760
|
-
table_expr = self._table
|
|
1761
|
-
if self._alias:
|
|
1762
|
-
table_expr = cast("exp.Expression", exp.alias_(table_expr, self._alias))
|
|
1763
|
-
|
|
1764
|
-
# Create the appropriate join type using same pattern as existing JoinClauseMixin
|
|
1765
|
-
if self._join_type == "INNER JOIN":
|
|
1766
|
-
return exp.Join(this=table_expr, on=condition_expr)
|
|
1767
|
-
if self._join_type == "LEFT JOIN":
|
|
1768
|
-
return exp.Join(this=table_expr, on=condition_expr, side="LEFT")
|
|
1769
|
-
if self._join_type == "RIGHT JOIN":
|
|
1770
|
-
return exp.Join(this=table_expr, on=condition_expr, side="RIGHT")
|
|
1771
|
-
if self._join_type == "FULL JOIN":
|
|
1772
|
-
return exp.Join(this=table_expr, on=condition_expr, side="FULL", kind="OUTER")
|
|
1773
|
-
if self._join_type == "CROSS JOIN":
|
|
1774
|
-
# CROSS JOIN doesn't use ON condition
|
|
1775
|
-
return exp.Join(this=table_expr, kind="CROSS")
|
|
1776
|
-
return exp.Join(this=table_expr, on=condition_expr)
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
1385
|
# Create a default SQL factory instance
|
|
1780
1386
|
sql = SQLFactory()
|
|
@@ -1,19 +1,29 @@
|
|
|
1
1
|
from sqlspec.adapters.aiosqlite._types import AiosqliteConnection
|
|
2
|
-
from sqlspec.adapters.aiosqlite.config import AiosqliteConfig, AiosqliteConnectionParams,
|
|
2
|
+
from sqlspec.adapters.aiosqlite.config import AiosqliteConfig, AiosqliteConnectionParams, AiosqlitePoolParams
|
|
3
3
|
from sqlspec.adapters.aiosqlite.driver import (
|
|
4
4
|
AiosqliteCursor,
|
|
5
5
|
AiosqliteDriver,
|
|
6
6
|
AiosqliteExceptionHandler,
|
|
7
7
|
aiosqlite_statement_config,
|
|
8
8
|
)
|
|
9
|
+
from sqlspec.adapters.aiosqlite.pool import (
|
|
10
|
+
AiosqliteConnectionPool,
|
|
11
|
+
AiosqliteConnectTimeoutError,
|
|
12
|
+
AiosqlitePoolClosedError,
|
|
13
|
+
AiosqlitePoolConnection,
|
|
14
|
+
)
|
|
9
15
|
|
|
10
16
|
__all__ = (
|
|
11
17
|
"AiosqliteConfig",
|
|
18
|
+
"AiosqliteConnectTimeoutError",
|
|
12
19
|
"AiosqliteConnection",
|
|
13
20
|
"AiosqliteConnectionParams",
|
|
14
21
|
"AiosqliteConnectionPool",
|
|
15
22
|
"AiosqliteCursor",
|
|
16
23
|
"AiosqliteDriver",
|
|
17
24
|
"AiosqliteExceptionHandler",
|
|
25
|
+
"AiosqlitePoolClosedError",
|
|
26
|
+
"AiosqlitePoolConnection",
|
|
27
|
+
"AiosqlitePoolParams",
|
|
18
28
|
"aiosqlite_statement_config",
|
|
19
29
|
)
|