1# ext/compiler.py
2# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7
8r"""Provides an API for creation of custom ClauseElements and compilers.
9
10Synopsis
11========
12
13Usage involves the creation of one or more
14:class:`~sqlalchemy.sql.expression.ClauseElement` subclasses and one or
15more callables defining its compilation::
16
17 from sqlalchemy.ext.compiler import compiles
18 from sqlalchemy.sql.expression import ColumnClause
19
20
21 class MyColumn(ColumnClause):
22 inherit_cache = True
23
24
25 @compiles(MyColumn)
26 def compile_mycolumn(element, compiler, **kw):
27 return "[%s]" % element.name
28
29Above, ``MyColumn`` extends :class:`~sqlalchemy.sql.expression.ColumnClause`,
30the base expression element for named column objects. The ``compiles``
31decorator registers itself with the ``MyColumn`` class so that it is invoked
32when the object is compiled to a string::
33
34 from sqlalchemy import select
35
36 s = select(MyColumn("x"), MyColumn("y"))
37 print(str(s))
38
39Produces:
40
41.. sourcecode:: sql
42
43 SELECT [x], [y]
44
45Dialect-specific compilation rules
46==================================
47
48Compilers can also be made dialect-specific. The appropriate compiler will be
49invoked for the dialect in use::
50
51 from sqlalchemy.schema import DDLElement
52
53
54 class AlterColumn(DDLElement):
55 inherit_cache = False
56
57 def __init__(self, column, cmd):
58 self.column = column
59 self.cmd = cmd
60
61
62 @compiles(AlterColumn)
63 def visit_alter_column(element, compiler, **kw):
64 return "ALTER COLUMN %s ..." % element.column.name
65
66
67 @compiles(AlterColumn, "postgresql")
68 def visit_alter_column(element, compiler, **kw):
69 return "ALTER TABLE %s ALTER COLUMN %s ..." % (
70 element.table.name,
71 element.column.name,
72 )
73
74The second ``visit_alter_table`` will be invoked when any ``postgresql``
75dialect is used.
76
77.. _compilerext_compiling_subelements:
78
79Compiling sub-elements of a custom expression construct
80=======================================================
81
82The ``compiler`` argument is the
83:class:`~sqlalchemy.engine.interfaces.Compiled` object in use. This object
84can be inspected for any information about the in-progress compilation,
85including ``compiler.dialect``, ``compiler.statement`` etc. The
86:class:`~sqlalchemy.sql.compiler.SQLCompiler` and
87:class:`~sqlalchemy.sql.compiler.DDLCompiler` both include a ``process()``
88method which can be used for compilation of embedded attributes::
89
90 from sqlalchemy.sql.expression import Executable, ClauseElement
91
92
93 class InsertFromSelect(Executable, ClauseElement):
94 inherit_cache = False
95
96 def __init__(self, table, select):
97 self.table = table
98 self.select = select
99
100
101 @compiles(InsertFromSelect)
102 def visit_insert_from_select(element, compiler, **kw):
103 return "INSERT INTO %s (%s)" % (
104 compiler.process(element.table, asfrom=True, **kw),
105 compiler.process(element.select, **kw),
106 )
107
108
109 insert = InsertFromSelect(t1, select(t1).where(t1.c.x > 5))
110 print(insert)
111
112Produces (formatted for readability):
113
114.. sourcecode:: sql
115
116 INSERT INTO mytable (
117 SELECT mytable.x, mytable.y, mytable.z
118 FROM mytable
119 WHERE mytable.x > :x_1
120 )
121
122.. note::
123
124 The above ``InsertFromSelect`` construct is only an example, this actual
125 functionality is already available using the
126 :meth:`_expression.Insert.from_select` method.
127
128
129Cross Compiling between SQL and DDL compilers
130---------------------------------------------
131
132SQL and DDL constructs are each compiled using different base compilers -
133``SQLCompiler`` and ``DDLCompiler``. A common need is to access the
134compilation rules of SQL expressions from within a DDL expression. The
135``DDLCompiler`` includes an accessor ``sql_compiler`` for this reason, such as
136below where we generate a CHECK constraint that embeds a SQL expression::
137
138 @compiles(MyConstraint)
139 def compile_my_constraint(constraint, ddlcompiler, **kw):
140 kw["literal_binds"] = True
141 return "CONSTRAINT %s CHECK (%s)" % (
142 constraint.name,
143 ddlcompiler.sql_compiler.process(constraint.expression, **kw),
144 )
145
146Above, we add an additional flag to the process step as called by
147:meth:`.SQLCompiler.process`, which is the ``literal_binds`` flag. This
148indicates that any SQL expression which refers to a :class:`.BindParameter`
149object or other "literal" object such as those which refer to strings or
150integers should be rendered **in-place**, rather than being referred to as
151a bound parameter; when emitting DDL, bound parameters are typically not
152supported.
153
154
155Changing the default compilation of existing constructs
156=======================================================
157
158The compiler extension applies just as well to the existing constructs. When
159overriding the compilation of a built in SQL construct, the @compiles
160decorator is invoked upon the appropriate class (be sure to use the class,
161i.e. ``Insert`` or ``Select``, instead of the creation function such
162as ``insert()`` or ``select()``).
163
164Within the new compilation function, to get at the "original" compilation
165routine, use the appropriate visit_XXX method - this
166because compiler.process() will call upon the overriding routine and cause
167an endless loop. Such as, to add "prefix" to all insert statements::
168
169 from sqlalchemy.sql.expression import Insert
170
171
172 @compiles(Insert)
173 def prefix_inserts(insert, compiler, **kw):
174 return compiler.visit_insert(insert.prefix_with("some prefix"), **kw)
175
176The above compiler will prefix all INSERT statements with "some prefix" when
177compiled.
178
179.. _type_compilation_extension:
180
181Changing Compilation of Types
182=============================
183
184``compiler`` works for types, too, such as below where we implement the
185MS-SQL specific 'max' keyword for ``String``/``VARCHAR``::
186
187 @compiles(String, "mssql")
188 @compiles(VARCHAR, "mssql")
189 def compile_varchar(element, compiler, **kw):
190 if element.length == "max":
191 return "VARCHAR('max')"
192 else:
193 return compiler.visit_VARCHAR(element, **kw)
194
195
196 foo = Table("foo", metadata, Column("data", VARCHAR("max")))
197
198Subclassing Guidelines
199======================
200
201A big part of using the compiler extension is subclassing SQLAlchemy
202expression constructs. To make this easier, the expression and
203schema packages feature a set of "bases" intended for common tasks.
204A synopsis is as follows:
205
206* :class:`~sqlalchemy.sql.expression.ClauseElement` - This is the root
207 expression class. Any SQL expression can be derived from this base, and is
208 probably the best choice for longer constructs such as specialized INSERT
209 statements.
210
211* :class:`~sqlalchemy.sql.expression.ColumnElement` - The root of all
212 "column-like" elements. Anything that you'd place in the "columns" clause of
213 a SELECT statement (as well as order by and group by) can derive from this -
214 the object will automatically have Python "comparison" behavior.
215
216 :class:`~sqlalchemy.sql.expression.ColumnElement` classes want to have a
217 ``type`` member which is expression's return type. This can be established
218 at the instance level in the constructor, or at the class level if its
219 generally constant::
220
221 class timestamp(ColumnElement):
222 type = TIMESTAMP()
223 inherit_cache = True
224
225* :class:`~sqlalchemy.sql.functions.FunctionElement` - This is a hybrid of a
226 ``ColumnElement`` and a "from clause" like object, and represents a SQL
227 function or stored procedure type of call. Since most databases support
228 statements along the line of "SELECT FROM <some function>"
229 ``FunctionElement`` adds in the ability to be used in the FROM clause of a
230 ``select()`` construct::
231
232 from sqlalchemy.sql.expression import FunctionElement
233
234
235 class coalesce(FunctionElement):
236 name = "coalesce"
237 inherit_cache = True
238
239
240 @compiles(coalesce)
241 def compile(element, compiler, **kw):
242 return "coalesce(%s)" % compiler.process(element.clauses, **kw)
243
244
245 @compiles(coalesce, "oracle")
246 def compile(element, compiler, **kw):
247 if len(element.clauses) > 2:
248 raise TypeError(
249 "coalesce only supports two arguments on " "Oracle Database"
250 )
251 return "nvl(%s)" % compiler.process(element.clauses, **kw)
252
253* :class:`.ExecutableDDLElement` - The root of all DDL expressions,
254 like CREATE TABLE, ALTER TABLE, etc. Compilation of
255 :class:`.ExecutableDDLElement` subclasses is issued by a
256 :class:`.DDLCompiler` instead of a :class:`.SQLCompiler`.
257 :class:`.ExecutableDDLElement` can also be used as an event hook in
258 conjunction with event hooks like :meth:`.DDLEvents.before_create` and
259 :meth:`.DDLEvents.after_create`, allowing the construct to be invoked
260 automatically during CREATE TABLE and DROP TABLE sequences.
261
262 .. seealso::
263
264 :ref:`metadata_ddl_toplevel` - contains examples of associating
265 :class:`.DDL` objects (which are themselves :class:`.ExecutableDDLElement`
266 instances) with :class:`.DDLEvents` event hooks.
267
268* :class:`~sqlalchemy.sql.expression.Executable` - This is a mixin which
269 should be used with any expression class that represents a "standalone"
270 SQL statement that can be passed directly to an ``execute()`` method. It
271 is already implicit within ``DDLElement`` and ``FunctionElement``.
272
273Most of the above constructs also respond to SQL statement caching. A
274subclassed construct will want to define the caching behavior for the object,
275which usually means setting the flag ``inherit_cache`` to the value of
276``False`` or ``True``. See the next section :ref:`compilerext_caching`
277for background.
278
279
280.. _compilerext_caching:
281
282Enabling Caching Support for Custom Constructs
283==============================================
284
285SQLAlchemy as of version 1.4 includes a
286:ref:`SQL compilation caching facility <sql_caching>` which will allow
287equivalent SQL constructs to cache their stringified form, along with other
288structural information used to fetch results from the statement.
289
290For reasons discussed at :ref:`caching_caveats`, the implementation of this
291caching system takes a conservative approach towards including custom SQL
292constructs and/or subclasses within the caching system. This includes that
293any user-defined SQL constructs, including all the examples for this
294extension, will not participate in caching by default unless they positively
295assert that they are able to do so. The :attr:`.HasCacheKey.inherit_cache`
296attribute when set to ``True`` at the class level of a specific subclass
297will indicate that instances of this class may be safely cached, using the
298cache key generation scheme of the immediate superclass. This applies
299for example to the "synopsis" example indicated previously::
300
301 class MyColumn(ColumnClause):
302 inherit_cache = True
303
304
305 @compiles(MyColumn)
306 def compile_mycolumn(element, compiler, **kw):
307 return "[%s]" % element.name
308
309Above, the ``MyColumn`` class does not include any new state that
310affects its SQL compilation; the cache key of ``MyColumn`` instances will
311make use of that of the ``ColumnClause`` superclass, meaning it will take
312into account the class of the object (``MyColumn``), the string name and
313datatype of the object::
314
315 >>> MyColumn("some_name", String())._generate_cache_key()
316 CacheKey(
317 key=('0', <class '__main__.MyColumn'>,
318 'name', 'some_name',
319 'type', (<class 'sqlalchemy.sql.sqltypes.String'>,
320 ('length', None), ('collation', None))
321 ), bindparams=[])
322
323For objects that are likely to be **used liberally as components within many
324larger statements**, such as :class:`_schema.Column` subclasses and custom SQL
325datatypes, it's important that **caching be enabled as much as possible**, as
326this may otherwise negatively affect performance.
327
328An example of an object that **does** contain state which affects its SQL
329compilation is the one illustrated at :ref:`compilerext_compiling_subelements`;
330this is an "INSERT FROM SELECT" construct that combines together a
331:class:`_schema.Table` as well as a :class:`_sql.Select` construct, each of
332which independently affect the SQL string generation of the construct. For
333this class, the example illustrates that it simply does not participate in
334caching::
335
336 class InsertFromSelect(Executable, ClauseElement):
337 inherit_cache = False
338
339 def __init__(self, table, select):
340 self.table = table
341 self.select = select
342
343
344 @compiles(InsertFromSelect)
345 def visit_insert_from_select(element, compiler, **kw):
346 return "INSERT INTO %s (%s)" % (
347 compiler.process(element.table, asfrom=True, **kw),
348 compiler.process(element.select, **kw),
349 )
350
351While it is also possible that the above ``InsertFromSelect`` could be made to
352produce a cache key that is composed of that of the :class:`_schema.Table` and
353:class:`_sql.Select` components together, the API for this is not at the moment
354fully public. However, for an "INSERT FROM SELECT" construct, which is only
355used by itself for specific operations, caching is not as critical as in the
356previous example.
357
358For objects that are **used in relative isolation and are generally
359standalone**, such as custom :term:`DML` constructs like an "INSERT FROM
360SELECT", **caching is generally less critical** as the lack of caching for such
361a construct will have only localized implications for that specific operation.
362
363
364Further Examples
365================
366
367"UTC timestamp" function
368-------------------------
369
370A function that works like "CURRENT_TIMESTAMP" except applies the
371appropriate conversions so that the time is in UTC time. Timestamps are best
372stored in relational databases as UTC, without time zones. UTC so that your
373database doesn't think time has gone backwards in the hour when daylight
374savings ends, without timezones because timezones are like character
375encodings - they're best applied only at the endpoints of an application
376(i.e. convert to UTC upon user input, re-apply desired timezone upon display).
377
378For PostgreSQL and Microsoft SQL Server::
379
380 from sqlalchemy.sql import expression
381 from sqlalchemy.ext.compiler import compiles
382 from sqlalchemy.types import DateTime
383
384
385 class utcnow(expression.FunctionElement):
386 type = DateTime()
387 inherit_cache = True
388
389
390 @compiles(utcnow, "postgresql")
391 def pg_utcnow(element, compiler, **kw):
392 return "TIMEZONE('utc', CURRENT_TIMESTAMP)"
393
394
395 @compiles(utcnow, "mssql")
396 def ms_utcnow(element, compiler, **kw):
397 return "GETUTCDATE()"
398
399Example usage::
400
401 from sqlalchemy import Table, Column, Integer, String, DateTime, MetaData
402
403 metadata = MetaData()
404 event = Table(
405 "event",
406 metadata,
407 Column("id", Integer, primary_key=True),
408 Column("description", String(50), nullable=False),
409 Column("timestamp", DateTime, server_default=utcnow()),
410 )
411
412"GREATEST" function
413-------------------
414
415The "GREATEST" function is given any number of arguments and returns the one
416that is of the highest value - its equivalent to Python's ``max``
417function. A SQL standard version versus a CASE based version which only
418accommodates two arguments::
419
420 from sqlalchemy.sql import expression, case
421 from sqlalchemy.ext.compiler import compiles
422 from sqlalchemy.types import Numeric
423
424
425 class greatest(expression.FunctionElement):
426 type = Numeric()
427 name = "greatest"
428 inherit_cache = True
429
430
431 @compiles(greatest)
432 def default_greatest(element, compiler, **kw):
433 return compiler.visit_function(element)
434
435
436 @compiles(greatest, "sqlite")
437 @compiles(greatest, "mssql")
438 @compiles(greatest, "oracle")
439 def case_greatest(element, compiler, **kw):
440 arg1, arg2 = list(element.clauses)
441 return compiler.process(case((arg1 > arg2, arg1), else_=arg2), **kw)
442
443Example usage::
444
445 Session.query(Account).filter(
446 greatest(Account.checking_balance, Account.savings_balance) > 10000
447 )
448
449"false" expression
450------------------
451
452Render a "false" constant expression, rendering as "0" on platforms that
453don't have a "false" constant::
454
455 from sqlalchemy.sql import expression
456 from sqlalchemy.ext.compiler import compiles
457
458
459 class sql_false(expression.ColumnElement):
460 inherit_cache = True
461
462
463 @compiles(sql_false)
464 def default_false(element, compiler, **kw):
465 return "false"
466
467
468 @compiles(sql_false, "mssql")
469 @compiles(sql_false, "mysql")
470 @compiles(sql_false, "oracle")
471 def int_false(element, compiler, **kw):
472 return "0"
473
474Example usage::
475
476 from sqlalchemy import select, union_all
477
478 exp = union_all(
479 select(users.c.name, sql_false().label("enrolled")),
480 select(customers.c.name, customers.c.enrolled),
481 )
482
483"""
484from __future__ import annotations
485
486from typing import Any
487from typing import Callable
488from typing import Dict
489from typing import Type
490from typing import TYPE_CHECKING
491from typing import TypeVar
492
493from .. import exc
494from ..sql import sqltypes
495
496if TYPE_CHECKING:
497 from ..sql.compiler import SQLCompiler
498
499_F = TypeVar("_F", bound=Callable[..., Any])
500
501
502def compiles(class_: Type[Any], *specs: str) -> Callable[[_F], _F]:
503 """Register a function as a compiler for a
504 given :class:`_expression.ClauseElement` type."""
505
506 def decorate(fn: _F) -> _F:
507 # get an existing @compiles handler
508 existing = class_.__dict__.get("_compiler_dispatcher", None)
509
510 # get the original handler. All ClauseElement classes have one
511 # of these, but some TypeEngine classes will not.
512 existing_dispatch = getattr(class_, "_compiler_dispatch", None)
513
514 if not existing:
515 existing = _dispatcher()
516
517 if existing_dispatch:
518
519 def _wrap_existing_dispatch(
520 element: Any, compiler: SQLCompiler, **kw: Any
521 ) -> Any:
522 try:
523 return existing_dispatch(element, compiler, **kw)
524 except exc.UnsupportedCompilationError as uce:
525 raise exc.UnsupportedCompilationError(
526 compiler,
527 type(element),
528 message="%s construct has no default "
529 "compilation handler." % type(element),
530 ) from uce
531
532 existing.specs["default"] = _wrap_existing_dispatch
533
534 # TODO: why is the lambda needed ?
535 setattr(
536 class_,
537 "_compiler_dispatch",
538 lambda *arg, **kw: existing(*arg, **kw),
539 )
540 setattr(class_, "_compiler_dispatcher", existing)
541
542 if specs:
543 for s in specs:
544 existing.specs[s] = fn
545
546 else:
547 existing.specs["default"] = fn
548 return fn
549
550 return decorate
551
552
553def deregister(class_: Type[Any]) -> None:
554 """Remove all custom compilers associated with a given
555 :class:`_expression.ClauseElement` type.
556
557 """
558
559 if hasattr(class_, "_compiler_dispatcher"):
560 class_._compiler_dispatch = class_._original_compiler_dispatch
561 del class_._compiler_dispatcher
562
563
564class _dispatcher:
565 def __init__(self) -> None:
566 self.specs: Dict[str, Callable[..., Any]] = {}
567
568 def __call__(self, element: Any, compiler: SQLCompiler, **kw: Any) -> Any:
569 # TODO: yes, this could also switch off of DBAPI in use.
570 fn = self.specs.get(compiler.dialect.name, None)
571 if not fn:
572 try:
573 fn = self.specs["default"]
574 except KeyError as ke:
575 raise exc.UnsupportedCompilationError(
576 compiler,
577 type(element),
578 message="%s construct has no default "
579 "compilation handler." % type(element),
580 ) from ke
581
582 # if compilation includes add_to_result_map, collect add_to_result_map
583 # arguments from the user-defined callable, which are probably none
584 # because this is not public API. if it wasn't called, then call it
585 # ourselves.
586 arm = kw.get("add_to_result_map", None)
587 if arm:
588 arm_collection = []
589 kw["add_to_result_map"] = lambda *args: arm_collection.append(args)
590
591 expr = fn(element, compiler, **kw)
592
593 if arm:
594 if not arm_collection:
595 arm_collection.append(
596 (None, None, (element,), sqltypes.NULLTYPE)
597 )
598 for tup in arm_collection:
599 arm(*tup)
600 return expr