Newer
Older
sqlmarker / Unit_testing / Schema.php
  1. <?php
  2. require_once "ArrayDataSet.php";
  3. require_once "Reporter.php";
  4.  
  5. abstract class PHPUnit_Extensions_Database_TestCase_CreateTable extends PHPUnit_Extensions_Database_TestCase
  6. {
  7. /**
  8. * Only instantiate PDO once for test clean-up/fixture load.
  9. *
  10. * @access private
  11. */
  12. static private $pdo = null;
  13. /**
  14. * Only instantiate PHPUnit_Extensions_Database_DB_IDatabaseConnection once per test.
  15. *
  16. * @access private
  17. */
  18. private $conn = null;
  19. /**
  20. * Reporter object. Only need one for the entire test run.
  21. *
  22. * @access private
  23. */
  24. static private $reporter = null;
  25. /**
  26. * List of possible mark adjustments for errors (negative) or bonuses (positive).
  27. *
  28. * @access protected
  29. */
  30. protected $markAdjustments = array(
  31. 'missingTable' => -5,
  32. 'missingColumn' => -1,
  33. 'incorrectPK' => -1,
  34. 'incorrectFK' => -1,
  35. 'misspelledIdentifier' => -1,
  36. 'incorrectDataType' => -1,
  37. 'missingCheck' => -1,
  38. 'delimitedIdentifier' => -0.5,
  39. 'unnamedConstraint' => -0.5,
  40. 'incorrectLength' => -0.5,
  41. 'incorrectDefault' => -0.5,
  42. 'incorrectCheck' => -0.5,
  43. 'incorrectNullability' => -0.5,
  44. );
  45.  
  46. /**
  47. * Asserts that two variables are equal.
  48. *
  49. * @param mixed $expected
  50. * @param mixed $actual
  51. * @param string $message
  52. * @param float $delta
  53. * @param integer $maxDepth
  54. * @param boolean $canonicalize
  55. * @param boolean $ignoreCase
  56. */
  57. public static function assertEqualsSC($expected, $actual, $message = '', $delta = 0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE)
  58. {
  59. $constraint = new PHPUnit_Framework_Constraint_IsEqualSC(
  60. $expected, $delta, $maxDepth, $canonicalize, $ignoreCase
  61. );
  62.  
  63. self::assertThat($actual, $constraint, $message);
  64. }
  65.  
  66.  
  67. /**
  68. * Asserts that a value is greater than or equal to another value.
  69. *
  70. * @param mixed $expected
  71. * @param mixed $actual
  72. * @param string $message
  73. * @since Method available since Release 3.1.0
  74. */
  75. public static function assertGreaterThanOrEqualSC($expected, $actual, $message = '')
  76. {
  77. self::assertThat(
  78. $actual, self::greaterThanOrEqualSC($expected), $message
  79. );
  80. }
  81.  
  82. /**
  83. * Asserts that a value is smaller than or equal to another value.
  84. *
  85. * @param mixed $expected
  86. * @param mixed $actual
  87. * @param string $message
  88. * @since Method available since Release 3.1.0
  89. */
  90. public static function assertLessThanOrEqualSC($expected, $actual, $message = '')
  91. {
  92. self::assertThat($actual, self::lessThanOrEqualSC($expected), $message);
  93. }
  94.  
  95.  
  96. /**
  97. * Returns a PHPUnit_Framework_Constraint_Or matcher object that wraps
  98. * a PHPUnit_Framework_Constraint_IsEqual and a
  99. * PHPUnit_Framework_Constraint_GreaterThan matcher object.
  100. *
  101. * @param mixed $value
  102. * @return PHPUnit_Framework_Constraint_Or
  103. * @since Method available since Release 3.1.0
  104. */
  105. public static function greaterThanOrEqualSC($value)
  106. {
  107. return self::logicalOr(
  108. new PHPUnit_Framework_Constraint_IsEqualSC($value),
  109. new PHPUnit_Framework_Constraint_GreaterThanSC($value)
  110. );
  111. }
  112.  
  113.  
  114. /**
  115. * Returns a PHPUnit_Framework_Constraint_Or matcher object that wraps
  116. * a PHPUnit_Framework_Constraint_IsEqual and a
  117. * PHPUnit_Framework_Constraint_LessThan matcher object.
  118. *
  119. * @param mixed $value
  120. * @return PHPUnit_Framework_Constraint_Or
  121. * @since Method available since Release 3.1.0
  122. */
  123. public static function lessThanOrEqualSC($value)
  124. {
  125. return self::logicalOr(
  126. new PHPUnit_Framework_Constraint_IsEqualSC($value),
  127. new PHPUnit_Framework_Constraint_LessThanSC($value)
  128. );
  129. }
  130.  
  131.  
  132. /**
  133. * Convert the input text into a form that is acceptable to SQL. Text values are wrapped in '', and any embedded ' are converted to ''. "&" is also converted to "'||chr(38)||'" (mainly for Oracle).
  134. */
  135. protected function sqlifyValue( $srcValue, $srcType )
  136. {
  137. $sqlifiedValue = $srcValue;
  138. if ( $srcType === 'TEXT' )
  139. {
  140. $sqlifiedValue = str_replace( "'", "''", $sqlifiedValue );
  141. $sqlifiedValue = str_replace( '&', "' || chr(38) || '", $sqlifiedValue );
  142. }
  143. if ( ( $srcType === 'TEXT' ) || ( $srcType === 'DATE' ) )
  144. {
  145. $sqlifiedValue = "'" . $sqlifiedValue . "'";
  146. }
  147. return $sqlifiedValue;
  148. }
  149. /**#@+
  150. * Return table name, list of columns, etc.
  151. *
  152. * These are a hack. I would have preferred to have them as class member variables, but this doesn't work with parameterised tests based on data providers, because the data providers are executed before the member variables are initialised (even before static member initialisation!!). This means that the member variables would be empty at the time the provider runs, and thus nothing would happen.
  153. *
  154. * The workaround is for subclasses to implement the following methods. Each method should return the appropriate value to the caller (e.g., an array of strings for the column list).
  155. *
  156. * @abstract
  157. * @access protected
  158. */
  159. /**
  160. * Return the SQL table name.
  161. *
  162. * @return string
  163. */
  164. abstract protected function getTableName();
  165. /**
  166. * Return a list of the table's column names.
  167. *
  168. * @return array( string+ )
  169. */
  170. abstract protected function getColumnList();
  171. /**
  172. * Return a list of the table's primary key column names.
  173. *
  174. * @return array( string+ )
  175. */
  176. abstract protected function getPKColumnList();
  177. /**
  178. * Return a list of the table's foreign key column names.
  179. *
  180. * This should return a list of column names for each FK, indexed by the name of the referenced table.
  181. *
  182. * @return array( string => array( string+ ) )
  183. */
  184. abstract protected function getFKColumnList();
  185. /**
  186. * Return a list of the table's UNIQUE constraint column names.
  187. *
  188. * This should return a list of column names for each UNIQUE constraint.
  189. *
  190. * @return array( array( string+ ) )
  191. */
  192. abstract protected function getUniqueColumnList();
  193. /**
  194. * Return whether or not the fixture should be loaded.
  195. *
  196. * @return boolean
  197. */
  198. abstract protected function willLoadFixture();
  199. /**#@-*/
  200. /**
  201. * Return the list of primary key column names as an array dataset.
  202. *
  203. * The argument specifies the name of the table in the dataset.
  204. *
  205. * @return SchemaTesting_DbUnit_ArrayDataSet
  206. */
  207. protected function getPKColumnListAsDataSet( $datasetTableName )
  208. {
  209. $theList = array();
  210. foreach ( $this->getPKColumnList() as $columnName )
  211. {
  212. array_push( $theList, array( 'COLUMN_NAME' => $columnName ) );
  213. }
  214. return new SchemaTesting_DbUnit_ArrayDataSet( array( $datasetTableName => $theList ) );
  215. }
  216. /**
  217. * Return the list of column names for the foreign key on the current table that references the specified table.
  218. *
  219. * @return array( string+ )
  220. */
  221. protected function getFKColumnListForTable( $referencedTable )
  222. {
  223. $theList = array();
  224. $allFKColumns = $this->getFKColumnList();
  225. return $allFKColumns[$referencedTable];
  226. }
  227. /**
  228. * Return the list of column names for a given foreign key as an array data set.
  229. *
  230. * The second argument specifies the name of the table in the dataset.
  231. *
  232. * @return SchemaTesting_DbUnit_ArrayDataSet
  233. */
  234. protected function getFKColumnListForTableAsDataSet( $referencedTable, $datasetTableName )
  235. {
  236. $theList = array();
  237. $fkColumns = $this->getFKColumnListForTable( $referencedTable );
  238. foreach ( $fkColumns as $columnName )
  239. {
  240. array_push( $theList, array( 'COLUMN_NAME' => $columnName ) );
  241. }
  242. return new SchemaTesting_DbUnit_ArrayDataSet( array( $datasetTableName => $theList ) );
  243. }
  244. /**
  245. * Return the list of column names for each unique constraint as an array data set.
  246. *
  247. * The argument specifies the name of the table in the dataset.
  248. *
  249. * @return SchemaTesting_DbUnit_ArrayDataSet
  250. */
  251. protected function getUniqueColumnListAsDataSet( $datasetTableName )
  252. {
  253. $theList = array();
  254. $uniqueColumns = $this->getUniqueColumnList();
  255. foreach ( $uniqueColumns as $constraint )
  256. {
  257. foreach ( $constraint as $key => $columnName )
  258. {
  259. array_push( $theList, array( 'COLUMN_NAME' => $columnName, 'POSITION' => $key + 1 ) );
  260. }
  261. }
  262. return new SchemaTesting_DbUnit_ArrayDataSet( array( $datasetTableName => $theList ) );
  263. }
  264. /**
  265. * Return a list of aliases for a given column name (if any).
  266. *
  267. * @return array
  268. */
  269. protected function getColumnAliases( $columnName )
  270. {
  271. $theColumns = $this->getColumnList();
  272. if ( isset( $theColumns[$columnName]['aliases'] ) )
  273. {
  274. return $theColumns[$columnName]['aliases'];
  275. }
  276. return array();
  277. }
  278. /**
  279. * Return a test INSERT statement, using standard test values except for those specified in the substitutions list. If you want to test NULL or DEFAULT, just substitute the standard test value with the 'NULL' and 'DEFAULT' keywords, respectively.
  280. *
  281. * @return string
  282. */
  283. protected function constructInsert( $substitutions )
  284. {
  285. $columnValues = array();
  286. foreach ( $this->getColumnList() as $name => $details )
  287. {
  288. if ( array_key_exists( $name, $substitutions ) )
  289. {
  290. $columnValues[] = $this->sqlifyValue( $substitutions[$name], $details['generic_type'] );
  291. unset( $substitutions[$name] );
  292. }
  293. else
  294. {
  295. $columnValues[] = $this->sqlifyValue( $details['test_value'], $details['generic_type'] );
  296. }
  297. }
  298. return sprintf(
  299. "INSERT INTO %s ( %s ) VALUES ( %s )",
  300. $this->getTableName(),
  301. implode( ', ', array_keys( $this->getColumnList() ) ),
  302. implode( ', ', $columnValues )
  303. );
  304. }
  305. /**
  306. * Return a test INSERT statement, using standard test values.
  307. *
  308. * This just works by calling constructInsert with an empty substitutions list.
  309. *
  310. * @return string
  311. */
  312. protected function getStandardTestInsert()
  313. {
  314. return constructInsert( array() );
  315. }
  316. /**
  317. * Return database connection.
  318. *
  319. * @access protected
  320. * @return PHPUnit_Extensions_Database_DB_IDatabaseConnection
  321. * @todo Parameterise the connection details.
  322. */
  323. protected function getConnection()
  324. {
  325. if ( $this->conn === null )
  326. {
  327. if ( self::$pdo == null )
  328. {
  329. self::$pdo = new PDO( "oci:dbname=" . ORACLE_SERVICE_ID, ORACLE_USERNAME, ORACLE_PASSWORD );
  330. }
  331. $this->conn = $this->createDefaultDBConnection( self::$pdo, ORACLE_USERNAME );
  332. }
  333.  
  334. return $this->conn;
  335. }
  336. /**
  337. * Return the reporter object.
  338. *
  339. * @access public
  340. * @return Reporter
  341. */
  342. static public function getReporter()
  343. {
  344. return self::$reporter;
  345. }
  346. /**
  347. * Set the reporter object.
  348. *
  349. * @access public
  350. * @return void
  351. */
  352. static public function setReporter( $newReporter )
  353. {
  354. self::$reporter = $newReporter;
  355. }
  356. /**
  357. * Return the appropriate fixture setup operation, depending on whether the fixture will be loaded.
  358. *
  359. * @access protected
  360. * @return PHPUnit_Extensions_Database_Operation_DatabaseOperation
  361. */
  362. protected function getSetUpOperation()
  363. {
  364. if ( $this->willLoadFixture() )
  365. {
  366. // We can't use the default fixture setup operations with Oracle, because TRUNCATE doesn't work on
  367. // tables that are referenced by foreign keys, even if the table is empty! We use the DELETE_ALL
  368. // operation instead.
  369. return new PHPUnit_Extensions_Database_Operation_Composite(
  370. array(
  371. PHPUnit_Extensions_Database_Operation_Factory::DELETE_ALL(),
  372. PHPUnit_Extensions_Database_Operation_Factory::INSERT()
  373. )
  374. );
  375. }
  376. else
  377. {
  378. // If we're testing whether a table exists, and it doesn't, attempting to set up the fixture
  379. // will fail with a nasty SQL error! So we just zero out the operation.
  380. return new PHPUnit_Extensions_Database_Operation_Composite( array() );
  381. }
  382. }
  383. /**
  384. * Return the appropriate fixture teardown operation, depending on whether the fixture will be loaded.
  385. *
  386. * We can't use the standard fixture teardown operation with Oracle, because TRUNCATE doesn't work on tables that are referenced by foreign keys, even if the table is empty! We use the DELETE_ALL action operation instead.
  387. *
  388. * @access protected
  389. * @return PHPUnit_Extensions_Database_Operation_DatabaseOperation
  390. */
  391. protected function getTearDownOperation()
  392. {
  393. if ( $this->willLoadFixture() )
  394. {
  395. // We can't use the default fixture teardown operations with Oracle, because TRUNCATE doesn't work on
  396. // tables that are referenced by foreign keys, even if the table is empty! We use the DELETE_ALL
  397. // operation instead.
  398. return new PHPUnit_Extensions_Database_Operation_Composite( array( PHPUnit_Extensions_Database_Operation_Factory::DELETE_ALL() ) );
  399. }
  400. else
  401. {
  402. // If we're testing whether a table exists, and it doesn't, attempting to tear down the fixture
  403. // will fail with a nasty SQL error! So we just zero out the operation.
  404. return new PHPUnit_Extensions_Database_Operation_Composite( array() );
  405. }
  406. }
  407. /**
  408. * Data provider to return a list of all column names.
  409. *
  410. * If your test needs to iterate through all of the table's columns, then use this method as the data provider. Each column name is presented to the consumer in turn.
  411. *
  412. * @access public
  413. * @return array( array( string )* )
  414. */
  415. public function provideColumnNames()
  416. {
  417. $theList = array();
  418. foreach ( array_keys( $this->getColumnList() ) as $columnName )
  419. {
  420. array_push( $theList, array( $columnName ) );
  421. }
  422. return $theList;
  423. }
  424. /**
  425. * Data provider to return a list of all columns and their data types.
  426. *
  427. * If your test needs to iterate through all column data types of a table, then use this method as the data provider. Each column name plus a list of possible data types is presented to the consumer in turn.
  428. *
  429. * @access public
  430. * @return array( array( string, array( string+ ) )* )
  431. */
  432. public function provideColumnTypes()
  433. {
  434. $theList = array();
  435. foreach ( $this->getColumnList() as $columnName => $columnDetails )
  436. {
  437. array_push( $theList, array( $columnName, $columnDetails['sql_type'] ) );
  438. }
  439. return $theList;
  440. }
  441. /**
  442. * Data provider to return a list of all columns and their length-related information.
  443. *
  444. * If your test needs to iterate through all column lengths of a table, then use this method as the data provider. Each column name plus its generic data type, minimum length, maximum length (for a numeric column, this represents the precision) and number of decimal places (technically, the scale) are presented to the consumer in turn. Columns with no specified length (e.g., dates, BLOBs, CLOBs) should not have their min_length and max_length values set (although CLOBs are a slightly tricky case where the same effect could be achieved using a largish VARCHAR2 --- this is handled as a special case in assertColumnLength() below).
  445. *
  446. * @access public
  447. * @return array( array( string, string, int, int, int )* )
  448. */
  449. public function provideColumnLengths()
  450. {
  451. $theList = array();
  452. foreach ( $this->getColumnList() as $columnName => $columnDetails )
  453. {
  454. $minLength = ( array_key_exists( 'min_length', $columnDetails ) ) ? $columnDetails['min_length'] : 0;
  455. $maxLength = ( array_key_exists( 'max_length', $columnDetails ) ) ? $columnDetails['max_length'] : 0;
  456. $numDecimals = ( array_key_exists( 'decimals', $columnDetails ) ) ? $columnDetails['decimals'] : 0;
  457. // If min_length and max_length are missing, then it has no length at all (e.g., DATE, BLOB, CLOB).
  458. // If min_length is null, then the length is effectively unlimited (implying that no length should be imposed.)
  459. if ( is_null( $minLength ) || ( $minLength > 0 ) || ( $maxLength > 0 ) )
  460. {
  461. array_push( $theList, array( $columnName, $columnDetails['generic_type'], $minLength, $maxLength, $numDecimals ) );
  462. }
  463. }
  464. // If there are none (pretty unlikely), push a marker onto the stack so that we can skip the test.
  465. if ( count( $theList ) == 0 )
  466. {
  467. array_push( $theList, array( '___NO_DATA___', 'NULL', 0, 0, 0 ) );
  468. }
  469. return $theList;
  470. }
  471. /**
  472. * Data provider to return a list of all columns and their nullabilities.
  473. *
  474. * If your test needs to iterate through all column nullabilities of a table, then use this method as the data provider. Each column name plus the appropriate nullability value is presented to the consumer in turn.
  475. *
  476. * @access public
  477. * @return array( array( string, string )* )
  478. */
  479. public function provideColumnNullabilities()
  480. {
  481. $theList = array();
  482. foreach ( $this->getColumnList() as $columnName => $columnDetails )
  483. {
  484. $isNullable = $columnDetails['nullable'] ? 'Y' : 'N';
  485. array_push( $theList, array( $columnName, $isNullable ) );
  486. }
  487. return $theList;
  488. }
  489. /**
  490. * Data provider to return a list of all columns that should have defaults.
  491. *
  492. * If your test needs to iterate through all column defaults of a table, then use this method as the data provider. Each column name is presented to the consumer in turn.
  493. *
  494. * @access public
  495. * @return array( array( string, string )* )
  496. */
  497. public function provideColumnDefaults()
  498. {
  499. $theList = array();
  500. foreach ( $this->getColumnList() as $columnName => $columnDetails )
  501. {
  502. if ( isset( $columnDetails['default'] ) )
  503. array_push( $theList, array( $columnName, $columnDetails['default'] ) );
  504. }
  505. return $theList;
  506. }
  507. /**
  508. * Data provider to return a list of all columns and their legal (enumerated) values.
  509. *
  510. * If your test needs to iterate through all legal values of the columns of a table, then use this method as the data provider. Each column name plus a valid legal value is presented to the consumer in turn. (Only for those columns that have them.)
  511. *
  512. * @access public
  513. * @return array( array( string, array( string+ ) )* )
  514. */
  515. public function provideColumnLegalValues()
  516. {
  517. $theList = array();
  518. foreach ( $this->getColumnList() as $columnName => $columnDetails )
  519. {
  520. if ( array_key_exists( 'legal_values', $columnDetails ) )
  521. {
  522. foreach ( $columnDetails['legal_values'] as $legalValue )
  523. {
  524. array_push( $theList, array( $columnName, $legalValue ) );
  525. }
  526. }
  527. }
  528. // If there are none, push a marker onto the stack so that we can skip the test.
  529. if ( count( $theList ) == 0 )
  530. {
  531. array_push( $theList, array( '___NO_DATA___', array() ) );
  532. }
  533. return $theList;
  534. }
  535. /**
  536. * Data provider to return a list of all text columns and some illegal (enumerated) values.
  537. *
  538. * If your test needs to iterate through some illegal values of the columns of a table, then use this method as the data provider. Each column name plus a known illegal value is presented to the consumer in turn. (Only for those columns that have them.)
  539. *
  540. * @access public
  541. * @return array( array( string, array( string+ ) )* )
  542. */
  543. public function provideColumnIllegalValues()
  544. {
  545. $theList = array();
  546. foreach ( $this->getColumnList() as $columnName => $columnDetails )
  547. {
  548. if ( array_key_exists( 'illegal_values', $columnDetails ) )
  549. {
  550. foreach ( $columnDetails['illegal_values'] as $illegalValue )
  551. {
  552. array_push( $theList, array( $columnName, $illegalValue ) );
  553. }
  554. }
  555. }
  556. // If there are none, push a marker onto the stack so that we can skip the test.
  557. if ( count( $theList ) == 0 )
  558. {
  559. array_push( $theList, array( '___NO_DATA___', array() ) );
  560. }
  561. return $theList;
  562. }
  563. /**
  564. * Data provider to return a list of all columns and their underflow values.
  565. *
  566. * If your test needs to iterate through all underflow values of the columns of a table, then use this method as the data provider. Each column name plus a valid underflow value is presented to the consumer in turn. (Only for those columns that have them.)
  567. *
  568. * @access public
  569. * @return array( array( string, array( string+ ) )* )
  570. */
  571. public function provideColumnUnderflowValues()
  572. {
  573. $theList = array();
  574. foreach ( $this->getColumnList() as $columnName => $columnDetails )
  575. {
  576. if ( array_key_exists( 'underflow', $columnDetails ) )
  577. {
  578. array_push( $theList, array( $columnName, $columnDetails['underflow'] ) );
  579. }
  580. }
  581. // If there are none, push a marker onto the stack so that we can skip the test.
  582. if ( count( $theList ) == 0 )
  583. {
  584. array_push( $theList, array( '___NO_DATA___', 0 ) );
  585. }
  586. return $theList;
  587. }
  588. /**
  589. * Data provider to return a list of all columns and their overflow values.
  590. *
  591. * If your test needs to iterate through all overflow values of the columns of a table, then use this method as the data provider. Each column name plus a valid overflow value is presented to the consumer in turn. (Only for those columns that have them.)
  592. *
  593. * @access public
  594. * @return array( array( string, array( string+ ) )* )
  595. */
  596. public function provideColumnOverflowValues()
  597. {
  598. $theList = array();
  599. foreach ( $this->getColumnList() as $columnName => $columnDetails )
  600. {
  601. if ( array_key_exists( 'overflow', $columnDetails ) )
  602. {
  603. array_push( $theList, array( $columnName, $columnDetails['overflow'] ) );
  604. }
  605. }
  606. // If there are none, push a marker onto the stack so that we can skip the test.
  607. if ( count( $theList ) == 0 )
  608. {
  609. array_push( $theList, array( '___NO_DATA___', 0 ) );
  610. }
  611. return $theList;
  612. }
  613. /**
  614. * Data provider to return a list of /actual/ constraint names and types for the current table.
  615. *
  616. * If your test needs to iterate through all of the /actual/ constraint names and types of the current table, then use this method as the data provider. Each constraint name and type is presented to the consumer in turn.
  617. *
  618. * @access public
  619. * @return array( array( string, string )* )
  620. */
  621. public function provideConstraintNames()
  622. {
  623. $theList = array();
  624. // We need to filter on Search_Condition so that we can ignore NOT NULL
  625. // constraints. However, Search_Condition is a LONG, so we can't query it
  626. // directly. Instead, we have to create a temporary table, converting the
  627. // LONG into a CLOB, then query that. Blech.
  628. //
  629. // TODO: better exception handling and cleanup around the temporary table.
  630. // A proper temporary table would be nice, but Oracle's temporary tables at
  631. // best only truncate themselves at the end of a transaction, rather than go
  632. // away completely like PostgreSQL temporary tables can. :(
  633. //
  634. // I thought it might be possible to use a WITH to solve the problem, but it
  635. // turns out it doesn't work :(.
  636. $createString = sprintf(
  637. "CREATE TABLE Temp_Constraints AS
  638. SELECT Constraint_Name, Constraint_Type,
  639. TO_LOB( Search_Condition ) AS Search_Condition
  640. FROM User_Constraints
  641. WHERE ( Table_Name = '%s' )",
  642. strtoupper( $this->getTableName() )
  643. );
  644. $stmt = $this->getConnection()->getConnection()->prepare( $createString );
  645. if ( $stmt->execute() )
  646. {
  647. // We also need an NVL on Search_Condition, as some constraint types (notably
  648. // 'P' and 'R') have a NULL Search_Condition. This couldn't be done in the
  649. // temporary table, presumably because of the LONG -> LOB conversion.
  650. $queryString = sprintf(
  651. "SELECT Constraint_Name, Constraint_Type
  652. FROM Temp_Constraints
  653. WHERE ( NVL( Search_Condition, 'N/A' ) NOT LIKE '%%IS NOT NULL' )",
  654. strtoupper( $this->getTableName() )
  655. );
  656. $actual = $this->getConnection()->createQueryTable( "constraints", $queryString );
  657. for ( $row = 0; $row < $actual->getRowCount(); $row++ )
  658. {
  659. array_push( $theList, array( $actual->getValue( $row, 'CONSTRAINT_NAME' ), $actual->getValue( $row, 'CONSTRAINT_TYPE' ) ) );
  660. }
  661. $stmt = $this->getConnection()->getConnection()->prepare( 'DROP TABLE Temp_Constraints' );
  662. $stmt->execute();
  663. }
  664. // If there are none, push a marker onto the stack so that we can skip the test.
  665. if ( count( $theList ) == 0 )
  666. {
  667. array_push( $theList, array( '___NO_DATA___', 0 ) );
  668. }
  669. return $theList;
  670. }
  671. /**
  672. * Data provider to return a list of column names for the primary key.
  673. *
  674. * If your test needs to iterate through all of the columns of the table's primary key, then use this method as the data provider. Each column name is presented to the consumer in turn.
  675. *
  676. * @access public
  677. * @return array( array( string )* )
  678. */
  679. public function providePKColumnList()
  680. {
  681. $theList = array();
  682. foreach ( $this->getPKColumnList() as $columnName )
  683. {
  684. array_push( $theList, array( $columnName ) );
  685. }
  686. return $theList;
  687. }
  688. /**
  689. * Data provider to return a list of referenced tables for each foreign key (if any).
  690. *
  691. * If your test needs to iterate through all of the referenced tables of the table's foreign keys, then use this method as the data provider. Each referenced table name is presented to the consumer in turn.
  692. *
  693. * @access public
  694. * @return array( array( string )* )
  695. */
  696. public function provideFKReferencedTables()
  697. {
  698. $theList = array();
  699. foreach ( $this->getFKColumnList() as $tableName => $columnList )
  700. {
  701. array_push( $theList, array( $tableName ) );
  702. }
  703. return $theList;
  704. }
  705. /**
  706. * Data provider to return a list of referenced table and columns for each foreign key.
  707. *
  708. * If your test needs to iterate through all of the referenced tables and columns of the table's foreign keys, then use this method as the data provider. Each referenced table name plus a list of the referencing columns is presented to the consumer in turn.
  709. *
  710. * @access public
  711. * @return array( array( string, array( string+ ) )* )
  712. */
  713. // public function provideFKDetails()
  714. // {
  715. // $theList = array();
  716. // foreach ( $this->getFKColumnList() as $tableName => $columnList )
  717. // {
  718. // array_push( $theList, array( $tableName, $columnList ) );
  719. // }
  720. //
  721. // return $theList;
  722. // }
  723. /**
  724. * Assert that the table exists.
  725. *
  726. * This queries Oracle's User_Tables data dictionary view for a table matching the current name.
  727. *
  728. * @access protected
  729. * @return void
  730. */
  731. protected function assertTableExists()
  732. {
  733. self::$reporter->report( Reporter::STATUS_TEST, "[[ %s ]] ", array( ucfirst( strtolower( $this->getTableName() ) ) ) );
  734. $queryString = sprintf(
  735. "SELECT Table_Name
  736. FROM User_Tables
  737. WHERE ( Table_Name = '%s' )",
  738. strtoupper( $this->getTableName() )
  739. );
  740. if ( RUN_MODE === 'staff' )
  741. {
  742. $errorString = sprintf(
  743. "couldn't find the %s table [%+1.1f] --- check for misspelled [%+1.1f] or delimited [%+1.1f] identifiers",
  744. ucfirst( strtolower( $this->getTableName() ) ),
  745. $this->markAdjustments['missingTable'],
  746. $this->markAdjustments['misspelledIdentifier'],
  747. $this->markAdjustments['delimitedIdentifier']
  748. );
  749. }
  750. else if ( RUN_MODE === 'student' )
  751. {
  752. $errorString = sprintf( "couldn't find the %s table", ucfirst( strtolower( $this->getTableName() ) ) );
  753. }
  754. $actual = $this->getConnection()->createQueryTable( "user_tables", $queryString );
  755. self::assertEquals( 1, $actual->getRowCount(), $errorString );
  756. }
  757. /**
  758. * Assert that the table has a particular column.
  759. *
  760. * This queries Oracle's User_Tab_Cols data dictionary view for a column with the specified name in the current table. Tests that use this should use provideColumnNames() as their data provider.
  761. *
  762. * @access protected
  763. * @return void
  764. */
  765. protected function assertColumnExists( $columnName )
  766. {
  767. self::$reporter->report( Reporter::STATUS_TEST, "[[ %s.%s ]] ",
  768. array( ucfirst( strtolower( $this->getTableName() ) ), ucfirst( strtolower( $columnName ) ) ) );
  769. $queryString = sprintf(
  770. "SELECT Column_Name
  771. FROM User_Tab_Cols
  772. WHERE ( Table_Name = '%s' ) AND ( Column_Name = '%s' )",
  773. strtoupper( $this->getTableName() ),
  774. strtoupper( $columnName )
  775. );
  776. $actual = $this->getConnection()->createQueryTable( $this->getTableName() . '_' . $columnName, $queryString );
  777. if ( RUN_MODE === 'staff' )
  778. {
  779. $errorString = sprintf(
  780. "couldn't find the %s.%s column --- check for misspelled [%+1.1f] or delimited [%+1.1f] identifiers",
  781. ucfirst( strtolower( $this->getTableName() ) ),
  782. ucfirst( strtolower( $columnName ) ),
  783. $this->markAdjustments['misspelledIdentifier'],
  784. $this->markAdjustments['delimitedIdentifier']
  785. );
  786. }
  787. else if ( RUN_MODE === 'student' )
  788. {
  789. $errorString = sprintf( "couldn't find the %s.%s column",
  790. ucfirst( strtolower( $this->getTableName() ) ),
  791. ucfirst( strtolower( $columnName ) ) );
  792. }
  793. $theCount = $actual->getRowCount();
  794. if ( $theCount === 0 )
  795. {
  796. // Column doesn't exist with the expected name; check for aliases.
  797. $aliases = $this->getColumnAliases( $columnName );
  798. if ( count( $aliases ) > 0 )
  799. {
  800. foreach ( $aliases as $alias )
  801. {
  802. $queryString = sprintf(
  803. "SELECT Column_Name
  804. FROM User_Tab_Cols
  805. WHERE ( Table_Name = '%s' ) AND ( Column_Name = '%s' )",
  806. strtoupper( $this->getTableName() ),
  807. strtoupper( $alias )
  808. );
  809. $actual = $this->getConnection()->createQueryTable( $this->getTableName() . '_' . $columnName, $queryString );
  810. if ( $actual->getRowCount() === 1 )
  811. {
  812. self::$reporter->report( Reporter::STATUS_WARNING,
  813. 'Found alternative name “%s” for %s.%s; please rename it to “%s”.',
  814. array( $alias, $this->getTableName(), $columnName, $columnName ) );
  815. break;
  816. }
  817. }
  818. }
  819. }
  820. self::assertEquals( 1, $theCount, $errorString );
  821. }
  822. /**
  823. * Assert that a column has a particular data type.
  824. *
  825. * This queries Oracle's User_Tab_Cols data dictionary view and compares the data type for the specified column of the current table with the expected column name. Tests that use this should use provideColumnTypes as their data provider.
  826. *
  827. * @access protected
  828. * @return void
  829. */
  830. protected function assertColumnDataType( $columnName, $columnTypeList )
  831. {
  832. if ( RUN_MODE === 'staff' )
  833. {
  834. self::$reporter->report( Reporter::STATUS_TEST, "[[ %s.%s: data type is %s ]] ",
  835. array( ucfirst( strtolower( $this->getTableName() ) ),
  836. ucfirst( strtolower( $columnName ) ),
  837. implode( ' | ', $columnTypeList ) ) );
  838. }
  839. else if ( RUN_MODE === 'student' )
  840. {
  841. self::$reporter->report( Reporter::STATUS_TEST, "[[ %s.%s data type ]] ",
  842. array( ucfirst( strtolower( $this->getTableName() ) ), ucfirst( strtolower( $columnName ) ) ) );
  843. }
  844. $queryString = sprintf(
  845. "SELECT Data_Type
  846. FROM User_Tab_Cols
  847. WHERE ( Table_Name = '%s' ) AND ( Column_Name = '%s' )",
  848. strtoupper( $this->getTableName() ),
  849. strtoupper( $columnName )
  850. );
  851. $actual = $this->getConnection()->createQueryTable( $this->getTableName() . '_' . $columnName, $queryString );
  852. if ( RUN_MODE === 'staff' )
  853. {
  854. $errorString = sprintf( 'column %s.%s has unexpected data type %s [%+1.1f]',
  855. ucfirst( strtolower( $this->getTableName() ) ),
  856. ucfirst( strtolower( $columnName ) ),
  857. $actual->getValue( 0, 'DATA_TYPE' ),
  858. $this->markAdjustments['incorrectDataType'] );
  859. }
  860. else if ( RUN_MODE === 'student' )
  861. {
  862. $errorString = sprintf(
  863. 'column %s.%s has unexpected data type %s; check the specification again or consult with the teaching staff',
  864. ucfirst( strtolower( $this->getTableName() ) ),
  865. ucfirst( strtolower( $columnName ) ),
  866. $actual->getValue( 0, 'DATA_TYPE' ),
  867. $this->markAdjustments['incorrectDataType'] );
  868. }
  869. self::assertContains( $actual->getValue( 0, 'DATA_TYPE' ), $columnTypeList, $errorString );
  870. }
  871. /**
  872. * Assert that a column has a particular length range.
  873. *
  874. * This queries Oracle's User_Tab_Cols data dictionary view and compares the length for the specified column of the current table with the expected length(s). Tests that use this should use provideColumnLengths as their data provider.
  875. *
  876. * @access protected
  877. * @return void
  878. */
  879. protected function assertColumnLength( $columnName, $columnType, $minLength, $maxLength, $numDecimals )
  880. {
  881. // This can only happen if all of the columns are things like DATE, BLOB or CLOB.
  882. // This is pretty unlikely in practice, but you never know...
  883. if ( $columnName == '___NO_DATA___' )
  884. {
  885. $this->markTestSkipped( 'all of the columns are of types that have no length' );
  886. }
  887. $lengthSpec = '';
  888. if ( RUN_MODE === 'staff' )
  889. {
  890. if ( is_null( $minLength ) )
  891. {
  892. $lengthSpec .= "is not specified";
  893. }
  894. elseif ( $maxLength == 0 )
  895. {
  896. $lengthSpec .= "≥ ${minLength}";
  897. }
  898. elseif ( $minLength == 0 )
  899. {
  900. $lengthSpec .= "≤ ${maxLength}";
  901. }
  902. elseif ( $minLength != $maxLength )
  903. {
  904. $lengthSpec .= "${minLength}–${maxLength}";
  905. }
  906. else
  907. {
  908. $lengthSpec .= "= ${maxLength}";
  909. }
  910. if ( $columnType === 'NUMBER' )
  911. {
  912. if ( is_null( $numDecimals ) )
  913. {
  914. $lengthSpec .= " (scale not specified ⇒ 0)";
  915. }
  916. else
  917. {
  918. $lengthSpec .= " (with scale " . $numDecimals . ")";
  919. }
  920. }
  921. }
  922. self::$reporter->report( Reporter::STATUS_TEST, "[[ %s.%s %s %s ]] ",
  923. array( ucfirst( strtolower( $this->getTableName() ) ),
  924. ucfirst( strtolower( $columnName ) ),
  925. ( $columnType === 'NUMBER' ) ? 'precision' : 'length',
  926. $lengthSpec ) );
  927. if ( $columnType === 'NUMBER' )
  928. {
  929. /*
  930. Note NVL() on Data_Precision to ensure that we don't get spurious errors for NUMBER
  931. columns with unspecified precision (=> Data_Precision is null). 38 is the maximum
  932. precision for a NUMBER and will always be larger than the specified minimum precision
  933. (but may be larger than any specified maximum precision). Data_Scale gets NVL'd to
  934. zero because that's the expected value for no decimal places. If decimal places /are/
  935. expected, this will still fail.
  936. */
  937. $queryString = sprintf(
  938. "SELECT Data_Type, NVL( Data_Precision, 38 ) AS Data_Precision, NVL( Data_Scale, 0 ) AS Data_Scale
  939. FROM User_Tab_Cols
  940. WHERE ( Table_Name = '%s' ) AND ( Column_Name = '%s' )",
  941. strtoupper( $this->getTableName() ),
  942. strtoupper( $columnName )
  943. );
  944. }
  945. else
  946. {
  947. // We need to include the data type for text columns so that we can ignore CLOBs.
  948. $queryString = sprintf(
  949. "SELECT Data_Type, Char_Length
  950. FROM User_Tab_Cols
  951. WHERE ( Table_Name = '%s' ) AND ( Column_Name = '%s' )",
  952. strtoupper( $this->getTableName() ),
  953. strtoupper( $columnName )
  954. );
  955. }
  956. $actual = $this->getConnection()->createQueryTable( $this->getTableName() . '_' . $columnName, $queryString );
  957. if ( $columnType === 'NUMBER' )
  958. {
  959. self::$reporter->report( Reporter::STATUS_DEBUG, "[[ expected: minimum precision = %s, maximum precision = %s, scale = %s ]]",
  960. array( $minLength, $maxLength, $numDecimals ) );
  961. $actualPrecision = $actual->getValue( 0, 'DATA_PRECISION' );
  962. $actualScale = $actual->getValue( 0, 'DATA_SCALE' );
  963. self::$reporter->report( Reporter::STATUS_DEBUG, "[[ actual: precision = %s, scale = %s ]] ",
  964. array( $actualPrecision, $actualScale ) );
  965. if ( RUN_MODE === 'staff' )
  966. {
  967. $errorString = sprintf( 'column %s.%s has unexpected precision and/or scale %d, %d [%+1.1f]',
  968. ucfirst( strtolower( $this->getTableName() ) ),
  969. ucfirst( strtolower( $columnName ) ),
  970. $actualPrecision,
  971. $actualScale,
  972. $this->markAdjustments['incorrectLength'] );
  973. }
  974. else if ( RUN_MODE === 'student' )
  975. {
  976. $errorString = sprintf(
  977. 'column %s.%s has unexpected precision and/or scale; check the specification again or consult with the teaching staff.',
  978. ucfirst( strtolower( $this->getTableName() ) ),
  979. ucfirst( strtolower( $columnName ) ) );
  980. }
  981. if ( $minLength > 0 )
  982. {
  983. self::assertGreaterThanOrEqualSC( $minLength, $actualPrecision, $errorString );
  984. }
  985. if ( $maxLength > 0 )
  986. {
  987. self::assertLessThanOrEqualSC( $maxLength, $actualPrecision, $errorString );
  988. }
  989. self::assertEqualsSC( $numDecimals, $actualScale, $errorString );
  990. }
  991. else
  992. {
  993. // We might encounter CLOBs as an alternative for a large VARCHAR2.
  994. // Ignore these, as they have no particular length. BLOBs, DATEs and
  995. // standalone CLOBs should never show up in the list in the first place,
  996. // as they should have no length specified.
  997. if ( $actual->getValue( 0, 'DATA_TYPE' ) != 'CLOB' )
  998. {
  999. self::$reporter->report( Reporter::STATUS_DEBUG, "[[ expected: minimum length = %s, maximum length = %2 ]]",
  1000. array( $minLength, $maxLength ) );
  1001.  
  1002. $actual_length = $actual->getValue( 0, 'CHAR_LENGTH' );
  1003. self::$reporter->report( Reporter::STATUS_DEBUG, "[[ actual: length = %s ]] ", array( $actual_length ) );
  1004. if ( RUN_MODE === 'staff' )
  1005. {
  1006. $errorString = sprintf( 'column %s.%s has unexpected length %d [%+1.1f]',
  1007. ucfirst( strtolower( $this->getTableName() ) ),
  1008. ucfirst( strtolower( $columnName ) ),
  1009. $actual_length,
  1010. $this->markAdjustments['incorrectLength'] );
  1011. }
  1012. else if ( RUN_MODE === 'student' )
  1013. {
  1014. $errorString = sprintf(
  1015. 'column %s.%s has unexpected length; check the specification again or consult with the teaching staff.',
  1016. ucfirst( strtolower( $this->getTableName() ) ),
  1017. ucfirst( strtolower( $columnName ) ) );
  1018. }
  1019. if ( $maxLength > 0 )
  1020. {
  1021. self::assertLessThanOrEqualSC( $maxLength, $actual_length, $errorString );
  1022. }
  1023. if ( $minLength > 0 )
  1024. {
  1025. self::assertGreaterThanOrEqualSC( $minLength, $actual_length, $errorString );
  1026. }
  1027. }
  1028. }
  1029. }
  1030. /****************************************************************************************************
  1031. * THE REMAINING ASSERT() METHODS ARE NOT INTENDED TO BE CALLED IN "STUDENT" RUN MODE.
  1032. ****************************************************************************************************/
  1033. /**
  1034. * Assert that a column allows or disallows nulls.
  1035. *
  1036. * This queries Oracle's User_Tab_Cols data dictionary view and compares the nullability for the specified column of the current table with the expected column nullability. Tests that use this should use provideColumnNullabilities as their data provider.
  1037. *
  1038. * @access protected
  1039. * @return void
  1040. */
  1041. protected function assertColumnNullability( $columnName, $columnNullability )
  1042. {
  1043. self::$reporter->report( Reporter::STATUS_TEST, "[[ %s.%s nullability should be %s ]] ",
  1044. array( ucfirst( strtolower( $this->getTableName() ) ), ucfirst( strtolower( $columnName ) ), $columnNullability ) );
  1045. $queryString = sprintf(
  1046. "SELECT Nullable
  1047. FROM User_Tab_Cols
  1048. WHERE ( Table_Name = '%s' ) AND ( Column_Name = '%s' )",
  1049. strtoupper( $this->getTableName() ),
  1050. strtoupper( $columnName )
  1051. );
  1052. $actual = $this->getConnection()->createQueryTable( $this->getTableName() . '_' . $columnName, $queryString );
  1053. $errorString = sprintf( 'column %s.%s has incorrect nullability "%s" [%+1.1f]',
  1054. ucfirst( strtolower( $this->getTableName() ) ),
  1055. ucfirst( strtolower( $columnName ) ),
  1056. $actual->getValue( 0, 'NULLABLE' ),
  1057. $this->markAdjustments['incorrectNullability'] );
  1058. self::assertEquals( $actual->getValue( 0, 'NULLABLE' ), $columnNullability, $errorString );
  1059. }
  1060. /**
  1061. * Assert that a column has a default value.
  1062. *
  1063. * This queries Oracle's User_Tab_Cols data dictionary view and checks whether the default values for the specified column of the current table is null. Tests that use this should use provideColumnDefaults as their data provider.
  1064. *
  1065. * @access protected
  1066. * @return void
  1067. */
  1068. protected function assertColumnDefault( $columnName, $columnDefault )
  1069. {
  1070. self::$reporter->report( Reporter::STATUS_TEST, "[[ %s.%s default should be %s ]] ",
  1071. array( ucfirst( strtolower( $this->getTableName() ) ), ucfirst( strtolower( $columnName ) ), $columnDefault ) );
  1072. $queryString = sprintf(
  1073. "SELECT Data_Default
  1074. FROM User_Tab_Cols
  1075. WHERE ( Table_Name = '%s' ) AND ( Column_Name = '%s' )",
  1076. strtoupper( $this->getTableName() ),
  1077. strtoupper( $columnName )
  1078. );
  1079. $actual = $this->getConnection()->createQueryTable( $this->getTableName() . '_' . $columnName, $queryString );
  1080. // Defaults for text and (literal) date columns come with single quotes around them. Also, for some reason trim() with ' added to the standard character list won't strip off the trailing quote if there's whitespace following it, so we have to trim twice: once for whitespace, once for the quotes. Grr.
  1081. $actualDefault = trim( trim( $actual->getValue( 0, 'DATA_DEFAULT' ) ), "'" );
  1082. $errorString = sprintf( 'column %s.%s has incorrect default "%s" [%+1.1f]',
  1083. ucfirst( strtolower( $this->getTableName() ) ),
  1084. ucfirst( strtolower( $columnName ) ),
  1085. $actualDefault,
  1086. $this->markAdjustments['incorrectDefault'] );
  1087. self::assertEquals( $actualDefault, $columnDefault, $errorString );
  1088. }
  1089. /**
  1090. * Assert that a column accepts a particular legal value.
  1091. *
  1092. * This attempts to insert a known legal value into a particular column of the current table, which should succeed. This should only be applied to columns with an enumerated set of possible values. Tests that use this should use provideColumnLegalValues as their data provider.
  1093. *
  1094. * @access protected
  1095. * @return void
  1096. */
  1097. protected function assertColumnLegalValue( $columnName, $legalValue )
  1098. {
  1099. if ( $columnName == '___NO_DATA___' )
  1100. {
  1101. $this->markTestSkipped( 'no columns with enumerated legal values' );
  1102. }
  1103. // Should never be called in student run mode.
  1104. self::$reporter->report( Reporter::STATUS_TEST, "[[ %s.%s accepts “%s” ]] ",
  1105. array( ucfirst( strtolower( $this->getTableName() ) ), ucfirst( strtolower( $columnName ) ), $legalValue ) );
  1106. $substitutions[$columnName] = $legalValue;
  1107. $insertString = $this->constructInsert( $substitutions );
  1108. self::$reporter->report( Reporter::STATUS_DEBUG, "[[ %s ]] ", array( $insertString ) );
  1109. $stmt = $this->getConnection()->getConnection()->prepare( $insertString );
  1110. $errorString = sprintf(
  1111. "column %s.%s won't accept legal value %s [%+1.1f]",
  1112. ucfirst( strtolower( $this->getTableName() ) ),
  1113. ucfirst( strtolower( $columnName ) ),
  1114. $legalValue,
  1115. $this->markAdjustments['incorrectCheck']
  1116. );
  1117. /* Note that if the constraint is incorrect (e.g., incorrect capitalisation of the legal values), then we'll get a check constraint violation. We therefore need to manually catch the exception and fail the test. Antyhing else gets thrown up the chain.
  1118. */
  1119. try
  1120. {
  1121. self::assertTrue( $stmt->execute(), $errorString );
  1122. }
  1123. catch ( PDOException $e )
  1124. {
  1125. if ( ( strpos( $e->getMessage(), "check constraint" ) !== FALSE ) )
  1126. {
  1127. self::assertTrue( FALSE, $errorString );
  1128. }
  1129. else
  1130. {
  1131. throw $e;
  1132. }
  1133. }
  1134. }
  1135. /**
  1136. * Assert that a text column rejects a particular illegal value, implicitly enforced by exceeding the column length.
  1137. *
  1138. * This attempts to insert a known illegal value into a particular text column of the current table, which should fail because it's larger than the specified column length. (Relying on this kind of implicit enforcement is bad practice in general, as the column length can be changed, but it's better than no enforcement at all!). This should only be applied to columns with an enumerated set of possible values. Tests that use this should use provideColumnIllegalValues as their data provider. Tests will also need to include the following expected exception annotations:
  1139. *
  1140. * @expectedException PDOException
  1141. * @expectedExceptionMessage length exceeded
  1142. * @expectedExceptionCode HY000
  1143. *
  1144. * @access protected
  1145. * @return void
  1146. */
  1147. protected function assertColumnIllegalValueImplicit( $columnName, $illegalValue )
  1148. {
  1149. if ( $columnName == '___NO_DATA___' )
  1150. {
  1151. $this->markTestSkipped( 'no columns with enumerated illegal values' );
  1152. }
  1153. self::$reporter->report( Reporter::STATUS_TEST, "[[ %s.%s rejects “%s” using column length (implicit) ]] ",
  1154. array( ucfirst( strtolower( $this->getTableName() ) ), ucfirst( strtolower( $columnName ) ), $illegalValue ) );
  1155. $substitutions[$columnName] = $illegalValue;
  1156. $insertString = $this->constructInsert( $substitutions );
  1157. $stmt = $this->getConnection()->getConnection()->prepare( $insertString );
  1158. $errorString = sprintf(
  1159. "column %s.%s accepts illegal value %s [%+1.1f]",
  1160. ucfirst( strtolower( $this->getTableName() ) ),
  1161. ucfirst( strtolower( $columnName ) ),
  1162. $illegalValue,
  1163. $this->markAdjustments['incorrectCheck']
  1164. );
  1165. /* Note that if the column being tested is a number or date, the error returned will be "value larger than specified precision", whereas for text columns, the error returned will be "value too large for column". We therefore need to manually catch the exception and throw a new "length exceeded" exception for these two cases. Otherwise we just let the exception propagate up the chain as normal. This somewhat subverts the normal unit testing methodology of one case per test, but this is really a single "logical" test case. Plus this isn't really a conventional use of unit testing anyway!
  1166. */
  1167. try
  1168. {
  1169. self::assertTrue( $stmt->execute(), $errorString );
  1170. }
  1171. catch ( PDOException $e )
  1172. {
  1173. if ( ( strpos( $e->getMessage(), "value larger than specified precision" ) !== FALSE ) ||
  1174. ( strpos( $e->getMessage(), "value too large for column" ) !== FALSE ) )
  1175. {
  1176. throw new PDOException( "length exceeded" );
  1177. }
  1178. else
  1179. {
  1180. throw $e;
  1181. }
  1182. }
  1183. }
  1184. /**
  1185. * Assert that a text column rejects a particular illegal value, explicitly enforced by a CHECK constraint.
  1186. *
  1187. * This attempts to insert a known illegal value into a particular text column of the current table, which should fail with a CHECK constraint violation. This should only be applied to columns with an enumerated set of possible values. Tests that use this should use provideColumnIllegalValues as their data provider. Tests will also need to include the following expected exception annotations:
  1188. *
  1189. * @expectedException PDOException
  1190. * @expectedExceptionMessage check constraint
  1191. * @expectedExceptionCode HY000
  1192. *
  1193. * @access protected
  1194. * @return void
  1195. */
  1196. protected function assertColumnIllegalValueExplicit( $columnName, $illegalValue )
  1197. {
  1198. if ( $columnName == '___NO_DATA___' )
  1199. {
  1200. $this->markTestSkipped( 'no columns with enumerated illegal values' );
  1201. }
  1202. self::$reporter->report( Reporter::STATUS_TEST, "[[ %s.%s rejects “%s” using CHECK ]] ",
  1203. array( ucfirst( strtolower( $this->getTableName() ) ), ucfirst( strtolower( $columnName ) ), $illegalValue ) );
  1204. $substitutions[$columnName] = $illegalValue;
  1205. $insertString = $this->constructInsert( $substitutions );
  1206. $stmt = $this->getConnection()->getConnection()->prepare( $insertString );
  1207. $errorString = sprintf(
  1208. "column %s.%s accepts illegal value %s [%+1.1f]",
  1209. ucfirst( strtolower( $this->getTableName() ) ),
  1210. ucfirst( strtolower( $columnName ) ),
  1211. $illegalValue,
  1212. $this->markAdjustments['incorrectCheck']
  1213. );
  1214. self::assertTrue( $stmt->execute(), $errorString );
  1215. }
  1216. /**
  1217. * Assert that a column only accepts values greater than its underflow value, explicitly enforced by a CHECK constraint.
  1218. *
  1219. * This attempts to insert a known illegal underflow value into a particular column of the current table, which should fail with a CHECK constraint violation. This should only be applied to columns with a continuous range of values, usually numbers and dates. Tests that use this should use provideColumnUnderflowValues as their data provider. Tests will also need to include the following expected exception annotations:
  1220. *
  1221. * @expectedException PDOException
  1222. * @expectedExceptionMessage check constraint
  1223. * @expectedExceptionCode HY000
  1224. *
  1225. * Note that there's no need for explicit/implicit variants like there is with overflow values, as an underflow value should never be rejected by exceeding the column length. Something much more fundamental is wrong if this happens!
  1226. *
  1227. * @access protected
  1228. * @return void
  1229. */
  1230. protected function assertColumnUnderflowValue( $columnName, $underflowValue )
  1231. {
  1232. if ( $columnName == '___NO_DATA___' )
  1233. {
  1234. $this->markTestSkipped( 'no columns with underflow values' );
  1235. }
  1236. self::$reporter->report( Reporter::STATUS_TEST, "[[ %s.%s rejects values ≤ %s using CHECK ]] ",
  1237. array( ucfirst( strtolower( $this->getTableName() ) ), ucfirst( strtolower( $columnName ) ), $underflowValue ) );
  1238. $substitutions[$columnName] = $underflowValue;
  1239. $insertString = $this->constructInsert( $substitutions );
  1240. $stmt = $this->getConnection()->getConnection()->prepare( $insertString );
  1241. $errorString = sprintf(
  1242. "column %s.%s accepts illegal values <= %s [%+1.1f]",
  1243. ucfirst( strtolower( $this->getTableName() ) ),
  1244. ucfirst( strtolower( $columnName ) ),
  1245. $underflowValue,
  1246. $this->markAdjustments['incorrectCheck']
  1247. );
  1248. self::assertTrue( $stmt->execute(), $errorString );
  1249. }
  1250. /**
  1251. * Assert that a column only accepts values less than its overflow value, implicitly enforced by exceeding the column length.
  1252. *
  1253. * This attempts to insert a known illegal overflow value into a particular column of the current table, which should fail because it's larger than the specified column length. (Relying on this kind of implicit enforcement is bad practice in general, as the column length can be changed, but it's better than no enforcement at all!) This should only be applied to columns with a continuous range of values, usually numbers and dates. Tests that use this should use provideColumnOverflowValues as their data provider. Tests will also need to include the following expected exception annotations:
  1254. *
  1255. * @expectedException PDOException
  1256. * @expectedExceptionMessage length exceeded
  1257. * @expectedExceptionCode HY000
  1258. *
  1259. * @access protected
  1260. * @return void
  1261. */
  1262. protected function assertColumnOverflowValueImplicit( $columnName, $overflowValue )
  1263. {
  1264. if ( $columnName == '___NO_DATA___' )
  1265. {
  1266. $this->markTestSkipped( 'no columns with overflow values' );
  1267. }
  1268. self::$reporter->report( Reporter::STATUS_TEST, "[[ %s.%s rejects values ≥ %s using column length (implicit) ]] ",
  1269. array( ucfirst( strtolower( $this->getTableName() ) ), ucfirst( strtolower( $columnName ) ), $overflowValue ) );
  1270. $substitutions[$columnName] = $overflowValue;
  1271. $insertString = $this->constructInsert( $substitutions );
  1272. $stmt = $this->getConnection()->getConnection()->prepare( $insertString );
  1273. $errorString = sprintf(
  1274. "column %s.%s accepts illegal values >= %s [%+1.1f]",
  1275. ucfirst( strtolower( $this->getTableName() ) ),
  1276. ucfirst( strtolower( $columnName ) ),
  1277. $overflowValue,
  1278. $this->markAdjustments['incorrectCheck']
  1279. );
  1280. /* Note that if the column being tested is a number or date, the error returned will be "value larger than specified precision", whereas for text columns, the error returned will be "value too large for column". We therefore need to manually catch the exception and throw a new "length exceeded" exception for these two cases. Otherwise we just let the exception propagate up the chain as normal. This somewhat subverts the normal unit testing methodology of one case per test, but this is really a single "logical" test case. Plus this isn't really a conventional use of unit testing anyway!
  1281. */
  1282. try
  1283. {
  1284. self::assertTrue( $stmt->execute(), $errorString );
  1285. }
  1286. catch ( PDOException $e )
  1287. {
  1288. if ( ( strpos( $e->getMessage(), "value larger than specified precision" ) !== FALSE ) ||
  1289. ( strpos( $e->getMessage(), "value too large for column" ) !== FALSE ) )
  1290. {
  1291. throw new PDOException( "length exceeded" );
  1292. }
  1293. else
  1294. {
  1295. throw $e;
  1296. }
  1297. }
  1298. }
  1299. /**
  1300. * Assert that a column only accepts values less than its overflow value, explicitly enforced by a CHECK constraint.
  1301. *
  1302. * This attempts to insert a known illegal overflow value into a particular column of the current table, which should fail with a CHECK constraint violation. This should only be applied to columns with a continuous range of values, usually numbers and dates. Tests that use this should use provideColumnOverflowValues as their data provider. Tests will also need to include the following expected exception annotations:
  1303. *
  1304. * @expectedException PDOException
  1305. * @expectedExceptionMessage check constraint
  1306. * @expectedExceptionCode HY000
  1307. *
  1308. * @access protected
  1309. * @return void
  1310. */
  1311. protected function assertColumnOverflowValueExplicit( $columnName, $overflowValue )
  1312. {
  1313. if ( $columnName == '___NO_DATA___' )
  1314. {
  1315. $this->markTestSkipped( 'no columns with overflow values' );
  1316. }
  1317. self::$reporter->report( Reporter::STATUS_TEST, "[[ %s.%s rejects values ≥ %s using CHECK ]] ",
  1318. array( ucfirst( strtolower( $this->getTableName() ) ), ucfirst( strtolower( $columnName ) ), $overflowValue ) );
  1319. $substitutions[$columnName] = $overflowValue;
  1320. $insertString = $this->constructInsert( $substitutions );
  1321. $stmt = $this->getConnection()->getConnection()->prepare( $insertString );
  1322. $errorString = sprintf(
  1323. "column %s.%s accepts illegal values >= %s [%+1.1f]",
  1324. ucfirst( strtolower( $this->getTableName() ) ),
  1325. ucfirst( strtolower( $columnName ) ),
  1326. $overflowValue,
  1327. $this->markAdjustments['incorrectCheck']
  1328. );
  1329. self::assertTrue( $stmt->execute(), $errorString );
  1330. }
  1331. /**
  1332. * Assert that the primary key constraint of a table exists.
  1333. *
  1334. * This queries Oracle's User_Constraints data dictionary view for a constraint of type 'P' on the current table. It returns the name of the primary key constraint.
  1335. *
  1336. * @access protected
  1337. * @return string
  1338. */
  1339. public function assertPKExists()
  1340. {
  1341. self::$reporter->report( Reporter::STATUS_TEST, "[[ %s PK ]] ",
  1342. array( ucfirst( strtolower( $this->getTableName() ) ) ) );
  1343. $queryString = sprintf(
  1344. "SELECT Constraint_Name
  1345. FROM User_Constraints
  1346. WHERE ( Table_Name = '%s' ) AND ( Constraint_Type = 'P' )",
  1347. strtoupper( $this->getTableName() )
  1348. );
  1349. $actual = $this->getConnection()->createQueryTable( $this->getTableName() . '_PK', $queryString );
  1350.  
  1351. $errorString = sprintf(
  1352. "couldn't find a PK constraint for %s [%+1.1f]",
  1353. ucfirst( strtolower( $this->getTableName() ) ),
  1354. $this->markAdjustments['incorrectPK']
  1355. );
  1356. self::assertEquals( 1, $actual->getRowCount(), $errorString );
  1357. return $actual->getValue( 0, 'CONSTRAINT_NAME' );
  1358. }
  1359. /**
  1360. * Assert that the primary key constraint of a table includes the correct columns.
  1361. *
  1362. * Tests that use this must depend on a test that calls assertPKExists(), which returns the name of the PK constraint. We can query User_Cons_Columns to see whether the lists match.
  1363. *
  1364. * @access protected
  1365. * @return void
  1366. */
  1367. public function assertPKColumns( $constraintName )
  1368. {
  1369. $tableName = $this->getTableName() . '_PK_cols';
  1370. $expected = $this->getPKColumnListAsDataSet( $tableName );
  1371. self::$reporter->report( Reporter::STATUS_TEST, "[[ %s PK: %s ]] ",
  1372. array( ucfirst( strtolower( $this->getTableName() ) ), ucwords( strtolower( implode( ', ', $this->getPKColumnList() ) ) ) ) );
  1373.  
  1374. $queryString = sprintf(
  1375. "SELECT Column_Name
  1376. FROM User_Cons_Columns
  1377. WHERE ( Constraint_Name = '%s' )
  1378. ORDER BY Position",
  1379. strtoupper( $constraintName )
  1380. );
  1381. $actual = $this->getConnection()->createQueryTable( $tableName, $queryString );
  1382.  
  1383. $errorString = sprintf(
  1384. "the PK constraint for %s has incorrect columns [%+1.1f]",
  1385. ucfirst( strtolower( $this->getTableName() ) ),
  1386. $this->markAdjustments['incorrectPK']
  1387. );
  1388. self::assertTablesEqual( $expected->getTable( $tableName ), $actual, $errorString );
  1389. }
  1390. /**
  1391. * Assert that the foreign key constraint(s) of a table exist.
  1392. *
  1393. * This queries Oracle's User_Constraints data dictionary view for a constraint of type 'R' on the current table that references the specified table. Tests that use this should use provideFKReferencedTables as their data provider.
  1394. *
  1395. * @access protected
  1396. * @return void
  1397. */
  1398. public function assertFKsExist( $referencedTableName )
  1399. {
  1400. self::$reporter->report( Reporter::STATUS_TEST, "[[ %s FK → %s ]] ",
  1401. array( ucfirst( strtolower( $this->getTableName() ) ), ucfirst( strtolower( $referencedTableName ) ) ) );
  1402. $queryString = sprintf(
  1403. "SELECT Child.Constraint_Name
  1404. FROM User_Constraints Child INNER JOIN User_Constraints Parent
  1405. ON ( Child.R_Constraint_Name = Parent.Constraint_Name )
  1406. WHERE ( Child.Table_Name = '%s' )
  1407. AND ( Parent.Table_Name = '%s' )
  1408. AND ( Child.Constraint_Type = 'R' )",
  1409. strtoupper( $this->getTableName() ),
  1410. strtoupper( $referencedTableName )
  1411. );
  1412. $actual = $this->getConnection()->createQueryTable( $this->getTableName() . '_FK', $queryString );
  1413.  
  1414. $errorString = sprintf(
  1415. "couldn't find a FK constraint for %s referencing %s [%+1.1f]",
  1416. ucfirst( strtolower( $this->getTableName() ) ),
  1417. ucfirst( strtolower( $referencedTableName ) ),
  1418. $this->markAdjustments['incorrectPK']
  1419. );
  1420. self::assertGreaterThan( 0, $actual->getRowCount(), $errorString );
  1421. }
  1422. /**
  1423. * Assert that the foreign key constraints of a table include the correct columns.
  1424. *
  1425. * We can query User_Cons_Columns to see whether the lists match. Tests that use this should use provideFKReferencedTables as their data provider.
  1426. *
  1427. * @access protected
  1428. * @return void
  1429. */
  1430. public function assertFKColumns( $referencedTableName )
  1431. {
  1432. $tableName = $referencedTableName . '_FK_cols';
  1433. $expected = $this->getFKColumnListForTableAsDataSet( $referencedTableName, $tableName );
  1434. $fkColumns = $this->getFKColumnlist();
  1435. self::$reporter->report( Reporter::STATUS_TEST, "[[ %s FK → %s: %s ]] ",
  1436. array( ucfirst( strtolower( $this->getTableName() ) ),
  1437. ucfirst( strtolower( $referencedTableName ) ),
  1438. ucwords( strtolower( implode( ', ', $fkColumns[$referencedTableName] ) ) ) ) );
  1439.  
  1440. $queryString = sprintf(
  1441. "SELECT User_Cons_Columns.Column_Name
  1442. FROM User_Constraints Child INNER JOIN User_Constraints Parent
  1443. ON ( Child.R_Constraint_Name = Parent.Constraint_Name )
  1444. INNER JOIN User_Cons_Columns
  1445. ON ( Child.Constraint_Name = User_Cons_Columns.Constraint_Name )
  1446. WHERE ( Child.Table_Name = '%s' )
  1447. AND ( Parent.Table_Name = '%s' )
  1448. AND ( Child.Constraint_Type = 'R' )
  1449. ORDER BY User_Cons_Columns.Position",
  1450. strtoupper( $this->getTableName() ),
  1451. strtoupper( $referencedTableName )
  1452. );
  1453. $actual = $this->getConnection()->createQueryTable( $tableName, $queryString );
  1454. // Note that we can't use the same trick as for PKs of the second test depending on the first, as this
  1455. // doesn't work when the first test is iterated by a data provider :(. (This probably makes sense when
  1456. // you think about how test execution works in general.) We also can't directly access the protected
  1457. // "data" member of the query table, so we resort to checking whether the row count is zero and skipping
  1458. // the test if so.
  1459. if ( $actual->getRowCount() == 0 ) $this->markTestSkipped( 'FK is missing anyway' );
  1460.  
  1461. $errorString = sprintf(
  1462. "the FK constraint for %s has incorrect columns [%+1.1f]",
  1463. ucfirst( strtolower( $this->getTableName() ) ),
  1464. $this->markAdjustments['unnamedConstraint']
  1465. );
  1466. self::assertTablesEqual( $expected->getTable( $tableName ), $actual, $errorString );
  1467. }
  1468. /**
  1469. * Assert that the unique constraints of a table include the correct columns.
  1470. *
  1471. * We can query User_Cons_Columns to see whether the lists match. [? ->] Tests that use this should use provideFKReferencedTables as their data provider.
  1472. *
  1473. * @access protected
  1474. * @return void
  1475. */
  1476. public function assertUniqueColumns()
  1477. {
  1478. $tableName = $this->getTableName() . '_unique_cols';
  1479. $expected = $this->getUniqueColumnListAsDataSet( $tableName );
  1480. $uniqueColumns = $this->getUniqueColumnlist();
  1481. // self::$reporter->report( Reporter::STATUS_TEST, "[[ %s FK → %s: %s ]] ",
  1482. // array( ucfirst( strtolower( $this->getTableName() ) ),
  1483. // ucfirst( strtolower( $referencedTableName ) ),
  1484. // ucwords( strtolower( implode( ', ', $fkColumns[$referencedTableName] ) ) ) ) );
  1485.  
  1486. $queryString = sprintf(
  1487. "SELECT User_Cons_Columns.Column_Name, User_Cons_Columns.Position
  1488. FROM User_Constraints INNER JOIN User_Cons_Columns USING ( Constraint_Name )
  1489. WHERE ( User_Constraints.Table_Name = '%s' )
  1490. AND ( User_Constraints.Constraint_Type = 'U' )
  1491. ORDER BY User_Cons_Columns.Position",
  1492. strtoupper( $this->getTableName() )
  1493. );
  1494. $actual = $this->getConnection()->createQueryTable( $tableName, $queryString );
  1495. // Note that we can't use the same trick as for PKs of the second test depending on the first, as this
  1496. // doesn't work when the first test is iterated by a data provider :(. (This probably makes sense when
  1497. // you think about how test execution works in general.) We also can't directly access the protected
  1498. // "data" member of the query table, so we resort to checking whether the row count is zero and skipping
  1499. // the test if so.
  1500. if ( $actual->getRowCount() == 0 ) $this->markTestSkipped( 'FK is missing anyway' );
  1501.  
  1502. $errorString = sprintf(
  1503. "the FK constraint for %s has incorrect columns [%+1.1f]",
  1504. ucfirst( strtolower( $this->getTableName() ) ),
  1505. $this->markAdjustments['unnamedConstraint']
  1506. );
  1507. self::assertTablesEqual( $expected->getTable( $tableName ), $actual, $errorString );
  1508. }
  1509. /**
  1510. * Assert that a constraint of a table has been explicitly named.
  1511. *
  1512. * If the constraint name starts with "SYS_", then it hasn't been explicitly named. Tests that use this should use provideConstraintNames as their data provider.
  1513. *
  1514. * @access protected
  1515. * @return void
  1516. */
  1517. public function assertConstraintNamed( $constraintName, $constraintType )
  1518. {
  1519. if ( $constraintName == '___NO_DATA___' )
  1520. {
  1521. $this->markTestSkipped( 'no constraints to be tested on this table' );
  1522. }
  1523. switch ( $constraintType )
  1524. {
  1525. case 'C':
  1526. $longType = 'check';
  1527. break;
  1528. case 'P':
  1529. $longType = 'primary key';
  1530. break;
  1531. case 'R':
  1532. $longType = 'foreign key';
  1533. break;
  1534. case 'U':
  1535. $longType = 'unique';
  1536. break;
  1537. default:
  1538. $longtype = "unknown (${constraintType})";
  1539. break;
  1540. }
  1541. self::$reporter->report( Reporter::STATUS_TEST, "[[ %s %s constraint %s ]] ",
  1542. array( ucfirst( strtolower( $this->getTableName() ) ), $longType, $constraintName ) );
  1543.  
  1544. $errorString = sprintf(
  1545. "the %s constraint %s for %s hasn't been explicitly named [%+1.1f]",
  1546. $longType,
  1547. $constraintName,
  1548. ucfirst( strtolower( $this->getTableName() ) ),
  1549. $this->markAdjustments['unnamedConstraint']
  1550. );
  1551. self::assertNotRegExp( '/^SYS_/', $constraintName, $errorString );
  1552. }
  1553.  
  1554. /**
  1555. * @expectedException PDOException
  1556. * @expectedExceptionMessage unique constraint
  1557. * @expectedExceptionCode HY000
  1558. */
  1559. // protected function testPrimaryKeyUnique()
  1560. // {
  1561. // echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . " table primary key (UNIQUE) ]]\n";
  1562. // $stmt = $this->getConnection()->getConnection()->prepare( "INSERT INTO $this->getTableName() VALUES ( 326, 'foo', 'bar', '1234567', 'baz', 'Manufacturing', 'Technician', 12345, 'quux' )" );
  1563. // self::assertTrue( $stmt->execute(), ucfirst( strtolower( $this->getTableName() ) ) . " PK constraint is missing or incorrectly implemented (permits duplicates) [-1]" );
  1564. // }
  1565. /**
  1566. * @expectedException PDOException
  1567. * @expectedExceptionMessage cannot insert NULL into
  1568. * @expectedExceptionCode HY000
  1569. */
  1570. // protected function testPrimaryKeyNotNull()
  1571. // {
  1572. // echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . " table primary key (NOT NULL) ]]\n";
  1573. // $stmt = $this->getConnection()->getConnection()->prepare( "INSERT INTO $this->getTableName() VALUES ( null, 'foo', 'bar', '1234567', 'baz', 'Manufacturing', 'Technician', 12345, 'quux' )" );
  1574. // self::assertTrue( $stmt->execute(), ucfirst( strtolower( $this->getTableName() ) ) . " PK constraint is missing or incorrectly implemented (permits nulls) [-1]" );
  1575. // }
  1576. /**
  1577. * @expectedException PDOException
  1578. * @expectedExceptionMessage invalid number
  1579. * @expectedExceptionCode HY000
  1580. */
  1581. // protected function testStaffIdDataType()
  1582. // {
  1583. // echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . ".Staff_ID data type (NUMBER) ]]\n";
  1584. // $stmt = $this->getConnection()->getConnection()->prepare( "INSERT INTO $this->getTableName() VALUES ( 'abc', 'foo', 'bar', '1234567', 'baz', 'Manufacturing', 'Technician', 12345, 'quux' )" );
  1585. // self::assertTrue( $stmt->execute(), ucfirst( strtolower( $this->getTableName() ) ) . '.Staff_ID data type is not NUMBER [-1]' );
  1586. // }
  1587. /**
  1588. * expectedException PDOException
  1589. * expectedExceptionMessage invalid number
  1590. * expectedExceptionCode HY000
  1591. */
  1592. // protected function testStaffIdMaximumValue()
  1593. // {
  1594. // echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . ".Staff_ID maximum value (9999999) ]]\n";
  1595. // $stmt = $this->getConnection()->getConnection()->prepare( "INSERT INTO $this->getTableName() VALUES ( 9999999, 'foo', 'bar', '1234567', 'baz', 'Manufacturing', 'Technician', 12345, 'quux' )" );
  1596. // self::assertTrue( $stmt->execute(), ucfirst( strtolower( $this->getTableName() ) ) . '.Staff_ID size is too small (< 7 digits) [-0.5]' );
  1597. // }
  1598. /**
  1599. * @expectedException PDOException
  1600. * @expectedExceptionMessage value larger than specified precision allowed for this column
  1601. * @expectedExceptionCode HY000
  1602. */
  1603. // protected function testStaffIdMaximumSize()
  1604. // {
  1605. // echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . ".Staff_ID maximum size (7 digits) ]]\n";
  1606. // $stmt = $this->getConnection()->getConnection()->prepare( "INSERT INTO $this->getTableName() VALUES ( 99999999, 'foo', 'bar', '1234567', 'baz', 'Manufacturing', 'Technician', 12345, 'quux' )" );
  1607. // self::assertTrue( $stmt->execute(), ucfirst( strtolower( $this->getTableName() ) ) . '.Staff_ID size is too large (> 7-digits) [-0.5]' );
  1608. // }
  1609. }
  1610. ?>
  1611.