sqlspec 0.17.0__py3-none-any.whl → 0.18.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 (80) hide show
  1. sqlspec/__init__.py +1 -1
  2. sqlspec/_sql.py +188 -234
  3. sqlspec/adapters/adbc/config.py +24 -30
  4. sqlspec/adapters/adbc/driver.py +42 -61
  5. sqlspec/adapters/aiosqlite/config.py +5 -10
  6. sqlspec/adapters/aiosqlite/driver.py +9 -25
  7. sqlspec/adapters/aiosqlite/pool.py +43 -35
  8. sqlspec/adapters/asyncmy/config.py +10 -7
  9. sqlspec/adapters/asyncmy/driver.py +18 -39
  10. sqlspec/adapters/asyncpg/config.py +4 -0
  11. sqlspec/adapters/asyncpg/driver.py +32 -79
  12. sqlspec/adapters/bigquery/config.py +12 -65
  13. sqlspec/adapters/bigquery/driver.py +39 -133
  14. sqlspec/adapters/duckdb/config.py +11 -15
  15. sqlspec/adapters/duckdb/driver.py +61 -85
  16. sqlspec/adapters/duckdb/pool.py +2 -5
  17. sqlspec/adapters/oracledb/_types.py +8 -1
  18. sqlspec/adapters/oracledb/config.py +55 -38
  19. sqlspec/adapters/oracledb/driver.py +35 -92
  20. sqlspec/adapters/oracledb/migrations.py +257 -0
  21. sqlspec/adapters/psqlpy/config.py +13 -9
  22. sqlspec/adapters/psqlpy/driver.py +28 -103
  23. sqlspec/adapters/psycopg/config.py +9 -5
  24. sqlspec/adapters/psycopg/driver.py +107 -175
  25. sqlspec/adapters/sqlite/config.py +7 -5
  26. sqlspec/adapters/sqlite/driver.py +37 -73
  27. sqlspec/adapters/sqlite/pool.py +3 -12
  28. sqlspec/base.py +1 -8
  29. sqlspec/builder/__init__.py +1 -1
  30. sqlspec/builder/_base.py +34 -20
  31. sqlspec/builder/_column.py +5 -1
  32. sqlspec/builder/_ddl.py +407 -183
  33. sqlspec/builder/_expression_wrappers.py +46 -0
  34. sqlspec/builder/_insert.py +2 -4
  35. sqlspec/builder/_update.py +5 -5
  36. sqlspec/builder/mixins/_insert_operations.py +26 -6
  37. sqlspec/builder/mixins/_merge_operations.py +1 -1
  38. sqlspec/builder/mixins/_order_limit_operations.py +16 -4
  39. sqlspec/builder/mixins/_select_operations.py +3 -7
  40. sqlspec/builder/mixins/_update_operations.py +4 -4
  41. sqlspec/config.py +32 -13
  42. sqlspec/core/__init__.py +89 -14
  43. sqlspec/core/cache.py +57 -104
  44. sqlspec/core/compiler.py +57 -112
  45. sqlspec/core/filters.py +1 -21
  46. sqlspec/core/hashing.py +13 -47
  47. sqlspec/core/parameters.py +272 -261
  48. sqlspec/core/result.py +12 -27
  49. sqlspec/core/splitter.py +17 -21
  50. sqlspec/core/statement.py +150 -159
  51. sqlspec/driver/_async.py +2 -15
  52. sqlspec/driver/_common.py +16 -95
  53. sqlspec/driver/_sync.py +2 -15
  54. sqlspec/driver/mixins/_result_tools.py +8 -29
  55. sqlspec/driver/mixins/_sql_translator.py +6 -8
  56. sqlspec/exceptions.py +1 -2
  57. sqlspec/loader.py +43 -115
  58. sqlspec/migrations/__init__.py +1 -1
  59. sqlspec/migrations/base.py +34 -45
  60. sqlspec/migrations/commands.py +34 -15
  61. sqlspec/migrations/loaders.py +1 -1
  62. sqlspec/migrations/runner.py +104 -19
  63. sqlspec/migrations/tracker.py +49 -2
  64. sqlspec/protocols.py +13 -6
  65. sqlspec/storage/__init__.py +4 -4
  66. sqlspec/storage/backends/fsspec.py +5 -6
  67. sqlspec/storage/backends/obstore.py +7 -8
  68. sqlspec/storage/registry.py +3 -3
  69. sqlspec/utils/__init__.py +2 -2
  70. sqlspec/utils/logging.py +6 -10
  71. sqlspec/utils/sync_tools.py +27 -4
  72. sqlspec/utils/text.py +6 -1
  73. {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/METADATA +1 -1
  74. sqlspec-0.18.0.dist-info/RECORD +138 -0
  75. sqlspec/builder/_ddl_utils.py +0 -103
  76. sqlspec-0.17.0.dist-info/RECORD +0 -137
  77. {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/WHEEL +0 -0
  78. {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/entry_points.txt +0 -0
  79. {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/licenses/LICENSE +0 -0
  80. {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/_sql.py CHANGED
@@ -1,6 +1,6 @@
1
- """Unified SQL factory for creating SQL builders and column expressions with a clean API.
1
+ """SQL factory for creating SQL builders and column expressions.
2
2
 
3
- Provides both statement builders (select, insert, update, etc.) and column expressions.
3
+ Provides statement builders (select, insert, update, etc.) and column expressions.
4
4
  """
5
5
 
6
6
  import logging
@@ -33,12 +33,21 @@ from sqlspec.builder import (
33
33
  Truncate,
34
34
  Update,
35
35
  )
36
+ from sqlspec.builder._expression_wrappers import (
37
+ AggregateExpression,
38
+ ConversionExpression,
39
+ FunctionExpression,
40
+ MathExpression,
41
+ StringExpression,
42
+ )
36
43
  from sqlspec.builder.mixins._join_operations import JoinBuilder
37
44
  from sqlspec.builder.mixins._select_operations import Case, SubqueryBuilder, WindowFunctionBuilder
45
+ from sqlspec.core.statement import SQL
38
46
  from sqlspec.exceptions import SQLBuilderError
39
47
 
40
48
  if TYPE_CHECKING:
41
- from sqlspec.core.statement import SQL
49
+ from sqlspec.builder._expression_wrappers import ExpressionWrapper
50
+
42
51
 
43
52
  __all__ = (
44
53
  "AlterTable",
@@ -100,7 +109,7 @@ SQL_STARTERS = {
100
109
 
101
110
 
102
111
  class SQLFactory:
103
- """Unified factory for creating SQL builders and column expressions with a fluent API."""
112
+ """Factory for creating SQL builders and column expressions."""
104
113
 
105
114
  @classmethod
106
115
  def detect_sql_type(cls, sql: str, dialect: DialectType = None) -> str:
@@ -127,9 +136,6 @@ class SQLFactory:
127
136
  """
128
137
  self.dialect = dialect
129
138
 
130
- # ===================
131
- # Callable Interface
132
- # ===================
133
139
  def __call__(self, statement: str, dialect: DialectType = None) -> "Any":
134
140
  """Create a SelectBuilder from a SQL string, only allowing SELECT/CTE queries.
135
141
 
@@ -175,9 +181,6 @@ class SQLFactory:
175
181
  )
176
182
  raise SQLBuilderError(msg)
177
183
 
178
- # ===================
179
- # Statement Builders
180
- # ===================
181
184
  def select(
182
185
  self, *columns_or_sql: Union[str, exp.Expression, Column, "SQL", "Case"], dialect: DialectType = None
183
186
  ) -> "Select":
@@ -262,10 +265,6 @@ class SQLFactory:
262
265
  return builder.into(table_or_sql)
263
266
  return builder
264
267
 
265
- # ===================
266
- # DDL Statement Builders
267
- # ===================
268
-
269
268
  def create_table(self, table_name: str, dialect: DialectType = None) -> "CreateTable":
270
269
  """Create a CREATE TABLE builder.
271
270
 
@@ -276,9 +275,7 @@ class SQLFactory:
276
275
  Returns:
277
276
  CreateTable builder instance
278
277
  """
279
- builder = CreateTable(table_name)
280
- builder.dialect = dialect or self.dialect
281
- return builder
278
+ return CreateTable(table_name, dialect=dialect or self.dialect)
282
279
 
283
280
  def create_table_as_select(self, dialect: DialectType = None) -> "CreateTableAsSelect":
284
281
  """Create a CREATE TABLE AS SELECT builder.
@@ -289,35 +286,31 @@ class SQLFactory:
289
286
  Returns:
290
287
  CreateTableAsSelect builder instance
291
288
  """
292
- builder = CreateTableAsSelect()
293
- builder.dialect = dialect or self.dialect
294
- return builder
289
+ return CreateTableAsSelect(dialect=dialect or self.dialect)
295
290
 
296
- def create_view(self, dialect: DialectType = None) -> "CreateView":
291
+ def create_view(self, view_name: str, dialect: DialectType = None) -> "CreateView":
297
292
  """Create a CREATE VIEW builder.
298
293
 
299
294
  Args:
295
+ view_name: Name of the view to create
300
296
  dialect: Optional SQL dialect
301
297
 
302
298
  Returns:
303
299
  CreateView builder instance
304
300
  """
305
- builder = CreateView()
306
- builder.dialect = dialect or self.dialect
307
- return builder
301
+ return CreateView(view_name, dialect=dialect or self.dialect)
308
302
 
309
- def create_materialized_view(self, dialect: DialectType = None) -> "CreateMaterializedView":
303
+ def create_materialized_view(self, view_name: str, dialect: DialectType = None) -> "CreateMaterializedView":
310
304
  """Create a CREATE MATERIALIZED VIEW builder.
311
305
 
312
306
  Args:
307
+ view_name: Name of the materialized view to create
313
308
  dialect: Optional SQL dialect
314
309
 
315
310
  Returns:
316
311
  CreateMaterializedView builder instance
317
312
  """
318
- builder = CreateMaterializedView()
319
- builder.dialect = dialect or self.dialect
320
- return builder
313
+ return CreateMaterializedView(view_name, dialect=dialect or self.dialect)
321
314
 
322
315
  def create_index(self, index_name: str, dialect: DialectType = None) -> "CreateIndex":
323
316
  """Create a CREATE INDEX builder.
@@ -331,18 +324,17 @@ class SQLFactory:
331
324
  """
332
325
  return CreateIndex(index_name, dialect=dialect or self.dialect)
333
326
 
334
- def create_schema(self, dialect: DialectType = None) -> "CreateSchema":
327
+ def create_schema(self, schema_name: str, dialect: DialectType = None) -> "CreateSchema":
335
328
  """Create a CREATE SCHEMA builder.
336
329
 
337
330
  Args:
331
+ schema_name: Name of the schema to create
338
332
  dialect: Optional SQL dialect
339
333
 
340
334
  Returns:
341
335
  CreateSchema builder instance
342
336
  """
343
- builder = CreateSchema()
344
- builder.dialect = dialect or self.dialect
345
- return builder
337
+ return CreateSchema(schema_name, dialect=dialect or self.dialect)
346
338
 
347
339
  def drop_table(self, table_name: str, dialect: DialectType = None) -> "DropTable":
348
340
  """Create a DROP TABLE builder.
@@ -356,16 +348,17 @@ class SQLFactory:
356
348
  """
357
349
  return DropTable(table_name, dialect=dialect or self.dialect)
358
350
 
359
- def drop_view(self, dialect: DialectType = None) -> "DropView":
351
+ def drop_view(self, view_name: str, dialect: DialectType = None) -> "DropView":
360
352
  """Create a DROP VIEW builder.
361
353
 
362
354
  Args:
355
+ view_name: Name of the view to drop
363
356
  dialect: Optional SQL dialect
364
357
 
365
358
  Returns:
366
359
  DropView builder instance
367
360
  """
368
- return DropView(dialect=dialect or self.dialect)
361
+ return DropView(view_name, dialect=dialect or self.dialect)
369
362
 
370
363
  def drop_index(self, index_name: str, dialect: DialectType = None) -> "DropIndex":
371
364
  """Create a DROP INDEX builder.
@@ -379,16 +372,17 @@ class SQLFactory:
379
372
  """
380
373
  return DropIndex(index_name, dialect=dialect or self.dialect)
381
374
 
382
- def drop_schema(self, dialect: DialectType = None) -> "DropSchema":
375
+ def drop_schema(self, schema_name: str, dialect: DialectType = None) -> "DropSchema":
383
376
  """Create a DROP SCHEMA builder.
384
377
 
385
378
  Args:
379
+ schema_name: Name of the schema to drop
386
380
  dialect: Optional SQL dialect
387
381
 
388
382
  Returns:
389
383
  DropSchema builder instance
390
384
  """
391
- return DropSchema(dialect=dialect or self.dialect)
385
+ return DropSchema(schema_name, dialect=dialect or self.dialect)
392
386
 
393
387
  def alter_table(self, table_name: str, dialect: DialectType = None) -> "AlterTable":
394
388
  """Create an ALTER TABLE builder.
@@ -400,22 +394,19 @@ class SQLFactory:
400
394
  Returns:
401
395
  AlterTable builder instance
402
396
  """
403
- builder = AlterTable(table_name)
404
- builder.dialect = dialect or self.dialect
405
- return builder
397
+ return AlterTable(table_name, dialect=dialect or self.dialect)
406
398
 
407
- def rename_table(self, dialect: DialectType = None) -> "RenameTable":
399
+ def rename_table(self, old_name: str, dialect: DialectType = None) -> "RenameTable":
408
400
  """Create a RENAME TABLE builder.
409
401
 
410
402
  Args:
403
+ old_name: Current name of the table
411
404
  dialect: Optional SQL dialect
412
405
 
413
406
  Returns:
414
407
  RenameTable builder instance
415
408
  """
416
- builder = RenameTable()
417
- builder.dialect = dialect or self.dialect
418
- return builder
409
+ return RenameTable(old_name, dialect=dialect or self.dialect)
419
410
 
420
411
  def comment_on(self, dialect: DialectType = None) -> "CommentOn":
421
412
  """Create a COMMENT ON builder.
@@ -426,17 +417,11 @@ class SQLFactory:
426
417
  Returns:
427
418
  CommentOn builder instance
428
419
  """
429
- builder = CommentOn()
430
- builder.dialect = dialect or self.dialect
431
- return builder
432
-
433
- # ===================
434
- # SQL Analysis Helpers
435
- # ===================
420
+ return CommentOn(dialect=dialect or self.dialect)
436
421
 
437
422
  @staticmethod
438
423
  def _looks_like_sql(candidate: str, expected_type: Optional[str] = None) -> bool:
439
- """Efficiently determine if a string looks like SQL.
424
+ """Determine if a string looks like SQL.
440
425
 
441
426
  Args:
442
427
  candidate: String to check
@@ -453,12 +438,7 @@ class SQLFactory:
453
438
  if expected_type:
454
439
  return candidate_upper.startswith(expected_type.upper())
455
440
 
456
- # More sophisticated check for SQL vs column names
457
- # Column names that start with SQL keywords are common (user_id, insert_date, etc.)
458
441
  if any(candidate_upper.startswith(starter) for starter in SQL_STARTERS):
459
- # Additional checks to distinguish real SQL from column names:
460
- # 1. Real SQL typically has spaces (SELECT ... FROM, INSERT INTO, etc.)
461
- # 2. Check for common SQL syntax patterns
462
442
  return " " in candidate
463
443
 
464
444
  return False
@@ -466,19 +446,16 @@ class SQLFactory:
466
446
  def _populate_insert_from_sql(self, builder: "Insert", sql_string: str) -> "Insert":
467
447
  """Parse SQL string and populate INSERT builder using SQLGlot directly."""
468
448
  try:
469
- # Use SQLGlot directly for parsing - no validation here
470
- parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect) # type: ignore[var-annotated]
449
+ parsed_expr: exp.Expression = exp.maybe_parse(sql_string, dialect=self.dialect)
471
450
 
472
451
  if isinstance(parsed_expr, exp.Insert):
473
452
  builder._expression = parsed_expr
474
453
  return builder
475
454
 
476
455
  if isinstance(parsed_expr, exp.Select):
477
- # The actual conversion logic can be handled by the builder itself
478
456
  logger.info("Detected SELECT statement for INSERT - may need target table specification")
479
457
  return builder
480
458
 
481
- # For other statement types, just return the builder as-is
482
459
  logger.warning("Cannot create INSERT from %s statement", type(parsed_expr).__name__)
483
460
 
484
461
  except Exception as e:
@@ -488,8 +465,7 @@ class SQLFactory:
488
465
  def _populate_select_from_sql(self, builder: "Select", sql_string: str) -> "Select":
489
466
  """Parse SQL string and populate SELECT builder using SQLGlot directly."""
490
467
  try:
491
- # Use SQLGlot directly for parsing - no validation here
492
- parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect) # type: ignore[var-annotated]
468
+ parsed_expr: exp.Expression = exp.maybe_parse(sql_string, dialect=self.dialect)
493
469
 
494
470
  if isinstance(parsed_expr, exp.Select):
495
471
  builder._expression = parsed_expr
@@ -504,8 +480,7 @@ class SQLFactory:
504
480
  def _populate_update_from_sql(self, builder: "Update", sql_string: str) -> "Update":
505
481
  """Parse SQL string and populate UPDATE builder using SQLGlot directly."""
506
482
  try:
507
- # Use SQLGlot directly for parsing - no validation here
508
- parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect) # type: ignore[var-annotated]
483
+ parsed_expr: exp.Expression = exp.maybe_parse(sql_string, dialect=self.dialect)
509
484
 
510
485
  if isinstance(parsed_expr, exp.Update):
511
486
  builder._expression = parsed_expr
@@ -520,8 +495,7 @@ class SQLFactory:
520
495
  def _populate_delete_from_sql(self, builder: "Delete", sql_string: str) -> "Delete":
521
496
  """Parse SQL string and populate DELETE builder using SQLGlot directly."""
522
497
  try:
523
- # Use SQLGlot directly for parsing - no validation here
524
- parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect) # type: ignore[var-annotated]
498
+ parsed_expr: exp.Expression = exp.maybe_parse(sql_string, dialect=self.dialect)
525
499
 
526
500
  if isinstance(parsed_expr, exp.Delete):
527
501
  builder._expression = parsed_expr
@@ -536,8 +510,7 @@ class SQLFactory:
536
510
  def _populate_merge_from_sql(self, builder: "Merge", sql_string: str) -> "Merge":
537
511
  """Parse SQL string and populate MERGE builder using SQLGlot directly."""
538
512
  try:
539
- # Use SQLGlot directly for parsing - no validation here
540
- parsed_expr = exp.maybe_parse(sql_string, dialect=self.dialect) # type: ignore[var-annotated]
513
+ parsed_expr: exp.Expression = exp.maybe_parse(sql_string, dialect=self.dialect)
541
514
 
542
515
  if isinstance(parsed_expr, exp.Merge):
543
516
  builder._expression = parsed_expr
@@ -549,10 +522,6 @@ class SQLFactory:
549
522
  logger.warning("Failed to parse MERGE SQL, falling back to traditional mode: %s", e)
550
523
  return builder
551
524
 
552
- # ===================
553
- # Column References
554
- # ===================
555
-
556
525
  def column(self, name: str, table: Optional[str] = None) -> Column:
557
526
  """Create a column reference.
558
527
 
@@ -567,10 +536,10 @@ class SQLFactory:
567
536
 
568
537
  @property
569
538
  def case_(self) -> "Case":
570
- """Create a CASE expression builder with improved syntax.
539
+ """Create a CASE expression builder.
571
540
 
572
541
  Returns:
573
- Case builder instance for fluent CASE expression building.
542
+ Case builder instance for CASE expression building.
574
543
 
575
544
  Example:
576
545
  ```python
@@ -669,23 +638,15 @@ class SQLFactory:
669
638
  Column object for the given name.
670
639
 
671
640
  Note:
672
- Special SQL constructs like case_, row_number_, etc. are now
673
- handled as properties for better type safety.
641
+ Special SQL constructs like case_, row_number_, etc. are
642
+ handled as properties for type safety.
674
643
  """
675
644
  return Column(name)
676
645
 
677
- # ===================
678
- # Raw SQL Expressions
679
- # ===================
680
-
681
646
  @staticmethod
682
647
  def raw(sql_fragment: str, **parameters: Any) -> "Union[exp.Expression, SQL]":
683
648
  """Create a raw SQL expression from a string fragment with optional parameters.
684
649
 
685
- This method makes it explicit that you are passing raw SQL that should
686
- be parsed and included directly in the query. Useful for complex expressions,
687
- database-specific functions, or when you need precise control over the SQL.
688
-
689
650
  Args:
690
651
  sql_fragment: Raw SQL string to parse into an expression.
691
652
  **parameters: Named parameters for parameter binding.
@@ -699,22 +660,21 @@ class SQLFactory:
699
660
 
700
661
  Example:
701
662
  ```python
702
- # Raw expression without parameters (current behavior)
703
663
  expr = sql.raw("COALESCE(name, 'Unknown')")
704
664
 
705
- # Raw SQL with named parameters (new functionality)
665
+
706
666
  stmt = sql.raw(
707
667
  "LOWER(name) LIKE LOWER(:pattern)", pattern=f"%{query}%"
708
668
  )
709
669
 
710
- # Raw complex expression with parameters
670
+
711
671
  expr = sql.raw(
712
672
  "price BETWEEN :min_price AND :max_price",
713
673
  min_price=100,
714
674
  max_price=500,
715
675
  )
716
676
 
717
- # Raw window function
677
+
718
678
  query = sql.select(
719
679
  "name",
720
680
  sql.raw(
@@ -724,11 +684,9 @@ class SQLFactory:
724
684
  ```
725
685
  """
726
686
  if not parameters:
727
- # Original behavior - return pure expression
728
687
  try:
729
- parsed: Optional[exp.Expression] = exp.maybe_parse(sql_fragment)
730
- if parsed is not None:
731
- return parsed
688
+ parsed: exp.Expression = exp.maybe_parse(sql_fragment)
689
+ return parsed
732
690
  if sql_fragment.strip().replace("_", "").replace(".", "").isalnum():
733
691
  return exp.to_identifier(sql_fragment)
734
692
  return exp.Literal.string(sql_fragment)
@@ -736,17 +694,12 @@ class SQLFactory:
736
694
  msg = f"Failed to parse raw SQL fragment '{sql_fragment}': {e}"
737
695
  raise SQLBuilderError(msg) from e
738
696
 
739
- # New behavior - return SQL statement with parameters
740
- from sqlspec.core.statement import SQL
741
-
742
697
  return SQL(sql_fragment, parameters)
743
698
 
744
- # ===================
745
- # Aggregate Functions
746
- # ===================
747
-
748
699
  @staticmethod
749
- def count(column: Union[str, exp.Expression] = "*", distinct: bool = False) -> exp.Expression:
700
+ def count(
701
+ column: Union[str, exp.Expression, "ExpressionWrapper", "Case", "Column"] = "*", distinct: bool = False
702
+ ) -> AggregateExpression:
750
703
  """Create a COUNT expression.
751
704
 
752
705
  Args:
@@ -756,12 +709,14 @@ class SQLFactory:
756
709
  Returns:
757
710
  COUNT expression.
758
711
  """
759
- if column == "*":
760
- return exp.Count(this=exp.Star(), distinct=distinct)
761
- col_expr = exp.column(column) if isinstance(column, str) else column
762
- return exp.Count(this=col_expr, distinct=distinct)
763
-
764
- def count_distinct(self, column: Union[str, exp.Expression]) -> exp.Expression:
712
+ if isinstance(column, str) and column == "*":
713
+ expr = exp.Count(this=exp.Star(), distinct=distinct)
714
+ else:
715
+ col_expr = SQLFactory._extract_expression(column)
716
+ expr = exp.Count(this=col_expr, distinct=distinct)
717
+ return AggregateExpression(expr)
718
+
719
+ def count_distinct(self, column: Union[str, exp.Expression, "ExpressionWrapper", "Case"]) -> AggregateExpression:
765
720
  """Create a COUNT(DISTINCT column) expression.
766
721
 
767
722
  Args:
@@ -773,7 +728,9 @@ class SQLFactory:
773
728
  return self.count(column, distinct=True)
774
729
 
775
730
  @staticmethod
776
- def sum(column: Union[str, exp.Expression], distinct: bool = False) -> exp.Expression:
731
+ def sum(
732
+ column: Union[str, exp.Expression, "ExpressionWrapper", "Case"], distinct: bool = False
733
+ ) -> AggregateExpression:
777
734
  """Create a SUM expression.
778
735
 
779
736
  Args:
@@ -783,11 +740,11 @@ class SQLFactory:
783
740
  Returns:
784
741
  SUM expression.
785
742
  """
786
- col_expr = exp.column(column) if isinstance(column, str) else column
787
- return exp.Sum(this=col_expr, distinct=distinct)
743
+ col_expr = SQLFactory._extract_expression(column)
744
+ return AggregateExpression(exp.Sum(this=col_expr, distinct=distinct))
788
745
 
789
746
  @staticmethod
790
- def avg(column: Union[str, exp.Expression]) -> exp.Expression:
747
+ def avg(column: Union[str, exp.Expression, "ExpressionWrapper", "Case"]) -> AggregateExpression:
791
748
  """Create an AVG expression.
792
749
 
793
750
  Args:
@@ -796,11 +753,11 @@ class SQLFactory:
796
753
  Returns:
797
754
  AVG expression.
798
755
  """
799
- col_expr = exp.column(column) if isinstance(column, str) else column
800
- return exp.Avg(this=col_expr)
756
+ col_expr = SQLFactory._extract_expression(column)
757
+ return AggregateExpression(exp.Avg(this=col_expr))
801
758
 
802
759
  @staticmethod
803
- def max(column: Union[str, exp.Expression]) -> exp.Expression:
760
+ def max(column: Union[str, exp.Expression, "ExpressionWrapper", "Case"]) -> AggregateExpression:
804
761
  """Create a MAX expression.
805
762
 
806
763
  Args:
@@ -809,11 +766,11 @@ class SQLFactory:
809
766
  Returns:
810
767
  MAX expression.
811
768
  """
812
- col_expr = exp.column(column) if isinstance(column, str) else column
813
- return exp.Max(this=col_expr)
769
+ col_expr = SQLFactory._extract_expression(column)
770
+ return AggregateExpression(exp.Max(this=col_expr))
814
771
 
815
772
  @staticmethod
816
- def min(column: Union[str, exp.Expression]) -> exp.Expression:
773
+ def min(column: Union[str, exp.Expression, "ExpressionWrapper", "Case"]) -> AggregateExpression:
817
774
  """Create a MIN expression.
818
775
 
819
776
  Args:
@@ -822,15 +779,11 @@ class SQLFactory:
822
779
  Returns:
823
780
  MIN expression.
824
781
  """
825
- col_expr = exp.column(column) if isinstance(column, str) else column
826
- return exp.Min(this=col_expr)
827
-
828
- # ===================
829
- # Advanced SQL Operations
830
- # ===================
782
+ col_expr = SQLFactory._extract_expression(column)
783
+ return AggregateExpression(exp.Min(this=col_expr))
831
784
 
832
785
  @staticmethod
833
- def rollup(*columns: Union[str, exp.Expression]) -> exp.Expression:
786
+ def rollup(*columns: Union[str, exp.Expression]) -> FunctionExpression:
834
787
  """Create a ROLLUP expression for GROUP BY clauses.
835
788
 
836
789
  Args:
@@ -841,7 +794,6 @@ class SQLFactory:
841
794
 
842
795
  Example:
843
796
  ```python
844
- # GROUP BY ROLLUP(product, region)
845
797
  query = (
846
798
  sql.select("product", "region", sql.sum("sales"))
847
799
  .from_("sales_data")
@@ -850,10 +802,10 @@ class SQLFactory:
850
802
  ```
851
803
  """
852
804
  column_exprs = [exp.column(col) if isinstance(col, str) else col for col in columns]
853
- return exp.Rollup(expressions=column_exprs)
805
+ return FunctionExpression(exp.Rollup(expressions=column_exprs))
854
806
 
855
807
  @staticmethod
856
- def cube(*columns: Union[str, exp.Expression]) -> exp.Expression:
808
+ def cube(*columns: Union[str, exp.Expression]) -> FunctionExpression:
857
809
  """Create a CUBE expression for GROUP BY clauses.
858
810
 
859
811
  Args:
@@ -864,7 +816,6 @@ class SQLFactory:
864
816
 
865
817
  Example:
866
818
  ```python
867
- # GROUP BY CUBE(product, region)
868
819
  query = (
869
820
  sql.select("product", "region", sql.sum("sales"))
870
821
  .from_("sales_data")
@@ -873,10 +824,10 @@ class SQLFactory:
873
824
  ```
874
825
  """
875
826
  column_exprs = [exp.column(col) if isinstance(col, str) else col for col in columns]
876
- return exp.Cube(expressions=column_exprs)
827
+ return FunctionExpression(exp.Cube(expressions=column_exprs))
877
828
 
878
829
  @staticmethod
879
- def grouping_sets(*column_sets: Union[tuple[str, ...], list[str]]) -> exp.Expression:
830
+ def grouping_sets(*column_sets: Union[tuple[str, ...], list[str]]) -> FunctionExpression:
880
831
  """Create a GROUPING SETS expression for GROUP BY clauses.
881
832
 
882
833
  Args:
@@ -887,7 +838,6 @@ class SQLFactory:
887
838
 
888
839
  Example:
889
840
  ```python
890
- # GROUP BY GROUPING SETS ((product), (region), ())
891
841
  query = (
892
842
  sql.select("product", "region", sql.sum("sales"))
893
843
  .from_("sales_data")
@@ -908,10 +858,10 @@ class SQLFactory:
908
858
  else:
909
859
  set_expressions.append(exp.column(column_set))
910
860
 
911
- return exp.GroupingSets(expressions=set_expressions)
861
+ return FunctionExpression(exp.GroupingSets(expressions=set_expressions))
912
862
 
913
863
  @staticmethod
914
- def any(values: Union[list[Any], exp.Expression, str]) -> exp.Expression:
864
+ def any(values: Union[list[Any], exp.Expression, str]) -> FunctionExpression:
915
865
  """Create an ANY expression for use with comparison operators.
916
866
 
917
867
  Args:
@@ -922,7 +872,6 @@ class SQLFactory:
922
872
 
923
873
  Example:
924
874
  ```python
925
- # WHERE id = ANY(subquery)
926
875
  subquery = sql.select("user_id").from_("active_users")
927
876
  query = (
928
877
  sql.select("*")
@@ -932,18 +881,15 @@ class SQLFactory:
932
881
  ```
933
882
  """
934
883
  if isinstance(values, list):
935
- literals = [SQLFactory._to_literal(v) for v in values]
936
- return exp.Any(this=exp.Array(expressions=literals))
884
+ literals = [SQLFactory.to_literal(v) for v in values]
885
+ return FunctionExpression(exp.Any(this=exp.Array(expressions=literals)))
937
886
  if isinstance(values, str):
938
- # Parse as SQL
939
- parsed = exp.maybe_parse(values) # type: ignore[var-annotated]
940
- if parsed:
941
- return exp.Any(this=parsed)
942
- return exp.Any(this=exp.Literal.string(values))
943
- return exp.Any(this=values)
887
+ parsed: exp.Expression = exp.maybe_parse(values)
888
+ return FunctionExpression(exp.Any(this=parsed))
889
+ return FunctionExpression(exp.Any(this=values))
944
890
 
945
891
  @staticmethod
946
- def not_any_(values: Union[list[Any], exp.Expression, str]) -> exp.Expression:
892
+ def not_any_(values: Union[list[Any], exp.Expression, str]) -> FunctionExpression:
947
893
  """Create a NOT ANY expression for use with comparison operators.
948
894
 
949
895
  Args:
@@ -954,7 +900,6 @@ class SQLFactory:
954
900
 
955
901
  Example:
956
902
  ```python
957
- # WHERE id <> ANY(subquery)
958
903
  subquery = sql.select("user_id").from_("blocked_users")
959
904
  query = (
960
905
  sql.select("*")
@@ -963,14 +908,10 @@ class SQLFactory:
963
908
  )
964
909
  ```
965
910
  """
966
- return SQLFactory.any(values) # NOT ANY is handled by the comparison operator
967
-
968
- # ===================
969
- # String Functions
970
- # ===================
911
+ return SQLFactory.any(values)
971
912
 
972
913
  @staticmethod
973
- def concat(*expressions: Union[str, exp.Expression]) -> exp.Expression:
914
+ def concat(*expressions: Union[str, exp.Expression]) -> StringExpression:
974
915
  """Create a CONCAT expression.
975
916
 
976
917
  Args:
@@ -980,10 +921,10 @@ class SQLFactory:
980
921
  CONCAT expression.
981
922
  """
982
923
  exprs = [exp.column(expr) if isinstance(expr, str) else expr for expr in expressions]
983
- return exp.Concat(expressions=exprs)
924
+ return StringExpression(exp.Concat(expressions=exprs))
984
925
 
985
926
  @staticmethod
986
- def upper(column: Union[str, exp.Expression]) -> exp.Expression:
927
+ def upper(column: Union[str, exp.Expression]) -> StringExpression:
987
928
  """Create an UPPER expression.
988
929
 
989
930
  Args:
@@ -993,10 +934,10 @@ class SQLFactory:
993
934
  UPPER expression.
994
935
  """
995
936
  col_expr = exp.column(column) if isinstance(column, str) else column
996
- return exp.Upper(this=col_expr)
937
+ return StringExpression(exp.Upper(this=col_expr))
997
938
 
998
939
  @staticmethod
999
- def lower(column: Union[str, exp.Expression]) -> exp.Expression:
940
+ def lower(column: Union[str, exp.Expression]) -> StringExpression:
1000
941
  """Create a LOWER expression.
1001
942
 
1002
943
  Args:
@@ -1006,10 +947,10 @@ class SQLFactory:
1006
947
  LOWER expression.
1007
948
  """
1008
949
  col_expr = exp.column(column) if isinstance(column, str) else column
1009
- return exp.Lower(this=col_expr)
950
+ return StringExpression(exp.Lower(this=col_expr))
1010
951
 
1011
952
  @staticmethod
1012
- def length(column: Union[str, exp.Expression]) -> exp.Expression:
953
+ def length(column: Union[str, exp.Expression]) -> StringExpression:
1013
954
  """Create a LENGTH expression.
1014
955
 
1015
956
  Args:
@@ -1019,14 +960,10 @@ class SQLFactory:
1019
960
  LENGTH expression.
1020
961
  """
1021
962
  col_expr = exp.column(column) if isinstance(column, str) else column
1022
- return exp.Length(this=col_expr)
1023
-
1024
- # ===================
1025
- # Math Functions
1026
- # ===================
963
+ return StringExpression(exp.Length(this=col_expr))
1027
964
 
1028
965
  @staticmethod
1029
- def round(column: Union[str, exp.Expression], decimals: int = 0) -> exp.Expression:
966
+ def round(column: Union[str, exp.Expression], decimals: int = 0) -> MathExpression:
1030
967
  """Create a ROUND expression.
1031
968
 
1032
969
  Args:
@@ -1038,19 +975,15 @@ class SQLFactory:
1038
975
  """
1039
976
  col_expr = exp.column(column) if isinstance(column, str) else column
1040
977
  if decimals == 0:
1041
- return exp.Round(this=col_expr)
1042
- return exp.Round(this=col_expr, expression=exp.Literal.number(decimals))
1043
-
1044
- # ===================
1045
- # Conversion Functions
1046
- # ===================
978
+ return MathExpression(exp.Round(this=col_expr))
979
+ return MathExpression(exp.Round(this=col_expr, expression=exp.Literal.number(decimals)))
1047
980
 
1048
981
  @staticmethod
1049
- def _to_literal(value: Any) -> exp.Expression:
982
+ def to_literal(value: Any) -> FunctionExpression:
1050
983
  """Convert a Python value to a SQLGlot literal expression.
1051
984
 
1052
- Uses SQLGlot's built-in exp.convert() function for optimal dialect-agnostic
1053
- literal creation. Handles all Python primitive types correctly:
985
+ Uses SQLGlot's built-in exp.convert() function for literal creation.
986
+ Handles all Python primitive types:
1054
987
  - None -> exp.Null (renders as NULL)
1055
988
  - bool -> exp.Boolean (renders as TRUE/FALSE or 1/0 based on dialect)
1056
989
  - int/float -> exp.Literal with is_number=True
@@ -1063,12 +996,51 @@ class SQLFactory:
1063
996
  Returns:
1064
997
  SQLGlot expression representing the literal value.
1065
998
  """
999
+ if isinstance(value, exp.Expression):
1000
+ return FunctionExpression(value)
1001
+ return FunctionExpression(exp.convert(value))
1002
+
1003
+ @staticmethod
1004
+ def _to_expression(value: Any) -> exp.Expression:
1005
+ """Convert a Python value to a raw SQLGlot expression.
1006
+
1007
+ Args:
1008
+ value: Python value or SQLGlot expression to convert.
1009
+
1010
+ Returns:
1011
+ Raw SQLGlot expression.
1012
+ """
1013
+ if isinstance(value, exp.Expression):
1014
+ return value
1015
+ return exp.convert(value)
1016
+
1017
+ @staticmethod
1018
+ def _extract_expression(value: Any) -> exp.Expression:
1019
+ """Extract SQLGlot expression from value, handling our wrapper types.
1020
+
1021
+ Args:
1022
+ value: String, SQLGlot expression, or our wrapper type.
1023
+
1024
+ Returns:
1025
+ Raw SQLGlot expression.
1026
+ """
1027
+ from sqlspec.builder._expression_wrappers import ExpressionWrapper
1028
+ from sqlspec.builder.mixins._select_operations import Case
1029
+
1030
+ if isinstance(value, str):
1031
+ return exp.column(value)
1032
+ if isinstance(value, Column):
1033
+ return value._expression
1034
+ if isinstance(value, ExpressionWrapper):
1035
+ return value.expression
1036
+ if isinstance(value, Case):
1037
+ return exp.Case(ifs=value._conditions, default=value._default)
1066
1038
  if isinstance(value, exp.Expression):
1067
1039
  return value
1068
1040
  return exp.convert(value)
1069
1041
 
1070
1042
  @staticmethod
1071
- def decode(column: Union[str, exp.Expression], *args: Union[str, exp.Expression, Any]) -> exp.Expression:
1043
+ def decode(column: Union[str, exp.Expression], *args: Union[str, exp.Expression, Any]) -> FunctionExpression:
1072
1044
  """Create a DECODE expression (Oracle-style conditional logic).
1073
1045
 
1074
1046
  DECODE compares column to each search value and returns the corresponding result.
@@ -1087,7 +1059,6 @@ class SQLFactory:
1087
1059
 
1088
1060
  Example:
1089
1061
  ```python
1090
- # DECODE(status, 'A', 'Active', 'I', 'Inactive', 'Unknown')
1091
1062
  sql.decode(
1092
1063
  "status", "A", "Active", "I", "Inactive", "Unknown"
1093
1064
  )
@@ -1104,23 +1075,22 @@ class SQLFactory:
1104
1075
 
1105
1076
  for i in range(0, len(args) - 1, 2):
1106
1077
  if i + 1 >= len(args):
1107
- # Odd number of args means last one is default
1108
- default = SQLFactory._to_literal(args[i])
1078
+ default = SQLFactory._to_expression(args[i])
1109
1079
  break
1110
1080
 
1111
1081
  search_val = args[i]
1112
1082
  result_val = args[i + 1]
1113
1083
 
1114
- search_expr = SQLFactory._to_literal(search_val)
1115
- result_expr = SQLFactory._to_literal(result_val)
1084
+ search_expr = SQLFactory._to_expression(search_val)
1085
+ result_expr = SQLFactory._to_expression(result_val)
1116
1086
 
1117
1087
  condition = exp.EQ(this=col_expr, expression=search_expr)
1118
- conditions.append(exp.When(this=condition, then=result_expr))
1088
+ conditions.append(exp.If(this=condition, true=result_expr))
1119
1089
 
1120
- return exp.Case(ifs=conditions, default=default)
1090
+ return FunctionExpression(exp.Case(ifs=conditions, default=default))
1121
1091
 
1122
1092
  @staticmethod
1123
- def cast(column: Union[str, exp.Expression], data_type: str) -> exp.Expression:
1093
+ def cast(column: Union[str, exp.Expression], data_type: str) -> ConversionExpression:
1124
1094
  """Create a CAST expression for type conversion.
1125
1095
 
1126
1096
  Args:
@@ -1131,10 +1101,10 @@ class SQLFactory:
1131
1101
  CAST expression.
1132
1102
  """
1133
1103
  col_expr = exp.column(column) if isinstance(column, str) else column
1134
- return exp.Cast(this=col_expr, to=exp.DataType.build(data_type))
1104
+ return ConversionExpression(exp.Cast(this=col_expr, to=exp.DataType.build(data_type)))
1135
1105
 
1136
1106
  @staticmethod
1137
- def coalesce(*expressions: Union[str, exp.Expression]) -> exp.Expression:
1107
+ def coalesce(*expressions: Union[str, exp.Expression]) -> ConversionExpression:
1138
1108
  """Create a COALESCE expression.
1139
1109
 
1140
1110
  Args:
@@ -1144,10 +1114,12 @@ class SQLFactory:
1144
1114
  COALESCE expression.
1145
1115
  """
1146
1116
  exprs = [exp.column(expr) if isinstance(expr, str) else expr for expr in expressions]
1147
- return exp.Coalesce(expressions=exprs)
1117
+ return ConversionExpression(exp.Coalesce(expressions=exprs))
1148
1118
 
1149
1119
  @staticmethod
1150
- def nvl(column: Union[str, exp.Expression], substitute_value: Union[str, exp.Expression, Any]) -> exp.Expression:
1120
+ def nvl(
1121
+ column: Union[str, exp.Expression], substitute_value: Union[str, exp.Expression, Any]
1122
+ ) -> ConversionExpression:
1151
1123
  """Create an NVL (Oracle-style) expression using COALESCE.
1152
1124
 
1153
1125
  Args:
@@ -1158,15 +1130,15 @@ class SQLFactory:
1158
1130
  COALESCE expression equivalent to NVL.
1159
1131
  """
1160
1132
  col_expr = exp.column(column) if isinstance(column, str) else column
1161
- sub_expr = SQLFactory._to_literal(substitute_value)
1162
- return exp.Coalesce(expressions=[col_expr, sub_expr])
1133
+ sub_expr = SQLFactory._to_expression(substitute_value)
1134
+ return ConversionExpression(exp.Coalesce(expressions=[col_expr, sub_expr]))
1163
1135
 
1164
1136
  @staticmethod
1165
1137
  def nvl2(
1166
1138
  column: Union[str, exp.Expression],
1167
1139
  value_if_not_null: Union[str, exp.Expression, Any],
1168
1140
  value_if_null: Union[str, exp.Expression, Any],
1169
- ) -> exp.Expression:
1141
+ ) -> ConversionExpression:
1170
1142
  """Create an NVL2 (Oracle-style) expression using CASE.
1171
1143
 
1172
1144
  NVL2 returns value_if_not_null if column is not NULL,
@@ -1182,31 +1154,25 @@ class SQLFactory:
1182
1154
 
1183
1155
  Example:
1184
1156
  ```python
1185
- # NVL2(salary, 'Has Salary', 'No Salary')
1186
1157
  sql.nvl2("salary", "Has Salary", "No Salary")
1187
1158
  ```
1188
1159
  """
1189
1160
  col_expr = exp.column(column) if isinstance(column, str) else column
1190
- not_null_expr = SQLFactory._to_literal(value_if_not_null)
1191
- null_expr = SQLFactory._to_literal(value_if_null)
1161
+ not_null_expr = SQLFactory._to_expression(value_if_not_null)
1162
+ null_expr = SQLFactory._to_expression(value_if_null)
1192
1163
 
1193
- # Create CASE WHEN column IS NOT NULL THEN value_if_not_null ELSE value_if_null END
1194
1164
  is_null = exp.Is(this=col_expr, expression=exp.Null())
1195
1165
  condition = exp.Not(this=is_null)
1196
1166
  when_clause = exp.If(this=condition, true=not_null_expr)
1197
1167
 
1198
- return exp.Case(ifs=[when_clause], default=null_expr)
1199
-
1200
- # ===================
1201
- # Bulk Operations
1202
- # ===================
1168
+ return ConversionExpression(exp.Case(ifs=[when_clause], default=null_expr))
1203
1169
 
1204
1170
  @staticmethod
1205
- def bulk_insert(table_name: str, column_count: int, placeholder_style: str = "?") -> exp.Expression:
1171
+ def bulk_insert(table_name: str, column_count: int, placeholder_style: str = "?") -> FunctionExpression:
1206
1172
  """Create bulk INSERT expression for executemany operations.
1207
1173
 
1208
- This is specifically for bulk loading operations like CSV ingestion where
1209
- we need an INSERT expression with placeholders for executemany().
1174
+ For bulk loading operations like CSV ingestion where
1175
+ an INSERT expression with placeholders for executemany() is needed.
1210
1176
 
1211
1177
  Args:
1212
1178
  table_name: Name of the table to insert into
@@ -1214,36 +1180,35 @@ class SQLFactory:
1214
1180
  placeholder_style: Placeholder style ("?" for SQLite/PostgreSQL, "%s" for MySQL, ":1" for Oracle)
1215
1181
 
1216
1182
  Returns:
1217
- INSERT expression with proper placeholders for bulk operations
1183
+ INSERT expression with placeholders for bulk operations
1218
1184
 
1219
1185
  Example:
1220
1186
  ```python
1221
1187
  from sqlspec import sql
1222
1188
 
1223
- # SQLite/PostgreSQL style
1189
+
1224
1190
  insert_expr = sql.bulk_insert("my_table", 3)
1225
- # Creates: INSERT INTO "my_table" VALUES (?, ?, ?)
1226
1191
 
1227
- # MySQL style
1192
+
1228
1193
  insert_expr = sql.bulk_insert(
1229
1194
  "my_table", 3, placeholder_style="%s"
1230
1195
  )
1231
- # Creates: INSERT INTO "my_table" VALUES (%s, %s, %s)
1232
1196
 
1233
- # Oracle style
1197
+
1234
1198
  insert_expr = sql.bulk_insert(
1235
1199
  "my_table", 3, placeholder_style=":1"
1236
1200
  )
1237
- # Creates: INSERT INTO "my_table" VALUES (:1, :2, :3)
1238
1201
  ```
1239
1202
  """
1240
- return exp.Insert(
1241
- this=exp.Table(this=exp.to_identifier(table_name)),
1242
- expression=exp.Values(
1243
- expressions=[
1244
- exp.Tuple(expressions=[exp.Placeholder(this=placeholder_style) for _ in range(column_count)])
1245
- ]
1246
- ),
1203
+ return FunctionExpression(
1204
+ exp.Insert(
1205
+ this=exp.Table(this=exp.to_identifier(table_name)),
1206
+ expression=exp.Values(
1207
+ expressions=[
1208
+ exp.Tuple(expressions=[exp.Placeholder(this=placeholder_style) for _ in range(column_count)])
1209
+ ]
1210
+ ),
1211
+ )
1247
1212
  )
1248
1213
 
1249
1214
  def truncate(self, table_name: str) -> "Truncate":
@@ -1259,10 +1224,10 @@ class SQLFactory:
1259
1224
  ```python
1260
1225
  from sqlspec import sql
1261
1226
 
1262
- # Simple truncate
1227
+
1263
1228
  truncate_sql = sql.truncate_table("my_table").build().sql
1264
1229
 
1265
- # Truncate with options
1230
+
1266
1231
  truncate_sql = (
1267
1232
  sql.truncate_table("my_table")
1268
1233
  .cascade()
@@ -1272,13 +1237,7 @@ class SQLFactory:
1272
1237
  )
1273
1238
  ```
1274
1239
  """
1275
- builder = Truncate(dialect=self.dialect)
1276
- builder._table_name = table_name
1277
- return builder
1278
-
1279
- # ===================
1280
- # Case Expressions
1281
- # ===================
1240
+ return Truncate(table_name, dialect=self.dialect)
1282
1241
 
1283
1242
  @staticmethod
1284
1243
  def case() -> "Case":
@@ -1289,15 +1248,11 @@ class SQLFactory:
1289
1248
  """
1290
1249
  return Case()
1291
1250
 
1292
- # ===================
1293
- # Window Functions
1294
- # ===================
1295
-
1296
1251
  def row_number(
1297
1252
  self,
1298
1253
  partition_by: Optional[Union[str, list[str], exp.Expression]] = None,
1299
1254
  order_by: Optional[Union[str, list[str], exp.Expression]] = None,
1300
- ) -> exp.Expression:
1255
+ ) -> FunctionExpression:
1301
1256
  """Create a ROW_NUMBER() window function.
1302
1257
 
1303
1258
  Args:
@@ -1313,7 +1268,7 @@ class SQLFactory:
1313
1268
  self,
1314
1269
  partition_by: Optional[Union[str, list[str], exp.Expression]] = None,
1315
1270
  order_by: Optional[Union[str, list[str], exp.Expression]] = None,
1316
- ) -> exp.Expression:
1271
+ ) -> FunctionExpression:
1317
1272
  """Create a RANK() window function.
1318
1273
 
1319
1274
  Args:
@@ -1329,7 +1284,7 @@ class SQLFactory:
1329
1284
  self,
1330
1285
  partition_by: Optional[Union[str, list[str], exp.Expression]] = None,
1331
1286
  order_by: Optional[Union[str, list[str], exp.Expression]] = None,
1332
- ) -> exp.Expression:
1287
+ ) -> FunctionExpression:
1333
1288
  """Create a DENSE_RANK() window function.
1334
1289
 
1335
1290
  Args:
@@ -1347,7 +1302,7 @@ class SQLFactory:
1347
1302
  func_args: list[exp.Expression],
1348
1303
  partition_by: Optional[Union[str, list[str], exp.Expression]] = None,
1349
1304
  order_by: Optional[Union[str, list[str], exp.Expression]] = None,
1350
- ) -> exp.Expression:
1305
+ ) -> FunctionExpression:
1351
1306
  """Helper to create window function expressions.
1352
1307
 
1353
1308
  Args:
@@ -1373,14 +1328,13 @@ class SQLFactory:
1373
1328
 
1374
1329
  if order_by:
1375
1330
  if isinstance(order_by, str):
1376
- over_args["order"] = [exp.column(order_by).asc()]
1331
+ over_args["order"] = exp.Order(expressions=[exp.column(order_by).asc()])
1377
1332
  elif isinstance(order_by, list):
1378
- over_args["order"] = [exp.column(col).asc() for col in order_by]
1333
+ over_args["order"] = exp.Order(expressions=[exp.column(col).asc() for col in order_by])
1379
1334
  elif isinstance(order_by, exp.Expression):
1380
- over_args["order"] = [order_by]
1335
+ over_args["order"] = exp.Order(expressions=[order_by])
1381
1336
 
1382
- return exp.Window(this=func_expr, **over_args)
1337
+ return FunctionExpression(exp.Window(this=func_expr, **over_args))
1383
1338
 
1384
1339
 
1385
- # Create a default SQL factory instance
1386
1340
  sql = SQLFactory()