sqlpiston 0.1.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.
sqlpiston/__init__.py ADDED
@@ -0,0 +1,27 @@
1
+ from sqlpiston._types import ColumnValue, SQLValue
2
+ from sqlpiston.builder.ddl import (
3
+ AlterTable, ColumnDef, CreateIndex, CreateTable, CreateView,
4
+ DropIndex, DropTable, DropView, Truncate,
5
+ )
6
+ from sqlpiston.builder.dml import Delete, Insert, Update, Upsert
7
+ from sqlpiston.builder.nodes import CaseNode, Field, SQLFunction
8
+ from sqlpiston.builder.selectable import CTE, CompoundSelect, Select
9
+ from sqlpiston.compiler.base import Dialect
10
+ from sqlpiston.core.engine import DBType
11
+ from sqlpiston.core.engine.base import DBEngine
12
+ from sqlpiston.core.pool import ConnectionPool
13
+ from sqlpiston.core.session import Session
14
+ from sqlpiston.orm.mapper import ResultSet
15
+
16
+ __all__ = [
17
+ 'SQLValue', 'ColumnValue',
18
+ 'AlterTable', 'ColumnDef', 'CreateIndex', 'CreateTable', 'CreateView',
19
+ 'DropIndex', 'DropTable', 'DropView', 'Truncate',
20
+ 'Delete', 'Insert', 'Update', 'Upsert',
21
+ 'CaseNode', 'Field', 'SQLFunction',
22
+ 'CTE', 'CompoundSelect', 'Select',
23
+ 'Dialect',
24
+ 'DBType', 'DBEngine',
25
+ 'ConnectionPool', 'Session',
26
+ 'ResultSet',
27
+ ]
sqlpiston/_types.py ADDED
@@ -0,0 +1,5 @@
1
+ from typing import TypeAlias, Union
2
+
3
+
4
+ SQLValue: TypeAlias = Union[str, int, float, bool, None, bytes]
5
+ ColumnValue = SQLValue
File without changes
@@ -0,0 +1,228 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+ from typing import TYPE_CHECKING, List, Optional, Tuple
4
+
5
+ from sqlpiston.builder.nodes import ASTNode
6
+ from sqlpiston._types import SQLValue
7
+
8
+ if TYPE_CHECKING:
9
+ from sqlpiston.builder.selectable import Select
10
+
11
+
12
+ class AlterAction(Enum):
13
+ ADD = 1
14
+ DROP = 2
15
+ MODIFY = 3
16
+
17
+
18
+ @dataclass
19
+ class ColumnDef:
20
+ """Column definition for CREATE TABLE."""
21
+ name: str
22
+ type_: str
23
+ nullable: bool = True
24
+ primary_key: bool = False
25
+ default: Optional[SQLValue] = None
26
+ unique: bool = False
27
+
28
+
29
+ class CreateTable(ASTNode):
30
+ """CREATE TABLE name (col1 TYPE, col2 TYPE, ...)"""
31
+
32
+ def __init__(self) -> None:
33
+ self._table: Optional[str] = None
34
+ self._if_not_exists: bool = False
35
+ self._columns: List[ColumnDef] = []
36
+
37
+ def table(self, name: str) -> 'CreateTable':
38
+ self._table = name
39
+ return self
40
+
41
+ def if_not_exists(self) -> 'CreateTable':
42
+ self._if_not_exists = True
43
+ return self
44
+
45
+ def column(self, name: str, type_: str, *,
46
+ nullable: bool = True,
47
+ primary_key: bool = False,
48
+ default: Optional[SQLValue] = None,
49
+ unique: bool = False) -> 'CreateTable':
50
+ self._columns.append(ColumnDef(
51
+ name=name, type_=type_, nullable=nullable,
52
+ primary_key=primary_key, default=default, unique=unique,
53
+ ))
54
+ return self
55
+
56
+ def columns(self, *col_defs: ColumnDef) -> 'CreateTable':
57
+ self._columns.extend(col_defs)
58
+ return self
59
+
60
+ def __repr__(self) -> str:
61
+ return f"CreateTable(table={self._table!r}, columns={len(self._columns)})"
62
+
63
+
64
+ class AlterTable(ASTNode):
65
+ """ALTER TABLE name ADD/DROP/MODIFY COLUMN ..."""
66
+
67
+ def __init__(self) -> None:
68
+ self._table: Optional[str] = None
69
+ self._actions: List[Tuple[AlterAction, str, Optional[str], Optional[ColumnDef]]] = []
70
+
71
+ def table(self, name: str) -> 'AlterTable':
72
+ self._table = name
73
+ return self
74
+
75
+ def add_column(self, name: str, type_: str, *,
76
+ nullable: bool = True,
77
+ default: Optional[SQLValue] = None) -> 'AlterTable':
78
+ col_def = ColumnDef(name=name, type_=type_, nullable=nullable, default=default)
79
+ self._actions.append((AlterAction.ADD, name, type_, col_def))
80
+ return self
81
+
82
+ def drop_column(self, name: str) -> 'AlterTable':
83
+ self._actions.append((AlterAction.DROP, name, None, None))
84
+ return self
85
+
86
+ def modify_column(self, name: str, type_: str, *,
87
+ nullable: bool = True,
88
+ default: Optional[SQLValue] = None) -> 'AlterTable':
89
+ col_def = ColumnDef(name=name, type_=type_, nullable=nullable, default=default)
90
+ self._actions.append((AlterAction.MODIFY, name, type_, col_def))
91
+ return self
92
+
93
+ def __repr__(self) -> str:
94
+ return f"AlterTable(table={self._table!r}, actions={len(self._actions)})"
95
+
96
+
97
+ class DropTable(ASTNode):
98
+ """DROP TABLE [IF EXISTS] name"""
99
+
100
+ def __init__(self) -> None:
101
+ self._table: Optional[str] = None
102
+ self._if_exists: bool = False
103
+
104
+ def table(self, name: str) -> 'DropTable':
105
+ self._table = name
106
+ return self
107
+
108
+ def if_exists(self) -> 'DropTable':
109
+ self._if_exists = True
110
+ return self
111
+
112
+ def __repr__(self) -> str:
113
+ return f"DropTable(table={self._table!r}, if_exists={self._if_exists})"
114
+
115
+
116
+ class CreateIndex(ASTNode):
117
+ """CREATE [UNIQUE] INDEX [IF NOT EXISTS] name ON table (col1, col2, ...)"""
118
+
119
+ def __init__(self) -> None:
120
+ self._name: Optional[str] = None
121
+ self._table: Optional[str] = None
122
+ self._columns: Tuple[str, ...] = ()
123
+ self._unique: bool = False
124
+ self._if_not_exists: bool = False
125
+
126
+ def name(self, idx_name: str) -> 'CreateIndex':
127
+ self._name = idx_name
128
+ return self
129
+
130
+ def on(self, table: str) -> 'CreateIndex':
131
+ self._table = table
132
+ return self
133
+
134
+ def columns(self, *cols: str) -> 'CreateIndex':
135
+ self._columns = cols
136
+ return self
137
+
138
+ def unique(self) -> 'CreateIndex':
139
+ self._unique = True
140
+ return self
141
+
142
+ def if_not_exists(self) -> 'CreateIndex':
143
+ self._if_not_exists = True
144
+ return self
145
+
146
+ def __repr__(self) -> str:
147
+ return f"CreateIndex(name={self._name!r}, table={self._table!r})"
148
+
149
+
150
+ class DropIndex(ASTNode):
151
+ """DROP INDEX [IF EXISTS] name ON table"""
152
+
153
+ def __init__(self) -> None:
154
+ self._name: Optional[str] = None
155
+ self._table: Optional[str] = None
156
+ self._if_exists: bool = False
157
+
158
+ def name(self, idx_name: str) -> 'DropIndex':
159
+ self._name = idx_name
160
+ return self
161
+
162
+ def on(self, table: str) -> 'DropIndex':
163
+ self._table = table
164
+ return self
165
+
166
+ def if_exists(self) -> 'DropIndex':
167
+ self._if_exists = True
168
+ return self
169
+
170
+ def __repr__(self) -> str:
171
+ return f"DropIndex(name={self._name!r}, table={self._table!r})"
172
+
173
+
174
+ class CreateView(ASTNode):
175
+ """CREATE VIEW [IF NOT EXISTS] name AS SELECT ..."""
176
+
177
+ def __init__(self) -> None:
178
+ self._name: Optional[str] = None
179
+ self._select: Optional['Select'] = None
180
+ self._if_not_exists: bool = False
181
+
182
+ def name(self, view_name: str) -> 'CreateView':
183
+ self._name = view_name
184
+ return self
185
+
186
+ def as_(self, select: 'Select') -> 'CreateView':
187
+ self._select = select
188
+ return self
189
+
190
+ def if_not_exists(self) -> 'CreateView':
191
+ self._if_not_exists = True
192
+ return self
193
+
194
+ def __repr__(self) -> str:
195
+ return f"CreateView(name={self._name!r})"
196
+
197
+
198
+ class DropView(ASTNode):
199
+ """DROP VIEW [IF EXISTS] name"""
200
+
201
+ def __init__(self) -> None:
202
+ self._name: Optional[str] = None
203
+ self._if_exists: bool = False
204
+
205
+ def name(self, view_name: str) -> 'DropView':
206
+ self._name = view_name
207
+ return self
208
+
209
+ def if_exists(self) -> 'DropView':
210
+ self._if_exists = True
211
+ return self
212
+
213
+ def __repr__(self) -> str:
214
+ return f"DropView(name={self._name!r})"
215
+
216
+
217
+ class Truncate(ASTNode):
218
+ """TRUNCATE TABLE name"""
219
+
220
+ def __init__(self) -> None:
221
+ self._table: Optional[str] = None
222
+
223
+ def table(self, name: str) -> 'Truncate':
224
+ self._table = name
225
+ return self
226
+
227
+ def __repr__(self) -> str:
228
+ return f"Truncate(table={self._table!r})"
@@ -0,0 +1,124 @@
1
+ from typing import TYPE_CHECKING, Dict, Optional, Tuple
2
+
3
+ from sqlpiston.builder.nodes import ASTNode
4
+ from sqlpiston._types import ColumnValue
5
+
6
+ if TYPE_CHECKING:
7
+ from sqlpiston.builder.selectable import Select
8
+
9
+
10
+ class Insert(ASTNode):
11
+ """INSERT INTO table (col1, col2) VALUES (v1, v2)
12
+ INSERT INTO table (col1, col2) SELECT ...
13
+ """
14
+
15
+ def __init__(self) -> None:
16
+ self._table: Optional[str] = None
17
+ self._data: Optional[Dict[str, ColumnValue]] = None
18
+ self._select: Optional['Select'] = None
19
+
20
+ def into(self, table: str) -> 'Insert':
21
+ self._table = table
22
+ return self
23
+
24
+ def values(self, data: Dict[str, ColumnValue]) -> 'Insert':
25
+ self._data = data
26
+ self._select = None
27
+ return self
28
+
29
+ def select(self, select: 'Select') -> 'Insert':
30
+ self._select = select
31
+ self._data = None
32
+ return self
33
+
34
+ def __repr__(self) -> str:
35
+ return f"Insert(into={self._table!r})"
36
+
37
+
38
+ class Update(ASTNode):
39
+ """UPDATE table SET col1=v1, col2=v2 WHERE condition"""
40
+
41
+ def __init__(self) -> None:
42
+ self._table: Optional[str] = None
43
+ self._data: Optional[Dict[str, ColumnValue]] = None
44
+ self._where: Optional[ASTNode] = None
45
+
46
+ def table(self, name: str) -> 'Update':
47
+ self._table = name
48
+ return self
49
+
50
+ def set(self, data: Dict[str, ColumnValue]) -> 'Update':
51
+ self._data = data
52
+ return self
53
+
54
+ def where(self, condition: ASTNode) -> 'Update':
55
+ self._where = condition
56
+ return self
57
+
58
+ def __repr__(self) -> str:
59
+ return f"Update(table={self._table!r})"
60
+
61
+
62
+ class Delete(ASTNode):
63
+ """DELETE FROM table WHERE condition"""
64
+
65
+ def __init__(self) -> None:
66
+ self._table: Optional[str] = None
67
+ self._where: Optional[ASTNode] = None
68
+
69
+ def from_table(self, table: str) -> 'Delete':
70
+ self._table = table
71
+ return self
72
+
73
+ def where(self, condition: ASTNode) -> 'Delete':
74
+ self._where = condition
75
+ return self
76
+
77
+ def __repr__(self) -> str:
78
+ return f"Delete(from={self._table!r})"
79
+
80
+
81
+ class Upsert(ASTNode):
82
+ """Standard UPSERT — AST stores intent; dialect compilers translate per DB.
83
+
84
+ MySQL compiler → INSERT INTO ... VALUES (...) ON DUPLICATE KEY UPDATE ...
85
+ SQLite compiler → INSERT INTO ... VALUES (...) ON CONFLICT (...) DO UPDATE SET ...
86
+
87
+ Usage:
88
+ Upsert()
89
+ .into("users")
90
+ .values({"id": 1, "name": "X"})
91
+ .on_conflict("id")
92
+ .do_update({"name": "X"})
93
+ """
94
+
95
+ def __init__(self) -> None:
96
+ self._table: Optional[str] = None
97
+ self._data: Optional[Dict[str, ColumnValue]] = None
98
+ self._conflict_columns: Optional[Tuple[str, ...]] = None
99
+ self._update_data: Optional[Dict[str, ColumnValue]] = None
100
+ self._do_nothing: bool = False
101
+
102
+ def into(self, table: str) -> 'Upsert':
103
+ self._table = table
104
+ return self
105
+
106
+ def values(self, data: Dict[str, ColumnValue]) -> 'Upsert':
107
+ self._data = data
108
+ return self
109
+
110
+ def on_conflict(self, *columns: str) -> 'Upsert':
111
+ self._conflict_columns = columns
112
+ return self
113
+
114
+ def do_update(self, data: Dict[str, ColumnValue]) -> 'Upsert':
115
+ self._update_data = data
116
+ self._do_nothing = False
117
+ return self
118
+
119
+ def do_nothing(self) -> 'Upsert':
120
+ self._do_nothing = True
121
+ return self
122
+
123
+ def __repr__(self) -> str:
124
+ return f"Upsert(into={self._table!r}, conflict={self._conflict_columns!r})"
@@ -0,0 +1,248 @@
1
+ from abc import ABC
2
+ from typing import (
3
+ TYPE_CHECKING, List, Literal, Optional, Sequence, Tuple, Union, cast, overload,
4
+ )
5
+
6
+ from sqlpiston._types import SQLValue
7
+
8
+ if TYPE_CHECKING:
9
+ from sqlpiston.builder.selectable import Select
10
+ from sqlpiston.compiler.base import Dialect
11
+
12
+ # ExprValue: a value that can appear in an AST expression.
13
+ # Can be a scalar literal, another Field reference, a Select subquery, or a function call.
14
+ # 'Select' is a string forward-reference to avoid circular imports with selectable.py.
15
+ ExprValue = Union[SQLValue, 'Field', 'Select', 'SQLFunction']
16
+
17
+
18
+ class Field:
19
+ """Column reference. Operator overloading returns AST nodes, not values.
20
+
21
+ Usage:
22
+ Field("age") >= 18 → ComparisonNode
23
+ Field("id", "users") → qualified column
24
+ Field("name").alias("n") → name AS n
25
+ """
26
+
27
+ def __init__(self, name: str, table: Optional[str] = None) -> None:
28
+ self._name = name
29
+ self._table = table
30
+ self._alias: Optional[str] = None
31
+
32
+ def __repr__(self) -> str:
33
+ parts: List[str] = []
34
+ if self._table:
35
+ parts.append(self._table)
36
+ parts.append(self._name)
37
+ base = ".".join(parts)
38
+ if self._alias:
39
+ return f"Field({base} AS {self._alias})"
40
+ return f"Field({base})"
41
+
42
+ def __eq__(self, value: object) -> 'ComparisonNode': # type: ignore[override] # intentional: return AST node, not bool
43
+ return ComparisonNode(self, '=', cast(ExprValue, value))
44
+
45
+ def __ne__(self, value: object) -> 'ComparisonNode': # type: ignore[override] # intentional: return AST node, not bool
46
+ return ComparisonNode(self, '!=', cast(ExprValue, value))
47
+
48
+ def __lt__(self, value: ExprValue) -> 'ComparisonNode':
49
+ return ComparisonNode(self, '<', value)
50
+
51
+ def __le__(self, value: ExprValue) -> 'ComparisonNode':
52
+ return ComparisonNode(self, '<=', value)
53
+
54
+ def __gt__(self, value: ExprValue) -> 'ComparisonNode':
55
+ return ComparisonNode(self, '>', value)
56
+
57
+ def __ge__(self, value: ExprValue) -> 'ComparisonNode':
58
+ return ComparisonNode(self, '>=', value)
59
+
60
+ def __hash__(self) -> int:
61
+ return hash((self._name, self._table, self._alias))
62
+
63
+ @overload
64
+ def is_in(self, values: Sequence[SQLValue]) -> 'InNode': ...
65
+
66
+ @overload
67
+ def is_in(self, values: 'Select') -> 'InNode': ...
68
+
69
+ def is_in(self, values: Union[Sequence[SQLValue], 'Select']) -> 'InNode':
70
+ return InNode(self, values)
71
+
72
+ def between(self, low: ExprValue, high: ExprValue) -> 'BetweenNode':
73
+ return BetweenNode(self, low, high)
74
+
75
+ def is_null(self) -> 'ComparisonNode':
76
+ return ComparisonNode(self, 'IS NULL', None)
77
+
78
+ def is_not_null(self) -> 'ComparisonNode':
79
+ return ComparisonNode(self, 'IS NOT NULL', None)
80
+
81
+ def alias(self, name: str) -> 'Field':
82
+ f = Field(self._name, self._table)
83
+ f._alias = name
84
+ return f
85
+
86
+ @property
87
+ def name(self) -> str:
88
+ return self._name
89
+
90
+ @property
91
+ def table(self) -> Optional[str]:
92
+ return self._table
93
+
94
+ @property
95
+ def _alias_prop(self) -> Optional[str]:
96
+ return self._alias
97
+
98
+
99
+ class ASTNode(ABC):
100
+ """Base for all expression/statement nodes.
101
+
102
+ compile() delegates to a dialect-specific compiler via the visitor pattern.
103
+ """
104
+
105
+ def __and__(self, other: 'ASTNode') -> 'LogicalNode':
106
+ return LogicalNode('AND', [self, other])
107
+
108
+ def __or__(self, other: 'ASTNode') -> 'LogicalNode':
109
+ return LogicalNode('OR', [self, other])
110
+
111
+ def __invert__(self) -> 'LogicalNode':
112
+ return LogicalNode('NOT', [self])
113
+
114
+ def compile(self, dialect: Optional['Dialect'] = None) -> Tuple[str, Tuple[ExprValue, ...]]:
115
+ if dialect is None:
116
+ from sqlpiston.compiler.base import Compiler, GenericCompiler
117
+ compiler: Compiler = GenericCompiler()
118
+ else:
119
+ compiler = dialect.get_compiler()
120
+ return compiler.process(self)
121
+
122
+
123
+ class ComparisonNode(ASTNode):
124
+ """leaf: field OP value
125
+
126
+ value can be:
127
+ - SQL literal → field OP %s (parameterized)
128
+ - Field → field OP other_field (table-qualified comparison)
129
+ - Select → field OP (SELECT ...) (scalar subquery)
130
+ - SQLFunction → field OP func(...)
131
+ """
132
+
133
+ def __init__(self, field: Field, operator: str, value: ExprValue) -> None:
134
+ self.field = field
135
+ self.operator = operator
136
+ self.value: ExprValue = value
137
+
138
+ def __repr__(self) -> str:
139
+ return f"ComparisonNode({self.field} {self.operator} {self.value!r})"
140
+
141
+
142
+ class InNode(ASTNode):
143
+ """leaf: field IN (...literals...) or field IN (SELECT ...)"""
144
+
145
+ def __init__(self, field: Field, values: Union[Sequence[SQLValue], 'Select']) -> None:
146
+ self.field = field
147
+ self.values: Union[Sequence[SQLValue], 'Select'] = values
148
+
149
+ def __repr__(self) -> str:
150
+ return f"InNode({self.field} IN {self.values!r})"
151
+
152
+
153
+ class BetweenNode(ASTNode):
154
+ """leaf: field BETWEEN low AND high"""
155
+
156
+ def __init__(self, field: Field, low: ExprValue, high: ExprValue) -> None:
157
+ self.field = field
158
+ self.low: ExprValue = low
159
+ self.high: ExprValue = high
160
+
161
+ def __repr__(self) -> str:
162
+ return f"BetweenNode({self.field} BETWEEN {self.low!r} AND {self.high!r})"
163
+
164
+
165
+ class LogicalNode(ASTNode):
166
+ """composite: AND / OR / NOT.
167
+
168
+ Same-operator flattening: A & B & C → one LogicalNode('AND', [A,B,C]),
169
+ not nested.
170
+ """
171
+
172
+ def __init__(self, operator: Literal['AND', 'OR', 'NOT'], children: List[ASTNode]) -> None:
173
+ self.operator = operator
174
+ self.children = children
175
+
176
+ def __and__(self, other: ASTNode) -> 'LogicalNode':
177
+ if self.operator == 'AND':
178
+ return LogicalNode('AND', self.children + [other])
179
+ return LogicalNode('AND', [self, other])
180
+
181
+ def __or__(self, other: ASTNode) -> 'LogicalNode':
182
+ if self.operator == 'OR':
183
+ return LogicalNode('OR', self.children + [other])
184
+ return LogicalNode('OR', [self, other])
185
+
186
+ def __repr__(self) -> str:
187
+ return f"LogicalNode({self.operator}, children={len(self.children)})"
188
+
189
+
190
+ class ExistsNode(ASTNode):
191
+ """EXISTS (SELECT ...) or NOT EXISTS (SELECT ...)"""
192
+
193
+ def __init__(self, select: 'Select', negated: bool = False) -> None:
194
+ self.select = select
195
+ self.negated = negated
196
+
197
+ def __repr__(self) -> str:
198
+ prefix = "NOT EXISTS" if self.negated else "EXISTS"
199
+ return f"ExistsNode({prefix} {self.select!r})"
200
+
201
+
202
+ class CaseNode(ASTNode):
203
+ """CASE WHEN cond1 THEN val1 WHEN cond2 THEN val2 ... [ELSE default] END
204
+
205
+ Usage:
206
+ CaseNode()
207
+ .when(Field("score") >= 90, "A")
208
+ .when(Field("score") >= 80, "B")
209
+ .else_("C")
210
+ """
211
+
212
+ def __init__(self) -> None:
213
+ self._whens: List[Tuple[ASTNode, ExprValue]] = []
214
+ self._else: Optional[ExprValue] = None
215
+
216
+ def when(self, condition: ASTNode, result: ExprValue) -> 'CaseNode':
217
+ self._whens.append((condition, result))
218
+ return self
219
+
220
+ def else_(self, default: ExprValue) -> 'CaseNode':
221
+ self._else = default
222
+ return self
223
+
224
+ def __repr__(self) -> str:
225
+ return f"CaseNode(whens={len(self._whens)}, has_else={self._else is not None})"
226
+
227
+
228
+ class SQLFunction(ASTNode):
229
+ """SQL function call: COUNT(*), SUM(col), COALESCE(a, b), etc.
230
+
231
+ Usage:
232
+ SQLFunction("count", "*")
233
+ SQLFunction("coalesce", Field("name"), "N/A")
234
+ SQLFunction("sum", Field("amount"))
235
+ """
236
+
237
+ def __init__(self, name: str, *args: Union[str, Field, 'SQLFunction']) -> None:
238
+ self.name = name
239
+ self.args: Tuple[Union[str, Field, 'SQLFunction'], ...] = args
240
+ self._alias: Optional[str] = None
241
+
242
+ def alias(self, name: str) -> 'SQLFunction':
243
+ self._alias = name
244
+ return self
245
+
246
+ def __repr__(self) -> str:
247
+ alias_str = f" AS {self._alias}" if self._alias else ""
248
+ return f"SQLFunction({self.name}({', '.join(repr(a) for a in self.args)}){alias_str})"