sqlspec 0.12.1__py3-none-any.whl → 0.13.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.

Files changed (113) hide show
  1. sqlspec/_sql.py +21 -180
  2. sqlspec/adapters/adbc/config.py +10 -12
  3. sqlspec/adapters/adbc/driver.py +120 -118
  4. sqlspec/adapters/aiosqlite/config.py +3 -3
  5. sqlspec/adapters/aiosqlite/driver.py +116 -141
  6. sqlspec/adapters/asyncmy/config.py +3 -4
  7. sqlspec/adapters/asyncmy/driver.py +123 -135
  8. sqlspec/adapters/asyncpg/config.py +3 -7
  9. sqlspec/adapters/asyncpg/driver.py +98 -140
  10. sqlspec/adapters/bigquery/config.py +4 -5
  11. sqlspec/adapters/bigquery/driver.py +231 -181
  12. sqlspec/adapters/duckdb/config.py +3 -6
  13. sqlspec/adapters/duckdb/driver.py +132 -124
  14. sqlspec/adapters/oracledb/config.py +6 -5
  15. sqlspec/adapters/oracledb/driver.py +242 -259
  16. sqlspec/adapters/psqlpy/config.py +3 -7
  17. sqlspec/adapters/psqlpy/driver.py +118 -93
  18. sqlspec/adapters/psycopg/config.py +34 -30
  19. sqlspec/adapters/psycopg/driver.py +342 -214
  20. sqlspec/adapters/sqlite/config.py +3 -3
  21. sqlspec/adapters/sqlite/driver.py +150 -104
  22. sqlspec/config.py +0 -4
  23. sqlspec/driver/_async.py +89 -98
  24. sqlspec/driver/_common.py +52 -17
  25. sqlspec/driver/_sync.py +81 -105
  26. sqlspec/driver/connection.py +207 -0
  27. sqlspec/driver/mixins/_csv_writer.py +91 -0
  28. sqlspec/driver/mixins/_pipeline.py +38 -49
  29. sqlspec/driver/mixins/_result_utils.py +27 -9
  30. sqlspec/driver/mixins/_storage.py +149 -216
  31. sqlspec/driver/mixins/_type_coercion.py +3 -4
  32. sqlspec/driver/parameters.py +138 -0
  33. sqlspec/exceptions.py +10 -2
  34. sqlspec/extensions/aiosql/adapter.py +0 -10
  35. sqlspec/extensions/litestar/handlers.py +0 -1
  36. sqlspec/extensions/litestar/plugin.py +0 -3
  37. sqlspec/extensions/litestar/providers.py +0 -14
  38. sqlspec/loader.py +31 -118
  39. sqlspec/protocols.py +542 -0
  40. sqlspec/service/__init__.py +3 -2
  41. sqlspec/service/_util.py +147 -0
  42. sqlspec/service/base.py +1116 -9
  43. sqlspec/statement/builder/__init__.py +42 -32
  44. sqlspec/statement/builder/_ddl_utils.py +0 -10
  45. sqlspec/statement/builder/_parsing_utils.py +10 -4
  46. sqlspec/statement/builder/base.py +70 -23
  47. sqlspec/statement/builder/column.py +283 -0
  48. sqlspec/statement/builder/ddl.py +102 -65
  49. sqlspec/statement/builder/delete.py +23 -7
  50. sqlspec/statement/builder/insert.py +29 -15
  51. sqlspec/statement/builder/merge.py +4 -4
  52. sqlspec/statement/builder/mixins/_aggregate_functions.py +113 -14
  53. sqlspec/statement/builder/mixins/_common_table_expr.py +0 -1
  54. sqlspec/statement/builder/mixins/_delete_from.py +1 -1
  55. sqlspec/statement/builder/mixins/_from.py +10 -8
  56. sqlspec/statement/builder/mixins/_group_by.py +0 -1
  57. sqlspec/statement/builder/mixins/_insert_from_select.py +0 -1
  58. sqlspec/statement/builder/mixins/_insert_values.py +0 -2
  59. sqlspec/statement/builder/mixins/_join.py +20 -13
  60. sqlspec/statement/builder/mixins/_limit_offset.py +3 -3
  61. sqlspec/statement/builder/mixins/_merge_clauses.py +3 -4
  62. sqlspec/statement/builder/mixins/_order_by.py +2 -2
  63. sqlspec/statement/builder/mixins/_pivot.py +4 -7
  64. sqlspec/statement/builder/mixins/_select_columns.py +6 -5
  65. sqlspec/statement/builder/mixins/_unpivot.py +6 -9
  66. sqlspec/statement/builder/mixins/_update_from.py +2 -1
  67. sqlspec/statement/builder/mixins/_update_set.py +11 -8
  68. sqlspec/statement/builder/mixins/_where.py +61 -34
  69. sqlspec/statement/builder/select.py +32 -17
  70. sqlspec/statement/builder/update.py +25 -11
  71. sqlspec/statement/filters.py +39 -14
  72. sqlspec/statement/parameter_manager.py +220 -0
  73. sqlspec/statement/parameters.py +210 -79
  74. sqlspec/statement/pipelines/__init__.py +166 -23
  75. sqlspec/statement/pipelines/analyzers/_analyzer.py +22 -25
  76. sqlspec/statement/pipelines/context.py +35 -39
  77. sqlspec/statement/pipelines/transformers/__init__.py +2 -3
  78. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +19 -187
  79. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +667 -43
  80. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +76 -0
  81. sqlspec/statement/pipelines/validators/_dml_safety.py +33 -18
  82. sqlspec/statement/pipelines/validators/_parameter_style.py +87 -14
  83. sqlspec/statement/pipelines/validators/_performance.py +38 -23
  84. sqlspec/statement/pipelines/validators/_security.py +39 -62
  85. sqlspec/statement/result.py +37 -129
  86. sqlspec/statement/splitter.py +0 -12
  87. sqlspec/statement/sql.py +885 -379
  88. sqlspec/statement/sql_compiler.py +140 -0
  89. sqlspec/storage/__init__.py +10 -2
  90. sqlspec/storage/backends/fsspec.py +82 -35
  91. sqlspec/storage/backends/obstore.py +66 -49
  92. sqlspec/storage/capabilities.py +101 -0
  93. sqlspec/storage/registry.py +56 -83
  94. sqlspec/typing.py +6 -434
  95. sqlspec/utils/cached_property.py +25 -0
  96. sqlspec/utils/correlation.py +0 -2
  97. sqlspec/utils/logging.py +0 -6
  98. sqlspec/utils/sync_tools.py +0 -4
  99. sqlspec/utils/text.py +0 -5
  100. sqlspec/utils/type_guards.py +892 -0
  101. {sqlspec-0.12.1.dist-info → sqlspec-0.13.0.dist-info}/METADATA +1 -1
  102. sqlspec-0.13.0.dist-info/RECORD +150 -0
  103. sqlspec/statement/builder/protocols.py +0 -20
  104. sqlspec/statement/pipelines/base.py +0 -315
  105. sqlspec/statement/pipelines/result_types.py +0 -41
  106. sqlspec/statement/pipelines/transformers/_remove_comments.py +0 -66
  107. sqlspec/statement/pipelines/transformers/_remove_hints.py +0 -81
  108. sqlspec/statement/pipelines/validators/base.py +0 -67
  109. sqlspec/storage/protocol.py +0 -170
  110. sqlspec-0.12.1.dist-info/RECORD +0 -145
  111. {sqlspec-0.12.1.dist-info → sqlspec-0.13.0.dist-info}/WHEEL +0 -0
  112. {sqlspec-0.12.1.dist-info → sqlspec-0.13.0.dist-info}/licenses/LICENSE +0 -0
  113. {sqlspec-0.12.1.dist-info → sqlspec-0.13.0.dist-info}/licenses/NOTICE +0 -0
