sqlspec 0.16.2__py3-none-any.whl → 0.17.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.

@@ -12,11 +12,11 @@ from sqlspec.exceptions import SQLBuilderError
12
12
  from sqlspec.utils.type_guards import has_query_builder_parameters, is_expression
13
13
 
14
14
  if TYPE_CHECKING:
15
- from sqlspec.builder._column import Column, FunctionColumn
15
+ from sqlspec.builder._column import Column, ColumnExpression, FunctionColumn
16
16
  from sqlspec.core.statement import SQL
17
17
  from sqlspec.protocols import SelectBuilderProtocol, SQLBuilderProtocol
18
18
 
19
- __all__ = ("CaseBuilder", "SelectClauseMixin")
19
+ __all__ = ("Case", "CaseBuilder", "SelectClauseMixin", "SubqueryBuilder", "WindowFunctionBuilder")
20
20
 
21
21
 
22
22
  @trait
@@ -28,7 +28,7 @@ class SelectClauseMixin:
28
28
  # Type annotation for PyRight - this will be provided by the base class
29
29
  _expression: Optional[exp.Expression]
30
30
 
31
- def select(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn", "SQL"]) -> Self:
31
+ def select(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn", "SQL", "Case"]) -> Self:
32
32
  """Add columns to SELECT clause.
33
33
 
34
34
  Raises:
@@ -602,3 +602,307 @@ class CaseBuilder:
602
602
  """
603
603
  select_expr = exp.alias_(self._case_expr, self._alias) if self._alias else self._case_expr
604
604
  return self._parent.select(select_expr)
605
+
606
+
607
+ @trait
608
+ class WindowFunctionBuilder:
609
+ """Builder for window functions with fluent syntax.
610
+
611
+ Example:
612
+ ```python
613
+ from sqlspec import sql
614
+
615
+ # sql.row_number_.partition_by("department").order_by("salary")
616
+ window_func = (
617
+ sql.row_number_.partition_by("department")
618
+ .order_by("salary")
619
+ .as_("row_num")
620
+ )
621
+ ```
622
+ """
623
+
624
+ def __init__(self, function_name: str) -> None:
625
+ """Initialize the window function builder.
626
+
627
+ Args:
628
+ function_name: Name of the window function (row_number, rank, etc.)
629
+ """
630
+ self._function_name = function_name
631
+ self._partition_by_cols: list[exp.Expression] = []
632
+ self._order_by_cols: list[exp.Expression] = []
633
+ self._alias: Optional[str] = None
634
+
635
+ def __eq__(self, other: object) -> "ColumnExpression": # type: ignore[override]
636
+ """Equal to (==) - convert to expression then compare."""
637
+ from sqlspec.builder._column import ColumnExpression
638
+
639
+ window_expr = self._build_expression()
640
+ if other is None:
641
+ return ColumnExpression(exp.Is(this=window_expr, expression=exp.Null()))
642
+ return ColumnExpression(exp.EQ(this=window_expr, expression=exp.convert(other)))
643
+
644
+ def __hash__(self) -> int:
645
+ """Make WindowFunctionBuilder hashable."""
646
+ return hash(id(self))
647
+
648
+ def partition_by(self, *columns: Union[str, exp.Expression]) -> "WindowFunctionBuilder":
649
+ """Add PARTITION BY clause.
650
+
651
+ Args:
652
+ *columns: Columns to partition by.
653
+
654
+ Returns:
655
+ Self for method chaining.
656
+ """
657
+ for col in columns:
658
+ col_expr = exp.column(col) if isinstance(col, str) else col
659
+ self._partition_by_cols.append(col_expr)
660
+ return self
661
+
662
+ def order_by(self, *columns: Union[str, exp.Expression]) -> "WindowFunctionBuilder":
663
+ """Add ORDER BY clause.
664
+
665
+ Args:
666
+ *columns: Columns to order by.
667
+
668
+ Returns:
669
+ Self for method chaining.
670
+ """
671
+ for col in columns:
672
+ if isinstance(col, str):
673
+ col_expr = exp.column(col).asc()
674
+ self._order_by_cols.append(col_expr)
675
+ else:
676
+ # Convert to ordered expression
677
+ self._order_by_cols.append(exp.Ordered(this=col, desc=False))
678
+ return self
679
+
680
+ def as_(self, alias: str) -> exp.Alias:
681
+ """Complete the window function with an alias.
682
+
683
+ Args:
684
+ alias: Alias name for the window function.
685
+
686
+ Returns:
687
+ Aliased window function expression.
688
+ """
689
+ window_expr = self._build_expression()
690
+ return cast("exp.Alias", exp.alias_(window_expr, alias))
691
+
692
+ def build(self) -> exp.Expression:
693
+ """Complete the window function without an alias.
694
+
695
+ Returns:
696
+ Window function expression.
697
+ """
698
+ return self._build_expression()
699
+
700
+ def _build_expression(self) -> exp.Expression:
701
+ """Build the complete window function expression."""
702
+ # Create the function expression
703
+ func_expr = exp.Anonymous(this=self._function_name.upper(), expressions=[])
704
+
705
+ # Build the OVER clause arguments
706
+ over_args: dict[str, Any] = {}
707
+
708
+ if self._partition_by_cols:
709
+ over_args["partition_by"] = self._partition_by_cols
710
+
711
+ if self._order_by_cols:
712
+ over_args["order"] = exp.Order(expressions=self._order_by_cols)
713
+
714
+ return exp.Window(this=func_expr, **over_args)
715
+
716
+
717
+ @trait
718
+ class SubqueryBuilder:
719
+ """Builder for subquery operations with fluent syntax.
720
+
721
+ Example:
722
+ ```python
723
+ from sqlspec import sql
724
+
725
+ # sql.exists_(subquery)
726
+ exists_check = sql.exists_(
727
+ sql.select("1")
728
+ .from_("orders")
729
+ .where_eq("user_id", sql.users.id)
730
+ )
731
+
732
+ # sql.in_(subquery)
733
+ in_check = sql.in_(
734
+ sql.select("category_id")
735
+ .from_("categories")
736
+ .where_eq("active", True)
737
+ )
738
+ ```
739
+ """
740
+
741
+ def __init__(self, operation: str) -> None:
742
+ """Initialize the subquery builder.
743
+
744
+ Args:
745
+ operation: Type of subquery operation (exists, in, any, all)
746
+ """
747
+ self._operation = operation
748
+
749
+ def __eq__(self, other: object) -> "ColumnExpression": # type: ignore[override]
750
+ """Equal to (==) - not typically used but needed for type consistency."""
751
+ from sqlspec.builder._column import ColumnExpression
752
+
753
+ # SubqueryBuilder doesn't have a direct expression, so this is a placeholder
754
+ # In practice, this shouldn't be called as subqueries are used differently
755
+ placeholder_expr = exp.Literal.string(f"subquery_{self._operation}")
756
+ if other is None:
757
+ return ColumnExpression(exp.Is(this=placeholder_expr, expression=exp.Null()))
758
+ return ColumnExpression(exp.EQ(this=placeholder_expr, expression=exp.convert(other)))
759
+
760
+ def __hash__(self) -> int:
761
+ """Make SubqueryBuilder hashable."""
762
+ return hash(id(self))
763
+
764
+ def __call__(self, subquery: Union[str, exp.Expression, Any]) -> exp.Expression:
765
+ """Build the subquery expression.
766
+
767
+ Args:
768
+ subquery: The subquery - can be a SQL string, SelectBuilder, or expression
769
+
770
+ Returns:
771
+ The subquery expression (EXISTS, IN, ANY, ALL, etc.)
772
+ """
773
+ subquery_expr: exp.Expression
774
+ if isinstance(subquery, str):
775
+ # Parse as SQL
776
+ parsed: Optional[exp.Expression] = exp.maybe_parse(subquery)
777
+ if not parsed:
778
+ msg = f"Could not parse subquery SQL: {subquery}"
779
+ raise SQLBuilderError(msg)
780
+ subquery_expr = parsed
781
+ elif hasattr(subquery, "build") and callable(getattr(subquery, "build", None)):
782
+ # It's a query builder - build it to get the SQL and parse
783
+ built_query = subquery.build() # pyright: ignore[reportAttributeAccessIssue]
784
+ subquery_expr = exp.maybe_parse(built_query.sql)
785
+ if not subquery_expr:
786
+ msg = f"Could not parse built query: {built_query.sql}"
787
+ raise SQLBuilderError(msg)
788
+ elif isinstance(subquery, exp.Expression):
789
+ subquery_expr = subquery
790
+ else:
791
+ # Try to convert to expression
792
+ parsed = exp.maybe_parse(str(subquery))
793
+ if not parsed:
794
+ msg = f"Could not convert subquery to expression: {subquery}"
795
+ raise SQLBuilderError(msg)
796
+ subquery_expr = parsed
797
+
798
+ # Build the appropriate expression based on operation
799
+ if self._operation == "exists":
800
+ return exp.Exists(this=subquery_expr)
801
+ if self._operation == "in":
802
+ # For IN, we create a subquery that can be used with WHERE column IN (subquery)
803
+ return exp.In(expressions=[subquery_expr])
804
+ if self._operation == "any":
805
+ return exp.Any(this=subquery_expr)
806
+ if self._operation == "all":
807
+ return exp.All(this=subquery_expr)
808
+ msg = f"Unknown subquery operation: {self._operation}"
809
+ raise SQLBuilderError(msg)
810
+
811
+
812
+ @trait
813
+ class Case:
814
+ """Builder for CASE expressions using the SQL factory.
815
+
816
+ Example:
817
+ ```python
818
+ from sqlspec import sql
819
+
820
+ case_expr = (
821
+ sql.case()
822
+ .when(sql.age < 18, "Minor")
823
+ .when(sql.age < 65, "Adult")
824
+ .else_("Senior")
825
+ .end()
826
+ )
827
+ ```
828
+ """
829
+
830
+ def __init__(self) -> None:
831
+ """Initialize the CASE expression builder."""
832
+ self._conditions: list[exp.If] = []
833
+ self._default: Optional[exp.Expression] = None
834
+
835
+ def __eq__(self, other: object) -> "ColumnExpression": # type: ignore[override]
836
+ """Equal to (==) - convert to expression then compare."""
837
+ from sqlspec.builder._column import ColumnExpression
838
+
839
+ case_expr = exp.Case(ifs=self._conditions, default=self._default)
840
+ if other is None:
841
+ return ColumnExpression(exp.Is(this=case_expr, expression=exp.Null()))
842
+ return ColumnExpression(exp.EQ(this=case_expr, expression=exp.convert(other)))
843
+
844
+ def __hash__(self) -> int:
845
+ """Make Case hashable."""
846
+ return hash(id(self))
847
+
848
+ def when(self, condition: Union[str, exp.Expression], value: Union[str, exp.Expression, Any]) -> Self:
849
+ """Add a WHEN clause.
850
+
851
+ Args:
852
+ condition: Condition to test.
853
+ value: Value to return if condition is true.
854
+
855
+ Returns:
856
+ Self for method chaining.
857
+ """
858
+ from sqlspec._sql import SQLFactory
859
+
860
+ cond_expr = exp.maybe_parse(condition) or exp.column(condition) if isinstance(condition, str) else condition
861
+ val_expr = SQLFactory._to_literal(value)
862
+
863
+ # SQLGlot uses exp.If for CASE WHEN clauses, not exp.When
864
+ when_clause = exp.If(this=cond_expr, true=val_expr)
865
+ self._conditions.append(when_clause)
866
+ return self
867
+
868
+ def else_(self, value: Union[str, exp.Expression, Any]) -> Self:
869
+ """Add an ELSE clause.
870
+
871
+ Args:
872
+ value: Default value to return.
873
+
874
+ Returns:
875
+ Self for method chaining.
876
+ """
877
+ from sqlspec._sql import SQLFactory
878
+
879
+ self._default = SQLFactory._to_literal(value)
880
+ return self
881
+
882
+ def end(self) -> Self:
883
+ """Complete the CASE expression.
884
+
885
+ Returns:
886
+ Complete CASE expression.
887
+ """
888
+ return self
889
+
890
+ @property
891
+ def _expression(self) -> exp.Case:
892
+ """Get the sqlglot expression for this case builder.
893
+
894
+ This allows the CaseBuilder to be used wherever expressions are expected.
895
+ """
896
+ return exp.Case(ifs=self._conditions, default=self._default)
897
+
898
+ def as_(self, alias: str) -> exp.Alias:
899
+ """Complete the CASE expression with an alias.
900
+
901
+ Args:
902
+ alias: Alias name for the CASE expression.
903
+
904
+ Returns:
905
+ Aliased CASE expression.
906
+ """
907
+ case_expr = exp.Case(ifs=self._conditions, default=self._default)
908
+ return cast("exp.Alias", exp.alias_(case_expr, alias))
@@ -214,20 +214,22 @@ class WhereClauseMixin:
214
214
  condition: Union[
215
215
  str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression", "SQL"
216
216
  ],
217
- value: Optional[Any] = None,
217
+ *values: Any,
218
218
  operator: Optional[str] = None,
219
+ **kwargs: Any,
219
220
  ) -> Self:
220
221
  """Add a WHERE clause to the statement.
221
222
 
222
223
  Args:
223
224
  condition: The condition for the WHERE clause. Can be:
224
- - A string condition (when value is None)
225
- - A string column name (when value is provided)
225
+ - A string condition with or without parameter placeholders
226
+ - A string column name (when values are provided)
226
227
  - A sqlglot Expression or Condition
227
228
  - A 2-tuple (column, value) for equality comparison
228
229
  - A 3-tuple (column, operator, value) for custom comparison
229
- value: Value for comparison (when condition is a column name)
230
- operator: Operator for comparison (when both condition and value provided)
230
+ *values: Positional values for parameter binding (when condition contains placeholders or is a column name)
231
+ operator: Operator for comparison (when condition is a column name)
232
+ **kwargs: Named parameters for parameter binding (when condition contains named placeholders)
231
233
 
232
234
  Raises:
233
235
  SQLBuilderError: If the current expression is not a supported statement type.
@@ -248,16 +250,63 @@ class WhereClauseMixin:
248
250
  msg = "WHERE clause requires a table to be set. Use from() to set the table first."
249
251
  raise SQLBuilderError(msg)
250
252
 
251
- if value is not None:
253
+ # Handle string conditions with external parameters
254
+ if values or kwargs:
252
255
  if not isinstance(condition, str):
253
- msg = "When value is provided, condition must be a column name (string)"
256
+ msg = "When values are provided, condition must be a string"
254
257
  raise SQLBuilderError(msg)
255
258
 
256
- if operator is not None:
257
- where_expr = self._process_tuple_condition((condition, operator, value))
259
+ # Check if condition contains parameter placeholders
260
+ from sqlspec.core.parameters import ParameterStyle, ParameterValidator
261
+
262
+ validator = ParameterValidator()
263
+ param_info = validator.extract_parameters(condition)
264
+
265
+ if param_info:
266
+ # String condition with placeholders - create SQL object with parameters
267
+ from sqlspec import sql as sql_factory
268
+
269
+ # Create parameter mapping based on the detected parameter info
270
+ param_dict = dict(kwargs) # Start with named parameters
271
+
272
+ # Handle positional parameters - these are ordinal-based ($1, $2, :1, :2, ?)
273
+ positional_params = [
274
+ param
275
+ for param in param_info
276
+ if param.style in {ParameterStyle.NUMERIC, ParameterStyle.POSITIONAL_COLON, ParameterStyle.QMARK}
277
+ ]
278
+
279
+ # Map positional values to positional parameters
280
+ if len(values) != len(positional_params):
281
+ msg = f"Parameter count mismatch: condition has {len(positional_params)} positional placeholders, got {len(values)} values"
282
+ raise SQLBuilderError(msg)
283
+
284
+ for i, value in enumerate(values):
285
+ param_dict[f"param_{i}"] = value
286
+
287
+ # Create SQL object with parameters that will be processed correctly
288
+ condition = sql_factory.raw(condition, **param_dict)
289
+ # Fall through to existing SQL object handling logic
290
+
291
+ elif len(values) == 1 and not kwargs:
292
+ # Single value - treat as column = value
293
+ if operator is not None:
294
+ where_expr = self._process_tuple_condition((condition, operator, values[0]))
295
+ else:
296
+ where_expr = self._process_tuple_condition((condition, values[0]))
297
+ # Process this condition and skip the rest
298
+ if isinstance(builder._expression, (exp.Select, exp.Update, exp.Delete)):
299
+ builder._expression = builder._expression.where(where_expr, copy=False)
300
+ else:
301
+ msg = f"WHERE clause not supported for {type(builder._expression).__name__}"
302
+ raise SQLBuilderError(msg)
303
+ return self
258
304
  else:
259
- where_expr = self._process_tuple_condition((condition, value))
260
- elif isinstance(condition, str):
305
+ msg = f"Cannot bind parameters to condition without placeholders: {condition}"
306
+ raise SQLBuilderError(msg)
307
+
308
+ # Handle all condition types (including SQL objects created above)
309
+ if isinstance(condition, str):
261
310
  where_expr = parse_condition_expression(condition)
262
311
  elif isinstance(condition, (exp.Expression, exp.Condition)):
263
312
  where_expr = condition
sqlspec/core/compiler.py CHANGED
@@ -277,11 +277,13 @@ class SQLProcessor:
277
277
  if self._config.parameter_config.needs_static_script_compilation and processed_params is None:
278
278
  final_sql, final_params = processed_sql, processed_params
279
279
  elif ast_was_transformed and expression is not None:
280
- final_sql = expression.sql(dialect=dialect_str)
281
- final_params = final_parameters
282
- logger.debug("AST was transformed - final SQL: %s, final params: %s", final_sql, final_params)
283
-
284
- # Apply output transformer if configured
280
+ final_sql, final_params = self._parameter_processor.process(
281
+ sql=expression.sql(dialect=dialect_str),
282
+ parameters=final_parameters,
283
+ config=self._config.parameter_config,
284
+ dialect=dialect_str,
285
+ is_many=is_many,
286
+ )
285
287
  output_transformer = self._config.output_transformer
286
288
  if output_transformer:
287
289
  final_sql, final_params = output_transformer(final_sql, final_params)
sqlspec/driver/_common.py CHANGED
@@ -275,7 +275,15 @@ class CommonDriverAttributesMixin:
275
275
  kwargs = kwargs or {}
276
276
 
277
277
  if isinstance(statement, QueryBuilder):
278
- return statement.to_statement(statement_config)
278
+ sql_statement = statement.to_statement(statement_config)
279
+ if parameters or kwargs:
280
+ merged_parameters = (
281
+ (*sql_statement._positional_parameters, *parameters)
282
+ if parameters
283
+ else sql_statement._positional_parameters
284
+ )
285
+ return SQL(sql_statement.sql, *merged_parameters, statement_config=statement_config, **kwargs)
286
+ return sql_statement
279
287
  if isinstance(statement, SQL):
280
288
  if parameters or kwargs:
281
289
  merged_parameters = (
sqlspec/loader.py CHANGED
@@ -14,9 +14,13 @@ from pathlib import Path
14
14
  from typing import Any, Optional, Union
15
15
 
16
16
  from sqlspec.core.cache import CacheKey, get_cache_config, get_default_cache
17
- from sqlspec.core.parameters import ParameterStyleConfig, ParameterValidator
18
- from sqlspec.core.statement import SQL, StatementConfig
19
- from sqlspec.exceptions import SQLFileNotFoundError, SQLFileParseError, StorageOperationFailedError
17
+ from sqlspec.core.statement import SQL
18
+ from sqlspec.exceptions import (
19
+ MissingDependencyError,
20
+ SQLFileNotFoundError,
21
+ SQLFileParseError,
22
+ StorageOperationFailedError,
23
+ )
20
24
  from sqlspec.storage import storage_registry
21
25
  from sqlspec.storage.registry import StorageRegistry
22
26
  from sqlspec.utils.correlation import CorrelationContext
@@ -29,7 +33,7 @@ logger = get_logger("loader")
29
33
  # Matches: -- name: query_name (supports hyphens and special suffixes)
30
34
  # We capture the name plus any trailing special characters
31
35
  QUERY_NAME_PATTERN = re.compile(r"^\s*--\s*name\s*:\s*([\w-]+[^\w\s]*)\s*$", re.MULTILINE | re.IGNORECASE)
32
- TRIM_SPECIAL_CHARS = re.compile(r"[^\w-]")
36
+ TRIM_SPECIAL_CHARS = re.compile(r"[^\w.-]")
33
37
 
34
38
  # Matches: -- dialect: dialect_name (optional dialect specification)
35
39
  DIALECT_PATTERN = re.compile(r"^\s*--\s*dialect\s*:\s*(?P<dialect>[a-zA-Z0-9_]+)\s*$", re.IGNORECASE | re.MULTILINE)
@@ -304,6 +308,12 @@ class SQLFileLoader:
304
308
  return backend.read_text(path_str, encoding=self.encoding)
305
309
  except KeyError as e:
306
310
  raise SQLFileNotFoundError(path_str) from e
311
+ except MissingDependencyError:
312
+ # Fall back to standard file reading when no storage backend is available
313
+ try:
314
+ return path.read_text(encoding=self.encoding) # type: ignore[union-attr]
315
+ except FileNotFoundError as e:
316
+ raise SQLFileNotFoundError(path_str) from e
307
317
  except StorageOperationFailedError as e:
308
318
  if "not found" in str(e).lower() or "no such file" in str(e).lower():
309
319
  raise SQLFileNotFoundError(path_str) from e
@@ -570,8 +580,11 @@ class SQLFileLoader:
570
580
  Raises:
571
581
  ValueError: If query name already exists.
572
582
  """
573
- if name in self._queries:
574
- existing_source = self._query_to_file.get(name, "<directly added>")
583
+ # Normalize the name for consistency with file-loaded queries
584
+ normalized_name = _normalize_query_name(name)
585
+
586
+ if normalized_name in self._queries:
587
+ existing_source = self._query_to_file.get(normalized_name, "<directly added>")
575
588
  msg = f"Query name '{name}' already exists (source: {existing_source})"
576
589
  raise ValueError(msg)
577
590
 
@@ -588,21 +601,16 @@ class SQLFileLoader:
588
601
  else:
589
602
  dialect = normalized_dialect
590
603
 
591
- statement = NamedStatement(name=name, sql=sql.strip(), dialect=dialect, start_line=0)
592
- self._queries[name] = statement
593
- self._query_to_file[name] = "<directly added>"
604
+ statement = NamedStatement(name=normalized_name, sql=sql.strip(), dialect=dialect, start_line=0)
605
+ self._queries[normalized_name] = statement
606
+ self._query_to_file[normalized_name] = "<directly added>"
594
607
 
595
- def get_sql(
596
- self, name: str, parameters: "Optional[Any]" = None, dialect: "Optional[str]" = None, **kwargs: "Any"
597
- ) -> "SQL":
598
- """Get a SQL object by statement name with dialect support.
608
+ def get_sql(self, name: str) -> "SQL":
609
+ """Get a SQL object by statement name.
599
610
 
600
611
  Args:
601
612
  name: Name of the statement (from -- name: in SQL file).
602
613
  Hyphens in names are converted to underscores.
603
- parameters: Parameters for the SQL statement.
604
- dialect: Optional dialect override.
605
- **kwargs: Additional parameters to pass to the SQL object.
606
614
 
607
615
  Returns:
608
616
  SQL object ready for execution.
@@ -629,46 +637,11 @@ class SQLFileLoader:
629
637
  raise SQLFileNotFoundError(name, path=f"Statement '{name}' not found. Available statements: {available}")
630
638
 
631
639
  parsed_statement = self._queries[safe_name]
632
-
633
- effective_dialect = dialect or parsed_statement.dialect
634
-
635
- if dialect is not None:
636
- normalized_dialect = _normalize_dialect(dialect)
637
- if normalized_dialect not in SUPPORTED_DIALECTS:
638
- suggestions = _get_dialect_suggestions(normalized_dialect)
639
- warning_msg = f"Unknown dialect '{dialect}'"
640
- if suggestions:
641
- warning_msg += f". Did you mean: {', '.join(suggestions)}?"
642
- warning_msg += f". Supported dialects: {', '.join(sorted(SUPPORTED_DIALECTS))}. Using dialect as-is."
643
- logger.warning(warning_msg)
644
- effective_dialect = dialect.lower()
645
- else:
646
- effective_dialect = normalized_dialect
647
-
648
- sql_kwargs = dict(kwargs)
649
- if parameters is not None:
650
- sql_kwargs["parameters"] = parameters
651
-
652
640
  sqlglot_dialect = None
653
- if effective_dialect:
654
- sqlglot_dialect = _normalize_dialect_for_sqlglot(effective_dialect)
655
-
656
- if not effective_dialect and "statement_config" not in sql_kwargs:
657
- validator = ParameterValidator()
658
- param_info = validator.extract_parameters(parsed_statement.sql)
659
- if param_info:
660
- styles = {p.style for p in param_info}
661
- if styles:
662
- detected_style = next(iter(styles))
663
- sql_kwargs["statement_config"] = StatementConfig(
664
- parameter_config=ParameterStyleConfig(
665
- default_parameter_style=detected_style,
666
- supported_parameter_styles=styles,
667
- preserve_parameter_format=True,
668
- )
669
- )
641
+ if parsed_statement.dialect:
642
+ sqlglot_dialect = _normalize_dialect_for_sqlglot(parsed_statement.dialect)
670
643
 
671
- return SQL(parsed_statement.sql, dialect=sqlglot_dialect, **sql_kwargs)
644
+ return SQL(parsed_statement.sql, dialect=sqlglot_dialect)
672
645
 
673
646
  def get_file(self, path: Union[str, Path]) -> "Optional[SQLFile]":
674
647
  """Get a loaded SQLFile object by path.
@@ -151,8 +151,8 @@ class StorageRegistry:
151
151
  return self._create_backend("fsspec", uri, **kwargs)
152
152
  except (ValueError, ImportError, NotImplementedError):
153
153
  pass
154
- msg = f"No storage backend available for scheme '{scheme}'. Install obstore or fsspec."
155
- raise MissingDependencyError(msg)
154
+ msg = "obstore"
155
+ raise MissingDependencyError(msg, "fsspec")
156
156
 
157
157
  def _determine_backend_class(self, uri: str) -> type[ObjectStoreProtocol]:
158
158
  """Determine the backend class for a URI based on availability."""