Newer
Older
sqlmarker / Unit_testing / Schema.php
  1. <?php
  2. require_once "ArrayDataSet.php";
  3.  
  4. abstract class PHPUnit_Extensions_Database_TestCase_CreateTable extends PHPUnit_Extensions_Database_TestCase
  5. {
  6. /**
  7. * List of possible mark adjustments for errors (negative) or bonuses (positive).
  8. *
  9. * @access protected
  10. */
  11. protected $markAdjustments = array(
  12. 'missingTable' => -5,
  13. 'missingColumn' => -1,
  14. 'incorrectPK' => -1,
  15. 'incorrectFK' => -1,
  16. 'misspelledIdentifier' => -1,
  17. 'incorrectDataType' => -1,
  18. 'missingCheck' => -1,
  19. 'delimitedIdentifier' => -0.5,
  20. 'unnamedConstraint' => -0.5,
  21. 'incorrectLength' => -0.5,
  22. 'incorrectDefault' => -0.5,
  23. 'incorrectCheck' => -0.5,
  24. 'incorrectNullability' => -0.5,
  25. );
  26. /**
  27. * 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).
  28. */
  29. protected function sqlifyValue( $srcValue, $srcType )
  30. {
  31. $sqlifiedValue = $srcValue;
  32. if ( $srcType === 'TEXT' )
  33. {
  34. $sqlifiedValue = str_replace( "'", "''", $sqlifiedValue );
  35. $sqlifiedValue = str_replace( '&', "' || chr(38) || '", $sqlifiedValue );
  36. }
  37. if ( ( $srcType === 'TEXT' ) || ( $srcType === 'DATE' ) )
  38. {
  39. $sqlifiedValue = "'" . $sqlifiedValue . "'";
  40. }
  41. return $sqlifiedValue;
  42. }
  43. /**#@+
  44. * Return table name, list of columns, etc.
  45. *
  46. * 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.
  47. *
  48. * 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).
  49. *
  50. * @abstract
  51. * @access protected
  52. */
  53. /**
  54. * Return the SQL table name.
  55. *
  56. * @return string
  57. */
  58. abstract protected function getTableName();
  59. /**
  60. * Return a list of the table's column names.
  61. *
  62. * @return array( string+ )
  63. */
  64. abstract protected function getColumnList();
  65. /**
  66. * Return a list of the table's primary key column names.
  67. *
  68. * @return array( string+ )
  69. */
  70. abstract protected function getPKColumnList();
  71. /**
  72. * Return a list of the table's foreign key column names.
  73. *
  74. * This should return a list of column names for each FK, indexed by the name of the referenced table.
  75. *
  76. * @return array( string => array( string+ ) )
  77. */
  78. abstract protected function getFKColumnList();
  79. /**#@-*/
  80. /**
  81. * Return the list of primary key column names as an array dataset.
  82. *
  83. * The argument specifies the name of the table in the dataset.
  84. *
  85. * @return SchemaTesting_DbUnit_ArrayDataSet
  86. */
  87. protected function getPKColumnListAsDataSet( $datasetTableName )
  88. {
  89. $theList = array();
  90. foreach ( $this->getPKColumnList() as $columnName )
  91. {
  92. array_push( $theList, array( 'COLUMN_NAME' => $columnName ) );
  93. }
  94. return new SchemaTesting_DbUnit_ArrayDataSet( array( $datasetTableName => $theList ) );
  95. }
  96. /**
  97. * Return the list of column names for the foreign key on the current table that references the specified table.
  98. *
  99. * @return array( string+ )
  100. */
  101. protected function getFKColumnListForTable( $referencedTable )
  102. {
  103. $theList = array();
  104. $allFKColumns = $this->getFKColumnList();
  105. return $allFKColumns[$referencedTable];
  106. }
  107. /**
  108. * Return the list of column names for a given foreign key as an array data set.
  109. *
  110. * The second argument specifies the name of the table in the dataset.
  111. *
  112. * @return SchemaTesting_DbUnit_ArrayDataSet
  113. */
  114. protected function getFKColumnListForTableAsDataSet( $referencedTable, $datasetTableName )
  115. {
  116. $theList = array();
  117. $fkColumns = $this->getFKColumnListForTable( $referencedTable );
  118. foreach ( $fkColumns as $columnName )
  119. {
  120. array_push( $theList, array( 'COLUMN_NAME' => $columnName ) );
  121. }
  122. return new SchemaTesting_DbUnit_ArrayDataSet( array( $datasetTableName => $theList ) );
  123. }
  124. /**
  125. * 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.
  126. *
  127. * @return string
  128. */
  129. protected function constructInsert( $substitutions )
  130. {
  131. $columnValues = array();
  132. foreach ( $this->getColumnList() as $name => $details )
  133. {
  134. if ( array_key_exists( $name, $substitutions ) )
  135. {
  136. $columnValues[] = $this->sqlifyValue( $substitutions[$name], $details['generic_type'] );
  137. unset( $substitutions[$name] );
  138. }
  139. else
  140. {
  141. $columnValues[] = $this->sqlifyValue( $details['test_value'], $details['generic_type'] );
  142. }
  143. }
  144. return sprintf(
  145. "INSERT INTO %s ( %s ) VALUES ( %s )",
  146. $this->getTableName(),
  147. implode( ', ', array_keys( $this->getColumnList() ) ),
  148. implode( ', ', $columnValues )
  149. );
  150. }
  151. /**
  152. * Return a test INSERT statement, using standard test values.
  153. *
  154. * This just works by calling constructInsert with an empty substitutions list.
  155. *
  156. * @return string
  157. */
  158. protected function getStandardTestInsert()
  159. {
  160. return constructInsert( array() );
  161. }
  162. /**
  163. * Return database connection.
  164. *
  165. * @access protected
  166. * @return PHPUnit_Extensions_Database_DB_IDatabaseConnection
  167. * @todo Parameterise the connection details.
  168. */
  169. protected function getConnection()
  170. {
  171. $pdo = new PDO( "oci:dbname=isorcl-400", "stani797", "b1ggles" );
  172. return $this->createDefaultDBConnection( $pdo, "stani797" );
  173. }
  174. /**
  175. * Return the fixture setup operation.
  176. *
  177. * We can't use the standard fixture setup 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.
  178. *
  179. * @access protected
  180. * @return PHPUnit_Extensions_Database_Operation_DatabaseOperation
  181. */
  182. protected function getSetUpOperation()
  183. {
  184. return new PHPUnit_Extensions_Database_Operation_Composite(
  185. array(
  186. PHPUnit_Extensions_Database_Operation_Factory::DELETE_ALL(),
  187. PHPUnit_Extensions_Database_Operation_Factory::INSERT()
  188. )
  189. );
  190. }
  191. /**
  192. * Return the fixture teardown operation.
  193. *
  194. * 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.
  195. *
  196. * @access protected
  197. * @return PHPUnit_Extensions_Database_Operation_DatabaseOperation
  198. */
  199. protected function getTearDownOperation()
  200. {
  201. return new PHPUnit_Extensions_Database_Operation_Composite(
  202. array( PHPUnit_Extensions_Database_Operation_Factory::DELETE_ALL() )
  203. );
  204. }
  205. /**
  206. * Data provider to return a list of all column names.
  207. *
  208. * 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.
  209. *
  210. * @access public
  211. * @return array( array( string )* )
  212. */
  213. public function provideColumnNames()
  214. {
  215. $theList = array();
  216. foreach ( array_keys( $this->getColumnList() ) as $columnName )
  217. {
  218. array_push( $theList, array( $columnName ) );
  219. }
  220. return $theList;
  221. }
  222. /**
  223. * Data provider to return a list of all columns and their data types.
  224. *
  225. * 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.
  226. *
  227. * @access public
  228. * @return array( array( string, array( string+ ) )* )
  229. */
  230. public function provideColumnTypes()
  231. {
  232. $theList = array();
  233. foreach ( $this->getColumnList() as $columnName => $columnDetails )
  234. {
  235. array_push( $theList, array( $columnName, $columnDetails['sql_type'] ) );
  236. }
  237. return $theList;
  238. }
  239. /**
  240. * Data provider to return a list of all columns and their length-related information.
  241. *
  242. * 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).
  243. *
  244. * @access public
  245. * @return array( array( string, string, int, int, int )* )
  246. */
  247. public function provideColumnLengths()
  248. {
  249. $theList = array();
  250. foreach ( $this->getColumnList() as $columnName => $columnDetails )
  251. {
  252. $minLength = ( array_key_exists( 'min_length', $columnDetails ) ) ? $columnDetails['min_length'] : 0;
  253. $maxLength = ( array_key_exists( 'max_length', $columnDetails ) ) ? $columnDetails['max_length'] : 0;
  254. $numDecimals = ( array_key_exists( 'decimals', $columnDetails ) ) ? $columnDetails['decimals'] : 0;
  255. // If min_length and max_length are missing, then it has no length at all (e.g., DATE, BLOB, CLOB).
  256. if ( ( $minLength > 0 ) || ( $maxLength > 0 ) )
  257. {
  258. array_push( $theList, array( $columnName, $columnDetails['generic_type'], $minLength, $maxLength, $numDecimals ) );
  259. }
  260. }
  261. // If there are none (pretty unlikely), push a marker onto the stack so that we can skip the test.
  262. if ( count( $theList ) == 0 )
  263. {
  264. array_push( $theList, array( '___NO_DATA___', 'NULL', 0, 0, 0 ) );
  265. }
  266. return $theList;
  267. }
  268. /**
  269. * Data provider to return a list of all columns and their nullabilities.
  270. *
  271. * 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.
  272. *
  273. * @access public
  274. * @return array( array( string, string )* )
  275. */
  276. public function provideColumnNullabilities()
  277. {
  278. $theList = array();
  279. foreach ( $this->getColumnList() as $columnName => $columnDetails )
  280. {
  281. $isNullable = $columnDetails['nullable'] ? 'Y' : 'N';
  282. array_push( $theList, array( $columnName, $isNullable ) );
  283. }
  284. return $theList;
  285. }
  286. /**
  287. * Data provider to return a list of all columns and their legal (enumerated) values.
  288. *
  289. * 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.)
  290. *
  291. * @access public
  292. * @return array( array( string, array( string+ ) )* )
  293. */
  294. public function provideColumnLegalValues()
  295. {
  296. $theList = array();
  297. foreach ( $this->getColumnList() as $columnName => $columnDetails )
  298. {
  299. if ( array_key_exists( 'legal_values', $columnDetails ) )
  300. {
  301. foreach ( $columnDetails['legal_values'] as $legalValue )
  302. {
  303. array_push( $theList, array( $columnName, $legalValue ) );
  304. }
  305. }
  306. }
  307. // If there are none, push a marker onto the stack so that we can skip the test.
  308. if ( count( $theList ) == 0 )
  309. {
  310. array_push( $theList, array( '___NO_DATA___', array() ) );
  311. }
  312. return $theList;
  313. }
  314. /**
  315. * Data provider to return a list of all text columns and some illegal (enumerated) values.
  316. *
  317. * 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.)
  318. *
  319. * @access public
  320. * @return array( array( string, array( string+ ) )* )
  321. */
  322. public function provideColumnIllegalValues()
  323. {
  324. $theList = array();
  325. foreach ( $this->getColumnList() as $columnName => $columnDetails )
  326. {
  327. if ( array_key_exists( 'illegal_values', $columnDetails ) )
  328. {
  329. foreach ( $columnDetails['illegal_values'] as $illegalValue )
  330. {
  331. array_push( $theList, array( $columnName, $illegalValue ) );
  332. }
  333. }
  334. }
  335. // If there are none, push a marker onto the stack so that we can skip the test.
  336. if ( count( $theList ) == 0 )
  337. {
  338. array_push( $theList, array( '___NO_DATA___', array() ) );
  339. }
  340. return $theList;
  341. }
  342. /**
  343. * Data provider to return a list of all columns and their underflow values.
  344. *
  345. * 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.)
  346. *
  347. * @access public
  348. * @return array( array( string, array( string+ ) )* )
  349. */
  350. public function provideColumnUnderflowValues()
  351. {
  352. $theList = array();
  353. foreach ( $this->getColumnList() as $columnName => $columnDetails )
  354. {
  355. if ( array_key_exists( 'underflow', $columnDetails ) )
  356. {
  357. array_push( $theList, array( $columnName, $columnDetails['underflow'] ) );
  358. }
  359. }
  360. // If there are none, push a marker onto the stack so that we can skip the test.
  361. if ( count( $theList ) == 0 )
  362. {
  363. array_push( $theList, array( '___NO_DATA___', 0 ) );
  364. }
  365. return $theList;
  366. }
  367. /**
  368. * Data provider to return a list of all columns and their overflow values.
  369. *
  370. * 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.)
  371. *
  372. * @access public
  373. * @return array( array( string, array( string+ ) )* )
  374. */
  375. public function provideColumnOverflowValues()
  376. {
  377. $theList = array();
  378. foreach ( $this->getColumnList() as $columnName => $columnDetails )
  379. {
  380. if ( array_key_exists( 'overflow', $columnDetails ) )
  381. {
  382. array_push( $theList, array( $columnName, $columnDetails['overflow'] ) );
  383. }
  384. }
  385. // If there are none, push a marker onto the stack so that we can skip the test.
  386. if ( count( $theList ) == 0 )
  387. {
  388. array_push( $theList, array( '___NO_DATA___', 0 ) );
  389. }
  390. return $theList;
  391. }
  392. /**
  393. * Data provider to return a list of /actual/ constraint names and types for the current table.
  394. *
  395. * 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.
  396. *
  397. * @access public
  398. * @return array( array( string, string )* )
  399. */
  400. public function provideConstraintNames()
  401. {
  402. $theList = array();
  403. // We need to filter on Search_Condition so that we can ignore NOT NULL
  404. // constraints. However, Search_Condition is a LONG, so we can't query it
  405. // directly. Instead, we have to create a temporary table, converting the
  406. // LONG into a CLOB, then query that. Blech.
  407. //
  408. // TODO: better exception handling and cleanup around the temporary table.
  409. // A proper temporary table would be nice, but Oracle's temporary tables at
  410. // best only truncate themselves at the end of a transaction, rather than go
  411. // away completely like PostgreSQL temporary tables can. :(
  412. $createString = sprintf(
  413. "CREATE TABLE Temp_Constraints AS
  414. SELECT Constraint_Name, Constraint_Type,
  415. TO_LOB( Search_Condition ) AS Search_Condition
  416. FROM User_Constraints
  417. WHERE ( Table_Name = '%s' )",
  418. strtoupper( $this->getTableName() )
  419. );
  420. $stmt = $this->getConnection()->getConnection()->prepare( $createString );
  421. if ( $stmt->execute() )
  422. {
  423. // We also need an NVL on Search_Condition, as some constraint types (notably
  424. // 'P' and 'R') have a NULL Search_Condition. This couldn't be done in the
  425. // temporary table, presumably because of the LONG -> LOB conversion.
  426. $queryString = sprintf(
  427. "SELECT Constraint_Name, Constraint_Type
  428. FROM Temp_Constraints
  429. WHERE ( NVL( Search_Condition, 'N/A' ) NOT LIKE '%%IS NOT NULL' )",
  430. strtoupper( $this->getTableName() )
  431. );
  432. $actual = $this->getConnection()->createQueryTable( "constraints", $queryString );
  433. for ( $row = 0; $row < $actual->getRowCount(); $row++ )
  434. {
  435. array_push( $theList, array( $actual->getValue( $row, 'CONSTRAINT_NAME' ), $actual->getValue( $row, 'CONSTRAINT_TYPE' ) ) );
  436. }
  437. $stmt = $this->getConnection()->getConnection()->prepare( 'DROP TABLE Temp_Constraints' );
  438. $stmt->execute();
  439. }
  440. // If there are none, push a marker onto the stack so that we can skip the test.
  441. if ( count( $theList ) == 0 )
  442. {
  443. array_push( $theList, array( '___NO_DATA___', 0 ) );
  444. }
  445. return $theList;
  446. }
  447. /**
  448. * Data provider to return a list of column names for the primary key.
  449. *
  450. * 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.
  451. *
  452. * @access public
  453. * @return array( array( string )* )
  454. */
  455. public function providePKColumnList()
  456. {
  457. $theList = array();
  458. foreach ( $this->getPKColumnList() as $columnName )
  459. {
  460. array_push( $theList, array( $columnName ) );
  461. }
  462. return $theList;
  463. }
  464. /**
  465. * Data provider to return a list of referenced tables for each foreign key (if any).
  466. *
  467. * 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.
  468. *
  469. * @access public
  470. * @return array( array( string )* )
  471. */
  472. public function provideFKReferencedTables()
  473. {
  474. $theList = array();
  475. foreach ( $this->getFKColumnList() as $tableName => $columnList )
  476. {
  477. array_push( $theList, array( $tableName ) );
  478. }
  479. return $theList;
  480. }
  481. // /**
  482. // * Data provider to return a list of referenced table and columns for each foreign key.
  483. // *
  484. // * 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.
  485. // *
  486. // * @access public
  487. // * @return array( array( string, array( string+ ) )* )
  488. // */
  489. // public function provideFKDetails()
  490. // {
  491. // $theList = array();
  492. // foreach ( $this->getFKColumnList() as $tableName => $columnList )
  493. // {
  494. // array_push( $theList, array( $tableName, $columnList ) );
  495. // }
  496. //
  497. // return $theList;
  498. // }
  499. /**
  500. * Assert that the table exists.
  501. *
  502. * This queries Oracle's User_Tables data dictionary view for a table matching the current name.
  503. *
  504. * @access protected
  505. * @return void
  506. */
  507. protected function assertTableExists()
  508. {
  509. echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . " table exists ]]\n";
  510. $queryString = sprintf(
  511. "SELECT Table_Name
  512. FROM User_Tables
  513. WHERE ( Table_Name = '%s' )",
  514. strtoupper( $this->getTableName() )
  515. );
  516. // echo "$queryString\n";
  517. $errorString = sprintf(
  518. "couldn't find the %s table [%+1.1f] --- check for misspelled [%+1.1f] or delimited [%+1.1f] identifiers",
  519. ucfirst( strtolower( $this->getTableName() ) ),
  520. $this->markAdjustments['missingTable'],
  521. $this->markAdjustments['misspelledIdentifier'],
  522. $this->markAdjustments['delimitedIdentifier']
  523. );
  524. $actual = $this->getConnection()->createQueryTable( "user_tables", $queryString );
  525. $this->assertEquals( 1, $actual->getRowCount(), $errorString );
  526. }
  527. /**
  528. * Assert that the table has a particular column.
  529. *
  530. * 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.
  531. *
  532. * @access protected
  533. * @return void
  534. */
  535. protected function assertColumnExists( $columnName )
  536. {
  537. echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . '.' . ucfirst( strtolower( $columnName ) ) . " exists ]]\n";
  538. $queryString = sprintf(
  539. "SELECT Column_Name
  540. FROM User_Tab_Cols
  541. WHERE ( Table_Name = '%s' ) AND ( Column_Name = '%s' )",
  542. strtoupper( $this->getTableName() ),
  543. strtoupper( $columnName )
  544. );
  545. // echo "$queryString\n";
  546. $actual = $this->getConnection()->createQueryTable( $this->getTableName() . '_' . $columnName, $queryString );
  547. $errorString = sprintf(
  548. "couldn't find the %s.%s column --- check for misspelled [%+1.1f] or delimited [%+1.1f] identifiers",
  549. ucfirst( strtolower( $this->getTableName() ) ),
  550. ucfirst( strtolower( $columnName ) ),
  551. $this->markAdjustments['misspelledIdentifier'],
  552. $this->markAdjustments['delimitedIdentifier']
  553. );
  554. $this->assertEquals( 1, $actual->getRowCount(), $errorString );
  555. }
  556. /**
  557. * Assert that a column has a particular data type.
  558. *
  559. * 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.
  560. *
  561. * @access protected
  562. * @return void
  563. */
  564. protected function assertColumnDataType( $columnName, $columnTypeList )
  565. {
  566. echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . '.' . ucfirst( strtolower( $columnName ) ) . " data type is " . implode( ' | ', $columnTypeList ) . " ]]\n";
  567. $queryString = sprintf(
  568. "SELECT Data_Type
  569. FROM User_Tab_Cols
  570. WHERE ( Table_Name = '%s' ) AND ( Column_Name = '%s' )",
  571. strtoupper( $this->getTableName() ),
  572. strtoupper( $columnName )
  573. );
  574. // echo "$queryString\n";
  575. $actual = $this->getConnection()->createQueryTable( $this->getTableName() . '_' . $columnName, $queryString );
  576. $errorString = sprintf(
  577. 'column %s.%s has unexpected data type %s [%+1.1f]',
  578. ucfirst( strtolower( $this->getTableName() ) ),
  579. ucfirst( strtolower( $columnName ) ),
  580. $actual->getValue( 0, 'DATA_TYPE' ),
  581. $this->markAdjustments['incorrectDataType']
  582. );
  583. $this->assertContains( $actual->getValue( 0, 'DATA_TYPE' ), $columnTypeList, $errorString );
  584. }
  585. /**
  586. * Assert that a column has a particular length range.
  587. *
  588. * 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.
  589. *
  590. * @access protected
  591. * @return void
  592. */
  593. protected function assertColumnLength( $columnName, $columnType, $minLength, $maxLength, $numDecimals )
  594. {
  595. // This can only happen if all of the columns are things like DATE, BLOB or CLOB.
  596. // This is pretty unlikely in practice, but you never know...
  597. if ( $columnName == '___NO_DATA___' )
  598. {
  599. $this->markTestSkipped( 'no columns with enumerated legal values' );
  600. }
  601. echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . '.' . ucfirst( strtolower( $columnName ) ) . " length is ";
  602. if ( $maxLength == 0 )
  603. {
  604. echo "at least " . $minLength;
  605. }
  606. elseif ( $minLength == 0 )
  607. {
  608. echo "at most " . $maxLength;
  609. }
  610. elseif ( $minLength != $maxLength )
  611. {
  612. echo "between " . $minLength . " and " . $maxLength;
  613. }
  614. else
  615. {
  616. echo $maxLength;
  617. }
  618. if ( $columnType === 'NUMBER' )
  619. {
  620. if ( $numDecimals > 0 ) // technically if could also be < 0, but this is uncommon
  621. {
  622. echo " (including " . $numDecimals . " decimal places)";
  623. }
  624. }
  625. echo " ]]\n";
  626. if ( $columnType === 'NUMBER' )
  627. {
  628. $queryString = sprintf(
  629. "SELECT Data_Type, Data_Precision, Data_Scale
  630. FROM User_Tab_Cols
  631. WHERE ( Table_Name = '%s' ) AND ( Column_Name = '%s' )",
  632. strtoupper( $this->getTableName() ),
  633. strtoupper( $columnName )
  634. );
  635. }
  636. else
  637. {
  638. // We need to include the data type for text columns so that we can ignore CLOBs.
  639. $queryString = sprintf(
  640. "SELECT Data_Type, Char_Length
  641. FROM User_Tab_Cols
  642. WHERE ( Table_Name = '%s' ) AND ( Column_Name = '%s' )",
  643. strtoupper( $this->getTableName() ),
  644. strtoupper( $columnName )
  645. );
  646. }
  647. $actual = $this->getConnection()->createQueryTable( $this->getTableName() . '_' . $columnName, $queryString );
  648. if ( $columnType === 'NUMBER' )
  649. {
  650. $errorString = sprintf(
  651. 'column %s.%s has incorrect length %d, %d [%+1.1f]',
  652. ucfirst( strtolower( $this->getTableName() ) ),
  653. ucfirst( strtolower( $columnName ) ),
  654. $actual->getValue( 0, 'DATA_PRECISION' ),
  655. $actual->getValue( 0, 'DATA_SCALE' ),
  656. $this->markAdjustments['incorrectLength']
  657. );
  658. $this->assertGreaterThanOrEqual( $minLength, $actual->getValue( 0, 'DATA_PRECISION' ), $errorString );
  659. $this->assertLessThanOrEqual( $maxLength, $actual->getValue( 0, 'DATA_PRECISION' ), $errorString );
  660. $this->assertEquals( $numDecimals, $actual->getValue( 0, 'DATA_SCALE' ), $errorString );
  661. }
  662. else
  663. {
  664. // We might encounter CLOBs as an alternative for a large VARCHAR2.
  665. // Ignore these, as they have no particular length. BLOBs, DATEs and
  666. // standalone CLOBs should never show up in the list in the first place,
  667. // as they should have no length specified.
  668. if ( $actual->getValue( 0, 'DATA_TYPE' ) != 'CLOB' )
  669. {
  670. $errorString = sprintf(
  671. 'column %s.%s has incorrect length %d [%+1.1f]',
  672. ucfirst( strtolower( $this->getTableName() ) ),
  673. ucfirst( strtolower( $columnName ) ),
  674. $actual->getValue( 0, 'CHAR_LENGTH' ),
  675. $this->markAdjustments['incorrectLength']
  676. );
  677. $this->assertGreaterThanOrEqual( $minLength, $actual->getValue( 0, 'CHAR_LENGTH' ), $errorString );
  678. $this->assertLessThanOrEqual( $maxLength, $actual->getValue( 0, 'CHAR_LENGTH' ), $errorString );
  679. }
  680. }
  681. }
  682. /**
  683. * Assert that a column allows or disallows nulls.
  684. *
  685. * 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.
  686. *
  687. * @access protected
  688. * @return void
  689. */
  690. protected function assertColumnNullability( $columnName, $columnNullability )
  691. {
  692. echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . '.' . ucfirst( strtolower( $columnName ) ) . " nullability is " . $columnNullability . " ]]\n";
  693. $queryString = sprintf(
  694. "SELECT Nullable
  695. FROM User_Tab_Cols
  696. WHERE ( Table_Name = '%s' ) AND ( Column_Name = '%s' )",
  697. strtoupper( $this->getTableName() ),
  698. strtoupper( $columnName )
  699. );
  700. // echo "$queryString\n";
  701. $actual = $this->getConnection()->createQueryTable( $this->getTableName() . '_' . $columnName, $queryString );
  702. $errorString = sprintf(
  703. 'column %s.%s has incorrect nullability "%s" [%+1.1f]',
  704. ucfirst( strtolower( $this->getTableName() ) ),
  705. ucfirst( strtolower( $columnName ) ),
  706. $actual->getValue( 0, 'NULLABLE' ),
  707. $this->markAdjustments['incorrectNullability']
  708. );
  709. $this->assertEquals( $actual->getValue( 0, 'NULLABLE' ), $columnNullability, $errorString );
  710. }
  711. /**
  712. * Assert that a column accepts a particular legal value.
  713. *
  714. * 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.
  715. *
  716. * @access protected
  717. * @return void
  718. */
  719. protected function assertColumnLegalValue( $columnName, $legalValue )
  720. {
  721. if ( $columnName == '___NO_DATA___' )
  722. {
  723. $this->markTestSkipped( 'no columns with enmuerated legal values' );
  724. }
  725. echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . '.' . ucfirst( strtolower( $columnName ) ) . " accepts legal value [" . $legalValue . "] ]]\n";
  726. $substitutions[$columnName] = $legalValue;
  727. $insertString = $this->constructInsert( $substitutions );
  728. $stmt = $this->getConnection()->getConnection()->prepare( $insertString );
  729. $errorString = sprintf(
  730. "column %s.%s won't accept legal value %s [%+1.1f]",
  731. ucfirst( strtolower( $this->getTableName() ) ),
  732. ucfirst( strtolower( $columnName ) ),
  733. $legalValue,
  734. $this->markAdjustments['incorrectCheck']
  735. );
  736. $this->assertTrue( $stmt->execute(), $errorString );
  737. }
  738. /**
  739. * Assert that a text column rejects a particular illegal value, implicitly enforced by exceeding the column length.
  740. *
  741. * 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:
  742. *
  743. * @expectedException PDOException
  744. * @expectedExceptionMessage length exceeded
  745. * @expectedExceptionCode HY000
  746. *
  747. * @access protected
  748. * @return void
  749. */
  750. protected function assertColumnIllegalValueImplicit( $columnName, $illegalValue )
  751. {
  752. if ( $columnName == '___NO_DATA___' )
  753. {
  754. $this->markTestSkipped( 'no columns with enmuerated illegal values' );
  755. }
  756. echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . '.' . ucfirst( strtolower( $columnName ) ) . " rejects illegal value [" . $illegalValue . "] using the column length (implicit) ]]\n";
  757. $substitutions[$columnName] = $illegalValue;
  758. $insertString = $this->constructInsert( $substitutions );
  759. $stmt = $this->getConnection()->getConnection()->prepare( $insertString );
  760. $errorString = sprintf(
  761. "column %s.%s accepts illegal value %s [%+1.1f]",
  762. ucfirst( strtolower( $this->getTableName() ) ),
  763. ucfirst( strtolower( $columnName ) ),
  764. $illegalValue,
  765. $this->markAdjustments['incorrectCheck']
  766. );
  767. /* 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!
  768. */
  769. try
  770. {
  771. $this->assertTrue( $stmt->execute(), $errorString );
  772. }
  773. catch ( PDOException $e )
  774. {
  775. if ( ( strpos( $e->getMessage(), "value larger than specified precision" ) !== FALSE ) ||
  776. ( strpos( $e->getMessage(), "value too large for column" ) !== FALSE ) )
  777. {
  778. throw new PDOException( "length exceeded" );
  779. }
  780. else
  781. {
  782. throw $e;
  783. }
  784. }
  785. }
  786. /**
  787. * Assert that a text column rejects a particular illegal value, explicitly enforced by a CHECK constraint.
  788. *
  789. * 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:
  790. *
  791. * @expectedException PDOException
  792. * @expectedExceptionMessage check constraint
  793. * @expectedExceptionCode HY000
  794. *
  795. * @access protected
  796. * @return void
  797. */
  798. protected function assertColumnIllegalValueExplicit( $columnName, $illegalValue )
  799. {
  800. if ( $columnName == '___NO_DATA___' )
  801. {
  802. $this->markTestSkipped( 'no columns with enmuerated illegal values' );
  803. }
  804. echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . '.' . ucfirst( strtolower( $columnName ) ) . " rejects illegal value [" . $illegalValue . "] using a CHECK constraint ]]\n";
  805. $substitutions[$columnName] = $illegalValue;
  806. $insertString = $this->constructInsert( $substitutions );
  807. $stmt = $this->getConnection()->getConnection()->prepare( $insertString );
  808. $errorString = sprintf(
  809. "column %s.%s accepts illegal value %s [%+1.1f]",
  810. ucfirst( strtolower( $this->getTableName() ) ),
  811. ucfirst( strtolower( $columnName ) ),
  812. $illegalValue,
  813. $this->markAdjustments['incorrectCheck']
  814. );
  815. $this->assertTrue( $stmt->execute(), $errorString );
  816. }
  817. /**
  818. * Assert that a column only accepts values greater than its underflow value, explicitly enforced by a CHECK constraint.
  819. *
  820. * 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:
  821. *
  822. * @expectedException PDOException
  823. * @expectedExceptionMessage check constraint
  824. * @expectedExceptionCode HY000
  825. *
  826. * 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!
  827. *
  828. * @access protected
  829. * @return void
  830. */
  831. protected function assertColumnUnderflowValue( $columnName, $underflowValue )
  832. {
  833. if ( $columnName == '___NO_DATA___' )
  834. {
  835. $this->markTestSkipped( 'no columns with underflow values' );
  836. }
  837. echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . '.' . ucfirst( strtolower( $columnName ) ) . " rejects illegal values <= [" . $underflowValue . "] using a CHECK constraint ]]\n";
  838. $substitutions[$columnName] = $underflowValue;
  839. $insertString = $this->constructInsert( $substitutions );
  840. $stmt = $this->getConnection()->getConnection()->prepare( $insertString );
  841. $errorString = sprintf(
  842. "column %s.%s accepts illegal values <= %s [%+1.1f]",
  843. ucfirst( strtolower( $this->getTableName() ) ),
  844. ucfirst( strtolower( $columnName ) ),
  845. $underflowValue,
  846. $this->markAdjustments['incorrectCheck']
  847. );
  848. $this->assertTrue( $stmt->execute(), $errorString );
  849. }
  850. /**
  851. * Assert that a column only accepts values less than its overflow value, implicitly enforced by exceeding the column length.
  852. *
  853. * 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:
  854. *
  855. * @expectedException PDOException
  856. * @expectedExceptionMessage length exceeded
  857. * @expectedExceptionCode HY000
  858. *
  859. * @access protected
  860. * @return void
  861. */
  862. protected function assertColumnOverflowValueImplicit( $columnName, $overflowValue )
  863. {
  864. if ( $columnName == '___NO_DATA___' )
  865. {
  866. $this->markTestSkipped( 'no columns with overflow values' );
  867. }
  868. echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . '.' . ucfirst( strtolower( $columnName ) ) . " rejects illgal values >= [" . $overflowValue . "] using the column length (implicit) ]]\n";
  869. $substitutions[$columnName] = $overflowValue;
  870. $insertString = $this->constructInsert( $substitutions );
  871. $stmt = $this->getConnection()->getConnection()->prepare( $insertString );
  872. $errorString = sprintf(
  873. "column %s.%s accepts illegal values >= %s [%+1.1f]",
  874. ucfirst( strtolower( $this->getTableName() ) ),
  875. ucfirst( strtolower( $columnName ) ),
  876. $overflowValue,
  877. $this->markAdjustments['incorrectCheck']
  878. );
  879. /* 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!
  880. */
  881. try
  882. {
  883. $this->assertTrue( $stmt->execute(), $errorString );
  884. }
  885. catch ( PDOException $e )
  886. {
  887. if ( ( strpos( $e->getMessage(), "value larger than specified precision" ) !== FALSE ) ||
  888. ( strpos( $e->getMessage(), "value too large for column" ) !== FALSE ) )
  889. {
  890. throw new PDOException( "length exceeded" );
  891. }
  892. else
  893. {
  894. throw $e;
  895. }
  896. }
  897. }
  898. /**
  899. * Assert that a column only accepts values less than its overflow value, explicitly enforced by a CHECK constraint.
  900. *
  901. * 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:
  902. *
  903. * @expectedException PDOException
  904. * @expectedExceptionMessage check constraint
  905. * @expectedExceptionCode HY000
  906. *
  907. * @access protected
  908. * @return void
  909. */
  910. protected function assertColumnOverflowValueExplicit( $columnName, $overflowValue )
  911. {
  912. if ( $columnName == '___NO_DATA___' )
  913. {
  914. $this->markTestSkipped( 'no columns with overflow values' );
  915. }
  916. echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . '.' . ucfirst( strtolower( $columnName ) ) . " rejects illegal values >= [" . $overflowValue . "] using a CHECK constraint (explicit) ]]\n";
  917. $substitutions[$columnName] = $overflowValue;
  918. $insertString = $this->constructInsert( $substitutions );
  919. $stmt = $this->getConnection()->getConnection()->prepare( $insertString );
  920. $errorString = sprintf(
  921. "column %s.%s accepts illegal values >= %s [%+1.1f]",
  922. ucfirst( strtolower( $this->getTableName() ) ),
  923. ucfirst( strtolower( $columnName ) ),
  924. $overflowValue,
  925. $this->markAdjustments['incorrectCheck']
  926. );
  927. $this->assertTrue( $stmt->execute(), $errorString );
  928. }
  929. /**
  930. * Assert that the primary key constraint of a table exists.
  931. *
  932. * 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.
  933. *
  934. * @access protected
  935. * @return string
  936. */
  937. public function assertPKExists()
  938. {
  939. echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . " table primary key constraint exists ]]\n";
  940. $queryString = sprintf(
  941. "SELECT Constraint_Name
  942. FROM User_Constraints
  943. WHERE ( Table_Name = '%s' ) AND ( Constraint_Type = 'P' )",
  944. strtoupper( $this->getTableName() )
  945. );
  946. $actual = $this->getConnection()->createQueryTable( $this->getTableName() . '_PK', $queryString );
  947.  
  948. $errorString = sprintf(
  949. "couldn't find a PK constraint for %s [%+1.1f]",
  950. ucfirst( strtolower( $this->getTableName() ) ),
  951. $this->markAdjustments['incorrectPK']
  952. );
  953. $this->assertEquals( 1, $actual->getRowCount(), $errorString );
  954. return $actual->getValue( 0, 'CONSTRAINT_NAME' );
  955. }
  956. /**
  957. * Assert that the primary key constraint of a table includes the correct columns.
  958. *
  959. * 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.
  960. *
  961. * @access protected
  962. * @return void
  963. */
  964. public function assertPKColumns( $constraintName )
  965. {
  966. $tableName = $this->getTableName() . '_PK_cols';
  967. $expected = $this->getPKColumnListAsDataSet( $tableName );
  968. echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) .
  969. " table primary key constraint contains (only) the column";
  970. if ( count( $this->getPKColumnlist() ) > 1 )
  971. {
  972. echo "s";
  973. }
  974. echo " " . ucwords( strtolower( implode( ', ', $this->getPKColumnList() ) ) ) . " ]]\n";
  975.  
  976. $queryString = sprintf(
  977. "SELECT Column_Name
  978. FROM User_Cons_Columns
  979. WHERE ( Constraint_Name = '%s' )
  980. ORDER BY Position",
  981. strtoupper( $constraintName )
  982. );
  983. $actual = $this->getConnection()->createQueryTable( $tableName, $queryString );
  984.  
  985. $errorString = sprintf(
  986. "the PK constraint for %s has incorrect columns [%+1.1f]",
  987. ucfirst( strtolower( $this->getTableName() ) ),
  988. $this->markAdjustments['incorrectPK']
  989. );
  990. $this->assertTablesEqual( $expected->getTable( $tableName ), $actual, $errorString );
  991. }
  992. /**
  993. * Assert that the foreign key constraint(s) of a table exist.
  994. *
  995. * 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.
  996. *
  997. * @access protected
  998. * @return void
  999. */
  1000. public function assertFKsExist( $referencedTableName )
  1001. {
  1002. echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . " table foreign key constraint referencing " . ucfirst( strtolower( $referencedTableName ) ) . " exists ]]\n";
  1003. $queryString = sprintf(
  1004. "SELECT Child.Constraint_Name
  1005. FROM User_Constraints Child INNER JOIN User_Constraints Parent
  1006. ON ( Child.R_Constraint_Name = Parent.Constraint_Name )
  1007. WHERE ( Child.Table_Name = '%s' )
  1008. AND ( Parent.Table_Name = '%s' )
  1009. AND ( Child.Constraint_Type = 'R' )",
  1010. strtoupper( $this->getTableName() ),
  1011. strtoupper( $referencedTableName )
  1012. );
  1013. $actual = $this->getConnection()->createQueryTable( $this->getTableName() . '_FK', $queryString );
  1014.  
  1015. $errorString = sprintf(
  1016. "couldn't find a FK constraint for %s referencing %s [%+1.1f]",
  1017. ucfirst( strtolower( $this->getTableName() ) ),
  1018. ucfirst( strtolower( $referencedTableName ) ),
  1019. $this->markAdjustments['incorrectPK']
  1020. );
  1021. $this->assertEquals( 1, $actual->getRowCount(), $errorString );
  1022. }
  1023. /**
  1024. * Assert that the foreign key constraints of a table include the correct columns.
  1025. *
  1026. * We can query User_Cons_Columns to see whether the lists match. Tests that use this should use provideFKReferencedTables as their data provider.
  1027. *
  1028. * @access protected
  1029. * @return void
  1030. */
  1031. public function assertFKColumns( $referencedTableName )
  1032. {
  1033. $tableName = $referencedTableName . '_FK_cols';
  1034. $expected = $this->getFKColumnListForTableAsDataSet( $referencedTableName, $tableName );
  1035. echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) .
  1036. " table foreign key constraint referencing " . ucfirst( strtolower( $referencedTableName ) ) . " contains (only) the column";
  1037. if ( count( $this->getFKColumnlist() ) > 1 )
  1038. {
  1039. echo "s";
  1040. }
  1041. echo " " . ucwords( strtolower( implode( ', ', $this->getFKColumnListForTable( $referencedTableName ) ) ) ) . " ]]\n";
  1042.  
  1043. $queryString = sprintf(
  1044. "SELECT User_Cons_Columns.Column_Name
  1045. FROM User_Constraints Child INNER JOIN User_Constraints Parent
  1046. ON ( Child.R_Constraint_Name = Parent.Constraint_Name )
  1047. INNER JOIN User_Cons_Columns
  1048. ON ( Child.Constraint_Name = User_Cons_Columns.Constraint_Name )
  1049. WHERE ( Child.Table_Name = '%s' )
  1050. AND ( Parent.Table_Name = '%s' )
  1051. AND ( Child.Constraint_Type = 'R' )
  1052. ORDER BY User_Cons_Columns.Position",
  1053. strtoupper( $this->getTableName() ),
  1054. strtoupper( $referencedTableName ),
  1055. strtoupper( implode( "', '", $this->getFKColumnListForTable( $referencedTableName ) ) )
  1056. );
  1057. $actual = $this->getConnection()->createQueryTable( $tableName, $queryString );
  1058. // Note that we can't use the same trick as for PKs of the second test depending on the first, as this
  1059. // doesn't work when the first test is iterated by a data provider :(. (This probably makes sense when
  1060. // you think about how test execution works in general.) We also can't directly access the protected
  1061. // "data" member of the query table, so we resort to checking whether the row count is zero and skipping
  1062. // the test if so.
  1063. if ( $actual->getRowCount() == 0 ) $this->markTestSkipped( 'FK is missing anyway' );
  1064.  
  1065. $errorString = sprintf(
  1066. "the FK constraint for %s has incorrect columns [%+1.1f]",
  1067. ucfirst( strtolower( $this->getTableName() ) ),
  1068. $this->markAdjustments['unnamedConstraint']
  1069. );
  1070. $this->assertTablesEqual( $expected->getTable( $tableName ), $actual, $errorString );
  1071. }
  1072. /**
  1073. * Assert that a constraint of a table has been explicitly named.
  1074. *
  1075. * 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.
  1076. *
  1077. * @access protected
  1078. * @return void
  1079. */
  1080. public function assertConstraintNamed( $constraintName, $constraintType )
  1081. {
  1082. if ( $constraintName == '___NO_DATA___' )
  1083. {
  1084. $this->markTestSkipped( 'no constraints to be tested on this table' );
  1085. }
  1086. switch ( $constraintType )
  1087. {
  1088. case 'C':
  1089. $longType = 'check';
  1090. break;
  1091. case 'P':
  1092. $longType = 'primary key';
  1093. break;
  1094. case 'R':
  1095. $longType = 'foreign key';
  1096. break;
  1097. case 'U':
  1098. $longType = 'unique';
  1099. break;
  1100. default:
  1101. $longtype = "unknown (${constraintType})";
  1102. break;
  1103. }
  1104. echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) .
  1105. " table " . $longType . " constraint " . $constraintName . " is explicitly named ]]\n";
  1106.  
  1107. $errorString = sprintf(
  1108. "the %s constraint %s for %s hasn't been explicitly named [%+1.1f]",
  1109. $longType,
  1110. $constraintName,
  1111. ucfirst( strtolower( $this->getTableName() ) ),
  1112. $this->markAdjustments['unnamedConstraint']
  1113. );
  1114. $this->assertNotRegExp( '/^SYS_/', $constraintName, $errorString );
  1115. }
  1116.  
  1117. /**
  1118. * @expectedException PDOException
  1119. * @expectedExceptionMessage unique constraint
  1120. * @expectedExceptionCode HY000
  1121. */
  1122. // protected function testPrimaryKeyUnique()
  1123. // {
  1124. // echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . " table primary key (UNIQUE) ]]\n";
  1125. // $stmt = $this->getConnection()->getConnection()->prepare( "INSERT INTO $this->getTableName() VALUES ( 326, 'foo', 'bar', '1234567', 'baz', 'Manufacturing', 'Technician', 12345, 'quux' )" );
  1126. // $this->assertTrue( $stmt->execute(), ucfirst( strtolower( $this->getTableName() ) ) . " PK constraint is missing or incorrectly implemented (permits duplicates) [-1]" );
  1127. // }
  1128. /**
  1129. * @expectedException PDOException
  1130. * @expectedExceptionMessage cannot insert NULL into
  1131. * @expectedExceptionCode HY000
  1132. */
  1133. // protected function testPrimaryKeyNotNull()
  1134. // {
  1135. // echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . " table primary key (NOT NULL) ]]\n";
  1136. // $stmt = $this->getConnection()->getConnection()->prepare( "INSERT INTO $this->getTableName() VALUES ( null, 'foo', 'bar', '1234567', 'baz', 'Manufacturing', 'Technician', 12345, 'quux' )" );
  1137. // $this->assertTrue( $stmt->execute(), ucfirst( strtolower( $this->getTableName() ) ) . " PK constraint is missing or incorrectly implemented (permits nulls) [-1]" );
  1138. // }
  1139. /**
  1140. * @expectedException PDOException
  1141. * @expectedExceptionMessage invalid number
  1142. * @expectedExceptionCode HY000
  1143. */
  1144. // protected function testStaffIdDataType()
  1145. // {
  1146. // echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . ".Staff_ID data type (NUMBER) ]]\n";
  1147. // $stmt = $this->getConnection()->getConnection()->prepare( "INSERT INTO $this->getTableName() VALUES ( 'abc', 'foo', 'bar', '1234567', 'baz', 'Manufacturing', 'Technician', 12345, 'quux' )" );
  1148. // $this->assertTrue( $stmt->execute(), ucfirst( strtolower( $this->getTableName() ) ) . '.Staff_ID data type is not NUMBER [-1]' );
  1149. // }
  1150. /**
  1151. * expectedException PDOException
  1152. * expectedExceptionMessage invalid number
  1153. * expectedExceptionCode HY000
  1154. */
  1155. // protected function testStaffIdMaximumValue()
  1156. // {
  1157. // echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . ".Staff_ID maximum value (9999999) ]]\n";
  1158. // $stmt = $this->getConnection()->getConnection()->prepare( "INSERT INTO $this->getTableName() VALUES ( 9999999, 'foo', 'bar', '1234567', 'baz', 'Manufacturing', 'Technician', 12345, 'quux' )" );
  1159. // $this->assertTrue( $stmt->execute(), ucfirst( strtolower( $this->getTableName() ) ) . '.Staff_ID size is too small (< 7 digits) [-0.5]' );
  1160. // }
  1161. /**
  1162. * @expectedException PDOException
  1163. * @expectedExceptionMessage value larger than specified precision allowed for this column
  1164. * @expectedExceptionCode HY000
  1165. */
  1166. // protected function testStaffIdMaximumSize()
  1167. // {
  1168. // echo "\n[[ Testing whether " . ucfirst( strtolower( $this->getTableName() ) ) . ".Staff_ID maximum size (7 digits) ]]\n";
  1169. // $stmt = $this->getConnection()->getConnection()->prepare( "INSERT INTO $this->getTableName() VALUES ( 99999999, 'foo', 'bar', '1234567', 'baz', 'Manufacturing', 'Technician', 12345, 'quux' )" );
  1170. // $this->assertTrue( $stmt->execute(), ucfirst( strtolower( $this->getTableName() ) ) . '.Staff_ID size is too large (> 7-digits) [-0.5]' );
  1171. // }
  1172. }
  1173. ?>
  1174.