@@ -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, cast
6
+ from typing import TYPE_CHECKING, Any, Generic, Literal, Optional, Union
9
7
 
10
- from typing_extensions import TypedDict, TypeVar
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", "DMLResultDict", "SQLResult", "ScriptResultDict", "SelectResultDict", "StatementResult")
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 # Total rows if pagination/limit was involved
158
- has_more: bool = False # For pagination
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
- op_type_upper = self.operation_type.upper()
193
-
194
- # For script execution, check if there are no errors and all statements succeeded
195
- if op_type_upper == "SCRIPT" or self.statement_results:
196
- return len(self.errors) == 0 and self.total_statements == self.successful_statements
197
-
198
- if op_type_upper == "SELECT":
199
- # For SELECT, success means we got some data container and rows_affected is not negative
200
- data_success = self.data is not None
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
- return False # Should not happen if operation_type is one of the above
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
- # For script execution, sum up rows affected from all statements
249
- total = 0
250
- for stmt_result in self.statement_results:
251
- if stmt_result.rows_affected is not None and stmt_result.rows_affected >= 0:
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 cast("RowT", self.data[index])
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 cast("list[RowT]", self.data)
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 cast("RowT", self.data[0])
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 cast("RowT", self.data[0])
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 = cast("str", next(iter(row.keys())))
406
- return cast("Any", row[first_key])
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 cast("Any", row[0])
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 cast("Any", row[0])
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 Arrow operation was successful.
368
+ """Check if the operation was successful.
461
369
 
462
370
  Returns:
463
- True if the operation completed successfully and has valid Arrow data.
371
+ True if Arrow table data is available, False otherwise.
464
372
  """
465
- return bool(self.data)
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.
@@ -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