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