sqlspec 0.16.1__cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.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 (148) hide show
  1. 51ff5a9eadfdefd49f98__mypyc.cpython-312-aarch64-linux-gnu.so +0 -0
  2. sqlspec/__init__.py +92 -0
  3. sqlspec/__main__.py +12 -0
  4. sqlspec/__metadata__.py +14 -0
  5. sqlspec/_serialization.py +77 -0
  6. sqlspec/_sql.py +1780 -0
  7. sqlspec/_typing.py +680 -0
  8. sqlspec/adapters/__init__.py +0 -0
  9. sqlspec/adapters/adbc/__init__.py +5 -0
  10. sqlspec/adapters/adbc/_types.py +12 -0
  11. sqlspec/adapters/adbc/config.py +361 -0
  12. sqlspec/adapters/adbc/driver.py +512 -0
  13. sqlspec/adapters/aiosqlite/__init__.py +19 -0
  14. sqlspec/adapters/aiosqlite/_types.py +13 -0
  15. sqlspec/adapters/aiosqlite/config.py +253 -0
  16. sqlspec/adapters/aiosqlite/driver.py +248 -0
  17. sqlspec/adapters/asyncmy/__init__.py +19 -0
  18. sqlspec/adapters/asyncmy/_types.py +12 -0
  19. sqlspec/adapters/asyncmy/config.py +180 -0
  20. sqlspec/adapters/asyncmy/driver.py +274 -0
  21. sqlspec/adapters/asyncpg/__init__.py +21 -0
  22. sqlspec/adapters/asyncpg/_types.py +17 -0
  23. sqlspec/adapters/asyncpg/config.py +229 -0
  24. sqlspec/adapters/asyncpg/driver.py +344 -0
  25. sqlspec/adapters/bigquery/__init__.py +18 -0
  26. sqlspec/adapters/bigquery/_types.py +12 -0
  27. sqlspec/adapters/bigquery/config.py +298 -0
  28. sqlspec/adapters/bigquery/driver.py +558 -0
  29. sqlspec/adapters/duckdb/__init__.py +22 -0
  30. sqlspec/adapters/duckdb/_types.py +12 -0
  31. sqlspec/adapters/duckdb/config.py +504 -0
  32. sqlspec/adapters/duckdb/driver.py +368 -0
  33. sqlspec/adapters/oracledb/__init__.py +32 -0
  34. sqlspec/adapters/oracledb/_types.py +14 -0
  35. sqlspec/adapters/oracledb/config.py +317 -0
  36. sqlspec/adapters/oracledb/driver.py +538 -0
  37. sqlspec/adapters/psqlpy/__init__.py +16 -0
  38. sqlspec/adapters/psqlpy/_types.py +11 -0
  39. sqlspec/adapters/psqlpy/config.py +214 -0
  40. sqlspec/adapters/psqlpy/driver.py +530 -0
  41. sqlspec/adapters/psycopg/__init__.py +32 -0
  42. sqlspec/adapters/psycopg/_types.py +17 -0
  43. sqlspec/adapters/psycopg/config.py +426 -0
  44. sqlspec/adapters/psycopg/driver.py +796 -0
  45. sqlspec/adapters/sqlite/__init__.py +15 -0
  46. sqlspec/adapters/sqlite/_types.py +11 -0
  47. sqlspec/adapters/sqlite/config.py +240 -0
  48. sqlspec/adapters/sqlite/driver.py +294 -0
  49. sqlspec/base.py +571 -0
  50. sqlspec/builder/__init__.py +62 -0
  51. sqlspec/builder/_base.py +473 -0
  52. sqlspec/builder/_column.py +320 -0
  53. sqlspec/builder/_ddl.py +1346 -0
  54. sqlspec/builder/_ddl_utils.py +103 -0
  55. sqlspec/builder/_delete.py +76 -0
  56. sqlspec/builder/_insert.py +256 -0
  57. sqlspec/builder/_merge.py +71 -0
  58. sqlspec/builder/_parsing_utils.py +140 -0
  59. sqlspec/builder/_select.py +170 -0
  60. sqlspec/builder/_update.py +188 -0
  61. sqlspec/builder/mixins/__init__.py +55 -0
  62. sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
  63. sqlspec/builder/mixins/_delete_operations.py +41 -0
  64. sqlspec/builder/mixins/_insert_operations.py +244 -0
  65. sqlspec/builder/mixins/_join_operations.py +122 -0
  66. sqlspec/builder/mixins/_merge_operations.py +476 -0
  67. sqlspec/builder/mixins/_order_limit_operations.py +135 -0
  68. sqlspec/builder/mixins/_pivot_operations.py +153 -0
  69. sqlspec/builder/mixins/_select_operations.py +603 -0
  70. sqlspec/builder/mixins/_update_operations.py +187 -0
  71. sqlspec/builder/mixins/_where_clause.py +621 -0
  72. sqlspec/cli.py +247 -0
  73. sqlspec/config.py +395 -0
  74. sqlspec/core/__init__.py +63 -0
  75. sqlspec/core/cache.cpython-312-aarch64-linux-gnu.so +0 -0
  76. sqlspec/core/cache.py +871 -0
  77. sqlspec/core/compiler.cpython-312-aarch64-linux-gnu.so +0 -0
  78. sqlspec/core/compiler.py +417 -0
  79. sqlspec/core/filters.cpython-312-aarch64-linux-gnu.so +0 -0
  80. sqlspec/core/filters.py +830 -0
  81. sqlspec/core/hashing.cpython-312-aarch64-linux-gnu.so +0 -0
  82. sqlspec/core/hashing.py +310 -0
  83. sqlspec/core/parameters.cpython-312-aarch64-linux-gnu.so +0 -0
  84. sqlspec/core/parameters.py +1237 -0
  85. sqlspec/core/result.cpython-312-aarch64-linux-gnu.so +0 -0
  86. sqlspec/core/result.py +677 -0
  87. sqlspec/core/splitter.cpython-312-aarch64-linux-gnu.so +0 -0
  88. sqlspec/core/splitter.py +819 -0
  89. sqlspec/core/statement.cpython-312-aarch64-linux-gnu.so +0 -0
  90. sqlspec/core/statement.py +676 -0
  91. sqlspec/driver/__init__.py +19 -0
  92. sqlspec/driver/_async.py +502 -0
  93. sqlspec/driver/_common.py +631 -0
  94. sqlspec/driver/_sync.py +503 -0
  95. sqlspec/driver/mixins/__init__.py +6 -0
  96. sqlspec/driver/mixins/_result_tools.py +193 -0
  97. sqlspec/driver/mixins/_sql_translator.py +86 -0
  98. sqlspec/exceptions.py +193 -0
  99. sqlspec/extensions/__init__.py +0 -0
  100. sqlspec/extensions/aiosql/__init__.py +10 -0
  101. sqlspec/extensions/aiosql/adapter.py +461 -0
  102. sqlspec/extensions/litestar/__init__.py +6 -0
  103. sqlspec/extensions/litestar/_utils.py +52 -0
  104. sqlspec/extensions/litestar/cli.py +48 -0
  105. sqlspec/extensions/litestar/config.py +92 -0
  106. sqlspec/extensions/litestar/handlers.py +260 -0
  107. sqlspec/extensions/litestar/plugin.py +145 -0
  108. sqlspec/extensions/litestar/providers.py +454 -0
  109. sqlspec/loader.cpython-312-aarch64-linux-gnu.so +0 -0
  110. sqlspec/loader.py +760 -0
  111. sqlspec/migrations/__init__.py +35 -0
  112. sqlspec/migrations/base.py +414 -0
  113. sqlspec/migrations/commands.py +443 -0
  114. sqlspec/migrations/loaders.py +402 -0
  115. sqlspec/migrations/runner.py +213 -0
  116. sqlspec/migrations/tracker.py +140 -0
  117. sqlspec/migrations/utils.py +129 -0
  118. sqlspec/protocols.py +407 -0
  119. sqlspec/py.typed +0 -0
  120. sqlspec/storage/__init__.py +23 -0
  121. sqlspec/storage/backends/__init__.py +0 -0
  122. sqlspec/storage/backends/base.py +163 -0
  123. sqlspec/storage/backends/fsspec.py +386 -0
  124. sqlspec/storage/backends/obstore.py +459 -0
  125. sqlspec/storage/capabilities.py +102 -0
  126. sqlspec/storage/registry.py +239 -0
  127. sqlspec/typing.py +299 -0
  128. sqlspec/utils/__init__.py +3 -0
  129. sqlspec/utils/correlation.py +150 -0
  130. sqlspec/utils/deprecation.py +106 -0
  131. sqlspec/utils/fixtures.cpython-312-aarch64-linux-gnu.so +0 -0
  132. sqlspec/utils/fixtures.py +58 -0
  133. sqlspec/utils/logging.py +127 -0
  134. sqlspec/utils/module_loader.py +89 -0
  135. sqlspec/utils/serializers.py +4 -0
  136. sqlspec/utils/singleton.py +32 -0
  137. sqlspec/utils/sync_tools.cpython-312-aarch64-linux-gnu.so +0 -0
  138. sqlspec/utils/sync_tools.py +237 -0
  139. sqlspec/utils/text.cpython-312-aarch64-linux-gnu.so +0 -0
  140. sqlspec/utils/text.py +96 -0
  141. sqlspec/utils/type_guards.cpython-312-aarch64-linux-gnu.so +0 -0
  142. sqlspec/utils/type_guards.py +1139 -0
  143. sqlspec-0.16.1.dist-info/METADATA +365 -0
  144. sqlspec-0.16.1.dist-info/RECORD +148 -0
  145. sqlspec-0.16.1.dist-info/WHEEL +7 -0
  146. sqlspec-0.16.1.dist-info/entry_points.txt +2 -0
  147. sqlspec-0.16.1.dist-info/licenses/LICENSE +21 -0
  148. sqlspec-0.16.1.dist-info/licenses/NOTICE +29 -0
