sqlspec 0.12.2__py3-none-any.whl → 0.13.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sqlspec might be problematic. Click here for more details.
- sqlspec/_sql.py +21 -180
- sqlspec/adapters/adbc/config.py +10 -12
- sqlspec/adapters/adbc/driver.py +120 -118
- sqlspec/adapters/aiosqlite/config.py +16 -3
- sqlspec/adapters/aiosqlite/driver.py +100 -130
- sqlspec/adapters/asyncmy/config.py +17 -4
- sqlspec/adapters/asyncmy/driver.py +123 -135
- sqlspec/adapters/asyncpg/config.py +17 -29
- sqlspec/adapters/asyncpg/driver.py +98 -140
- sqlspec/adapters/bigquery/config.py +4 -5
- sqlspec/adapters/bigquery/driver.py +125 -167
- sqlspec/adapters/duckdb/config.py +3 -6
- sqlspec/adapters/duckdb/driver.py +114 -111
- sqlspec/adapters/oracledb/config.py +32 -5
- sqlspec/adapters/oracledb/driver.py +242 -259
- sqlspec/adapters/psqlpy/config.py +18 -9
- sqlspec/adapters/psqlpy/driver.py +118 -93
- sqlspec/adapters/psycopg/config.py +44 -31
- sqlspec/adapters/psycopg/driver.py +283 -236
- sqlspec/adapters/sqlite/config.py +3 -3
- sqlspec/adapters/sqlite/driver.py +103 -97
- sqlspec/config.py +0 -4
- sqlspec/driver/_async.py +89 -98
- sqlspec/driver/_common.py +52 -17
- sqlspec/driver/_sync.py +81 -105
- sqlspec/driver/connection.py +207 -0
- sqlspec/driver/mixins/_csv_writer.py +91 -0
- sqlspec/driver/mixins/_pipeline.py +38 -49
- sqlspec/driver/mixins/_result_utils.py +27 -9
- sqlspec/driver/mixins/_storage.py +67 -181
- sqlspec/driver/mixins/_type_coercion.py +3 -4
- sqlspec/driver/parameters.py +138 -0
- sqlspec/exceptions.py +10 -2
- sqlspec/extensions/aiosql/adapter.py +0 -10
- sqlspec/extensions/litestar/handlers.py +0 -1
- sqlspec/extensions/litestar/plugin.py +0 -3
- sqlspec/extensions/litestar/providers.py +0 -14
- sqlspec/loader.py +25 -90
- sqlspec/protocols.py +542 -0
- sqlspec/service/__init__.py +3 -2
- sqlspec/service/_util.py +147 -0
- sqlspec/service/base.py +1116 -9
- sqlspec/statement/builder/__init__.py +42 -32
- sqlspec/statement/builder/_ddl_utils.py +0 -10
- sqlspec/statement/builder/_parsing_utils.py +10 -4
- sqlspec/statement/builder/base.py +67 -22
- sqlspec/statement/builder/column.py +283 -0
- sqlspec/statement/builder/ddl.py +91 -67
- sqlspec/statement/builder/delete.py +23 -7
- sqlspec/statement/builder/insert.py +29 -15
- sqlspec/statement/builder/merge.py +4 -4
- sqlspec/statement/builder/mixins/_aggregate_functions.py +113 -14
- sqlspec/statement/builder/mixins/_common_table_expr.py +0 -1
- sqlspec/statement/builder/mixins/_delete_from.py +1 -1
- sqlspec/statement/builder/mixins/_from.py +10 -8
- sqlspec/statement/builder/mixins/_group_by.py +0 -1
- sqlspec/statement/builder/mixins/_insert_from_select.py +0 -1
- sqlspec/statement/builder/mixins/_insert_values.py +0 -2
- sqlspec/statement/builder/mixins/_join.py +20 -13
- sqlspec/statement/builder/mixins/_limit_offset.py +3 -3
- sqlspec/statement/builder/mixins/_merge_clauses.py +3 -4
- sqlspec/statement/builder/mixins/_order_by.py +2 -2
- sqlspec/statement/builder/mixins/_pivot.py +4 -7
- sqlspec/statement/builder/mixins/_select_columns.py +6 -5
- sqlspec/statement/builder/mixins/_unpivot.py +6 -9
- sqlspec/statement/builder/mixins/_update_from.py +2 -1
- sqlspec/statement/builder/mixins/_update_set.py +11 -8
- sqlspec/statement/builder/mixins/_where.py +61 -34
- sqlspec/statement/builder/select.py +32 -17
- sqlspec/statement/builder/update.py +25 -11
- sqlspec/statement/filters.py +39 -14
- sqlspec/statement/parameter_manager.py +220 -0
- sqlspec/statement/parameters.py +210 -79
- sqlspec/statement/pipelines/__init__.py +166 -23
- sqlspec/statement/pipelines/analyzers/_analyzer.py +21 -20
- sqlspec/statement/pipelines/context.py +35 -39
- sqlspec/statement/pipelines/transformers/__init__.py +2 -3
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +19 -187
- sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +628 -58
- sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +76 -0
- sqlspec/statement/pipelines/validators/_dml_safety.py +33 -18
- sqlspec/statement/pipelines/validators/_parameter_style.py +87 -14
- sqlspec/statement/pipelines/validators/_performance.py +38 -23
- sqlspec/statement/pipelines/validators/_security.py +39 -62
- sqlspec/statement/result.py +37 -129
- sqlspec/statement/splitter.py +0 -12
- sqlspec/statement/sql.py +863 -391
- sqlspec/statement/sql_compiler.py +140 -0
- sqlspec/storage/__init__.py +10 -2
- sqlspec/storage/backends/fsspec.py +53 -8
- sqlspec/storage/backends/obstore.py +15 -19
- sqlspec/storage/capabilities.py +101 -0
- sqlspec/storage/registry.py +56 -83
- sqlspec/typing.py +6 -434
- sqlspec/utils/cached_property.py +25 -0
- sqlspec/utils/correlation.py +0 -2
- sqlspec/utils/logging.py +0 -6
- sqlspec/utils/sync_tools.py +0 -4
- sqlspec/utils/text.py +0 -5
- sqlspec/utils/type_guards.py +892 -0
- {sqlspec-0.12.2.dist-info → sqlspec-0.13.1.dist-info}/METADATA +1 -1
- sqlspec-0.13.1.dist-info/RECORD +150 -0
- sqlspec/statement/builder/protocols.py +0 -20
- sqlspec/statement/pipelines/base.py +0 -315
- sqlspec/statement/pipelines/result_types.py +0 -41
- sqlspec/statement/pipelines/transformers/_remove_comments.py +0 -66
- sqlspec/statement/pipelines/transformers/_remove_hints.py +0 -81
- sqlspec/statement/pipelines/validators/base.py +0 -67
- sqlspec/storage/protocol.py +0 -173
- sqlspec-0.12.2.dist-info/RECORD +0 -145
- {sqlspec-0.12.2.dist-info → sqlspec-0.13.1.dist-info}/WHEEL +0 -0
- {sqlspec-0.12.2.dist-info → sqlspec-0.13.1.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.12.2.dist-info → sqlspec-0.13.1.dist-info}/licenses/NOTICE +0 -0
sqlspec/statement/result.py
CHANGED
|
@@ -1,68 +1,23 @@
|
|
|
1
1
|
"""SQL statement result classes for handling different types of SQL operations."""
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
|
|
5
|
-
# Import Mapping for type checking in __post_init__
|
|
6
4
|
from collections.abc import Mapping, Sequence
|
|
7
5
|
from dataclasses import dataclass, field
|
|
8
|
-
from typing import TYPE_CHECKING, Any, Generic, Optional, Union
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Generic, Literal, Optional, Union
|
|
9
7
|
|
|
10
|
-
from typing_extensions import
|
|
8
|
+
from typing_extensions import TypeVar
|
|
11
9
|
|
|
12
10
|
from sqlspec.typing import ArrowTable, RowT
|
|
13
11
|
|
|
14
12
|
if TYPE_CHECKING:
|
|
15
13
|
from sqlspec.statement.sql import SQL
|
|
16
14
|
|
|
17
|
-
__all__ = ("ArrowResult", "
|
|
15
|
+
__all__ = ("ArrowResult", "SQLResult", "StatementResult")
|
|
18
16
|
|
|
19
17
|
|
|
20
18
|
T = TypeVar("T")
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
class SelectResultDict(TypedDict):
|
|
24
|
-
"""TypedDict for SELECT/RETURNING query results.
|
|
25
|
-
|
|
26
|
-
This structure is returned by drivers when executing SELECT queries
|
|
27
|
-
or DML queries with RETURNING clauses.
|
|
28
|
-
"""
|
|
29
|
-
|
|
30
|
-
data: "list[Any]"
|
|
31
|
-
"""List of rows returned by the query."""
|
|
32
|
-
column_names: "list[str]"
|
|
33
|
-
"""List of column names in the result set."""
|
|
34
|
-
rows_affected: int
|
|
35
|
-
"""Number of rows affected (-1 when unsupported)."""
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class DMLResultDict(TypedDict, total=False):
|
|
39
|
-
"""TypedDict for DML (INSERT/UPDATE/DELETE) results without RETURNING.
|
|
40
|
-
|
|
41
|
-
This structure is returned by drivers when executing DML operations
|
|
42
|
-
that don't return data (no RETURNING clause).
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
rows_affected: int
|
|
46
|
-
"""Number of rows affected by the operation."""
|
|
47
|
-
status_message: str
|
|
48
|
-
"""Status message from the database (-1 when unsupported)."""
|
|
49
|
-
description: str
|
|
50
|
-
"""Optional description of the operation."""
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class ScriptResultDict(TypedDict, total=False):
|
|
54
|
-
"""TypedDict for script execution results.
|
|
55
|
-
|
|
56
|
-
This structure is returned by drivers when executing multi-statement
|
|
57
|
-
SQL scripts.
|
|
58
|
-
"""
|
|
59
|
-
|
|
60
|
-
statements_executed: int
|
|
61
|
-
"""Number of statements that were executed."""
|
|
62
|
-
status_message: str
|
|
63
|
-
"""Overall status message from the script execution."""
|
|
64
|
-
description: str
|
|
65
|
-
"""Optional description of the script execution."""
|
|
20
|
+
OperationType = Literal["SELECT", "INSERT", "UPDATE", "DELETE", "EXECUTE", "SCRIPT"]
|
|
66
21
|
|
|
67
22
|
|
|
68
23
|
@dataclass
|
|
@@ -133,9 +88,6 @@ class StatementResult(ABC, Generic[RowT]):
|
|
|
133
88
|
self.metadata[key] = value
|
|
134
89
|
|
|
135
90
|
|
|
136
|
-
# RowT is introduced for clarity within SQLResult, representing the type of a single row.
|
|
137
|
-
|
|
138
|
-
|
|
139
91
|
@dataclass
|
|
140
92
|
class SQLResult(StatementResult[RowT], Generic[RowT]):
|
|
141
93
|
"""Unified result class for SQL operations that return a list of rows
|
|
@@ -147,22 +99,16 @@ class SQLResult(StatementResult[RowT], Generic[RowT]):
|
|
|
147
99
|
For script execution, this class also tracks multiple statement results and errors.
|
|
148
100
|
"""
|
|
149
101
|
|
|
102
|
+
data: "list[RowT]" = field(default_factory=list)
|
|
150
103
|
error: Optional[Exception] = None
|
|
104
|
+
operation_type: OperationType = "SELECT"
|
|
151
105
|
operation_index: Optional[int] = None
|
|
152
106
|
pipeline_sql: Optional["SQL"] = None
|
|
153
107
|
parameters: Optional[Any] = None
|
|
154
|
-
|
|
155
|
-
# Attributes primarily for SELECT-like results or results with column structure
|
|
156
108
|
column_names: "list[str]" = field(default_factory=list)
|
|
157
|
-
total_count: Optional[int] = None
|
|
158
|
-
has_more: bool = False
|
|
159
|
-
|
|
160
|
-
# Attributes primarily for DML-like results
|
|
161
|
-
operation_type: str = "SELECT" # Default, override for DML
|
|
109
|
+
total_count: Optional[int] = None
|
|
110
|
+
has_more: bool = False
|
|
162
111
|
inserted_ids: "list[Union[int, str]]" = field(default_factory=list)
|
|
163
|
-
# rows_affected and last_inserted_id are inherited from StatementResult
|
|
164
|
-
|
|
165
|
-
# Attributes for script execution
|
|
166
112
|
statement_results: "list[SQLResult[Any]]" = field(default_factory=list)
|
|
167
113
|
"""Individual statement results when executing scripts."""
|
|
168
114
|
errors: "list[str]" = field(default_factory=list)
|
|
@@ -176,40 +122,35 @@ class SQLResult(StatementResult[RowT], Generic[RowT]):
|
|
|
176
122
|
"""Post-initialization to infer column names and total count if not provided."""
|
|
177
123
|
if not self.column_names and self.data and isinstance(self.data[0], Mapping):
|
|
178
124
|
self.column_names = list(self.data[0].keys())
|
|
179
|
-
|
|
180
125
|
if self.total_count is None:
|
|
181
126
|
self.total_count = len(self.data) if self.data is not None else 0
|
|
182
127
|
|
|
183
|
-
# If data is populated for a DML, it implies returning data.
|
|
184
|
-
# No separate returning_data field needed; self.data serves this purpose.
|
|
185
|
-
|
|
186
128
|
def is_success(self) -> bool:
|
|
187
129
|
"""Check if the operation was successful.
|
|
130
|
+
|
|
188
131
|
- For SELECT: True if data is not None and rows_affected is not negative.
|
|
189
132
|
- For DML (INSERT, UPDATE, DELETE, EXECUTE): True if rows_affected is >= 0.
|
|
190
133
|
- For SCRIPT: True if no errors and all statements succeeded.
|
|
191
134
|
"""
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
rows_success = self.rows_affected is None or self.rows_affected >= 0
|
|
202
|
-
return data_success and rows_success
|
|
203
|
-
if op_type_upper in {"INSERT", "UPDATE", "DELETE", "EXECUTE"}:
|
|
135
|
+
op_type = self.operation_type.upper()
|
|
136
|
+
|
|
137
|
+
if op_type == "SCRIPT" or self.statement_results:
|
|
138
|
+
return not self.errors and self.total_statements == self.successful_statements
|
|
139
|
+
|
|
140
|
+
if op_type == "SELECT":
|
|
141
|
+
return self.data is not None and (self.rows_affected is None or self.rows_affected >= 0)
|
|
142
|
+
|
|
143
|
+
if op_type in {"INSERT", "UPDATE", "DELETE", "EXECUTE"}:
|
|
204
144
|
return self.rows_affected is not None and self.rows_affected >= 0
|
|
205
|
-
|
|
145
|
+
|
|
146
|
+
return False
|
|
206
147
|
|
|
207
148
|
def get_data(self) -> "Union[list[RowT], dict[str, Any]]":
|
|
208
149
|
"""Get the data from the result.
|
|
150
|
+
|
|
209
151
|
For regular operations, returns the list of rows.
|
|
210
152
|
For script operations, returns a summary dictionary.
|
|
211
153
|
"""
|
|
212
|
-
# For script execution, return summary data
|
|
213
154
|
if self.operation_type.upper() == "SCRIPT" or self.statement_results:
|
|
214
155
|
return {
|
|
215
156
|
"total_statements": self.total_statements,
|
|
@@ -219,11 +160,7 @@ class SQLResult(StatementResult[RowT], Generic[RowT]):
|
|
|
219
160
|
"statement_results": self.statement_results,
|
|
220
161
|
"total_rows_affected": self.get_total_rows_affected(),
|
|
221
162
|
}
|
|
222
|
-
|
|
223
|
-
# For regular operations, return the data as usual
|
|
224
|
-
return cast("list[RowT]", self.data)
|
|
225
|
-
|
|
226
|
-
# --- Script execution methods ---
|
|
163
|
+
return self.data
|
|
227
164
|
|
|
228
165
|
def add_statement_result(self, result: "SQLResult[Any]") -> None:
|
|
229
166
|
"""Add a statement result to the script execution results."""
|
|
@@ -245,15 +182,10 @@ class SQLResult(StatementResult[RowT], Generic[RowT]):
|
|
|
245
182
|
def get_total_rows_affected(self) -> int:
|
|
246
183
|
"""Get the total number of rows affected across all statements."""
|
|
247
184
|
if self.statement_results:
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
# Only count non-negative values, -1 indicates failure
|
|
253
|
-
total += stmt_result.rows_affected
|
|
254
|
-
return total
|
|
255
|
-
# For single statement execution
|
|
256
|
-
return max(self.rows_affected or 0, 0) # Treat negative values as 0
|
|
185
|
+
return sum(
|
|
186
|
+
stmt.rows_affected for stmt in self.statement_results if stmt.rows_affected and stmt.rows_affected > 0
|
|
187
|
+
)
|
|
188
|
+
return self.rows_affected if self.rows_affected and self.rows_affected > 0 else 0
|
|
257
189
|
|
|
258
190
|
@property
|
|
259
191
|
def num_rows(self) -> int:
|
|
@@ -272,8 +204,6 @@ class SQLResult(StatementResult[RowT], Generic[RowT]):
|
|
|
272
204
|
"""Check if there are any errors from script execution."""
|
|
273
205
|
return len(self.errors) > 0
|
|
274
206
|
|
|
275
|
-
# --- Existing methods for regular operations ---
|
|
276
|
-
|
|
277
207
|
def get_first(self) -> "Optional[RowT]":
|
|
278
208
|
"""Get the first row from the result, if any."""
|
|
279
209
|
return self.data[0] if self.data else None
|
|
@@ -286,25 +216,10 @@ class SQLResult(StatementResult[RowT], Generic[RowT]):
|
|
|
286
216
|
"""Check if the result set (self.data) is empty."""
|
|
287
217
|
return not self.data
|
|
288
218
|
|
|
289
|
-
# --- Methods related to DML operations ---
|
|
290
219
|
def get_affected_count(self) -> int:
|
|
291
220
|
"""Get the number of rows affected by a DML operation."""
|
|
292
221
|
return self.rows_affected or 0
|
|
293
222
|
|
|
294
|
-
def get_inserted_id(self) -> "Optional[Union[int, str]]":
|
|
295
|
-
"""Get the last inserted ID (typically for single row inserts)."""
|
|
296
|
-
return self.last_inserted_id
|
|
297
|
-
|
|
298
|
-
def get_inserted_ids(self) -> "list[Union[int, str]]":
|
|
299
|
-
"""Get all inserted IDs (useful for batch inserts)."""
|
|
300
|
-
return self.inserted_ids
|
|
301
|
-
|
|
302
|
-
def get_returning_data(self) -> "list[RowT]":
|
|
303
|
-
"""Get data returned by RETURNING clauses.
|
|
304
|
-
This is effectively self.data for this unified class.
|
|
305
|
-
"""
|
|
306
|
-
return cast("list[RowT]", self.data)
|
|
307
|
-
|
|
308
223
|
def was_inserted(self) -> bool:
|
|
309
224
|
"""Check if this was an INSERT operation."""
|
|
310
225
|
return self.operation_type.upper() == "INSERT"
|
|
@@ -340,9 +255,7 @@ class SQLResult(StatementResult[RowT], Generic[RowT]):
|
|
|
340
255
|
if self.data is None:
|
|
341
256
|
msg = "No data available"
|
|
342
257
|
raise TypeError(msg)
|
|
343
|
-
return
|
|
344
|
-
|
|
345
|
-
# --- SQLAlchemy-style convenience methods ---
|
|
258
|
+
return self.data[index]
|
|
346
259
|
|
|
347
260
|
def all(self) -> "list[RowT]":
|
|
348
261
|
"""Return all rows as a list.
|
|
@@ -352,7 +265,7 @@ class SQLResult(StatementResult[RowT], Generic[RowT]):
|
|
|
352
265
|
"""
|
|
353
266
|
if self.data is None:
|
|
354
267
|
return []
|
|
355
|
-
return
|
|
268
|
+
return self.data
|
|
356
269
|
|
|
357
270
|
def one(self) -> "RowT":
|
|
358
271
|
"""Return exactly one row.
|
|
@@ -369,7 +282,7 @@ class SQLResult(StatementResult[RowT], Generic[RowT]):
|
|
|
369
282
|
if len(self.data) > 1:
|
|
370
283
|
msg = f"Multiple results found ({len(self.data)}), exactly one row expected"
|
|
371
284
|
raise ValueError(msg)
|
|
372
|
-
return
|
|
285
|
+
return self.data[0]
|
|
373
286
|
|
|
374
287
|
def one_or_none(self) -> "Optional[RowT]":
|
|
375
288
|
"""Return at most one row.
|
|
@@ -385,7 +298,7 @@ class SQLResult(StatementResult[RowT], Generic[RowT]):
|
|
|
385
298
|
if len(self.data) > 1:
|
|
386
299
|
msg = f"Multiple results found ({len(self.data)}), at most one row expected"
|
|
387
300
|
raise ValueError(msg)
|
|
388
|
-
return
|
|
301
|
+
return self.data[0]
|
|
389
302
|
|
|
390
303
|
def scalar(self) -> Any:
|
|
391
304
|
"""Return the first column of the first row.
|
|
@@ -398,19 +311,16 @@ class SQLResult(StatementResult[RowT], Generic[RowT]):
|
|
|
398
311
|
"""
|
|
399
312
|
row = self.one()
|
|
400
313
|
if isinstance(row, Mapping):
|
|
401
|
-
# For dict-like rows, get the first column value
|
|
402
314
|
if not row:
|
|
403
315
|
msg = "Row has no columns"
|
|
404
316
|
raise ValueError(msg)
|
|
405
|
-
first_key =
|
|
406
|
-
return
|
|
317
|
+
first_key = next(iter(row.keys()))
|
|
318
|
+
return row[first_key]
|
|
407
319
|
if isinstance(row, Sequence) and not isinstance(row, (str, bytes)):
|
|
408
|
-
# For tuple/list-like rows
|
|
409
320
|
if len(row) == 0:
|
|
410
321
|
msg = "Row has no columns"
|
|
411
322
|
raise ValueError(msg)
|
|
412
|
-
return
|
|
413
|
-
# For scalar values returned directly
|
|
323
|
+
return row[0]
|
|
414
324
|
return row
|
|
415
325
|
|
|
416
326
|
def scalar_or_none(self) -> Any:
|
|
@@ -429,11 +339,9 @@ class SQLResult(StatementResult[RowT], Generic[RowT]):
|
|
|
429
339
|
first_key = next(iter(row.keys()))
|
|
430
340
|
return row[first_key]
|
|
431
341
|
if isinstance(row, Sequence) and not isinstance(row, (str, bytes)):
|
|
432
|
-
# For tuple/list-like rows
|
|
433
342
|
if len(row) == 0:
|
|
434
343
|
return None
|
|
435
|
-
return
|
|
436
|
-
# For scalar values returned directly
|
|
344
|
+
return row[0]
|
|
437
345
|
return row
|
|
438
346
|
|
|
439
347
|
|
|
@@ -457,12 +365,12 @@ class ArrowResult(StatementResult[ArrowTable]):
|
|
|
457
365
|
"""The result data from the operation."""
|
|
458
366
|
|
|
459
367
|
def is_success(self) -> bool:
|
|
460
|
-
"""Check if the
|
|
368
|
+
"""Check if the operation was successful.
|
|
461
369
|
|
|
462
370
|
Returns:
|
|
463
|
-
True if
|
|
371
|
+
True if Arrow table data is available, False otherwise.
|
|
464
372
|
"""
|
|
465
|
-
return
|
|
373
|
+
return self.data is not None
|
|
466
374
|
|
|
467
375
|
def get_data(self) -> "ArrowTable":
|
|
468
376
|
"""Get the Apache Arrow Table from the result.
|
sqlspec/statement/splitter.py
CHANGED
|
@@ -115,7 +115,6 @@ class DialectConfig(ABC):
|
|
|
115
115
|
# 3. Dynamically build and insert keyword/separator patterns
|
|
116
116
|
all_keywords = self.block_starters | self.block_enders | self.batch_separators
|
|
117
117
|
if all_keywords:
|
|
118
|
-
# Sort by length descending to match longer keywords first
|
|
119
118
|
sorted_keywords = sorted(all_keywords, key=len, reverse=True)
|
|
120
119
|
patterns.append((TokenType.KEYWORD, r"\b(" + "|".join(re.escape(kw) for kw in sorted_keywords) + r")\b"))
|
|
121
120
|
|
|
@@ -337,7 +336,6 @@ class PostgreSQLDialectConfig(DialectConfig):
|
|
|
337
336
|
tag = start_match.group(0) # The full opening tag, e.g., "$tag$"
|
|
338
337
|
content_start = position + len(tag)
|
|
339
338
|
|
|
340
|
-
# Find the corresponding closing tag
|
|
341
339
|
try:
|
|
342
340
|
content_end = text.index(tag, content_start)
|
|
343
341
|
full_value = text[position : content_end + len(tag)]
|
|
@@ -502,7 +500,6 @@ class StatementSplitter:
|
|
|
502
500
|
column = pos - line_start + 1
|
|
503
501
|
token = pattern(sql, pos, line, column)
|
|
504
502
|
if token:
|
|
505
|
-
# Update position tracking
|
|
506
503
|
newlines = token.value.count("\n")
|
|
507
504
|
if newlines > 0:
|
|
508
505
|
line += newlines
|
|
@@ -520,7 +517,6 @@ class StatementSplitter:
|
|
|
520
517
|
value = match.group(0)
|
|
521
518
|
column = pos - line_start + 1
|
|
522
519
|
|
|
523
|
-
# Update line tracking
|
|
524
520
|
newlines = value.count("\n")
|
|
525
521
|
if newlines > 0:
|
|
526
522
|
line += newlines
|
|
@@ -544,7 +540,6 @@ class StatementSplitter:
|
|
|
544
540
|
current_statement_chars = []
|
|
545
541
|
block_stack = []
|
|
546
542
|
|
|
547
|
-
# Convert token generator to list so we can look ahead
|
|
548
543
|
all_tokens = list(self._tokenize(sql))
|
|
549
544
|
|
|
550
545
|
for token_idx, token in enumerate(all_tokens):
|
|
@@ -559,7 +554,6 @@ class StatementSplitter:
|
|
|
559
554
|
current_statement_tokens.append(token)
|
|
560
555
|
token_upper = token.value.upper()
|
|
561
556
|
|
|
562
|
-
# Update block nesting
|
|
563
557
|
if token.type == TokenType.KEYWORD:
|
|
564
558
|
if token_upper in self.dialect.block_starters:
|
|
565
559
|
block_stack.append(token_upper)
|
|
@@ -567,7 +561,6 @@ class StatementSplitter:
|
|
|
567
561
|
msg = f"Maximum nesting depth ({self.dialect.max_nesting_depth}) exceeded"
|
|
568
562
|
raise ValueError(msg)
|
|
569
563
|
elif token_upper in self.dialect.block_enders:
|
|
570
|
-
# Check if this is actually a block ender (not END IF, END LOOP, etc.)
|
|
571
564
|
if block_stack and self.dialect.is_real_block_ender(all_tokens, token_idx):
|
|
572
565
|
block_stack.pop()
|
|
573
566
|
|
|
@@ -576,7 +569,6 @@ class StatementSplitter:
|
|
|
576
569
|
if not block_stack: # Only terminate when not inside a block
|
|
577
570
|
if token.type == TokenType.TERMINATOR:
|
|
578
571
|
if token.value in self.dialect.statement_terminators:
|
|
579
|
-
# Check if we should delay this termination (e.g., for Oracle END; /)
|
|
580
572
|
should_delay = self.dialect.should_delay_semicolon_termination(all_tokens, token_idx)
|
|
581
573
|
|
|
582
574
|
# Also check if there's a batch separator coming up (for T-SQL GO)
|
|
@@ -601,7 +593,6 @@ class StatementSplitter:
|
|
|
601
593
|
# Save the statement
|
|
602
594
|
statement = "".join(current_statement_chars).strip()
|
|
603
595
|
|
|
604
|
-
# Determine if this is a PL/SQL block
|
|
605
596
|
is_plsql_block = self._is_plsql_block(current_statement_tokens)
|
|
606
597
|
|
|
607
598
|
# Optionally strip the trailing terminator
|
|
@@ -619,7 +610,6 @@ class StatementSplitter:
|
|
|
619
610
|
current_statement_tokens = []
|
|
620
611
|
current_statement_chars = []
|
|
621
612
|
|
|
622
|
-
# Handle any remaining content
|
|
623
613
|
if current_statement_chars:
|
|
624
614
|
statement = "".join(current_statement_chars).strip()
|
|
625
615
|
if statement and self._contains_executable_content(statement):
|
|
@@ -637,7 +627,6 @@ class StatementSplitter:
|
|
|
637
627
|
Returns:
|
|
638
628
|
True if this is a PL/SQL block (BEGIN...END or DECLARE...END)
|
|
639
629
|
"""
|
|
640
|
-
# Find the first meaningful keyword token (skip whitespace and comments)
|
|
641
630
|
for token in tokens:
|
|
642
631
|
if token.type == TokenType.KEYWORD:
|
|
643
632
|
return token.value.upper() in {"BEGIN", "DECLARE"}
|
|
@@ -655,7 +644,6 @@ class StatementSplitter:
|
|
|
655
644
|
# Tokenize the statement to check its content
|
|
656
645
|
tokens = list(self._tokenize(statement))
|
|
657
646
|
|
|
658
|
-
# Check if there are any non-comment, non-whitespace tokens
|
|
659
647
|
for token in tokens:
|
|
660
648
|
if token.type not in {TokenType.WHITESPACE, TokenType.COMMENT_LINE, TokenType.COMMENT_BLOCK}:
|
|
661
649
|
return True
|