Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,11 @@ v0.63.0 - Transactions, table fixtures, SQL fragments, storage, and kwargs param
probe branches.
(`#782 <https://github.com/litestar-org/sqlspec/pull/782>`_)

* Driver execution methods (:meth:`~sqlspec.driver.SyncDriverAdapterBase.execute`,
:meth:`~sqlspec.driver.SyncDriverAdapterBase.select`, etc.) enforce keyword argument parameter passing
(``execute(sql, a=1, b=2)`` or ``execute(sql, **params)``). Passing positional dictionary literals
is prohibited across documentation, examples, and internal extensions to take advantage of the driver
fast-path parameter dispatch.
* Docs, examples, and built-in extensions now pass named query values as keyword arguments
(``execute(sql, a=1, b=2)`` or ``execute(sql, **params)``). Drivers still accept a dict, list,
or tuple as a positional argument. Existing calls need no changes.
``execute_many`` still accepts a collection of rows.
(`#769 <https://github.com/litestar-org/sqlspec/pull/769>`_)

* The Litestar extension now requires ``litestar>=2.23.0``.

Expand All @@ -212,11 +212,22 @@ v0.63.0 - Transactions, table fixtures, SQL fragments, storage, and kwargs param
consumption without intermediate tuple relays.
(`#771 <https://github.com/litestar-org/sqlspec/pull/771>`_)

* ``sql.values`` creates a :class:`~sqlspec.builder.Values` builder for parameterized bulk row lists rather than resolving as a column named ``values``. Use ``sql.column("values")`` to construct column expressions referencing that identifier.
* ``sql.values(...)`` creates a :class:`~sqlspec.builder.Values` builder that binds values for bulk row lists.
Use ``sql.column("values")`` to refer to a column named ``values``.
(`#779 <https://github.com/litestar-org/sqlspec/pull/779>`_)

* Removed unused private helpers and shared repeated code across builders, drivers, and migrations.
Loader, service, and ADK artifact modules now group public methods before private helpers.
Supported public APIs and query behavior stay the same.
(`#784 <https://github.com/litestar-org/sqlspec/pull/784>`_)

**Fixed:**

* Cached statements no longer retry SQL when a query or result conversion fails. This prevents
duplicate writes. Cached dict and record rows keep their values. With pymssql, statement stacks
leave the caller's open transaction in place.
(`#742 <https://github.com/litestar-org/sqlspec/pull/742>`_)

* Preserve JSON objects and arrays as individual query parameters after placeholder conversion,
instead of reinterpreting them as batches during parameter validation.

Expand Down
27 changes: 6 additions & 21 deletions sqlspec/adapters/adbc/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,23 @@
from sqlspec.adapters.adbc.core import (
apply_driver_features,
build_connection_config,
build_postgres_extension_probe_names,
detect_postgres_extensions,
get_statement_config,
is_postgres_dialect,
is_postgres_extension_active,
resolve_dialect_from_config,
resolve_dialect_name,
resolve_driver_connect_func,
resolve_postgres_extension_state,
resolve_runtime_statement_config,
)
from sqlspec.adapters.adbc.driver import AdbcDriver, AdbcExceptionHandler
from sqlspec.config import ExtensionConfigs, NoPoolSyncConfig
from sqlspec.core import StatementConfig
from sqlspec.core.capabilities import TypeCoercionCapabilities
from sqlspec.core.config_runtime import (
build_postgres_extension_probe_names,
is_postgres_extension_active,
resolve_postgres_extension_state,
resolve_runtime_statement_config,
)
from sqlspec.driver._sync import SyncPoolConnectionContext, SyncPoolSessionFactory
from sqlspec.exceptions import ImproperConfigurationError
from sqlspec.extensions.events import EventRuntimeHints
Expand Down Expand Up @@ -297,23 +299,6 @@ def create_connection(self) -> AdbcConnection:
msg = f"Could not configure connection using driver '{err_driver_name}'. Error: {e}"
raise ImproperConfigurationError(msg) from e

def _update_dialect_for_extensions(self) -> None:
"""Update statement_config dialect based on detected extensions.

Priority: paradedb > pg_textsearch > pgvector > postgres (default).
Only switches when current dialect is ``postgres``.
"""
current_dialect = self.statement_config.dialect or "postgres"
if current_dialect != "postgres":
return

if self._paradedb_available:
self.statement_config = self.statement_config.replace(dialect="paradedb")
elif self._pg_textsearch_available:
self.statement_config = self.statement_config.replace(dialect="pg_textsearch")
elif self._pgvector_available:
self.statement_config = self.statement_config.replace(dialect="pgvector")

