1# ext/compiler.py
2# Copyright (C) 2005-2026 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"""
484
485from __future__ import annotations
486
487from typing import Any
488from typing import Callable
489from typing import Dict
490from typing import Type
491from typing import TYPE_CHECKING
492from typing import TypeVar
493
494from .. import exc
495from ..sql import sqltypes
496
497if TYPE_CHECKING:
498 from ..sql.compiler import SQLCompiler
499
500_F = TypeVar("_F", bound=Callable[..., Any])
501
502
503def compiles(class_: Type[Any], *specs: str) -> Callable[[_F], _F]:
504 """Register a function as a compiler for a
505 given :class:`_expression.ClauseElement` type."""
506
507 def decorate(fn: _F) -> _F:
508 # get an existing @compiles handler
509 existing = class_.__dict__.get("_compiler_dispatcher", None)
510
511 # get the original handler. All ClauseElement classes have one
512 # of these, but some TypeEngine classes will not.
513 existing_dispatch = getattr(class_, "_compiler_dispatch", None)
514
515 if not existing:
516 existing = _dispatcher()
517
518 if existing_dispatch:
519
520 def _wrap_existing_dispatch(
521 element: Any, compiler: SQLCompiler, **kw: Any
522 ) -> Any:
523 try:
524 return existing_dispatch(element, compiler, **kw)
525 except exc.UnsupportedCompilationError as uce:
526 raise exc.UnsupportedCompilationError(
527 compiler,
528 type(element),
529 message="%s construct has no default "
530 "compilation handler." % type(element),
531 ) from uce
532
533 existing.specs["default"] = _wrap_existing_dispatch
534
535 # TODO: why is the lambda needed ?
536 setattr(
537 class_,
538 "_compiler_dispatch",
539 lambda *arg, **kw: existing(*arg, **kw),
540 )
541 setattr(class_, "_compiler_dispatcher", existing)
542
543 if specs:
544 for s in specs:
545 existing.specs[s] = fn
546
547 else:
548 existing.specs["default"] = fn
549 return fn
550
551 return decorate
552
553
554def deregister(class_: Type[Any]) -> None:
555 """Remove all custom compilers associated with a given
556 :class:`_expression.ClauseElement` type.
557
558 """
559
560 if hasattr(class_, "_compiler_dispatcher"):
561 class_._compiler_dispatch = class_._original_compiler_dispatch
562 del class_._compiler_dispatcher
563
564
565class _dispatcher:
566 def __init__(self) -> None:
567 self.specs: Dict[str, Callable[..., Any]] = {}
568
569 def __call__(self, element: Any, compiler: SQLCompiler, **kw: Any) -> Any:
570 # TODO: yes, this could also switch off of DBAPI in use.
571 fn = self.specs.get(compiler.dialect.name, None)
572 if not fn:
573 try:
574 fn = self.specs["default"]
575 except KeyError as ke:
576 raise exc.UnsupportedCompilationError(
577 compiler,
578 type(element),
579 message="%s construct has no default "
580 "compilation handler." % type(element),
581 ) from ke
582
583 # if compilation includes add_to_result_map, collect add_to_result_map
584 # arguments from the user-defined callable, which are probably none
585 # because this is not public API. if it wasn't called, then call it
586 # ourselves.
587 arm = kw.get("add_to_result_map", None)
588 if arm:
589 arm_collection = []
590 kw["add_to_result_map"] = lambda *args: arm_collection.append(args)
591
592 expr = fn(element, compiler, **kw)
593
594 if arm:
595 if not arm_collection:
596 arm_collection.append(
597 (None, None, (element,), sqltypes.NULLTYPE)
598 )
599 for tup in arm_collection:
600 arm(*tup)
601 return expr