Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sqlalchemy/ext/compiler.py: 57%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

60 statements  

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