requirements.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800
  1. # testing/requirements.py
  2. # Copyright (C) 2005-2017 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: http://www.opensource.org/licenses/mit-license.php
  7. """Global database feature support policy.
  8. Provides decorators to mark tests requiring specific feature support from the
  9. target database.
  10. External dialect test suites should subclass SuiteRequirements
  11. to provide specific inclusion/exclusions.
  12. """
  13. import sys
  14. from . import exclusions
  15. from .. import util
  16. class Requirements(object):
  17. pass
  18. class SuiteRequirements(Requirements):
  19. @property
  20. def create_table(self):
  21. """target platform can emit basic CreateTable DDL."""
  22. return exclusions.open()
  23. @property
  24. def drop_table(self):
  25. """target platform can emit basic DropTable DDL."""
  26. return exclusions.open()
  27. @property
  28. def foreign_keys(self):
  29. """Target database must support foreign keys."""
  30. return exclusions.open()
  31. @property
  32. def on_update_cascade(self):
  33. """"target database must support ON UPDATE..CASCADE behavior in
  34. foreign keys."""
  35. return exclusions.open()
  36. @property
  37. def non_updating_cascade(self):
  38. """target database must *not* support ON UPDATE..CASCADE behavior in
  39. foreign keys."""
  40. return exclusions.closed()
  41. @property
  42. def deferrable_fks(self):
  43. return exclusions.closed()
  44. @property
  45. def on_update_or_deferrable_fks(self):
  46. # TODO: exclusions should be composable,
  47. # somehow only_if([x, y]) isn't working here, negation/conjunctions
  48. # getting confused.
  49. return exclusions.only_if(
  50. lambda: self.on_update_cascade.enabled or
  51. self.deferrable_fks.enabled
  52. )
  53. @property
  54. def self_referential_foreign_keys(self):
  55. """Target database must support self-referential foreign keys."""
  56. return exclusions.open()
  57. @property
  58. def foreign_key_ddl(self):
  59. """Target database must support the DDL phrases for FOREIGN KEY."""
  60. return exclusions.open()
  61. @property
  62. def named_constraints(self):
  63. """target database must support names for constraints."""
  64. return exclusions.open()
  65. @property
  66. def subqueries(self):
  67. """Target database must support subqueries."""
  68. return exclusions.open()
  69. @property
  70. def offset(self):
  71. """target database can render OFFSET, or an equivalent, in a
  72. SELECT.
  73. """
  74. return exclusions.open()
  75. @property
  76. def bound_limit_offset(self):
  77. """target database can render LIMIT and/or OFFSET using a bound
  78. parameter
  79. """
  80. return exclusions.open()
  81. @property
  82. def parens_in_union_contained_select_w_limit_offset(self):
  83. """Target database must support parenthesized SELECT in UNION
  84. when LIMIT/OFFSET is specifically present.
  85. E.g. (SELECT ...) UNION (SELECT ..)
  86. This is known to fail on SQLite.
  87. """
  88. return exclusions.open()
  89. @property
  90. def parens_in_union_contained_select_wo_limit_offset(self):
  91. """Target database must support parenthesized SELECT in UNION
  92. when OFFSET/LIMIT is specifically not present.
  93. E.g. (SELECT ... LIMIT ..) UNION (SELECT .. OFFSET ..)
  94. This is known to fail on SQLite. It also fails on Oracle
  95. because without LIMIT/OFFSET, there is currently no step that
  96. creates an additional subquery.
  97. """
  98. return exclusions.open()
  99. @property
  100. def boolean_col_expressions(self):
  101. """Target database must support boolean expressions as columns"""
  102. return exclusions.closed()
  103. @property
  104. def nullsordering(self):
  105. """Target backends that support nulls ordering."""
  106. return exclusions.closed()
  107. @property
  108. def standalone_binds(self):
  109. """target database/driver supports bound parameters as column expressions
  110. without being in the context of a typed column.
  111. """
  112. return exclusions.closed()
  113. @property
  114. def intersect(self):
  115. """Target database must support INTERSECT or equivalent."""
  116. return exclusions.closed()
  117. @property
  118. def except_(self):
  119. """Target database must support EXCEPT or equivalent (i.e. MINUS)."""
  120. return exclusions.closed()
  121. @property
  122. def window_functions(self):
  123. """Target database must support window functions."""
  124. return exclusions.closed()
  125. @property
  126. def autoincrement_insert(self):
  127. """target platform generates new surrogate integer primary key values
  128. when insert() is executed, excluding the pk column."""
  129. return exclusions.open()
  130. @property
  131. def fetch_rows_post_commit(self):
  132. """target platform will allow cursor.fetchone() to proceed after a
  133. COMMIT.
  134. Typically this refers to an INSERT statement with RETURNING which
  135. is invoked within "autocommit". If the row can be returned
  136. after the autocommit, then this rule can be open.
  137. """
  138. return exclusions.open()
  139. @property
  140. def empty_inserts(self):
  141. """target platform supports INSERT with no values, i.e.
  142. INSERT DEFAULT VALUES or equivalent."""
  143. return exclusions.only_if(
  144. lambda config: config.db.dialect.supports_empty_insert or
  145. config.db.dialect.supports_default_values,
  146. "empty inserts not supported"
  147. )
  148. @property
  149. def insert_from_select(self):
  150. """target platform supports INSERT from a SELECT."""
  151. return exclusions.open()
  152. @property
  153. def returning(self):
  154. """target platform supports RETURNING."""
  155. return exclusions.only_if(
  156. lambda config: config.db.dialect.implicit_returning,
  157. "%(database)s %(does_support)s 'returning'"
  158. )
  159. @property
  160. def duplicate_names_in_cursor_description(self):
  161. """target platform supports a SELECT statement that has
  162. the same name repeated more than once in the columns list."""
  163. return exclusions.open()
  164. @property
  165. def denormalized_names(self):
  166. """Target database must have 'denormalized', i.e.
  167. UPPERCASE as case insensitive names."""
  168. return exclusions.skip_if(
  169. lambda config: not config.db.dialect.requires_name_normalize,
  170. "Backend does not require denormalized names."
  171. )
  172. @property
  173. def multivalues_inserts(self):
  174. """target database must support multiple VALUES clauses in an
  175. INSERT statement."""
  176. return exclusions.skip_if(
  177. lambda config: not config.db.dialect.supports_multivalues_insert,
  178. "Backend does not support multirow inserts."
  179. )
  180. @property
  181. def implements_get_lastrowid(self):
  182. """"target dialect implements the executioncontext.get_lastrowid()
  183. method without reliance on RETURNING.
  184. """
  185. return exclusions.open()
  186. @property
  187. def emulated_lastrowid(self):
  188. """"target dialect retrieves cursor.lastrowid, or fetches
  189. from a database-side function after an insert() construct executes,
  190. within the get_lastrowid() method.
  191. Only dialects that "pre-execute", or need RETURNING to get last
  192. inserted id, would return closed/fail/skip for this.
  193. """
  194. return exclusions.closed()
  195. @property
  196. def dbapi_lastrowid(self):
  197. """"target platform includes a 'lastrowid' accessor on the DBAPI
  198. cursor object.
  199. """
  200. return exclusions.closed()
  201. @property
  202. def views(self):
  203. """Target database must support VIEWs."""
  204. return exclusions.closed()
  205. @property
  206. def schemas(self):
  207. """Target database must support external schemas, and have one
  208. named 'test_schema'."""
  209. return exclusions.closed()
  210. @property
  211. def server_side_cursors(self):
  212. """Target dialect must support server side cursors."""
  213. return exclusions.only_if([
  214. lambda config: config.db.dialect.supports_server_side_cursors
  215. ], "no server side cursors support")
  216. @property
  217. def sequences(self):
  218. """Target database must support SEQUENCEs."""
  219. return exclusions.only_if([
  220. lambda config: config.db.dialect.supports_sequences
  221. ], "no sequence support")
  222. @property
  223. def sequences_optional(self):
  224. """Target database supports sequences, but also optionally
  225. as a means of generating new PK values."""
  226. return exclusions.only_if([
  227. lambda config: config.db.dialect.supports_sequences and
  228. config.db.dialect.sequences_optional
  229. ], "no sequence support, or sequences not optional")
  230. @property
  231. def reflects_pk_names(self):
  232. return exclusions.closed()
  233. @property
  234. def table_reflection(self):
  235. return exclusions.open()
  236. @property
  237. def view_column_reflection(self):
  238. """target database must support retrieval of the columns in a view,
  239. similarly to how a table is inspected.
  240. This does not include the full CREATE VIEW definition.
  241. """
  242. return self.views
  243. @property
  244. def view_reflection(self):
  245. """target database must support inspection of the full CREATE VIEW definition.
  246. """
  247. return self.views
  248. @property
  249. def schema_reflection(self):
  250. return self.schemas
  251. @property
  252. def primary_key_constraint_reflection(self):
  253. return exclusions.open()
  254. @property
  255. def foreign_key_constraint_reflection(self):
  256. return exclusions.open()
  257. @property
  258. def foreign_key_constraint_option_reflection(self):
  259. return exclusions.closed()
  260. @property
  261. def temp_table_reflection(self):
  262. return exclusions.open()
  263. @property
  264. def temp_table_names(self):
  265. """target dialect supports listing of temporary table names"""
  266. return exclusions.closed()
  267. @property
  268. def temporary_tables(self):
  269. """target database supports temporary tables"""
  270. return exclusions.open()
  271. @property
  272. def temporary_views(self):
  273. """target database supports temporary views"""
  274. return exclusions.closed()
  275. @property
  276. def index_reflection(self):
  277. return exclusions.open()
  278. @property
  279. def unique_constraint_reflection(self):
  280. """target dialect supports reflection of unique constraints"""
  281. return exclusions.open()
  282. @property
  283. def duplicate_key_raises_integrity_error(self):
  284. """target dialect raises IntegrityError when reporting an INSERT
  285. with a primary key violation. (hint: it should)
  286. """
  287. return exclusions.open()
  288. @property
  289. def unbounded_varchar(self):
  290. """Target database must support VARCHAR with no length"""
  291. return exclusions.open()
  292. @property
  293. def unicode_data(self):
  294. """Target database/dialect must support Python unicode objects with
  295. non-ASCII characters represented, delivered as bound parameters
  296. as well as in result rows.
  297. """
  298. return exclusions.open()
  299. @property
  300. def unicode_ddl(self):
  301. """Target driver must support some degree of non-ascii symbol
  302. names.
  303. """
  304. return exclusions.closed()
  305. @property
  306. def datetime_literals(self):
  307. """target dialect supports rendering of a date, time, or datetime as a
  308. literal string, e.g. via the TypeEngine.literal_processor() method.
  309. """
  310. return exclusions.closed()
  311. @property
  312. def datetime(self):
  313. """target dialect supports representation of Python
  314. datetime.datetime() objects."""
  315. return exclusions.open()
  316. @property
  317. def datetime_microseconds(self):
  318. """target dialect supports representation of Python
  319. datetime.datetime() with microsecond objects."""
  320. return exclusions.open()
  321. @property
  322. def datetime_historic(self):
  323. """target dialect supports representation of Python
  324. datetime.datetime() objects with historic (pre 1970) values."""
  325. return exclusions.closed()
  326. @property
  327. def date(self):
  328. """target dialect supports representation of Python
  329. datetime.date() objects."""
  330. return exclusions.open()
  331. @property
  332. def date_coerces_from_datetime(self):
  333. """target dialect accepts a datetime object as the target
  334. of a date column."""
  335. return exclusions.open()
  336. @property
  337. def date_historic(self):
  338. """target dialect supports representation of Python
  339. datetime.datetime() objects with historic (pre 1970) values."""
  340. return exclusions.closed()
  341. @property
  342. def time(self):
  343. """target dialect supports representation of Python
  344. datetime.time() objects."""
  345. return exclusions.open()
  346. @property
  347. def time_microseconds(self):
  348. """target dialect supports representation of Python
  349. datetime.time() with microsecond objects."""
  350. return exclusions.open()
  351. @property
  352. def binary_comparisons(self):
  353. """target database/driver can allow BLOB/BINARY fields to be compared
  354. against a bound parameter value.
  355. """
  356. return exclusions.open()
  357. @property
  358. def binary_literals(self):
  359. """target backend supports simple binary literals, e.g. an
  360. expression like::
  361. SELECT CAST('foo' AS BINARY)
  362. Where ``BINARY`` is the type emitted from :class:`.LargeBinary`,
  363. e.g. it could be ``BLOB`` or similar.
  364. Basically fails on Oracle.
  365. """
  366. return exclusions.open()
  367. @property
  368. def json_type(self):
  369. """target platform implements a native JSON type."""
  370. return exclusions.closed()
  371. @property
  372. def json_array_indexes(self):
  373. """"target platform supports numeric array indexes
  374. within a JSON structure"""
  375. return self.json_type
  376. @property
  377. def precision_numerics_general(self):
  378. """target backend has general support for moderately high-precision
  379. numerics."""
  380. return exclusions.open()
  381. @property
  382. def precision_numerics_enotation_small(self):
  383. """target backend supports Decimal() objects using E notation
  384. to represent very small values."""
  385. return exclusions.closed()
  386. @property
  387. def precision_numerics_enotation_large(self):
  388. """target backend supports Decimal() objects using E notation
  389. to represent very large values."""
  390. return exclusions.closed()
  391. @property
  392. def precision_numerics_many_significant_digits(self):
  393. """target backend supports values with many digits on both sides,
  394. such as 319438950232418390.273596, 87673.594069654243
  395. """
  396. return exclusions.closed()
  397. @property
  398. def precision_numerics_retains_significant_digits(self):
  399. """A precision numeric type will return empty significant digits,
  400. i.e. a value such as 10.000 will come back in Decimal form with
  401. the .000 maintained."""
  402. return exclusions.closed()
  403. @property
  404. def precision_generic_float_type(self):
  405. """target backend will return native floating point numbers with at
  406. least seven decimal places when using the generic Float type.
  407. """
  408. return exclusions.open()
  409. @property
  410. def floats_to_four_decimals(self):
  411. """target backend can return a floating-point number with four
  412. significant digits (such as 15.7563) accurately
  413. (i.e. without FP inaccuracies, such as 15.75629997253418).
  414. """
  415. return exclusions.open()
  416. @property
  417. def fetch_null_from_numeric(self):
  418. """target backend doesn't crash when you try to select a NUMERIC
  419. value that has a value of NULL.
  420. Added to support Pyodbc bug #351.
  421. """
  422. return exclusions.open()
  423. @property
  424. def text_type(self):
  425. """Target database must support an unbounded Text() "
  426. "type such as TEXT or CLOB"""
  427. return exclusions.open()
  428. @property
  429. def empty_strings_varchar(self):
  430. """target database can persist/return an empty string with a
  431. varchar.
  432. """
  433. return exclusions.open()
  434. @property
  435. def empty_strings_text(self):
  436. """target database can persist/return an empty string with an
  437. unbounded text."""
  438. return exclusions.open()
  439. @property
  440. def selectone(self):
  441. """target driver must support the literal statement 'select 1'"""
  442. return exclusions.open()
  443. @property
  444. def savepoints(self):
  445. """Target database must support savepoints."""
  446. return exclusions.closed()
  447. @property
  448. def two_phase_transactions(self):
  449. """Target database must support two-phase transactions."""
  450. return exclusions.closed()
  451. @property
  452. def update_from(self):
  453. """Target must support UPDATE..FROM syntax"""
  454. return exclusions.closed()
  455. @property
  456. def update_where_target_in_subquery(self):
  457. """Target must support UPDATE where the same table is present in a
  458. subquery in the WHERE clause.
  459. This is an ANSI-standard syntax that apparently MySQL can't handle,
  460. such as:
  461. UPDATE documents SET flag=1 WHERE documents.title IN
  462. (SELECT max(documents.title) AS title
  463. FROM documents GROUP BY documents.user_id
  464. )
  465. """
  466. return exclusions.open()
  467. @property
  468. def mod_operator_as_percent_sign(self):
  469. """target database must use a plain percent '%' as the 'modulus'
  470. operator."""
  471. return exclusions.closed()
  472. @property
  473. def percent_schema_names(self):
  474. """target backend supports weird identifiers with percent signs
  475. in them, e.g. 'some % column'.
  476. this is a very weird use case but often has problems because of
  477. DBAPIs that use python formatting. It's not a critical use
  478. case either.
  479. """
  480. return exclusions.closed()
  481. @property
  482. def order_by_label_with_expression(self):
  483. """target backend supports ORDER BY a column label within an
  484. expression.
  485. Basically this::
  486. select data as foo from test order by foo || 'bar'
  487. Lots of databases including PostgreSQL don't support this,
  488. so this is off by default.
  489. """
  490. return exclusions.closed()
  491. @property
  492. def unicode_connections(self):
  493. """Target driver must support non-ASCII characters being passed at
  494. all.
  495. """
  496. return exclusions.open()
  497. @property
  498. def graceful_disconnects(self):
  499. """Target driver must raise a DBAPI-level exception, such as
  500. InterfaceError, when the underlying connection has been closed
  501. and the execute() method is called.
  502. """
  503. return exclusions.open()
  504. @property
  505. def skip_mysql_on_windows(self):
  506. """Catchall for a large variety of MySQL on Windows failures"""
  507. return exclusions.open()
  508. @property
  509. def ad_hoc_engines(self):
  510. """Test environment must allow ad-hoc engine/connection creation.
  511. DBs that scale poorly for many connections, even when closed, i.e.
  512. Oracle, may use the "--low-connections" option which flags this
  513. requirement as not present.
  514. """
  515. return exclusions.skip_if(
  516. lambda config: config.options.low_connections)
  517. @property
  518. def timing_intensive(self):
  519. return exclusions.requires_tag("timing_intensive")
  520. @property
  521. def memory_intensive(self):
  522. return exclusions.requires_tag("memory_intensive")
  523. @property
  524. def threading_with_mock(self):
  525. """Mark tests that use threading and mock at the same time - stability
  526. issues have been observed with coverage + python 3.3
  527. """
  528. return exclusions.skip_if(
  529. lambda config: util.py3k and config.options.has_coverage,
  530. "Stability issues with coverage + py3k"
  531. )
  532. @property
  533. def python2(self):
  534. return exclusions.skip_if(
  535. lambda: sys.version_info >= (3,),
  536. "Python version 2.xx is required."
  537. )
  538. @property
  539. def python3(self):
  540. return exclusions.skip_if(
  541. lambda: sys.version_info < (3,),
  542. "Python version 3.xx is required."
  543. )
  544. @property
  545. def cpython(self):
  546. return exclusions.only_if(
  547. lambda: util.cpython,
  548. "cPython interpreter needed"
  549. )
  550. @property
  551. def non_broken_pickle(self):
  552. from sqlalchemy.util import pickle
  553. return exclusions.only_if(
  554. lambda: not util.pypy and pickle.__name__ == 'cPickle'
  555. or sys.version_info >= (3, 2),
  556. "Needs cPickle+cPython or newer Python 3 pickle"
  557. )
  558. @property
  559. def predictable_gc(self):
  560. """target platform must remove all cycles unconditionally when
  561. gc.collect() is called, as well as clean out unreferenced subclasses.
  562. """
  563. return self.cpython
  564. @property
  565. def no_coverage(self):
  566. """Test should be skipped if coverage is enabled.
  567. This is to block tests that exercise libraries that seem to be
  568. sensitive to coverage, such as PostgreSQL notice logging.
  569. """
  570. return exclusions.skip_if(
  571. lambda config: config.options.has_coverage,
  572. "Issues observed when coverage is enabled"
  573. )
  574. def _has_mysql_on_windows(self, config):
  575. return False
  576. def _has_mysql_fully_case_sensitive(self, config):
  577. return False
  578. @property
  579. def sqlite(self):
  580. return exclusions.skip_if(lambda: not self._has_sqlite())
  581. @property
  582. def cextensions(self):
  583. return exclusions.skip_if(
  584. lambda: not self._has_cextensions(), "C extensions not installed"
  585. )
  586. def _has_sqlite(self):
  587. from sqlalchemy import create_engine
  588. try:
  589. create_engine('sqlite://')
  590. return True
  591. except ImportError:
  592. return False
  593. def _has_cextensions(self):
  594. try:
  595. from sqlalchemy import cresultproxy, cprocessors
  596. return True
  597. except ImportError:
  598. return False