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