sqlspec/core/result.py ADDED
@@ -0,0 +1,677 @@
1
+ """SQL result classes for query execution results.
2
+
3
+ This module provides result classes for handling SQL query execution results
4
+ including regular results and Apache Arrow format results.
5
+
6
+ Components:
7
+ - StatementResult: Abstract base class for SQL results
8
+ - SQLResult: Main implementation for regular results
9
+ - ArrowResult: Arrow-based results for high-performance data interchange
10
+
11
+ Features:
12
+ - Consistent interface across all result types
13
+ - Support for both regular and Arrow format results
14
+ - Comprehensive result metadata and statistics
15
+ - Iterator support for result rows
16
+ """
17
+
18
+ from abc import ABC, abstractmethod
19
+ from typing import TYPE_CHECKING, Any, Optional, Union, cast
20
+
21
+ from mypy_extensions import mypyc_attr
22
+ from typing_extensions import TypeVar
23
+
24
+ from sqlspec.core.compiler import OperationType
25
+
26
+ if TYPE_CHECKING:
27
+ from collections.abc import Iterator
28
+
29
+ from sqlspec.core.statement import SQL
30
+
31
+
32
+ __all__ = ("ArrowResult", "SQLResult", "StatementResult")
33
+
34
+ T = TypeVar("T")
35
+
36
+
37
+ @mypyc_attr(allow_interpreted_subclasses=True)
38
+ class StatementResult(ABC):
39
+ """Base class for SQL statement execution results.
40
+
41
+ Provides a common interface for handling different types of SQL operation
42
+ results. Subclasses implement specific behavior for SELECT, INSERT/UPDATE/DELETE,
43
+ and script operations.
44
+
45
+ Args:
46
+ statement: The original SQL statement that was executed.
47
+ data: The result data from the operation (type varies by subclass).
48
+ rows_affected: Number of rows affected by the operation (if applicable).
49
+ last_inserted_id: Last inserted ID (if applicable).
50
+ execution_time: Time taken to execute the statement in seconds.
51
+ metadata: Additional metadata about the operation.
52
+ """
53
+
54
+ __slots__ = ("data", "execution_time", "last_inserted_id", "metadata", "rows_affected", "statement")
55
+
56
+ def __init__(
57
+ self,
58
+ statement: "SQL",
59
+ data: Any = None,
60
+ rows_affected: int = 0,
61
+ last_inserted_id: Optional[Union[int, str]] = None,
62
+ execution_time: Optional[float] = None,
63
+ metadata: Optional["dict[str, Any]"] = None,
64
+ ) -> None:
65
+ """Initialize statement result.
66
+
67
+ Args:
68
+ statement: The original SQL statement that was executed.
69
+ data: The result data from the operation.
70
+ rows_affected: Number of rows affected by the operation.
71
+ last_inserted_id: Last inserted ID from the operation.
72
+ execution_time: Time taken to execute the statement in seconds.
73
+ metadata: Additional metadata about the operation.
74
+ """
75
+ self.statement = statement
76
+ self.data = data
77
+ self.rows_affected = rows_affected
78
+ self.last_inserted_id = last_inserted_id
79
+ self.execution_time = execution_time
80
+ self.metadata = metadata if metadata is not None else {}
81
+
82
+ @abstractmethod
83
+ def is_success(self) -> bool:
84
+ """Check if the operation was successful.
85
+
86
+ Returns:
87
+ True if the operation completed successfully, False otherwise.
88
+ """
89
+
90
+ @abstractmethod
91
+ def get_data(self) -> "Any":
92
+ """Get the processed data from the result.
93
+
94
+ Returns:
95
+ The processed result data in an appropriate format.
96
+ """
97
+
98
+ def get_metadata(self, key: str, default: Any = None) -> Any:
99
+ """Get metadata value by key.
100
+
101
+ Args:
102
+ key: The metadata key to retrieve.
103
+ default: Default value if key is not found.
104
+
105
+ Returns:
106
+ The metadata value or default.
107
+ """
108
+ return self.metadata.get(key, default)
109
+
110
+ def set_metadata(self, key: str, value: Any) -> None:
111
+ """Set metadata value by key.
112
+
113
+ Args:
114
+ key: The metadata key to set.
115
+ value: The value to set.
116
+ """
117
+ self.metadata[key] = value
118
+
119
+ @property
120
+ def operation_type(self) -> OperationType:
121
+ """Get operation type from the statement.
122
+
123
+ Returns:
124
+ The type of SQL operation that produced this result.
125
+ """
126
+ if hasattr(self.statement, "operation_type"):
127
+ return cast("OperationType", self.statement.operation_type)
128
+ return "SELECT"
129
+
130
+
131
+ @mypyc_attr(allow_interpreted_subclasses=True)
132
+ class SQLResult(StatementResult):
133
+ """Result class for SQL operations that return rows or affect rows.
134
+
135
+ Handles SELECT, INSERT, UPDATE, DELETE operations. For DML operations with
136
+ RETURNING clauses, the returned data will be in `self.data`. The `operation_type`
137
+ attribute helps distinguish the nature of the operation.
138
+
139
+ For script execution, this class also tracks multiple statement results and errors.
140
+ """
141
+
142
+ __slots__ = (
143
+ "_operation_type",
144
+ "column_names",
145
+ "error",
146
+ "errors",
147
+ "has_more",
148
+ "inserted_ids",
149
+ "operation_index",
150
+ "parameters",
151
+ "statement_results",
152
+ "successful_statements",
153
+ "total_count",
154
+ "total_statements",
155
+ )
156
+
157
+ def __init__(
158
+ self,
159
+ statement: "SQL",
160
+ data: Optional[list[dict[str, Any]]] = None,
161
+ rows_affected: int = 0,
162
+ last_inserted_id: Optional[Union[int, str]] = None,
163
+ execution_time: Optional[float] = None,
164
+ metadata: Optional["dict[str, Any]"] = None,
165
+ error: Optional[Exception] = None,
166
+ operation_type: OperationType = "SELECT",
167
+ operation_index: Optional[int] = None,
168
+ parameters: Optional[Any] = None,
169
+ column_names: Optional["list[str]"] = None,
170
+ total_count: Optional[int] = None,
171
+ has_more: bool = False,
172
+ inserted_ids: Optional["list[Union[int, str]]"] = None,
173
+ statement_results: Optional["list[SQLResult]"] = None,
174
+ errors: Optional["list[str]"] = None,
175
+ total_statements: int = 0,
176
+ successful_statements: int = 0,
177
+ ) -> None:
178
+ """Initialize SQL result."""
179
+ super().__init__(
180
+ statement=statement,
181
+ data=data,
182
+ rows_affected=rows_affected,
183
+ last_inserted_id=last_inserted_id,
184
+ execution_time=execution_time,
185
+ metadata=metadata,
186
+ )
187
+ self.error = error
188
+ self._operation_type = operation_type
189
+ self.operation_index = operation_index
190
+ self.parameters = parameters
191
+
192
+ # Optimize list initialization to avoid unnecessary object creation
193
+ self.column_names = column_names or []
194
+ self.total_count = total_count
195
+ self.has_more = has_more
196
+ self.inserted_ids = inserted_ids or []
197
+ self.statement_results = statement_results or []
198
+ self.errors = errors or []
199
+ self.total_statements = total_statements
200
+ self.successful_statements = successful_statements
201
+
202
+ # Optimize column name extraction and count calculation
203
+ if not self.column_names and data and len(data) > 0:
204
+ self.column_names = list(data[0].keys())
205
+ if self.total_count is None:
206
+ self.total_count = len(data) if data is not None else 0
207
+
208
+ @property
209
+ def operation_type(self) -> "OperationType":
210
+ """Get operation type for this result."""
211
+ return cast("OperationType", self._operation_type) # type: ignore[redundant-cast]
212
+
213
+ def get_metadata(self, key: str, default: Any = None) -> Any:
214
+ """Get metadata value by key.
215
+
216
+ Args:
217
+ key: The metadata key to retrieve.
218
+ default: Default value if key is not found.
219
+
220
+ Returns:
221
+ The metadata value or default.
222
+ """
223
+ return self.metadata.get(key, default)
224
+
225
+ def set_metadata(self, key: str, value: Any) -> None:
226
+ """Set metadata value by key.
227
+
228
+ Args:
229
+ key: The metadata key to set.
230
+ value: The value to set.
231
+ """
232
+ self.metadata[key] = value
233
+
234
+ def is_success(self) -> bool:
235
+ """Check if the operation was successful.
236
+
237
+ Returns:
238
+ True if operation was successful, False otherwise.
239
+ """
240
+ op_type = self.operation_type.upper()
241
+
242
+ if op_type == "SCRIPT" or self.statement_results:
243
+ return not self.errors and self.total_statements == self.successful_statements
244
+
245
+ if op_type == "SELECT":
246
+ return self.data is not None and self.rows_affected >= 0
247
+
248
+ if op_type in {"INSERT", "UPDATE", "DELETE", "EXECUTE"}:
249
+ return self.rows_affected >= 0
250
+
251
+ return False
252
+
253
+ def get_data(self) -> "list[dict[str,Any]]":
254
+ """Get the data from the result.
255
+
256
+ For regular operations, returns the list of rows.
257
+ For script operations, returns a summary dictionary.
258
+
259
+ Returns:
260
+ List of result rows or script summary.
261
+ """
262
+ op_type_upper = self.operation_type.upper()
263
+ if op_type_upper == "SCRIPT":
264
+ # Cache calculation to avoid redundant work
265
+ failed_statements = self.total_statements - self.successful_statements
266
+ return [
267
+ {
268
+ "total_statements": self.total_statements,
269
+ "successful_statements": self.successful_statements,
270
+ "failed_statements": failed_statements,
271
+ "errors": self.errors,
272
+ "statement_results": self.statement_results,
273
+ "total_rows_affected": self.get_total_rows_affected(),
274
+ }
275
+ ]
276
+ return self.data or []
277
+
278
+ def add_statement_result(self, result: "SQLResult") -> None:
279
+ """Add a statement result to the script execution results.
280
+
281
+ Args:
282
+ result: Statement result to add.
283
+ """
284
+ self.statement_results.append(result)
285
+ self.total_statements += 1
286
+ if result.is_success():
287
+ self.successful_statements += 1
288
+
289
+ def get_total_rows_affected(self) -> int:
290
+ """Get the total number of rows affected across all statements.
291
+
292
+ Returns:
293
+ Total rows affected.
294
+ """
295
+ if self.statement_results:
296
+ total = 0
297
+ for stmt in self.statement_results:
298
+ if stmt.rows_affected and stmt.rows_affected > 0:
299
+ total += stmt.rows_affected
300
+ return total
301
+ return self.rows_affected if self.rows_affected and self.rows_affected > 0 else 0
302
+
303
+ @property
304
+ def num_rows(self) -> int:
305
+ """Get the number of rows affected (alias for get_total_rows_affected).
306
+
307
+ Returns:
308
+ Total rows affected.
309
+ """
310
+ return self.get_total_rows_affected()
311
+
312
+ @property
313
+ def num_columns(self) -> int:
314
+ """Get the number of columns in the result data.
315
+
316
+ Returns:
317
+ Number of columns.
318
+ """
319
+ return len(self.column_names) if self.column_names else 0
320
+
321
+ def get_first(self) -> "Optional[dict[str, Any]]":
322
+ """Get the first row from the result, if any.
323
+
324
+ Returns:
325
+ First row or None if no data.
326
+ """
327
+ return self.data[0] if self.data else None
328
+
329
+ def get_count(self) -> int:
330
+ """Get the number of rows in the current result set (e.g., a page of data).
331
+
332
+ Returns:
333
+ Number of rows in current result set.
334
+ """
335
+ return len(self.data) if self.data is not None else 0
336
+
337
+ def is_empty(self) -> bool:
338
+ """Check if the result set (self.data) is empty.
339
+
340
+ Returns:
341
+ True if result set is empty.
342
+ """
343
+ return not self.data if self.data is not None else True
344
+
345
+ def get_affected_count(self) -> int:
346
+ """Get the number of rows affected by a DML operation.
347
+
348
+ Returns:
349
+ Number of affected rows.
350
+ """
351
+ return self.rows_affected or 0
352
+
353
+ def was_inserted(self) -> bool:
354
+ """Check if this was an INSERT operation.
355
+
356
+ Returns:
357
+ True if INSERT operation.
358
+ """
359
+ return self.operation_type.upper() == "INSERT"
360
+
361
+ def was_updated(self) -> bool:
362
+ """Check if this was an UPDATE operation.
363
+
364
+ Returns:
365
+ True if UPDATE operation.
366
+ """
367
+ return self.operation_type.upper() == "UPDATE"
368
+
369
+ def was_deleted(self) -> bool:
370
+ """Check if this was a DELETE operation.
371
+
372
+ Returns:
373
+ True if DELETE operation.
374
+ """
375
+ return self.operation_type.upper() == "DELETE"
376
+
377
+ def __len__(self) -> int:
378
+ """Get the number of rows in the result set.
379
+
380
+ Returns:
381
+ Number of rows in the data.
382
+ """
383
+ return len(self.data) if self.data is not None else 0
384
+
385
+ def __getitem__(self, index: int) -> "dict[str, Any]":
386
+ """Get a row by index.
387
+
388
+ Args:
389
+ index: Row index
390
+
391
+ Returns:
392
+ The row at the specified index
393
+ """
394
+ if self.data is None:
395
+ msg = "No data available"
396
+ raise IndexError(msg)
397
+ return cast("dict[str, Any]", self.data[index])
398
+
399
+ def __iter__(self) -> "Iterator[dict[str, Any]]":
400
+ """Iterate over the rows in the result.
401
+
402
+ Returns:
403
+ Iterator that yields each row as a dictionary
404
+ """
405
+ return iter(self.data or [])
406
+
407
+ def all(self) -> list[dict[str, Any]]:
408
+ """Return all rows as a list.
409
+
410
+ Returns:
411
+ List of all rows in the result
412
+ """
413
+ return self.data or []
414
+
415
+ def one(self) -> "dict[str, Any]":
416
+ """Return exactly one row.
417
+
418
+ Returns:
419
+ The single row
420
+
421
+ Raises:
422
+ ValueError: If no results or more than one result
423
+ """
424
+ if not self.data:
425
+ msg = "No result found, exactly one row expected"
426
+ raise ValueError(msg)
427
+
428
+ data_len = len(self.data)
429
+ if data_len == 0:
430
+ msg = "No result found, exactly one row expected"
431
+ raise ValueError(msg)
432
+ if data_len > 1:
433
+ msg = f"Multiple results found ({data_len}), exactly one row expected"
434
+ raise ValueError(msg)
435
+
436
+ return cast("dict[str, Any]", self.data[0])
437
+
438
+ def one_or_none(self) -> "Optional[dict[str, Any]]":
439
+ """Return at most one row.
440
+
441
+ Returns:
442
+ The single row or None if no results
443
+
444
+ Raises:
445
+ ValueError: If more than one result
446
+ """
447
+ if not self.data:
448
+ return None
449
+
450
+ data_len = len(self.data)
451
+ if data_len == 0:
452
+ return None
453
+ if data_len > 1:
454
+ msg = f"Multiple results found ({data_len}), at most one row expected"
455
+ raise ValueError(msg)
456
+
457
+ return cast("dict[str, Any]", self.data[0])
458
+
459
+ def scalar(self) -> Any:
460
+ """Return the first column of the first row.
461
+
462
+ Returns:
463
+ The scalar value from first column of first row
464
+ """
465
+ row = self.one()
466
+ return next(iter(row.values()))
467
+
468
+ def scalar_or_none(self) -> Any:
469
+ """Return the first column of the first row, or None if no results.
470
+
471
+ Returns:
472
+ The scalar value from first column of first row, or None
473
+ """
474
+ row = self.one_or_none()
475
+ if row is None:
476
+ return None
477
+
478
+ return next(iter(row.values()))
479
+
480
+
481
+ @mypyc_attr(allow_interpreted_subclasses=True)
482
+ class ArrowResult(StatementResult):
483
+ """Result class for SQL operations that return Apache Arrow data.
484
+
485
+ This class is used when database drivers support returning results as
486
+ Apache Arrow format for high-performance data interchange, especially
487
+ useful for analytics workloads and data science applications.
488
+
489
+ Performance Features:
490
+ - __slots__ optimization for memory efficiency
491
+ - Direct Arrow table access without intermediate copying
492
+ - Cached property evaluation for table metadata
493
+ - MyPyC compatibility for critical operations
494
+
495
+ Compatibility Features:
496
+ - Complete interface preservation with existing ArrowResult
497
+ - Same method signatures and behavior
498
+ - Same error handling and exceptions
499
+ - Identical Arrow table integration
500
+
501
+ Args:
502
+ statement: The original SQL statement that was executed.
503
+ data: The Apache Arrow Table containing the result data.
504
+ schema: Optional Arrow schema information.
505
+ """
506
+
507
+ __slots__ = ("schema",)
508
+
509
+ def __init__(
510
+ self,
511
+ statement: "SQL",
512
+ data: Any,
513
+ rows_affected: int = 0,
514
+ last_inserted_id: Optional[Union[int, str]] = None,
515
+ execution_time: Optional[float] = None,
516
+ metadata: Optional["dict[str, Any]"] = None,
517
+ schema: Optional["dict[str, Any]"] = None,
518
+ ) -> None:
519
+ """Initialize Arrow result with enhanced performance.
520
+
521
+ Args:
522
+ statement: The original SQL statement that was executed.
523
+ data: The Apache Arrow Table containing the result data.
524
+ rows_affected: Number of rows affected by the operation.
525
+ last_inserted_id: Last inserted ID (if applicable).
526
+ execution_time: Time taken to execute the statement in seconds.
527
+ metadata: Additional metadata about the operation.
528
+ schema: Optional Arrow schema information.
529
+ """
530
+ super().__init__(
531
+ statement=statement,
532
+ data=data,
533
+ rows_affected=rows_affected,
534
+ last_inserted_id=last_inserted_id,
535
+ execution_time=execution_time,
536
+ metadata=metadata,
537
+ )
538
+
539
+ self.schema = schema
540
+
541
+ def is_success(self) -> bool:
542
+ """Check if the operation was successful.
543
+
544
+ Returns:
545
+ True if Arrow table data is available, False otherwise.
546
+ """
547
+ return self.data is not None
548
+
549
+ def get_data(self) -> Any:
550
+ """Get the Apache Arrow Table from the result.
551
+
552
+ Returns:
553
+ The Arrow table containing the result data.
554
+
555
+ Raises:
556
+ ValueError: If no Arrow table is available.
557
+ """
558
+ if self.data is None:
559
+ msg = "No Arrow table available for this result"
560
+ raise ValueError(msg)
561
+ return self.data
562
+
563
+ @property
564
+ def column_names(self) -> "list[str]":
565
+ """Get the column names from the Arrow table.
566
+
567
+ Returns:
568
+ List of column names.
569
+
570
+ Raises:
571
+ ValueError: If no Arrow table is available.
572
+ """
573
+ if self.data is None:
574
+ msg = "No Arrow table available"
575
+ raise ValueError(msg)
576
+
577
+ return cast("list[str]", self.data.column_names)
578
+
579
+ @property
580
+ def num_rows(self) -> int:
581
+ """Get the number of rows in the Arrow table.
582
+
583
+ Returns:
584
+ Number of rows.
585
+
586
+ Raises:
587
+ ValueError: If no Arrow table is available.
588
+ """
589
+ if self.data is None:
590
+ msg = "No Arrow table available"
591
+ raise ValueError(msg)
592
+
593
+ return cast("int", self.data.num_rows)
594
+
595
+ @property
596
+ def num_columns(self) -> int:
597
+ """Get the number of columns in the Arrow table.
598
+
599
+ Returns:
600
+ Number of columns.
601
+
602
+ Raises:
603
+ ValueError: If no Arrow table is available.
604
+ """
605
+ if self.data is None:
606
+ msg = "No Arrow table available"
607
+ raise ValueError(msg)
608
+
609
+ return cast("int", self.data.num_columns)
610
+
611
+
612
+ def create_sql_result(
613
+ statement: "SQL",
614
+ data: Optional[list[dict[str, Any]]] = None,
615
+ rows_affected: int = 0,
616
+ last_inserted_id: Optional[Union[int, str]] = None,
617
+ execution_time: Optional[float] = None,
618
+ metadata: Optional["dict[str, Any]"] = None,
619
+ **kwargs: Any,
620
+ ) -> SQLResult:
621
+ """Create SQLResult instance.
622
+
623
+ Args:
624
+ statement: The SQL statement that produced this result.
625
+ data: Result data from query execution.
626
+ rows_affected: Number of rows affected by the operation.
627
+ last_inserted_id: Last inserted ID (for INSERT operations).
628
+ execution_time: Execution time in seconds.
629
+ metadata: Additional metadata about the result.
630
+ **kwargs: Additional arguments for SQLResult initialization.
631
+
632
+ Returns:
633
+ SQLResult instance.
634
+ """
635
+ return SQLResult(
636
+ statement=statement,
637
+ data=data,
638
+ rows_affected=rows_affected,
639
+ last_inserted_id=last_inserted_id,
640
+ execution_time=execution_time,
641
+ metadata=metadata,
642
+ **kwargs,
643
+ )
644
+
645
+
646
+ def create_arrow_result(
647
+ statement: "SQL",
648
+ data: Any,
649
+ rows_affected: int = 0,
650
+ last_inserted_id: Optional[Union[int, str]] = None,
651
+ execution_time: Optional[float] = None,
652
+ metadata: Optional["dict[str, Any]"] = None,
653
+ schema: Optional["dict[str, Any]"] = None,
654
+ ) -> ArrowResult:
655
+ """Create ArrowResult instance.
656
+
657
+ Args:
658
+ statement: The SQL statement that produced this result.
659
+ data: Arrow-based result data.
660
+ rows_affected: Number of rows affected by the operation.
661
+ last_inserted_id: Last inserted ID (for INSERT operations).
662
+ execution_time: Execution time in seconds.
663
+ metadata: Additional metadata about the result.
664
+ schema: Optional Arrow schema information.
665
+
666
+ Returns:
667
+ ArrowResult instance.
668
+ """
669
+ return ArrowResult(
670
+ statement=statement,
671
+ data=data,
672
+ rows_affected=rows_affected,
673
+ last_inserted_id=last_inserted_id,
674
+ execution_time=execution_time,
675
+ metadata=metadata,
676
+ schema=schema,
677
+ )