@property
def pg_textsearch_available(self) -> bool:
"""Return True if the pg_textsearch extension is available."""
Expand Down
7 changes: 0 additions & 7 deletions sqlspec/adapters/adbc/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,19 +544,13 @@ def select_to_arrow(
arrow_schema=arrow_schema,
)

# Use ADBC cursor for native Arrow
with self.with_cursor(self.connection) as cursor, exc_handler:
if cursor is None:
msg = "Failed to create cursor"
raise DatabaseConnectionError(msg)

# Get compiled SQL and parameters
sql, driver_params = self._compiled_sql(prepared_statement, config)

# Execute query
cursor.execute(sql, driver_params or ())

# Fetch as Arrow table (zero-copy!)
arrow_table = cursor.fetch_arrow_table()

arrow_result = build_arrow_result_from_table(
Expand Down Expand Up @@ -594,7 +588,6 @@ def select_to_storage(
) -> "StorageBridgeJob":
"""Stream query results to storage via the Arrow fast path."""

_ = kwargs
self._require_capability("arrow_export_enabled")
arrow_result = self.select_to_arrow(statement, *parameters, statement_config=statement_config, **kwargs)
sync_pipeline = self._storage_pipeline()
Expand Down
10 changes: 6 additions & 4 deletions sqlspec/adapters/asyncpg/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,19 @@
from sqlspec.adapters.asyncpg.core import (
apply_driver_features,
build_connection_config,
build_postgres_extension_probe_names,
default_statement_config,
is_postgres_extension_active,
register_json_codecs,
register_pgvector_support,
resolve_postgres_extension_state,
resolve_runtime_statement_config,
)
from sqlspec.adapters.asyncpg.driver import AsyncpgDriver, AsyncpgExceptionHandler
from sqlspec.config import AsyncDatabaseConfig, ExtensionConfigs
from sqlspec.core.capabilities import TypeCoercionCapabilities
from sqlspec.core.config_runtime import (
build_postgres_extension_probe_names,
is_postgres_extension_active,
resolve_postgres_extension_state,
resolve_runtime_statement_config,
)
from sqlspec.driver._async import AsyncPoolConnectionContext, AsyncPoolSessionFactory
from sqlspec.exceptions import ImproperConfigurationError, MissingDependencyError
from sqlspec.extensions.events import EventRuntimeHints
Expand Down
5 changes: 0 additions & 5 deletions sqlspec/adapters/bigquery/adk/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from sqlspec.extensions.adk import BaseSyncADKStore, StoredEvent, StoredSession, normalize_session_list_options
from sqlspec.extensions.adk._config_utils import _adk_config_from_extension
from sqlspec.utils.serializers import from_json, to_json
from sqlspec.utils.uuids import uuid4

if TYPE_CHECKING:
from collections.abc import Iterable
Expand Down Expand Up @@ -647,10 +646,6 @@ def _decode_json(value: Any) -> "dict[str, Any] | None":
msg = f"Unsupported JSON column representation from BigQuery: {type(value).__name__}"
raise TypeError(msg)

@staticmethod
def _new_id() -> str:
return str(uuid4())


def _session_record_from_row(row: "dict[str, Any]") -> StoredSession:
return {
Expand Down
2 changes: 1 addition & 1 deletion sqlspec/adapters/cockroach_asyncpg/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
default_statement_config,
register_json_codecs,
register_pgvector_support,
resolve_runtime_statement_config,
)
from sqlspec.adapters.cockroach_asyncpg._typing import (
CockroachAsyncpgConnection,
Expand All @@ -22,6 +21,7 @@
from sqlspec.adapters.cockroach_asyncpg.driver import CockroachAsyncpgDriver, CockroachAsyncpgExceptionHandler
from sqlspec.config import AsyncDatabaseConfig, ExtensionConfigs
from sqlspec.core.capabilities import TypeCoercionCapabilities
from sqlspec.core.config_runtime import resolve_runtime_statement_config
from sqlspec.driver._async import AsyncPoolConnectionContext, AsyncPoolSessionFactory
from sqlspec.exceptions import ImproperConfigurationError
from sqlspec.extensions.events import EventRuntimeHints
Expand Down
2 changes: 1 addition & 1 deletion sqlspec/adapters/cockroach_psycopg/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
CockroachPsycopgSyncDriver,
CockroachPsycopgSyncExceptionHandler,
)
from sqlspec.adapters.psycopg.core import resolve_runtime_statement_config
from sqlspec.config import AsyncDatabaseConfig, ExtensionConfigs, SyncDatabaseConfig
from sqlspec.core.capabilities import TypeCoercionCapabilities
from sqlspec.core.config_runtime import resolve_runtime_statement_config
from sqlspec.driver._async import AsyncPoolConnectionContext, AsyncPoolSessionFactory
from sqlspec.driver._sync import SyncPoolConnectionContext, SyncPoolSessionFactory
from sqlspec.exceptions import ImproperConfigurationError
Expand Down
12 changes: 5 additions & 7 deletions sqlspec/adapters/psqlpy/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,16 @@
from typing_extensions import NotRequired

from sqlspec.adapters.psqlpy._typing import PsqlpyConnection, PsqlpyCursor, PsqlpySessionContext
from sqlspec.adapters.psqlpy.core import (
apply_driver_features,
build_connection_config,
from sqlspec.adapters.psqlpy.core import apply_driver_features, build_connection_config, default_statement_config
from sqlspec.adapters.psqlpy.driver import PsqlpyDriver, PsqlpyExceptionHandler
from sqlspec.config import AsyncDatabaseConfig, ExtensionConfigs
from sqlspec.core.capabilities import TypeCoercionCapabilities
from sqlspec.core.config_runtime import (
build_postgres_extension_probe_names,
default_statement_config,
is_postgres_extension_active,
resolve_postgres_extension_state,
resolve_runtime_statement_config,
)
from sqlspec.adapters.psqlpy.driver import PsqlpyDriver, PsqlpyExceptionHandler
from sqlspec.config import AsyncDatabaseConfig, ExtensionConfigs
from sqlspec.core.capabilities import TypeCoercionCapabilities
from sqlspec.driver._async import AsyncPoolConnectionContext, AsyncPoolSessionFactory
from sqlspec.extensions.events import EventRuntimeHints
from sqlspec.utils.config_tools import normalize_connection_config
Expand Down
15 changes: 7 additions & 8 deletions sqlspec/adapters/psycopg/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,7 @@
PsycopgSyncCursor,
PsycopgSyncSessionContext,
)
from sqlspec.adapters.psycopg.core import (
apply_driver_features,
build_postgres_extension_probe_names,
default_statement_config,
is_postgres_extension_active,
resolve_postgres_extension_state,
resolve_runtime_statement_config,
)
from sqlspec.adapters.psycopg.core import apply_driver_features, default_statement_config
from sqlspec.adapters.psycopg.driver import (
PsycopgAsyncDriver,
PsycopgAsyncExceptionHandler,
Expand All @@ -32,6 +25,12 @@
from sqlspec.adapters.psycopg.type_converter import register_pgvector_async, register_pgvector_sync
from sqlspec.config import AsyncDatabaseConfig, ExtensionConfigs, SyncDatabaseConfig
from sqlspec.core.capabilities import TypeCoercionCapabilities
from sqlspec.core.config_runtime import (
build_postgres_extension_probe_names,
is_postgres_extension_active,
resolve_postgres_extension_state,
resolve_runtime_statement_config,
)
from sqlspec.driver._async import AsyncPoolConnectionContext, AsyncPoolSessionFactory
from sqlspec.driver._sync import SyncPoolConnectionContext, SyncPoolSessionFactory
from sqlspec.exceptions import ImproperConfigurationError, MissingDependencyError
Expand Down
18 changes: 6 additions & 12 deletions sqlspec/adapters/sqlite/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,13 @@ def dispatch_execute_script(self, cursor: Any, statement: "SQL") -> "ExecutionRe
statements = self.split_script_statements(sql, statement.statement_config, strip_trailing_semicolon=True)

successful_count = 0
last_cursor = cursor

for stmt in statements:
cursor.execute(stmt, normalize_execute_parameters(prepared_parameters))
successful_count += 1

return self.create_execution_result(
last_cursor, statement_count=len(statements), successful_statements=successful_count, is_script_result=True
cursor, statement_count=len(statements), successful_statements=successful_count, is_script_result=True
)

def execute_many(
Expand Down Expand Up @@ -414,12 +413,12 @@ def _execute_cache_hit(
returns_rows = cached.operation_profile.returns_rows
self._invalidate_rowid_target_cache(cached.operation_type)
try:
if not returns_rows:
try:
cursor = self.connection.execute(cached.compiled_sql, params)
except sqlite3.Error as exc:
raise create_mapped_exception(exc) from exc
try:
cursor = self.connection.execute(cached.compiled_sql, params)
except sqlite3.Error as exc:
raise create_mapped_exception(exc) from exc

if not returns_rows:
rowcount = cursor.rowcount
affected_rows = rowcount if isinstance(rowcount, int) and rowcount > 0 else 0
last_inserted_id = resolve_lastrowid(
Expand All @@ -432,11 +431,6 @@ def _execute_cache_hit(
)
return DMLResult(cached.operation_type, affected_rows, last_inserted_id)

try:
cursor = self.connection.execute(cached.compiled_sql, params)
except sqlite3.Error as exc:
raise create_mapped_exception(exc) from exc

fetched_data = cursor.fetchall()
affected_rows = resolve_rowcount(cursor)
last_inserted_id = resolve_lastrowid(
Expand Down
2 changes: 1 addition & 1 deletion sqlspec/builder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
InsertFromSelectMixin,
InsertIntoClauseMixin,
InsertValuesMixin,
ReturningClauseMixin,
UpdateFromClauseMixin,
UpdateSetClauseMixin,
UpdateTableClauseMixin,
Expand Down Expand Up @@ -80,7 +81,6 @@
LimitOffsetClauseMixin,
OrderByClauseMixin,
PivotClauseMixin,
ReturningClauseMixin,
Select,
SelectClauseMixin,
SetOperationMixin,
Expand Down
14 changes: 0 additions & 14 deletions sqlspec/builder/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
SQL,
ParameterStyle,
ParameterStyleConfig,
SQLResult,
StatementConfig,
get_cache,
get_cache_config,
Expand Down Expand Up @@ -237,15 +236,6 @@ def _create_base_expression(self) -> exp.Expr:
A new sqlglot expression appropriate for the query type.
"""

@property
@abstractmethod
def _expected_result_type(self) -> "type[SQLResult]":
"""The expected result type for the query being built.

Returns:
type[ResultT]: The type of the result.
"""

@staticmethod
def _raise_builder_error(message: str, cause: BaseException | None = None) -> NoReturn:
"""Helper to raise SQLBuilderError, potentially with a cause.
Expand Down Expand Up @@ -1254,10 +1244,6 @@ def _create_base_expression(self) -> exp.Expr:
self._raise_builder_error(msg)
return self._expression

@property
def _expected_result_type(self) -> "type[SQLResult]":
return SQLResult


class _BuilderCacheEntry:
__slots__ = ("dialect", "expression")
Expand Down
6 changes: 1 addition & 5 deletions sqlspec/builder/_ddl.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from sqlspec.builder._base import BuiltQuery, QueryBuilder
from sqlspec.builder._parsing_utils import _normalize_dialect
from sqlspec.builder._select import Select
from sqlspec.core import SQL, SQLResult, StatementConfig
from sqlspec.core import SQL, StatementConfig
from sqlspec.exceptions import SQLBuilderError
from sqlspec.utils.type_guards import has_sqlglot_expression, has_with_method

Expand Down Expand Up @@ -248,10 +248,6 @@ def _resolve_select_query(self, query: object, context: str, *, require_select_t

return select_expr

@property
def _expected_result_type(self) -> "type[SQLResult]":
return SQLResult

def _prepare_expression(self, dialect: "DialectType" = None) -> None:
target_dialect = _normalize_dialect(dialect or self.dialect)
if self._expression is not None and target_dialect != self._expression_dialect:
Expand Down
14 changes: 2 additions & 12 deletions sqlspec/builder/_delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@
from sqlglot import exp

from sqlspec.builder._base import BuiltQuery, QueryBuilder
from sqlspec.builder._dml import DeleteFromClauseMixin
from sqlspec.builder._dml import DeleteFromClauseMixin, ReturningClauseMixin
from sqlspec.builder._explain import ExplainMixin
from sqlspec.builder._select import ReturningClauseMixin, WhereClauseMixin
from sqlspec.core import SQLResult
from sqlspec.builder._select import WhereClauseMixin
from sqlspec.exceptions import SQLBuilderError

if TYPE_CHECKING:
Expand Down Expand Up @@ -44,15 +43,6 @@ def __init__(self, table: str | None = None, **kwargs: Any) -> None:
if table:
self.from_(table)

@property
def _expected_result_type(self) -> "type[SQLResult]":
"""Get the expected result type for DELETE operations.

Returns:
The ExecuteResult type for DELETE statements.
"""
return SQLResult

def _create_base_expression(self) -> "exp.Delete":
"""Create a new sqlglot Delete expression.

Expand Down
Loading
Loading