From 0f0d3c62e608287cdf77f1a3239371b521ecb40b Mon Sep 17 00:00:00 2001 From: wei <> Date: Sat, 14 Apr 2007 05:02:29 +0000 Subject: Refactor ActiveRecordGateway to use TDataGatewayCommand --- .gitattributes | 11 + build.xml | 80 ++++++- buildscripts/phing/tasks/PradoPackageTask.php | 140 ++++++++++++ .../protected/pages/Controls/Validation.page | 7 + .../Exceptions/TActiveRecordException.php | 2 +- .../Data/ActiveRecord/Exceptions/messages.txt | 3 +- .../Scaffold/InputBuilder/TIbmScaffoldInput.php | 6 +- .../Scaffold/InputBuilder/TMssqlScaffoldInput.php | 43 ++++ .../Scaffold/InputBuilder/TMysqlScaffoldInput.php | 10 +- .../Scaffold/InputBuilder/TPgsqlScaffoldInput.php | 4 +- .../Scaffold/InputBuilder/TScaffoldInputBase.php | 15 +- .../Scaffold/InputBuilder/TScaffoldInputCommon.php | 32 ++- .../Scaffold/InputBuilder/TSqliteScaffoldInput.php | 10 +- .../Data/ActiveRecord/Scaffold/TScaffoldBase.php | 22 +- .../ActiveRecord/Scaffold/TScaffoldEditView.php | 4 +- .../ActiveRecord/Scaffold/TScaffoldEditView.tpl | 2 +- .../ActiveRecord/Scaffold/TScaffoldListView.php | 2 +- .../Data/ActiveRecord/Scaffold/TScaffoldSearch.php | 4 +- framework/Data/ActiveRecord/Scaffold/style.css | 2 +- framework/Data/ActiveRecord/TActiveRecord.php | 135 ++++------- .../Data/ActiveRecord/TActiveRecordGateway.php | 249 ++++++++++++--------- .../Data/ActiveRecord/TActiveRecordManager.php | 56 ----- .../ActiveRecord/TActiveRecordStateRegistry.php | 8 +- .../Data/Common/IbmDb2/TIbmColumnMetaData.php | 157 +++++++++++++ framework/Data/Common/IbmDb2/TIbmMetaData.php | 113 ++++++++++ .../Data/Common/IbmDb2/TIbmMetaDataInspector.php | 113 ++++++++++ .../Data/Common/Mssql/TMssqlCommandBuilder.php | 173 ++++++++++++++ framework/Data/Common/Mssql/TMssqlMetaData.php | 202 +++++++++++++++++ framework/Data/Common/Mssql/TMssqlTableColumn.php | 56 +++++ framework/Data/Common/Mssql/TMssqlTableInfo.php | 62 +++++ framework/Data/Common/Mysql/TMysqlMetaData.php | 14 +- framework/Data/Common/Mysql/TMysqlTableColumn.php | 2 +- framework/Data/Common/Mysql/TMysqlTableInfo.php | 5 +- framework/Data/Common/Pgsql/TPgsqlMetaData.php | 3 +- framework/Data/Common/Pgsql/TPgsqlTableInfo.php | 5 +- framework/Data/Common/Sqlite/TSqliteMetaData.php | 22 +- .../Data/Common/Sqlite/TSqliteTableColumn.php | 2 +- framework/Data/Common/TDbCommandBuilder.php | 18 ++ framework/Data/Common/TDbMetaData.php | 9 +- framework/Data/Common/TDbTableColumn.php | 27 ++- framework/Data/DataGateway/TDataGatewayCommand.php | 23 +- framework/Exceptions/messages.txt | 2 +- .../Javascripts/source/prado/ratings/ratings.js | 2 +- .../source/prado/validator/validation3.js | 4 +- framework/Web/UI/WebControls/TRangeValidator.php | 54 ++++- framework/prado-cli.php | 8 +- .../ActiveRecordDynamicCallTestCase.php | 2 +- tests/simple_unit/ActiveRecord/SqliteTestCase.php | 6 - .../ActiveRecord/ViewRecordTestCase.php | 4 +- .../DbCommon/CommandBuilderMssqlTest.php | 46 ++++ tests/simple_unit/DbCommon/MssqlColumnTest.php | 48 ++++ 51 files changed, 1672 insertions(+), 357 deletions(-) create mode 100644 buildscripts/phing/tasks/PradoPackageTask.php create mode 100644 framework/Data/ActiveRecord/Scaffold/InputBuilder/TMssqlScaffoldInput.php create mode 100644 framework/Data/Common/IbmDb2/TIbmColumnMetaData.php create mode 100644 framework/Data/Common/IbmDb2/TIbmMetaData.php create mode 100644 framework/Data/Common/IbmDb2/TIbmMetaDataInspector.php create mode 100644 framework/Data/Common/Mssql/TMssqlCommandBuilder.php create mode 100644 framework/Data/Common/Mssql/TMssqlMetaData.php create mode 100644 framework/Data/Common/Mssql/TMssqlTableColumn.php create mode 100644 framework/Data/Common/Mssql/TMssqlTableInfo.php create mode 100644 tests/simple_unit/DbCommon/CommandBuilderMssqlTest.php create mode 100644 tests/simple_unit/DbCommon/MssqlColumnTest.php diff --git a/.gitattributes b/.gitattributes index c58d5019..ca32894f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -617,6 +617,7 @@ buildscripts/phing/tasks/BuildPradoPEARPackageTask.php -text buildscripts/phing/tasks/ManualIndexTask.php -text buildscripts/phing/tasks/PhpLintTask.php -text buildscripts/phing/tasks/PradoDocTask.php -text +buildscripts/phing/tasks/PradoPackageTask.php -text buildscripts/phing/tasks/PradoPearTask.php -text buildscripts/phing/tasks/PradoTestTask.php -text buildscripts/phing/tasks/PradoVersionTask.php -text @@ -1606,6 +1607,7 @@ framework/Configuration/TProtectedConfiguration.php -text framework/Data/ActiveRecord/Exceptions/TActiveRecordException.php -text framework/Data/ActiveRecord/Exceptions/messages.txt -text framework/Data/ActiveRecord/Scaffold/InputBuilder/TIbmScaffoldInput.php -text +framework/Data/ActiveRecord/Scaffold/InputBuilder/TMssqlScaffoldInput.php -text framework/Data/ActiveRecord/Scaffold/InputBuilder/TMysqlScaffoldInput.php -text framework/Data/ActiveRecord/Scaffold/InputBuilder/TPgsqlScaffoldInput.php -text framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputBase.php -text @@ -1642,6 +1644,13 @@ framework/Data/ActiveRecord/Vendor/TPgsqlMetaDataInspector.php -text framework/Data/ActiveRecord/Vendor/TSqliteColumnMetaData.php -text framework/Data/ActiveRecord/Vendor/TSqliteMetaData.php -text framework/Data/ActiveRecord/Vendor/TSqliteMetaDataInspector.php -text +framework/Data/Common/IbmDb2/TIbmColumnMetaData.php -text +framework/Data/Common/IbmDb2/TIbmMetaData.php -text +framework/Data/Common/IbmDb2/TIbmMetaDataInspector.php -text +framework/Data/Common/Mssql/TMssqlCommandBuilder.php -text +framework/Data/Common/Mssql/TMssqlMetaData.php -text +framework/Data/Common/Mssql/TMssqlTableColumn.php -text +framework/Data/Common/Mssql/TMssqlTableInfo.php -text framework/Data/Common/Mysql/TMysqlMetaData.php -text framework/Data/Common/Mysql/TMysqlTableColumn.php -text framework/Data/Common/Mysql/TMysqlTableInfo.php -text @@ -2665,8 +2674,10 @@ tests/simple_unit/ActiveRecord/records/DepartmentRecord.php -text tests/simple_unit/ActiveRecord/records/SimpleUser.php -text tests/simple_unit/ActiveRecord/records/SqliteUsers.php -text tests/simple_unit/ActiveRecord/records/UserRecord.php -text +tests/simple_unit/DbCommon/CommandBuilderMssqlTest.php -text tests/simple_unit/DbCommon/CommandBuilderMysqlTest.php -text tests/simple_unit/DbCommon/CommandBuilderPgsqlTest.php -text +tests/simple_unit/DbCommon/MssqlColumnTest.php -text tests/simple_unit/DbCommon/MysqlColumnTest.php -text tests/simple_unit/DbCommon/PgsqlColumnTest.php -text tests/simple_unit/DbCommon/SqliteColumnTest.php -text diff --git a/build.xml b/build.xml index 40000661..8b6db816 100644 --- a/build.xml +++ b/build.xml @@ -24,6 +24,8 @@ + + @@ -33,7 +35,8 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Building pradolite.php... diff --git a/buildscripts/phing/tasks/PradoPackageTask.php b/buildscripts/phing/tasks/PradoPackageTask.php new file mode 100644 index 00000000..fefeb1e0 --- /dev/null +++ b/buildscripts/phing/tasks/PradoPackageTask.php @@ -0,0 +1,140 @@ +output=$file; + } + + function setStrip($value) + { + $this->strip = (boolean)$value; + } + + /** + * Supports embedded element. + * @return FileList + */ + function createFileList() { + $num = array_push($this->filelists, new FileList()); + return $this->filelists[$num-1]; + } + + function main() + { + $project = $this->getProject(); + + $content = ''; + $files=array(); + // append the files in the filelists + foreach($this->filelists as $fl) + { + $fromDir = $fl->getDir($project); + foreach($fl->getFiles($project) as $file) + { + $src = new PhingFile($fromDir,$file); + $files[] = $file; + $content .= file_get_contents($src->getAbsolutePath()); + } + } + + $content = $this->processPhp($content,$files); + file_put_contents($this->output->getAbsolutePath(), $content); + } + + function processPhp($content,$files) + { + $content = preg_replace('/^\s*Prado::trace.*\s*;\s*$/mu','',$content); + $content = preg_replace('/(PradoBase::using|Prado::using|require_once|include_once)\s*\(.*?\);/mu','',$content); + $content = str_replace('Prado::', 'PradoBase::', $content); + if($this->strip) + $content=$this->strip_comments($content); + $content=$this->strip_empty_lines($content); + $content="getFileComment($files).preg_replace('/(\?>\s?|<\?php\s?)/mu','',$content)."\n?>"; + return $content; + } + +function strip_comments($source) +{ + $tokens = token_get_all($source); + /* T_ML_COMMENT does not exist in PHP 5. + * The following three lines define it in order to + * preserve backwards compatibility. + * + * The next two lines define the PHP 5-only T_DOC_COMMENT, + * which we will mask as T_ML_COMMENT for PHP 4. + */ + if (!defined('T_ML_COMMENT')) { + @define('T_ML_COMMENT', T_COMMENT); + } else { + @define('T_DOC_COMMENT', T_ML_COMMENT); + } + $output = ''; + foreach ($tokens as $token) { + if (is_string($token)) { + // simple 1-character token + $output .= $token; + } else { + // token array + list($id, $text) = $token; + switch ($id) { + case T_COMMENT: + case T_ML_COMMENT: // we've defined this + case T_DOC_COMMENT: // and this + // no action on comments + break; + default: + // anything else -> output "as is" + $output .= $text; + break; + } + } + } + return $output; +} + +function strip_empty_lines($string) +{ + $string = preg_replace("/[\r\n]+[\s\t]*[\r\n]+/", "\n", $string); + $string = preg_replace("/^[\s\t]*[\r\n]+/", "", $string); + return $string; +} +function getFileComment($files) +{ + $lastupdate=date('Y/m/d H:i:s'); + $year=date('Y'); + $fileList=array(); + foreach($files as $file) + $fileList[] = " * $file"; + $fileListStr = implode("\n", $fileList); +$comments=" +/** + * Last Update: $lastupdate + * + * Do not modify this file manually. This file was auto-generated by combining + * the following classes from the Prado framework. + * + * Files: +{$fileListStr} + * + * @author Qiang Xue , Wei Zhuo + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-{$year} PradoSoft + * @license http://www.pradosoft.com/license/ + */ + +"; + return $comments; +} +} +?> \ No newline at end of file diff --git a/demos/quickstart/protected/pages/Controls/Validation.page b/demos/quickstart/protected/pages/Controls/Validation.page index 3b41000c..78ec0a4e 100644 --- a/demos/quickstart/protected/pages/Controls/Validation.page +++ b/demos/quickstart/protected/pages/Controls/Validation.page @@ -109,6 +109,13 @@ TDataTypeValidator verifies if the input data is of specific type indicated by <

TRangeValidator verifies whether an input value is within a specified range. TRangeValidator uses three key properties to perform its validation. The MinValue and MaxValue properties specify the minimum and maximum values of the valid range. The DataType property specifies the data type of the value being validated. The value will be first converted into the specified type and then compare with the valid range. The data types that can be checked against are the same as those in TCompareValidator.

+ +

+If StrictComparison property is set to true, then the ranges +are compared as strictly less than the MaxValue and/or strictly greater than the +MinValue. +

+

N.B. If validating against a TDatePicker the DataType must be equal to "Date" and the DateFormat property of the validator must be equal to the DateFormat of the TDatePicker.

diff --git a/framework/Data/ActiveRecord/Exceptions/TActiveRecordException.php b/framework/Data/ActiveRecord/Exceptions/TActiveRecordException.php index 45a575dd..8654c35c 100644 --- a/framework/Data/ActiveRecord/Exceptions/TActiveRecordException.php +++ b/framework/Data/ActiveRecord/Exceptions/TActiveRecordException.php @@ -18,7 +18,7 @@ * @package System.Data.ActiveRecord * @since 3.1 */ -class TActiveRecordException extends TException +class TActiveRecordException extends TDbException { /** * @return string path to the error message file diff --git a/framework/Data/ActiveRecord/Exceptions/messages.txt b/framework/Data/ActiveRecord/Exceptions/messages.txt index 9ab1693e..b3d567e4 100644 --- a/framework/Data/ActiveRecord/Exceptions/messages.txt +++ b/framework/Data/ActiveRecord/Exceptions/messages.txt @@ -16,4 +16,5 @@ ar_pk_value_count_mismatch = Composite key value count mismatch in forming I ar_must_copy_from_array_or_object = $data in {0}::copyFrom($data) must be an object or an array. ar_mismatch_column_names = In dynamic __call() method '{0}', no matching columns were found, valid columns for table '{2}' are '{1}'. ar_invalid_table = Missing, invalid or no permission for table/view '{0}'. -ar_invalid_finder_class_name = Class name for finder($className) method must not be 'TActiveRecord', you should override the finder() method in your record class or pass in a valid record class name. \ No newline at end of file +ar_invalid_finder_class_name = Class name for finder($className) method must not be 'TActiveRecord', you should override the finder() method in your record class or pass in a valid record class name. +ar_invalid_criteria = Invalid criteria object, must be a string or instance of TSqlCriteria. diff --git a/framework/Data/ActiveRecord/Scaffold/InputBuilder/TIbmScaffoldInput.php b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TIbmScaffoldInput.php index ada2091e..2c315468 100644 --- a/framework/Data/ActiveRecord/Scaffold/InputBuilder/TIbmScaffoldInput.php +++ b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TIbmScaffoldInput.php @@ -13,7 +13,7 @@ class TIbmScaffoldInput extends TScaffoldInputCommon { protected function createControl($container, $column, $record) { - switch(strtolower($column->getType())) + switch(strtolower($column->getDbType())) { case 'date': return $this->createDateControl($container, $column, $record); @@ -34,7 +34,7 @@ class TIbmScaffoldInput extends TScaffoldInputCommon protected function getControlValue($container, $column, $record) { - switch(strtolower($column->getType())) + switch(strtolower($column->getDbType())) { case 'date': return $container->findControl(self::DEFAULT_ID)->getDate(); @@ -43,7 +43,7 @@ class TIbmScaffoldInput extends TScaffoldInputCommon case 'timestamp': return $this->getDateTimeValue($container, $column, $record); default: - return $this->getDefaultControlValue($container,$column, $record); + return $this->getDefaultControlValue($container,$column, $record); } } } diff --git a/framework/Data/ActiveRecord/Scaffold/InputBuilder/TMssqlScaffoldInput.php b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TMssqlScaffoldInput.php new file mode 100644 index 00000000..840e5b63 --- /dev/null +++ b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TMssqlScaffoldInput.php @@ -0,0 +1,43 @@ +getDbType())) + { + case 'bit': + return $this->createBooleanControl($container, $column, $record); + case 'text': + return $this->createMultiLineControl($container, $column, $record); + case 'smallint': case 'int': case 'bigint': case 'tinyint': + return $this->createIntegerControl($container, $column, $record); + case 'decimal': case 'float': case 'money': case 'numeric': case 'real': case 'smallmoney': + return $this->createFloatControl($container, $column, $record); + case 'datetime': case 'smalldatetime': + return $this->createDateTimeControl($container, $column, $record); + default: + $control = $this->createDefaultControl($container,$column, $record); + if($column->getIsExcluded()) + $control->setEnabled(false); + return $control; + } + } + + protected function getControlValue($container, $column, $record) + { + switch(strtolower($column->getDbType())) + { + case 'boolean': + return $container->findControl(self::DEFAULT_ID)->getChecked(); + case 'datetime': case 'smalldatetime': + return $this->getDateTimeValue($container,$column, $record); + default: + return $this->getDefaultControlValue($container,$column, $record); + } + } +} + +?> \ No newline at end of file diff --git a/framework/Data/ActiveRecord/Scaffold/InputBuilder/TMysqlScaffoldInput.php b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TMysqlScaffoldInput.php index 89df0f2e..42f8cead 100644 --- a/framework/Data/ActiveRecord/Scaffold/InputBuilder/TMysqlScaffoldInput.php +++ b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TMysqlScaffoldInput.php @@ -6,7 +6,8 @@ class TMysqlScaffoldInput extends TScaffoldInputCommon { protected function createControl($container, $column, $record) { - switch(strtolower($column->getType())) + $dbtype = trim(str_replace(array('unsigned', 'zerofill'),array('','',),strtolower($column->getDbType()))); + switch($dbtype) { case 'date': return $this->createDateControl($container, $column, $record); @@ -34,7 +35,8 @@ class TMysqlScaffoldInput extends TScaffoldInputCommon protected function getControlValue($container, $column, $record) { - switch(strtolower($column->getType())) + $dbtype = trim(str_replace(array('unsigned', 'zerofill'),array('','',),strtolower($column->getDbType()))); + switch($dbtype) { case 'date': return $container->findControl(self::DEFAULT_ID)->getDate(); @@ -57,7 +59,7 @@ class TMysqlScaffoldInput extends TScaffoldInputCommon protected function createIntegerControl($container, $column, $record) { - if($column->getLength()==1) + if($column->getColumnSize()==1) return $this->createBooleanControl($container, $column, $record); else parent::createIntegerControl($container, $column, $record); @@ -65,7 +67,7 @@ class TMysqlScaffoldInput extends TScaffoldInputCommon protected function getIntBooleanValue($container,$column, $record) { - if($column->getLength()==1) + if($column->getColumnSize()==1) return (int)$container->findControl(self::DEFAULT_ID)->getChecked(); else return $this->getDefaultControlValue($container,$column, $record); diff --git a/framework/Data/ActiveRecord/Scaffold/InputBuilder/TPgsqlScaffoldInput.php b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TPgsqlScaffoldInput.php index f5e11eae..a024c73b 100644 --- a/framework/Data/ActiveRecord/Scaffold/InputBuilder/TPgsqlScaffoldInput.php +++ b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TPgsqlScaffoldInput.php @@ -6,7 +6,7 @@ class TPgsqlScaffoldInput extends TScaffoldInputCommon { protected function createControl($container, $column, $record) { - switch(strtolower($column->getType())) + switch(strtolower($column->getDbType())) { case 'boolean': return $this->createBooleanControl($container, $column, $record); @@ -29,7 +29,7 @@ class TPgsqlScaffoldInput extends TScaffoldInputCommon protected function getControlValue($container, $column, $record) { - switch(strtolower($column->getType())) + switch(strtolower($column->getDbType())) { case 'boolean': return $container->findControl(self::DEFAULT_ID)->getChecked(); diff --git a/framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputBase.php b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputBase.php index 05e83157..f9ca9180 100644 --- a/framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputBase.php +++ b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputBase.php @@ -27,6 +27,9 @@ class TScaffoldInputBase case 'pgsql': require_once(dirname(__FILE__).'/TPgsqlScaffoldInput.php'); return new TPgsqlScaffoldInput($conn); + case 'mssql': + require_once(dirname(__FILE__).'/TMssqlScaffoldInput.php'); + return new TMssqlScaffoldInput($conn); case 'ibm': require_once(dirname(__FILE__).'/TIbmScaffoldInput.php'); return new TIbmScaffoldInput($conn); @@ -39,7 +42,7 @@ class TScaffoldInputBase public function createScaffoldInput($parent, $item, $column, $record) { $this->_parent=$parent; - $item->setCustomData($column->getProperty()); + $item->setCustomData($column->getColumnId()); $this->createControl($item->_input, $column, $record); if($item->_input->findControl(self::DEFAULT_ID)) $this->createControlLabel($item->_label, $column, $record); @@ -47,7 +50,7 @@ class TScaffoldInputBase protected function createControlLabel($label, $column, $record) { - $fieldname = ucwords(str_replace('_', ' ', $column->getProperty())).':'; + $fieldname = ucwords(str_replace('_', ' ', $column->getColumnId())).':'; $label->setText($fieldname); $label->setForControl(self::DEFAULT_ID); } @@ -57,7 +60,7 @@ class TScaffoldInputBase $this->_parent=$parent; if($this->getIsEnabled($column, $record)) { - $prop = $column->getProperty(); + $prop = $column->getColumnId(); $record->{$prop} = $this->getControlValue($item->_input, $column, $record); } } @@ -70,7 +73,11 @@ class TScaffoldInputBase protected function getRecordPropertyValue($column, $record) { - return $record->{$column->getProperty()}; + $value = $record->{$column->getColumnId()}; + if($column->getDefaultValue()!==TDbTableColumn::UNDEFINED_VALUE && $value===null) + return $column->getDefaultValue(); + else + return $value; } protected function setRecordPropertyValue($item, $record, $input) diff --git a/framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputCommon.php b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputCommon.php index 049ceb1f..e1d57124 100644 --- a/framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputCommon.php +++ b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TScaffoldInputCommon.php @@ -12,7 +12,7 @@ class TScaffoldInputCommon extends TScaffoldInputBase protected function setNotNullProperty($container, $control, $column, $record) { $this->setDefaultProperty($container, $control, $column, $record); - if($column->getNotNull() && !$column->hasSequence()) + if(!$column->getAllowNull() && !$column->hasSequence()) $this->createRequiredValidator($container, $column, $record); } @@ -32,7 +32,7 @@ class TScaffoldInputCommon extends TScaffoldInputBase $control = new TTextBox(); $control->setText($value); $control->setCssClass('default-textbox scaffold_input'); - if(($len=$column->getLength())!==null) + if(($len=$column->getColumnSize())!==null) $control->setMaxLength($len); $this->setNotNullProperty($container, $control, $column, $record); return $control; @@ -89,6 +89,14 @@ class TScaffoldInputCommon extends TScaffoldInputBase $val = $this->createTypeValidator($container, $column, $record); $val->setDataType(TValidationDataType::Float); $val->setErrorMessage('Please entery a decimal number.'); + if(($max= $column->getMaxiumNumericConstraint())!==null) + { + $val = $this->createRangeValidator($container,$column,$record); + $val->setDataType(TValidationDataType::Float); + $val->setMaxValue($max); + $val->setStrictComparison(true); + $val->setErrorMessage('Please entery a decimal number strictly less than '.$max.'.'); + } return $control; } @@ -117,6 +125,18 @@ class TScaffoldInputCommon extends TScaffoldInputBase return $val; } + protected function createRangeValidator($container, $column, $record) + { + $val = new TRangeValidator(); + $val->setControlCssClass('required-input3'); + $val->setCssClass('required'); + $val->setControlToValidate(self::DEFAULT_ID); + $val->setValidationGroup($this->getParent()->getValidationGroup()); + $val->setDisplay(TValidatorDisplayStyle::Dynamic); + $container->Controls[] = $val; + return $val; + } + protected function createTimeControl($container, $column, $record) { $value = $this->getRecordPropertyValue($column, $record); @@ -222,10 +242,10 @@ class TScaffoldInputCommon extends TScaffoldInputBase $value = $this->getRecordPropertyValue($column, $record); $selectedValues = preg_split('/\s*,\s*/', $value); $control = new TCheckBoxList(); - $values = $column->getTypeValues(); + $values = $column->getDbTypeValues(); $control->setDataSource($values); $control->dataBind(); - $control->setSelectedIndices($this->getMatchingIndices($selectedValues, $values)); + $control->setSelectedIndices($this->getMatchingIndices($values,$selectedValues)); $control->setID(self::DEFAULT_ID); $control->setCssClass('set-checkboxes'); $this->setNotNullProperty($container, $control, $column, $record); @@ -248,10 +268,10 @@ class TScaffoldInputCommon extends TScaffoldInputBase $value = $this->getRecordPropertyValue($column, $record); $selectedValues = preg_split('/\s*,\s*/', $value); $control = new TRadioButtonList(); - $values = $column->getTypeValues(); + $values = $column->getDbTypeValues(); $control->setDataSource($values); $control->dataBind(); - $index = $this->getMatchingIndices($selectedValues, $values); + $index = $this->getMatchingIndices($values,$selectedValues); if(count($index) > 0) $control->setSelectedIndex($index[0]); $control->setID(self::DEFAULT_ID); diff --git a/framework/Data/ActiveRecord/Scaffold/InputBuilder/TSqliteScaffoldInput.php b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TSqliteScaffoldInput.php index b26d44fd..a61da1f3 100644 --- a/framework/Data/ActiveRecord/Scaffold/InputBuilder/TSqliteScaffoldInput.php +++ b/framework/Data/ActiveRecord/Scaffold/InputBuilder/TSqliteScaffoldInput.php @@ -6,7 +6,7 @@ class TSqliteScaffoldInput extends TScaffoldInputCommon { protected function createControl($container, $column, $record) { - switch(strtolower($column->getType())) + switch(strtolower($column->getDbType())) { case 'boolean': return $this->createBooleanControl($container, $column, $record); @@ -32,7 +32,7 @@ class TSqliteScaffoldInput extends TScaffoldInputCommon protected function getControlValue($container, $column, $record) { - switch(strtolower($column->getType())) + switch(strtolower($column->getDbType())) { case 'boolean': return $container->findControl(self::DEFAULT_ID)->getChecked(); @@ -53,7 +53,7 @@ class TSqliteScaffoldInput extends TScaffoldInputCommon { $control = parent::createDateControl($container, $column, $record); $value = $this->getRecordPropertyValue($column, $record); - if(!empty($value) && preg_match('/timestamp/i', $column->getType())) + if(!empty($value) && preg_match('/timestamp/i', $column->getDbType())) $control->setTimestamp(intval($value)); return $control; } @@ -62,7 +62,7 @@ class TSqliteScaffoldInput extends TScaffoldInputCommon { $value = $this->getRecordPropertyValue($column, $record); $time = parent::createDateTimeControl($container, $column, $record); - if(!empty($value) && preg_match('/timestamp/i', $column->getType())) + if(!empty($value) && preg_match('/timestamp/i', $column->getDbType())) { $s = Prado::createComponent('System.Util.TDateTimeStamp'); $date = $s->getDate(intval($value)); @@ -75,7 +75,7 @@ class TSqliteScaffoldInput extends TScaffoldInputCommon protected function getDateTimeValue($container, $column, $record) { - if(preg_match('/timestamp/i', $column->getType())) + if(preg_match('/timestamp/i', $column->getDbType())) { $time = $container->findControl(self::DEFAULT_ID)->getTimestamp(); $s = Prado::createComponent('System.Util.TDateTimeStamp'); diff --git a/framework/Data/ActiveRecord/Scaffold/TScaffoldBase.php b/framework/Data/ActiveRecord/Scaffold/TScaffoldBase.php index 266d4346..a9bf2d59 100644 --- a/framework/Data/ActiveRecord/Scaffold/TScaffoldBase.php +++ b/framework/Data/ActiveRecord/Scaffold/TScaffoldBase.php @@ -36,23 +36,15 @@ abstract class TScaffoldBase extends TTemplateControl * @var TActiveRecord record instance (may be new or retrieved from db) */ private $_record; - /** - * @var TDbMetaData table/view information. - */ - private $_meta; /** * @return TDbMetaData table/view information */ - protected function getTableMetaData() + protected function getTableInfo() { - if($this->_meta===null) - { - $finder = $this->getRecordFinder(); - $gateway = $finder->getRecordManager()->getRecordGateWay(); - $this->_meta = $gateway->getMetaData($finder); - } - return $this->_meta; + $finder = $this->getRecordFinder(); + $gateway = $finder->getRecordManager()->getRecordGateWay(); + return $gateway->getRecordTableInfo($finder); } /** @@ -62,7 +54,7 @@ abstract class TScaffoldBase extends TTemplateControl protected function getRecordPropertyValues($record) { $data = array(); - foreach($this->getTableMetaData()->getColumns() as $name=>$column) + foreach($this->getTableInfo()->getColumns() as $name=>$column) $data[] = $record->{$name}; return $data; } @@ -73,7 +65,7 @@ abstract class TScaffoldBase extends TTemplateControl */ protected function getRecordPkValues($record) { - foreach($this->getTableMetaData()->getColumns() as $name=>$column) + foreach($this->getTableInfo()->getColumns() as $name=>$column) { if($column->getIsPrimaryKey()) $data[] = $record->{$name}; @@ -106,7 +98,6 @@ abstract class TScaffoldBase extends TTemplateControl protected function copyFrom(TScaffoldBase $obj) { $this->_record = $obj->_record; - $this->_meta = $obj->_meta; $this->setRecordClass($obj->getRecordClass()); } @@ -116,7 +107,6 @@ abstract class TScaffoldBase extends TTemplateControl protected function clearRecordObject() { $this->_record=null; - $this->_meta=null; } /** diff --git a/framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.php b/framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.php index efc356f5..0550864b 100644 --- a/framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.php +++ b/framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.php @@ -114,7 +114,7 @@ class TScaffoldEditView extends TScaffoldBase $classPath = $this->getEditRenderer(); if($classPath === '') { - $columns = $this->getTableMetaData()->getColumns(); + $columns = $this->getTableInfo()->getColumns(); $this->getInputRepeater()->setDataSource($columns); $this->getInputRepeater()->dataBind(); } @@ -199,7 +199,7 @@ class TScaffoldEditView extends TScaffoldBase $record = $this->getCurrentRecord(); if($this->_editRenderer===null) { - $table = $this->getTableMetaData(); + $table = $this->getTableInfo(); $builder = $this->getScaffoldInputBuilder($record); foreach($this->getInputRepeater()->getItems() as $item) { diff --git a/framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.tpl b/framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.tpl index 884ec2a3..b3289c09 100644 --- a/framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.tpl +++ b/framework/Data/ActiveRecord/Scaffold/TScaffoldEditView.tpl @@ -3,7 +3,7 @@
+ input_<%# $this->ItemIndex %> property_<%# $this->DataItem->ColumnId %>"> diff --git a/framework/Data/ActiveRecord/Scaffold/TScaffoldListView.php b/framework/Data/ActiveRecord/Scaffold/TScaffoldListView.php index 6cb490e3..71dc83cd 100644 --- a/framework/Data/ActiveRecord/Scaffold/TScaffoldListView.php +++ b/framework/Data/ActiveRecord/Scaffold/TScaffoldListView.php @@ -65,7 +65,7 @@ class TScaffoldListView extends TScaffoldBase */ protected function initializeSort() { - $table = $this->getTableMetaData(); + $table = $this->getTableInfo(); $sorts = array('Sort By', str_repeat('-',15)); $headers = array(); foreach($table->getColumns() as $name=>$colum) diff --git a/framework/Data/ActiveRecord/Scaffold/TScaffoldSearch.php b/framework/Data/ActiveRecord/Scaffold/TScaffoldSearch.php index fdfddd4f..d1a5980f 100644 --- a/framework/Data/ActiveRecord/Scaffold/TScaffoldSearch.php +++ b/framework/Data/ActiveRecord/Scaffold/TScaffoldSearch.php @@ -93,7 +93,7 @@ class TScaffoldSearch extends TScaffoldBase */ protected function createSearchCondition() { - $table = $this->getTableMetaData(); + $table = $this->getTableInfo(); if(strlen($str=$this->getSearchText()->getText()) > 0) return $table->getSearchRegExpCriteria($this->getFields(), $str); } @@ -106,7 +106,7 @@ class TScaffoldSearch extends TScaffoldBase if(strlen(trim($str=$this->getSearchableFields()))>0) $fields = preg_split('/\s*,\s*/', $str); else - $fields = array_keys($this->getTableMetaData()->getColumns()); + $fields = array_keys($this->getTableInfo()->getColumns()); return $fields; } diff --git a/framework/Data/ActiveRecord/Scaffold/style.css b/framework/Data/ActiveRecord/Scaffold/style.css index a1fc7a16..864ddb6f 100644 --- a/framework/Data/ActiveRecord/Scaffold/style.css +++ b/framework/Data/ActiveRecord/Scaffold/style.css @@ -117,7 +117,7 @@ body font-weight: bold; padding: 0.2em; } -.edit-inputs .required-input, .edit-inputs .required-input2 +.edit-inputs .required-input, .edit-inputs .required-input2 .required-input3 .required-input4 { border: 1px solid red; background-color: #FFF5EE; diff --git a/framework/Data/ActiveRecord/TActiveRecord.php b/framework/Data/ActiveRecord/TActiveRecord.php index 70fd7b23..0400661d 100644 --- a/framework/Data/ActiveRecord/TActiveRecord.php +++ b/framework/Data/ActiveRecord/TActiveRecord.php @@ -189,7 +189,7 @@ abstract class TActiveRecord extends TComponent $registry = $this->getRecordManager()->getObjectStateRegistry(); $gateway = $this->getRecordManager()->getRecordGateway(); if(!$this->_readOnly) - $this->_readOnly = $gateway->getMetaData($this)->getIsView(); + $this->_readOnly = $gateway->getRecordTableInfo($this)->getIsView(); if($this->_readOnly) throw new TActiveRecordException('ar_readonly_exception',get_class($this)); return $registry->commit($this,$gateway); @@ -247,12 +247,8 @@ abstract class TActiveRecord extends TComponent */ public function deleteAll($criteria, $parameters=array()) { - if(is_string($criteria)) - { - if(!is_array($parameters) && func_num_args() > 1) - $parameters = array_slice(func_get_args(),1); - $criteria=new TActiveRecordCriteria($criteria,$parameters); - } + $args = func_num_args() > 1 ? array_slice(func_get_args(),1) : null; + $criteria = $this->getCriteria($criteria,$parameters, $args); $gateway = $this->getRecordManager()->getRecordGateway(); return $gateway->deleteRecordsByCriteria($this, $criteria); } @@ -278,12 +274,24 @@ abstract class TActiveRecord extends TComponent $obj->{$name} = $value; $gateway = $this->getRecordManager()->getRecordGateway(); - $obj->_readOnly = $gateway->getMetaData($this)->getIsView(); + $obj->_readOnly = $gateway->getRecordTableInfo($this)->getIsView(); //cache it return $registry->addCachedInstance($data,$obj); } + /** + * @param TDbDataReader data reader + */ + protected function collectObjects($reader) + { + $result=array(); + $class = get_class($this); + foreach($reader as $data) + $result[] = $this->populateObject($class, $data); + return $result; + } + /** * Find one single record that matches the criteria. * @@ -303,12 +311,8 @@ abstract class TActiveRecord extends TComponent */ public function find($criteria,$parameters=array()) { - if(is_string($criteria)) - { - if(!is_array($parameters) && func_num_args() > 1) - $parameters = array_slice(func_get_args(),1); - $criteria=new TActiveRecordCriteria($criteria,$parameters); - } + $args = func_num_args() > 1 ? array_slice(func_get_args(),1) : null; + $criteria = $this->getCriteria($criteria,$parameters, $args); $gateway = $this->getRecordManager()->getRecordGateway(); $data = $gateway->findRecordsByCriteria($this,$criteria); return $this->populateObject(get_class($this), $data); @@ -323,18 +327,12 @@ abstract class TActiveRecord extends TComponent */ public function findAll($criteria=null,$parameters=array()) { - if(is_string($criteria)) - { - if(!is_array($parameters) && func_num_args() > 1) - $parameters = array_slice(func_get_args(),1); - $criteria=new TActiveRecordCriteria($criteria,$parameters); - } + $args = func_num_args() > 1 ? array_slice(func_get_args(),1) : null; + if($criteria!==null) + $criteria = $this->getCriteria($criteria,$parameters, $args); $gateway = $this->getRecordManager()->getRecordGateway(); - $results = array(); - $class = get_class($this); - foreach($gateway->findRecordsByCriteria($this,$criteria,true) as $data) - $results[] = $this->populateObject($class,$data); - return $results; + $result = $gateway->findRecordsByCriteria($this,$criteria,true); + return $this->collectObjects($result); } /** @@ -380,11 +378,8 @@ abstract class TActiveRecord extends TComponent if(func_num_args() > 1) $keys = func_get_args(); $gateway = $this->getRecordManager()->getRecordGateway(); - $results = array(); - $class = get_class($this); - foreach($gateway->findRecordsByPks($this,(array)$keys) as $data) - $results[] = $this->populateObject($class,$data); - return $results; + $result = $gateway->findRecordsByPks($this,(array)$keys); + return $this->collectObjects($result); } /** @@ -397,15 +392,11 @@ abstract class TActiveRecord extends TComponent */ public function findBySql($sql,$parameters=array()) { - if(!is_array($parameters) && func_num_args() > 1) - $parameters = array_slice(func_get_args(),1); + $args = func_num_args() > 1 ? array_slice(func_get_args(),1) : null; + $criteria = $this->getCriteria($sql,$parameters, $args); $gateway = $this->getRecordManager()->getRecordGateway(); - $data = $gateway->findRecordsBySql($this,$sql,$parameters); - $results = array(); - $class = get_class($this); - foreach($gateway->findRecordsBySql($this,$sql,$parameters) as $data) - $results[] = $this->populateObject($class,$data); - return $results; + $result = $gateway->findRecordsBySql($this,$criteria); + return $this->collectObjects($result); } /** @@ -416,12 +407,9 @@ abstract class TActiveRecord extends TComponent */ public function count($criteria=null,$parameters=array()) { - if(is_string($criteria)) - { - if(!is_array($parameters) && func_num_args() > 1) - $parameters = array_slice(func_get_args(),1); - $criteria=new TActiveRecordCriteria($criteria,$parameters); - } + $args = func_num_args() > 1 ? array_slice(func_get_args(),1) : null; + if($criteria!==null) + $criteria = $this->getCriteria($criteria,$parameters, $args); $gateway = $this->getRecordManager()->getRecordGateway(); return $gateway->countRecords($this,$criteria); } @@ -467,7 +455,8 @@ abstract class TActiveRecord extends TComponent else return null;//throw new TActiveRecordException('ar_invalid_finder_method',$method); - $criteria = $this->createCriteriaFromString($method, $condition, $args); + $gateway = $this->getRecordManager()->getRecordGateway(); + $criteria = $gateway->getCommand($this)->createCriteriaFromString($method, $condition, $args); if($delete) return $this->deleteAll($criteria); else @@ -475,51 +464,25 @@ abstract class TActiveRecord extends TComponent } /** - * @param string __call method name - * @param string criteria conditions - * @param array method arguments - * @return TActiveRecordCriteria criteria created from the method name and its arguments. - */ - private function createCriteriaFromString($method, $condition, $args) - { - $fields = $this->extractMatchingConditions($method, $condition); - $args=count($args) === 1 && is_array($args[0]) ? $args[0] : $args; - if(count($fields)>count($args)) - { - throw new TActiveRecordException('ar_mismatch_args_exception', - $method,count($fields),count($args)); - } - return new TActiveRecordCriteria(implode(' ',$fields),$args); - } - - /** - * Calculates the AND/OR condition from dynamic method substrings using - * table meta data, allows for any AND-OR combinations. - * @param string dynamic method name - * @param string dynamic method search criteria - * @return array search condition substrings + * Create a new TSqlCriteria object from a string $criteria. The $args + * are additional parameters and are used in place of the $parameters + * if $parameters is not an array and $args is an arrary. + * @param string|TSqlCriteria sql criteria + * @param mixed parameters passed by the user. + * @param array additional parameters obtained from function_get_args(). + * @return TSqlCriteria criteria object. */ - private function extractMatchingConditions($method, $condition) + protected function getCriteria($criteria, $parameters, $args) { - $meta = $this->getRecordManager()->getRecordGateway()->getMetaData($this); - $search = implode('|', $meta->getColumnNames()); - $regexp = '/('.$search.')(and|_and_|or|_or_)?/i'; - $matches = array(); - if(!preg_match_all($regexp, strtolower($condition), $matches,PREG_SET_ORDER)) - { - throw new TActiveRecordException('ar_mismatch_column_names', - $method, implode(', ', $meta->getColumnNames()), $meta->getTableName()); - } - $fields = array(); - foreach($matches as $match) + if(is_string($criteria)) { - $column = $meta->getColumn($match[1]); - $sql = $column->getName() . ' = ? '; - if(count($match) > 2) - $sql .= strtoupper(str_replace('_', '', $match[2])); - $fields[] = $sql; + $useArgs = !is_array($parameters) && is_array($args); + return new TActiveRecordCriteria($criteria,$useArgs ? $args : $parameters); } - return $fields; + else if($criteria instanceof TSqlCriteria) + return $criteria; + else + throw new TActiveRecordException('ar_invalid_criteria'); } } diff --git a/framework/Data/ActiveRecord/TActiveRecordGateway.php b/framework/Data/ActiveRecord/TActiveRecordGateway.php index be9731c5..9c480ad0 100644 --- a/framework/Data/ActiveRecord/TActiveRecordGateway.php +++ b/framework/Data/ActiveRecord/TActiveRecordGateway.php @@ -22,8 +22,9 @@ class TActiveRecordGateway extends TComponent { private $_manager; - private $_tables=array(); //meta data cache. - + private $_tables=array(); //table cache + private $_meta=array(); //meta data cache. + private $_commandBuilders=array(); /** * Constant name for specifying optional table name in TActiveRecord. */ @@ -52,7 +53,7 @@ class TActiveRecordGateway extends TComponent * @param TActiveRecord active record instance * @return string table name for the given record class. */ - public function getTableName(TActiveRecord $record) + protected function getRecordTableName(TActiveRecord $record) { $class = new ReflectionClass($record); if($class->hasConstant(self::TABLE_CONST)) @@ -68,70 +69,65 @@ class TActiveRecordGateway extends TComponent } /** - * @param TActiveRecord active record. - * @return TDbMetaData table meta data, null if not found. + * Returns table information, trys the application cache first. + * @param TActiveRecord $record + * @return TDbTableInfo table information. */ - protected function getCachedMetaData($record) + public function getRecordTableInfo(TActiveRecord $record) { - $type=get_class($record); - if(isset($this->_tables[$type])) - return $this->_tables[$type]; - if(($cache=$this->getManager()->getCache())!==null) - { - //force loading of the table inspector to load the required classes - // before unserializing cached meta data. - $this->getManager()->getTableInspector($record->getDbConnection()); - $data = $cache->get($this->getMetaDataCacheKey($record)); - if($data !== false && $data !== null) - { - $this->_tables[$type] = $data; - return $data; - } - } + $tableName = $this->getRecordTableName($record); + return $this->getTableInfo($record->getDbConnection(), $tableName); } /** - * @param TActiveRecord active record. - * @return string cache key, using connection string + record class name + * Returns table information for table in the database connection. + * @param TDbConnection database connection + * @param string table name + * @return TDbTableInfo table details. */ - protected function getMetaDataCacheKey($record) + public function getTableInfo($connection, $tableName) { - $conn = $record->getDbConnection()->getConnectionString(); - return $conn.':'.get_class($record); - } - - /** - * Cache the meta data, tries the application cache if applicable. - * @param TActiveRecord active record. - * @param TDbMetaData table meta data - * @return TDbMetaData table meta data. - */ - protected function cacheMetaData($record,$data) - { - $type = get_class($record); - if(($cache=$this->getManager()->getCache())!==null) - $cache->set($this->getMetaDataCacheKey($record), $data); - $this->_tables[$type] = $data; - return $data; + $connStr = $connection->getConnectionString(); + $key = $connStr.$tableName; + if(!isset($this->_tables[$key])) + { + $tableInfo = null; + if(($cache=$this->getManager()->getCache())!==null) + $tableInfo = $cache->get($key); + if($tableInfo===null) + { + if(!isset($this->_meta[$connStr])) + { + Prado::using('System.Data.Common.TDbMetaData'); + $this->_meta[$connStr] = TDbMetaData::getMetaData($connection); + } + $tableInfo = $this->_meta[$connStr]->getTableInfo($tableName); + } + $this->_tables[$key] = $tableInfo; + if($cache!==null) + $cache->set($key, $tableInfo); + } + return $this->_tables[$key]; } /** - * Gets the meta data for given database and table. + * @param TActiveRecord $record + * @return TDataGatewayCommand */ - public function getMetaData(TActiveRecord $record) + public function getCommand(TActiveRecord $record) { - $type=get_class($record); - if(!($data = $this->getCachedMetaData($record))) + $conn = $record->getDbConnection(); + $connStr = $conn->getConnectionString(); + $tableInfo = $this->getRecordTableInfo($record); + if(!isset($this->_commandBuilders[$connStr])) { - $conn = $record->getDbConnection(); - $inspector = $this->getManager()->getTableInspector($conn); - $table = $this->getTableName($record); - $meta = $inspector->getTableMetaData($table); - if(count($meta->getColumns()) == 0) - throw new TActiveRecordException('ar_invalid_table', $table); - $data = $this->cacheMetaData($record,$meta); + $builder = $tableInfo->createCommandBuilder($record->getDbConnection()); + Prado::using('System.Data.DataGateway.TDataGatewayCommand'); + $this->_commandBuilders[$connStr] = new TDataGatewayCommand($builder); } - return $data; + $this->_commandBuilders[$connStr]->getBuilder()->setTableInfo($tableInfo); + + return $this->_commandBuilders[$connStr]; } /** @@ -143,11 +139,7 @@ class TActiveRecordGateway extends TComponent */ public function findRecordByPK(TActiveRecord $record,$keys) { - $meta = $this->getMetaData($record); - $command = $meta->getFindByPkCommand($record->getDbConnection(),$keys); - $this->raiseCommandEvent(TActiveRecordStatementType::Select,$command,$record,$keys); - Prado::trace(get_class($record).'::FindRecordByPk('.var_export($keys,true).')', 'System.Data.ActiveRecord'); - return $meta->postQueryRow($command->queryRow()); + return $this->getCommand($record)->findByPk($keys); } /** @@ -158,11 +150,7 @@ class TActiveRecordGateway extends TComponent */ public function findRecordsByPks(TActiveRecord $record, $keys) { - $meta = $this->getMetaData($record); - $command = $meta->getFindInPksCommand($record->getDbConnection(), $keys); - $this->raiseCommandEvent(TActiveRecordStatementType::Select,$command,$record,$keys); - Prado::trace(get_class($record).'::FindRecordsByPks('.var_export($keys,true).')', 'System.Data.ActiveRecord'); - return $meta->postQuery($command->query()); + return $this->getCommand($record)->findAllByPk($keys); } @@ -176,27 +164,21 @@ class TActiveRecordGateway extends TComponent */ public function findRecordsByCriteria(TActiveRecord $record, $criteria, $iterator=false) { - $meta = $this->getMetaData($record); - $command = $meta->getFindByCriteriaCommand($record->getDbConnection(),$criteria); - $this->raiseCommandEvent(TActiveRecordStatementType::Select,$command,$record,$criteria); - Prado::trace(get_class($record).'::FindRecordsByCriteria('.((string)$criteria).')', 'System.Data.ActiveRecord'); - return $iterator ? $meta->postQuery($command->query()) : $meta->postQueryRow($command->queryRow()); + if($iterator) + return $this->getCommand($record)->findAll($criteria); + else + return $this->getCommand($record)->find($criteria); } /** * Return record data from sql query. * @param TActiveRecord active record finder instance. - * @param string SQL string - * @param array query parameters. + * @param TActiveRecordCriteria sql query * @return TDbDataReader result iterator. */ - public function findRecordsBySql(TActiveRecord $record, $sql,$parameters=array()) + public function findRecordsBySql(TActiveRecord $record, $criteria) { - $meta = $this->getMetaData($record); - $command = $meta->getFindBySqlCommand($record->getDbConnection(),$sql,$parameters); - $this->raiseCommandEvent(TActiveRecordStatementType::Select,$command,$record,$parameters); - Prado::trace(get_class($record).'::FindRecordsBySql('.var_export($parameters,true).')', 'System.Data.ActiveRecord'); - return $meta->postQuery($command->query()); + return $this->getCommand($record)->findBySql($criteria); } /** @@ -207,11 +189,7 @@ class TActiveRecordGateway extends TComponent */ public function countRecords(TActiveRecord $record, $criteria) { - $meta = $this->getMetaData($record); - $command = $meta->getCountRecordsCommand($record->getDbConnection(),$criteria); - $this->raiseCommandEvent(TActiveRecordStatementType::Select,$command,$record,$criteria); - Prado::trace(get_class($record).'::CountRecords('.((string)$criteria).')', 'System.Data.ActiveRecord'); - return intval($command->queryScalar()); + return $this->getCommand($record)->count($criteria); } /** @@ -221,14 +199,46 @@ class TActiveRecordGateway extends TComponent */ public function insert(TActiveRecord $record) { - $meta = $this->getMetaData($record); - $command = $meta->getInsertCommand($record->getDbConnection(),$record); - $this->raiseCommandEvent(TActiveRecordStatementType::Insert,$command,$record); - Prado::trace(get_class($record).'::Insert()', 'System.Data.ActiveRecord'); - $rowsAffected = $command->execute(); - if($rowsAffected===1) - $meta->updatePostInsert($record->getDbConnection(),$record); - return $rowsAffected; + $result = $this->getCommand($record)->insert($this->getInsertValues($record)); + if($result) + $this->updatePostInsert($record); + return $result; + } + + protected function updatePostInsert($record) + { + $command = $this->getCommand($record); + $tableInfo = $command->getTableInfo(); + foreach($tableInfo->getColumns() as $name => $column) + { + if($column->hasSequence()) + $record->{$name} = $command->getLastInsertID($column->getSequenceName()); + } + } + + /** + * @param TActiveRecord record + * @return array insert values. + */ + protected function getInsertValues(TActiveRecord $record) + { + $values=array(); + $tableInfo = $this->getCommand($record)->getTableInfo(); + foreach($tableInfo->getColumns() as $name=>$column) + { + if($column->getIsExcluded()) + continue; + $value = $record->{$name}; + if(!$column->getAllowNull() && $value===null && !$column->hasSequence()) + { + throw new TActiveRecordException( + 'ar_value_must_not_be_null', get_class($record), + $tableInfo->getTableFullName(), $name); + } + if($value!==null) + $values[$name] = $value; + } + return $values; } /** @@ -238,11 +248,32 @@ class TActiveRecordGateway extends TComponent */ public function update(TActiveRecord $record) { - $meta = $this->getMetaData($record); - $command = $meta->getUpdateCommand($record->getDbConnection(),$record); - $this->raiseCommandEvent(TActiveRecordStatementType::Update,$command,$record); - Prado::trace(get_class($record).'::Update()', 'System.Data.ActiveRecord'); - return $command->execute(); + list($data, $keys) = $this->getUpdateValues($record); + return $this->getCommand($record)->updateByPk($data, $keys); + } + + protected function getUpdateValues(TActiveRecord $record) + { + $values=array(); + $tableInfo = $this->getCommand($record)->getTableInfo(); + $primary=array(); + foreach($tableInfo->getColumns() as $name=>$column) + { + if($column->getIsExcluded()) + continue; + $value = $record->{$name}; + if(!$column->getAllowNull() && $value===null) + { + throw new TActiveRecordException( + 'ar_value_must_not_be_null', get_class($record), + $tableInfo->getTableFullName(), $name); + } + if($column->getIsPrimaryKey()) + $primary[] = $value; + else + $values[$name] = $value; + } + return array($values,$primary); } /** @@ -252,11 +283,19 @@ class TActiveRecordGateway extends TComponent */ public function delete(TActiveRecord $record) { - $meta = $this->getMetaData($record); - $command = $meta->getDeleteCommand($record->getDbConnection(),$record); - $this->raiseCommandEvent(TActiveRecordStatementType::Delete,$command,$record); - Prado::trace(get_class($record).'::Delete()', 'System.Data.ActiveRecord'); - return $command->execute(); + return $this->getCommand($record)->deleteByPk($this->getPrimaryKeyValues($record)); + } + + protected function getPrimaryKeyValues(TActiveRecord $record) + { + $tableInfo = $this->getCommand($record)->getTableInfo(); + $primary=array(); + foreach($tableInfo->getColumns() as $name=>$column) + { + if($column->getIsPrimaryKey()) + $primary[$name] = $record->{$name}; + } + return $primary; } /** @@ -266,11 +305,7 @@ class TActiveRecordGateway extends TComponent */ public function deleteRecordsByPk(TActiveRecord $record, $keys) { - $meta = $this->getMetaData($record); - $command = $meta->getDeleteByPkCommand($record->getDBConnection(),$keys); - $this->raiseCommandEvent(TActiveRecordStatementType::Delete,$command,$record,$keys); - Prado::trace(get_class($record).'::DeleteRecordsByPk('.var_export($keys,true).')', 'System.Data.ActiveRecord'); - return $command->execute(); + return $this->getCommand($record)->deleteByPk($keys); } /** @@ -281,11 +316,7 @@ class TActiveRecordGateway extends TComponent */ public function deleteRecordsByCriteria(TActiveRecord $record, $criteria) { - $meta = $this->getMetaData($record); - $command = $meta->getDeleteByCriteriaCommand($record->getDBConnection(),$criteria); - $this->raiseCommandEvent(TActiveRecordStatementType::Delete,$command,$record,$criteria); - Prado::trace(get_class($record).'::DeleteRecordsByCriteria('.((string)$criteria).')', 'System.Data.ActiveRecord'); - return $command->execute(); + return $this->getCommand($record)->delete($criteria); } /** diff --git a/framework/Data/ActiveRecord/TActiveRecordManager.php b/framework/Data/ActiveRecord/TActiveRecordManager.php index 0179479e..5e463d2d 100644 --- a/framework/Data/ActiveRecord/TActiveRecordManager.php +++ b/framework/Data/ActiveRecord/TActiveRecordManager.php @@ -120,17 +120,6 @@ class TActiveRecordManager extends TComponent return $this->_gateway; } - /** - * @param string|TActiveRecord active record class name or instance - * @return TDbMetaData record specific meta data - */ - public function getMetaData($record) - { - if(is_string($record)) - $record = TActiveRecord::finder($record); - return $this->getRecordGateway()->getMetaData($record); - } - /** * @return TActiveRecordGateway default record gateway. */ @@ -139,51 +128,6 @@ class TActiveRecordManager extends TComponent return new TActiveRecordGateway($this); } - /** - * Get table meta data for particular database and table. - * @param TDbConnection database connection. - * @return TDbMetaDataInspector table meta inspector - */ - public function getTableInspector(TDbConnection $conn) - { - $database = $conn->getConnectionString(); - if(!isset($this->_meta[$database])) - $this->_meta[$database] = $this->createMetaDataInspector($conn); - return $this->_meta[$database]; - } - - /** - * Create an instance of a database meta inspector corresponding to the - * given database vendor specified by the $driver parameter. - * @param TDbConnection database connection - * @return TDbMetaDataInspector table meta inspector - */ - protected function createMetaDataInspector($conn) - { - $conn->setActive(true); //must be connected before retrieving driver name! - $driver = $conn->getDriverName(); - switch(strtolower($driver)) - { - case 'pgsql': - Prado::using('System.Data.ActiveRecord.Vendor.TPgsqlMetaDataInspector'); - return new TPgsqlMetaDataInspector($conn); - case 'mysqli': - case 'mysql': - Prado::using('System.Data.ActiveRecord.Vendor.TMysqlMetaDataInspector'); - return new TMysqlMetaDataInspector($conn); - case 'sqlite': //sqlite 3 - case 'sqlite2': //sqlite 2 - Prado::using('System.Data.ActiveRecord.Vendor.TSqliteMetaDataInspector'); - return new TSqliteMetaDataInspector($conn); - case 'ibm': - Prado::using('System.Data.ActiveRecord.Vendor.TIbmMetaDataInspector'); - return new TIbmMetaDataInspector($conn); - default: - throw new TActiveRecordConfigurationException( - 'ar_invalid_database_driver',$driver); - } - } - /** * This method is invoked before the object is inserted into the database. * The method raises 'OnInsert' event. diff --git a/framework/Data/ActiveRecord/TActiveRecordStateRegistry.php b/framework/Data/ActiveRecord/TActiveRecordStateRegistry.php index 59222e44..ef415b5e 100644 --- a/framework/Data/ActiveRecord/TActiveRecordStateRegistry.php +++ b/framework/Data/ActiveRecord/TActiveRecordStateRegistry.php @@ -240,12 +240,12 @@ class TActiveRecordStateRegistry */ public function commit($record,$gateway) { - $rowsAffected=0; + $rowsAffected=false; if($this->getIsRemovedObject($record)) { $rowsAffected = $gateway->delete($record); - if($rowsAffected===1) + if($rowsAffected) $this->removeRemovedObject($record); } else @@ -255,10 +255,10 @@ class TActiveRecordStateRegistry else if($this->getIsNewObject($record)) $rowsAffected = $gateway->insert($record); - if($rowsAffected===1) + if($rowsAffected) $this->registerClean($record); } - return $rowsAffected===1; + return (boolean)$rowsAffected; } } diff --git a/framework/Data/Common/IbmDb2/TIbmColumnMetaData.php b/framework/Data/Common/IbmDb2/TIbmColumnMetaData.php new file mode 100644 index 00000000..284a5fd2 --- /dev/null +++ b/framework/Data/Common/IbmDb2/TIbmColumnMetaData.php @@ -0,0 +1,157 @@ + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2007 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id: TIbmColumnMetaData.php 1807 2007-03-31 06:42:15Z wei $ + * @package System.Data.ActiveRecord.Vendor + */ + +/** + * TIbmColumnMetaData class. + * + * Column details for IBM DB2 database. Using php_pdo_ibm.dll extension. + * + * @author Cesar Ramos + * @version $Id: TIbmColumnMetaData.php 1807 2007-03-31 06:42:15Z wei $ + * @package System.Data.ActiveRecord.Vendor + * @since 3.1 + */ +class TIbmColumnMetaData extends TComponent +{ + private $_name; + private $_type; + private $_length; + private $_autoIncrement; + private $_default; + private $_notNull=true; + + private $_isPrimary=null; + + private $_property; + + /** + * Initialize column meta data. + * + * @param string column name. + * @param string column data type. + * @param string column data length. + * @param boolean column can not be null. + * @param string serial name. + * @param string default value. + */ + public function __construct($name,$type,$length,$notNull,$autoIncrement,$default,$primary) + { + $this->_property=$name; + $this->_name=$name; + $this->_type=$type; + $this->_length=$length; + $this->_notNull=$notNull; + $this->_autoIncrement=$autoIncrement; + $this->_default=$default; + $this->_isPrimary=$primary; + } + + /** + * @return string quoted column name. + */ + public function getName() + { + return $this->_name; + } + + /** + * @return integer length. + */ + public function getLength() + { + return $this->_length; + } + + /** + * @return string active record property name + */ + public function getProperty() + { + return $this->_property; + } + + /** + * @return boolean true if column is a sequence, false otherwise. + */ + public function hasSequence() + { + return $this->_autoIncrement; + } + + /** + * @return null no sequence name. + */ + public function getSequenceName() + { + return null; + } + + /** + * @return boolean true if the column is a primary key, or part of a composite primary key. + */ + public function getIsPrimaryKey() + { + return $this->_isPrimary; + } + + /** + * @return string column type + */ + public function getType() + { + return $this->_type; + } + + + /** + * @return boolean false if column can be null, true otherwise. + */ + public function getNotNull() + { + return $this->_notNull; + } + + /** + * @return boolean true if column has default value, false otherwise. + */ + public function hasDefault() + { + return $this->_default !== null; + } + + /** + * @return string default column value. + */ + public function getDefaultValue() + { + return $this->_default; + } + + /** + * @return string PHP primative type derived from the column type. + */ + public function getPHPType() + { + switch(strtolower($this->_type)) + { + case 'smallint': case 'integer': + return 'integer'; + case 'real': case 'float': case 'double': case 'decimal': case 'bigint': + return 'float'; + default: + return 'string'; + } + } + +} + +?> \ No newline at end of file diff --git a/framework/Data/Common/IbmDb2/TIbmMetaData.php b/framework/Data/Common/IbmDb2/TIbmMetaData.php new file mode 100644 index 00000000..99df5465 --- /dev/null +++ b/framework/Data/Common/IbmDb2/TIbmMetaData.php @@ -0,0 +1,113 @@ + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2007 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id: TIbmMetaData.php 1807 2007-03-31 06:42:15Z wei $ + * @package System.Data.ActiveRecord.Vendor + */ +Prado::using('System.Data.ActiveRecord.Vendor.TDbMetaDataCommon'); + +/** + * TIbmMetaData class. + * + * Column details for IBM DB2 database. Using php_pdo_ibm.dll extension. + * + * Does not support LIMIT and OFFSET criterias. + * + * @author Cesar Ramos + * @version $Id: TIbmMetaData.php 1807 2007-03-31 06:42:15Z wei $ + * @package System.Data.ActiveRecord.Vendor + * @since 3.1 + */ +class TIbmMetaData extends TDbMetaDataCommon +{ + /** + * Build the SQL search string from the criteria object for IBM DB2 database. + * @param TDbConnection database connection. + * @param TActiveRecordCriteria search criteria. + * @return string SQL search. + */ + protected function getSqlFromCriteria($conn, $criteria) + { + if($criteria===null) return ''; + $sql = ''; + if(($condition = $criteria->getCondition())!==null) + $sql .= ' WHERE '.$condition; + $orders=array(); + foreach($criteria->getOrdersBy() as $by=>$ordering) + $orders[] = $this->getOrdering($by, $ordering); + if(count($orders) > 0) + $sql .= ' ORDER BY '.implode(', ', $orders); + //if(($limit = $criteria->getLimit())!==null) + //{ + // $sql .= ' FETCH FIRST '.intval($limit).' ROWS ONLY'; + //} + return strlen($sql) > 0 ? $sql : ''; + } + + /** + * Lowercase the data keys, IBM DB2 returns uppercase column names + * @param mixed record row + * @return array record row + */ + public function postQueryRow($row) + { + if(!is_array($row)) return $row; + $result=array(); + foreach($row as $k=>$v) + $result[strtolower($k)]=$v; + return $result; + } + + /** + * Lowercase the data keys, IBM DB2 returns uppercase column names + * @param mixed record row + * @return array record row + */ + public function postQuery($rows) + { + $data = array(); + foreach($rows as $k=>$v) + $data[$k] = $this->postQueryRow($v); + return $data; + } + + public function getSearchRegExpCriteria($fields, $keywords) + { + if(strlen(trim($keywords)) == 0) return ''; + $words = array(); + preg_match_all('/([a-zA-Z0-9-+]+)/', $keywords, $words); + $result = array(); + foreach($fields as $field) + { + $column = $this->getColumn($field); + if($this->isSearchableColumn($column)) + $result[] = $this->getLikeCriteriaStr($column->getName(), $words[0]); + } + $a = '('.implode(' OR ', $result).')'; + error_log($a); + return '('.implode(' OR ', $result).')'; + } + + protected function isSearchableColumn($column) + { + $type = strtolower($column->getType()); + return $type === 'char' || $type === 'varchar'; + } + + protected function getLikeCriteriaStr($column, $words) + { + $result=array(); + foreach($words as $word) + $result[] = "{$column} LIKE '%{$word}%'"; + return '('.implode(' AND ', $result).')'; + } + + +} +?> \ No newline at end of file diff --git a/framework/Data/Common/IbmDb2/TIbmMetaDataInspector.php b/framework/Data/Common/IbmDb2/TIbmMetaDataInspector.php new file mode 100644 index 00000000..1cd8af95 --- /dev/null +++ b/framework/Data/Common/IbmDb2/TIbmMetaDataInspector.php @@ -0,0 +1,113 @@ + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2007 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id: TIbmMetaDataInspector.php 1807 2007-03-31 06:42:15Z wei $ + * @package System.Data.ActiveRecord.Vendor + */ +Prado::using('System.Data.ActiveRecord.Vendor.TDbMetaDataInspector'); +Prado::using('System.Data.ActiveRecord.Vendor.TIbmColumnMetaData'); +Prado::using('System.Data.ActiveRecord.Vendor.TIbmMetaData'); + +/** + * TIbmMetaDataInspector class. + * + * Column details for IBM DB2 database. Using php_pdo_ibm.dll extension. + * + * @author Cesar Ramos + * @version $Id: TIbmMetaDataInspector.php 1807 2007-03-31 06:42:15Z wei $ + * @package System.Data.ActiveRecord.Vendor + * @since 3.1 + */ +class TIbmMetaDataInspector extends TDbMetaDataInspector +{ + private $_schema; + + /** + * @param string default schema. + */ + public function setSchema($schema) + { + $this->_schema=$schema; + } + + /** + * @return string default schema. + */ + public function getSchema() + { + return $this->_schema; + } + /** + * Get the column definitions for given table. + * @param string table name. + * @return array column name value pairs of column meta data. + */ + protected function getColumnDefinitions($table) + { + if(count($parts= explode('.', $table)) > 1) + { + $tablename = $parts[1]; + $schema = $parts[0]; + } + else + { + $tablename = $parts[0]; + $schema = $this->getSchema(); + } + $sql="SELECT * FROM SYSCAT.COLUMNS WHERE TABNAME='".strtoupper($tablename)."'"; + if ($schema) + $sql=$sql." AND TABSCHEMA='".strtoupper($schema)."'"; + + $conn = $this->getDbConnection(); + $conn->setActive(true); + $command = $conn->createCommand($sql); + $command->prepare(); + $result=$command->query($sql); + foreach ($result as $col) + $cols[strtolower($col['COLNAME'])] = $this->getColumnMetaData($col); + return $cols; + } + + protected function getColumnMetaData($col) + { + $name = strtolower($col['COLNAME']); + $type = $col['TYPENAME']; + $length = $col['LENGTH']; + $notNull = $col['NULLS']==='N'?1:0; + $autoIncrement=$col['IDENTITY']==='Y'?1:0; + $default = $col['DEFAULT']; + $primaryKey = $col['KEYSEQ']?1:0; + return new TIbmColumnMetaData($name,$type,$length,$notNull,$autoIncrement,$default,$primaryKey); + } + + /** + * Not implemented, IBM does not always have foreign key constraints. + */ + protected function getConstraintKeys($table) + { + return array('primary'=>array(), 'foreign'=>array()); + } + + /** + * Create a new instance of meta data. + * @param string table name + * @param array column meta data + * @param array primary key meta data + * @param array foreign key meta data. + * @return TDbMetaData table meta data. + */ + protected function createMetaData($table, $columns, $primary, $foreign) + { + $pks = array(); + foreach($columns as $name=>$column) + if($column->getIsPrimaryKey()) + $pks[] = $name; + return new TIbmMetaData($table,$columns,$pks); + } +} +?> \ No newline at end of file diff --git a/framework/Data/Common/Mssql/TMssqlCommandBuilder.php b/framework/Data/Common/Mssql/TMssqlCommandBuilder.php new file mode 100644 index 00000000..b22c08a5 --- /dev/null +++ b/framework/Data/Common/Mssql/TMssqlCommandBuilder.php @@ -0,0 +1,173 @@ + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2007 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id: TDbCommandBuilder.php 1863 2007-04-12 12:43:49Z wei $ + * @package System.Data.Common + */ + +Prado::using('System.Data.Common.TDbCommandBuilder'); + +/** + * TDbCommandBuilder provides basic methods to create query commands for tables + * giving by {@link setTableInfo TableInfo} the property. + * + * @author Wei Zhuo + * @version $Id: TDbCommandBuilder.php 1863 2007-04-12 12:43:49Z wei $ + * @package System.Data.Common + * @since 3.1 + */ +class TMssqlCommandBuilder extends TDbCommandBuilder +{ + /** + * Overrides parent implementation. Uses "SELECT @@Identity". + * @return integer last insert id, null if none is found. + */ + public function getLastInsertID() + { + foreach($this->getTableInfo()->getColumns() as $column) + { + if($column->hasSequence()) + { + $command = $this->getDbConnection()->createCommand('SELECT @@Identity'); + return intval($command->queryScalar()); + } + } + } + + /** + * Overrides parent implementation. Alters the sql to apply $limit and $offset. + * The idea for limit with offset is done by modifying the sql on the fly + * with numerous assumptions on the structure of the sql string. + * The modification is done with reference to the notes from + * http://troels.arvin.dk/db/rdbms/#select-limit-offset + * + * + * SELECT * FROM ( + * SELECT TOP n * FROM ( + * SELECT TOP z columns -- (z=n+skip) + * FROM tablename + * ORDER BY key ASC + * ) AS FOO ORDER BY key DESC -- ('FOO' may be anything) + * ) AS BAR ORDER BY key ASC -- ('BAR' may be anything) + * + * + * Regular expressions are used to alter the SQL query. The resulting SQL query + * may be malformed for complex queries. The following restrictions apply + * + *
    + *
  • + * In particular, commas should NOT + * be used as part of the ordering expression or identifier. Commas must only be + * used for separating the ordering clauses. + *
  • + *
  • + * In the ORDER BY clause, the column name should NOT be be qualified + * with a table name or view name. Alias the column names or use column index. + *
  • + *
  • + * No clauses should follow the ORDER BY clause, e.g. no COMPUTE or FOR clauses. + *
  • + * + * @param string SQL query string. + * @param integer maximum number of rows, -1 to ignore limit. + * @param integer row offset, -1 to ignore offset. + * @return string SQL with limit and offset. + */ + public function applyLimitOffset($sql, $limit=-1, $offset=-1) + { + $limit = $limit!==null ? intval($limit) : -1; + $offset = $offset!==null ? intval($offset) : -1; + if ($limit > 0 && $offset <= 0) //just limit + $sql = preg_replace('/^([\s(])*SELECT( DISTINCT)?(?!\s*TOP\s*\()/i',"\\1SELECT\\2 TOP $limit", $sql); + else if($limit > 0 && $offset > 0) + $sql = $this->rewriteLimitOffsetSql($sql, $limit,$offset); + return $sql; + } + + /** + * Rewrite sql to apply $limit > and $offset > 0 for MSSQL database. + * See http://troels.arvin.dk/db/rdbms/#select-limit-offset + * @param string sql query + * @param integer $limit > 0 + * @param integer $offset > 0 + * @return sql modified sql query applied with limit and offset. + */ + protected function rewriteLimitOffsetSql($sql, $limit, $offset) + { + $fetch = $limit+$offset; + $sql = preg_replace('/^([\s(])*SELECT( DISTINCT)?(?!\s*TOP\s*\()/i',"\\1SELECT\\2 TOP $fetch", $sql); + $ordering = $this->findOrdering($sql); + + $orginalOrdering = $this->joinOrdering($ordering); + $reverseOrdering = $this->joinOrdering($this->reverseDirection($ordering)); + $sql = "SELECT * FROM (SELECT TOP {$limit} * FROM ($sql) as [__inner top table__] {$reverseOrdering}) as [__outer top table__] {$orginalOrdering}"; + return $sql; + } + + /** + * Base on simplified syntax http://msdn2.microsoft.com/en-us/library/aa259187(SQL.80).aspx + * + * @param string $sql + * @return array ordering expression as key and ordering direction as value + */ + protected function findOrdering($sql) + { + if(!preg_match('/ORDER BY/i', $sql)) + return array(); + $matches=array(); + $ordering=array(); + preg_match_all('/(ORDER BY)[\s"\[](.*)(ASC|DESC)?(?:[\s"\[]|$|COMPUTE|FOR)/i', $sql, $matches); + if(count($matches)>1 && count($matches[2]) > 0) + { + $parts = explode(',', $matches[2][0]); + foreach($parts as $part) + { + $subs=array(); + if(preg_match_all('/(.*)[\s"\]](ASC|DESC)$/i', trim($part), $subs)) + { + if(count($subs) > 1 && count($subs[2]) > 0) + { + $ordering[$subs[1][0]] = $subs[2][0]; + } + //else what? + } + else + $ordering[trim($part)] = 'ASC'; + } + } + return $ordering; + } + + /** + * @param array ordering obtained from findOrdering() + * @return string concat the orderings + */ + protected function joinOrdering($orders) + { + if(count($orders)>0) + { + $str=array(); + foreach($orders as $column => $direction) + $str[] = $column.' '.$direction; + return 'ORDER BY '.implode(', ', $str); + } + } + + /** + * @param array original ordering + * @return array ordering with reversed direction. + */ + protected function reverseDirection($orders) + { + foreach($orders as $column => $direction) + $orders[$column] = strtolower(trim($direction))==='desc' ? 'ASC' : 'DESC'; + return $orders; + } +} + +?> \ No newline at end of file diff --git a/framework/Data/Common/Mssql/TMssqlMetaData.php b/framework/Data/Common/Mssql/TMssqlMetaData.php new file mode 100644 index 00000000..c70bc349 --- /dev/null +++ b/framework/Data/Common/Mssql/TMssqlMetaData.php @@ -0,0 +1,202 @@ +getCatalogSchemaTableName($table); + $this->getDbConnection()->setActive(true); + $sql = <<getDbConnection()->createCommand($sql); + $command->bindValue(':table', $tableName); + if($schemaName!==null) + $command->bindValue(':schema', $schemaName); + if($catalogName!==null) + $command->bindValue(':catalog', $catalogName); + + $tableInfo=null; + foreach($command->query() as $col) + { + if($tableInfo===null) + $tableInfo = $this->createNewTableInfo($col); + $this->processColumn($tableInfo,$col); + } + return $tableInfo; + } + + protected function getCatalogSchemaTableName($table) + { + //remove possible delimiters + $result = explode('.', preg_replace('/\[|\]|"/', '', $table)); + if(count($result)===1) + return array(null,null,$result[0]); + if(count($result)===2) + return array(null,$result[0],$result[1]); + if(count($result)>2) + return array($result[0],$result[1],$result[2]); + } + + /** + * @param TMssqlTableInfo table information. + * @param array column information. + */ + protected function processColumn($tableInfo, $col) + { + $columnId = $col['COLUMN_NAME']; + + $info['ColumnName'] = "[$columnId]"; //quote the column names! + $info['ColumnId'] = $columnId; + $info['ColumnIndex'] = intval($col['ORDINAL_POSITION'])-1; //zero-based index + if($col['IS_NULLABLE']!=='NO') + $info['AllowNull'] = true; + if($col['COLUMN_DEFAULT']!==null) + $info['DefaultValue'] = $col['COLUMN_DEFAULT']; + + if(in_array($columnId, $tableInfo->getPrimaryKeys())) + $info['IsPrimaryKey'] = true; + if($this->isForeignKeyColumn($columnId, $tableInfo)) + $info['IsForeignKey'] = true; + + if($col['IsIdentity']==='1') + $info['AutoIncrement'] = true; + $info['DbType'] = $col['DATA_TYPE']; + if($col['CHARACTER_MAXIMUM_LENGTH']!==null) + $info['ColumnSize'] = intval($col['CHARACTER_MAXIMUM_LENGTH']); + if($col['NUMERIC_PRECISION'] !== null) + $info['NumericPrecision'] = intval($col['NUMERIC_PRECISION']); + if($col['NUMERIC_SCALE']!==null) + $info['NumericScale'] = intval($col['NUMERIC_SCALE']); + $tableInfo->Columns[$columnId] = new TMssqlTableColumn($info); + } + + /** + * @param string table schema name + * @param string table name. + * @return TMssqlTableInfo + */ + protected function createNewTableInfo($col) + { + $info['CatalogName'] = $col['TABLE_CATALOG']; + $info['SchemaName'] = $col['TABLE_SCHEMA']; + $info['TableName'] = $col['TABLE_NAME']; + if($col['TABLE_TYPE']==='VIEW') + $info['IsView'] = true; + list($primary, $foreign) = $this->getConstraintKeys($col); + return new TMssqlTableInfo($info,$primary,$foreign); + } + + /** + * Gets the primary and foreign key column details for the given table. + * @param string schema name + * @param string table name. + * @return array tuple ($primary, $foreign) + */ + protected function getConstraintKeys($col) + { + $sql = <<getDbConnection()->createCommand($sql); + $command->bindValue(':table', $col['TABLE_NAME']); + $primary = array(); + foreach($command->query()->readAll() as $field) + $primary[] = $field['field_name']; + $foreign = $this->getForeignConstraints($col); + return array($primary,$foreign); + } + + /** + * Gets foreign relationship constraint keys and table name + * @param string database name + * @param string table name + * @return array foreign relationship table name and keys. + */ + protected function getForeignConstraints($col) + { + //From http://msdn2.microsoft.com/en-us/library/aa175805(SQL.80).aspx + $sql = <<getDbConnection()->createCommand($sql); + $command->bindValue(':table', $col['TABLE_NAME']); + $fkeys=array(); + $catalogSchema = "[{$col['TABLE_CATALOG']}].[{$col['TABLE_SCHEMA']}]"; + foreach($command->query() as $info) + { + $fkeys[$info['FK_CONSTRAINT_NAME']]['keys'][$info['FK_COLUMN_NAME']] = $info['UQ_COLUMN_NAME']; + $fkeys[$info['FK_CONSTRAINT_NAME']]['table'] = "{$catalogSchema}.[{$info['UQ_TABLE_NAME']}]"; + } + return count($fkeys) > 0 ? array_values($fkeys) : $fkeys; + } + + /** + * @param string column name. + * @param TPgsqlTableInfo table information. + * @return boolean true if column is a foreign key. + */ + protected function isForeignKeyColumn($columnId, $tableInfo) + { + foreach($tableInfo->getForeignKeys() as $fk) + { + if(in_array($columnId, array_keys($fk['keys']))) + return true; + } + return false; + } +} + +?> \ No newline at end of file diff --git a/framework/Data/Common/Mssql/TMssqlTableColumn.php b/framework/Data/Common/Mssql/TMssqlTableColumn.php new file mode 100644 index 00000000..77c115e5 --- /dev/null +++ b/framework/Data/Common/Mssql/TMssqlTableColumn.php @@ -0,0 +1,56 @@ + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2007 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id: TMssqlTableColumn.php 1863 2007-04-12 12:43:49Z wei $ + * @package System.Data.Common.Mssql + */ + +/** + * Load common TDbTableCommon class. + */ +Prado::using('System.Data.Common.TDbTableColumn'); + +/** + * Describes the column metadata of the schema for a Mssql database table. + * + * @author Wei Zhuo + * @version $Id: TMssqlTableColumn.php 1863 2007-04-12 12:43:49Z wei $ + * @package System.Data.Common.Mssql + * @since 3.1 + */ +class TMssqlTableColumn extends TDbTableColumn +{ + private static $types = array(); + + /** + * Overrides parent implementation, returns PHP type from the db type. + * @return boolean derived PHP primitive type from the column db type. + */ + public function getPHPType() + { + + return 'string'; + } + + public function getAutoIncrement() + { + return $this->getInfo('AutoIncrement',false); + } + + public function hasSequence() + { + return $this->getAutoIncrement(); + } + + public function getIsExcluded() + { + return strtolower($this->getDbType())==='timestamp'; + } +} + +?> \ No newline at end of file diff --git a/framework/Data/Common/Mssql/TMssqlTableInfo.php b/framework/Data/Common/Mssql/TMssqlTableInfo.php new file mode 100644 index 00000000..b407ad44 --- /dev/null +++ b/framework/Data/Common/Mssql/TMssqlTableInfo.php @@ -0,0 +1,62 @@ + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2007 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id: TMssqlTableInfo.php 1861 2007-04-12 08:05:03Z wei $ + * @package System.Data.Common.Mssql + */ + +/** + * Loads the base TDbTableInfo class and TMssqlTableColumn class. + */ +Prado::using('System.Data.Common.TDbTableInfo'); +Prado::using('System.Data.Common.Mssql.TMssqlTableColumn'); + +/** + * TMssqlTableInfo class provides additional table information for Mssql database. + * + * @author Wei Zhuo + * @version $Id: TMssqlTableInfo.php 1861 2007-04-12 08:05:03Z wei $ + * @package System.Data.Common.Mssql + * @since 3.1 + */ +class TMssqlTableInfo extends TDbTableInfo +{ + /** + * @return string name of the schema this column belongs to. + */ + public function getSchemaName() + { + return $this->getInfo('SchemaName'); + } + + public function getCatalogName() + { + return $this->getInfo('CatalogName'); + } + + /** + * @return string full name of the table, database dependent. + */ + public function getTableFullName() + { + return '['.$this->getCatalogName().'].['.$this->getSchemaName().'].['.$this->getTableName().']'; + } + + /** + * @param TDbConnection database connection. + * @return TDbCommandBuilder new command builder + */ + public function createCommandBuilder($connection) + { + Prado::using('System.Data.Common.Mssql.TMssqlCommandBuilder'); + return new TMssqlCommandBuilder($connection,$this); + } + +} + +?> \ No newline at end of file diff --git a/framework/Data/Common/Mysql/TMysqlMetaData.php b/framework/Data/Common/Mysql/TMysqlMetaData.php index 2a6d80bd..9e947298 100644 --- a/framework/Data/Common/Mysql/TMysqlMetaData.php +++ b/framework/Data/Common/Mysql/TMysqlMetaData.php @@ -1,5 +1,4 @@ -getSchemaTableName($table); + $find = $schemaName===null ? "`{$tableName}`" : "`{$schemaName}`.`{$tableName}`"; $this->getDbConnection()->setActive(true); - $sql = "SHOW FULL FIELDS FROM {$table}"; + $sql = "SHOW FULL FIELDS FROM {$find}"; $command = $this->getDbConnection()->createCommand($sql); $tableInfo = $this->createNewTableInfo($table); $index=0; @@ -37,6 +38,7 @@ class TMysqlMetaData extends TDbMetaData $columnId = $col['Field']; $info['ColumnName'] = "`$columnId`"; //quote the column names! + $info['ColumnId'] = $columnId; $info['ColumnIndex'] = $col['index']; if($col['Null']!=='NO') $info['AllowNull'] = true; @@ -122,7 +124,8 @@ class TMysqlMetaData extends TDbMetaData /** * http://dev.mysql.com/doc/refman/5.0/en/identifiers.html - * @param unknown_type $name + * @param string identifier name + * @param boolean true if valid identifier. */ protected function isValidIdentifier($name) { @@ -139,7 +142,8 @@ class TMysqlMetaData extends TDbMetaData list($schemaName,$tableName) = $this->getSchemaTableName($table); $info['SchemaName'] = $schemaName; $info['TableName'] = $tableName; - $info['IsView'] = $this->getIsView($schemaName,$tableName); + if($this->getIsView($schemaName,$tableName)) + $info['IsView'] = true; list($primary, $foreign) = $this->getConstraintKeys($schemaName, $tableName); return new TMysqlTableInfo($info,$primary,$foreign); } diff --git a/framework/Data/Common/Mysql/TMysqlTableColumn.php b/framework/Data/Common/Mysql/TMysqlTableColumn.php index 4f0f7da2..0e991d6b 100644 --- a/framework/Data/Common/Mysql/TMysqlTableColumn.php +++ b/framework/Data/Common/Mysql/TMysqlTableColumn.php @@ -59,7 +59,7 @@ class TMysqlTableColumn extends TDbTableColumn /** * @return boolean true if auto increment is true. */ - public function getHasSequence() + public function hasSequence() { return $this->getAutoIncrement(); } diff --git a/framework/Data/Common/Mysql/TMysqlTableInfo.php b/framework/Data/Common/Mysql/TMysqlTableInfo.php index e8585730..9bc01717 100644 --- a/framework/Data/Common/Mysql/TMysqlTableInfo.php +++ b/framework/Data/Common/Mysql/TMysqlTableInfo.php @@ -39,7 +39,10 @@ class TMysqlTableInfo extends TDbTableInfo */ public function getTableFullName() { - return '`'.$this->getSchemaName().'`.`'.$this->getTableName().'`'; + if(($schema=$this->getSchemaName())!==null) + return '`'.$schema.'`.`'.$this->getTableName(); + else + return '`'.$this->getTableName().'`'; } } diff --git a/framework/Data/Common/Pgsql/TPgsqlMetaData.php b/framework/Data/Common/Pgsql/TPgsqlMetaData.php index e964cfe7..ac7e6b7a 100644 --- a/framework/Data/Common/Pgsql/TPgsqlMetaData.php +++ b/framework/Data/Common/Pgsql/TPgsqlMetaData.php @@ -50,7 +50,7 @@ class TPgsqlMetaData extends TDbMetaData */ protected function getSchemaTableName($table) { - if(count($parts= explode('.', $table)) > 1) + if(count($parts= explode('.', str_replace('"','',$table))) > 1) return array($parts[0], $parts[1]); else return array($this->getDefaultSchema(),$parts[0]); @@ -171,6 +171,7 @@ EOD; $columnId = $col['attname']; //use column name as column Id $info['ColumnName'] = '"'.$columnId.'"'; //quote the column names! + $info['ColumnId'] = $columnId; $info['ColumnIndex'] = $col['index']; if(!$col['attnotnull']) $info['AllowNull'] = true; diff --git a/framework/Data/Common/Pgsql/TPgsqlTableInfo.php b/framework/Data/Common/Pgsql/TPgsqlTableInfo.php index 88a56635..a2670fe0 100644 --- a/framework/Data/Common/Pgsql/TPgsqlTableInfo.php +++ b/framework/Data/Common/Pgsql/TPgsqlTableInfo.php @@ -39,7 +39,10 @@ class TPgsqlTableInfo extends TDbTableInfo */ public function getTableFullName() { - return $this->getSchemaName().'.'.$this->getTableName(); + if(($schema=$this->getSchemaName())!==null) + return $schema.'.'.$this->getTableName(); + else + $this->getTableName(); } } diff --git a/framework/Data/Common/Sqlite/TSqliteMetaData.php b/framework/Data/Common/Sqlite/TSqliteMetaData.php index 68734046..2ce46fb7 100644 --- a/framework/Data/Common/Sqlite/TSqliteMetaData.php +++ b/framework/Data/Common/Sqlite/TSqliteMetaData.php @@ -80,6 +80,7 @@ class TSqliteMetaData extends TDbMetaData $columnId = $col['name']; //use column name as column Id $info['ColumnName'] = '"'.$columnId.'"'; //quote the column names! + $info['ColumnId'] = $columnId; $info['ColumnIndex'] = $col['index']; if($col['notnull']!=='99') @@ -115,6 +116,8 @@ class TSqliteMetaData extends TDbMetaData } /** + * + * * @param string quoted table name. * @return array foreign key details. */ @@ -145,6 +148,23 @@ class TSqliteMetaData extends TDbMetaData } return false; } +} -} +/** + +CREATE TABLE foo +( + id INTEGER NOT NULL PRIMARY KEY, + id2 CHAR(2) +); + +CREATE TABLE bar +( + id INTEGER NOT NULL PRIMARY KEY, + foo_id INTEGER NOT NULL CONSTRAINT fk_foo_id REFERENCES foo(id) ON DELETE CASCADE, + foo_id2 CHAR(2) CONSTRAINT fk_foo_id REFERENCES foo(id2) ON DELETE CASCADE, + bar_id INTEGER NOT NULL CONSTRAINT fk_bar_id REFERENCES bar(id) +); +*/ + ?> \ No newline at end of file diff --git a/framework/Data/Common/Sqlite/TSqliteTableColumn.php b/framework/Data/Common/Sqlite/TSqliteTableColumn.php index cb379bfd..b8287218 100644 --- a/framework/Data/Common/Sqlite/TSqliteTableColumn.php +++ b/framework/Data/Common/Sqlite/TSqliteTableColumn.php @@ -53,7 +53,7 @@ class TSqliteTableColumn extends TDbTableColumn /** * @return boolean true if auto increment is true. */ - public function getHasSequence() + public function hasSequence() { return $this->getAutoIncrement(); } diff --git a/framework/Data/Common/TDbCommandBuilder.php b/framework/Data/Common/TDbCommandBuilder.php index 2a8f93ee..2c3578f0 100644 --- a/framework/Data/Common/TDbCommandBuilder.php +++ b/framework/Data/Common/TDbCommandBuilder.php @@ -66,6 +66,20 @@ class TDbCommandBuilder extends TComponent return $this->_tableInfo; } + /** + * Iterate through all the columns and returns the last insert id of the + * first column that has a sequence or serial. + * @return mixed last insert id, null if none is found. + */ + public function getLastInsertID() + { + foreach($this->getTableInfo()->getColumns() as $column) + { + if($column->hasSequence()) + return $this->getDbConnection()->getLastInsertID($column->getSequenceName()); + } + } + /** * Alters the sql to apply $limit and $offset. Default implementation is applicable * for PostgreSQL, MySQL and SQLite. @@ -122,6 +136,8 @@ class TDbCommandBuilder extends TComponent */ public function createFindCommand($where='1=1', $parameters=array(), $ordering=array(), $limit=-1, $offset=-1) { + if($where===null) + $where='1=1'; $table = $this->getTableInfo()->getTableFullName(); $sql = "SELECT * FROM {$table} WHERE {$where}"; if(count($ordering) > 0) @@ -141,6 +157,8 @@ class TDbCommandBuilder extends TComponent */ public function createCountCommand($where='1=1', $parameters=array(),$ordering=array(), $limit=-1, $offset=-1) { + if($where===null) + $where='1=1'; $table = $this->getTableInfo()->getTableFullName(); $sql = "SELECT COUNT(*) FROM {$table} WHERE {$where}"; if(count($ordering) > 0) diff --git a/framework/Data/Common/TDbMetaData.php b/framework/Data/Common/TDbMetaData.php index 477e2805..6838d7dc 100644 --- a/framework/Data/Common/TDbMetaData.php +++ b/framework/Data/Common/TDbMetaData.php @@ -64,9 +64,12 @@ abstract class TDbMetaData extends TComponent case 'sqlite2': //sqlite 2 Prado::using('System.Data.Common.Sqlite.TSqliteMetaData'); return new TSqliteMetaData($conn); - case 'ibm': - Prado::using('System.Data.Common.IbmDb2.TIbmDb2MetaData'); - return new TIbmDb2MetaData($conn); + case 'mssql': + Prado::using('System.Data.Common.Mssql.TMssqlMetaData'); + return new TMssqlMetaData($conn); +// case 'ibm': +// Prado::using('System.Data.Common.IbmDb2.TIbmDb2MetaData'); +// return new TIbmDb2MetaData($conn); default: throw new TDbException('ar_invalid_database_driver',$driver); } diff --git a/framework/Data/Common/TDbTableColumn.php b/framework/Data/Common/TDbTableColumn.php index c5c9ac80..6ff2ff46 100644 --- a/framework/Data/Common/TDbTableColumn.php +++ b/framework/Data/Common/TDbTableColumn.php @@ -83,6 +83,14 @@ class TDbTableColumn extends TComponent return $this->getInfo('ColumnName'); } + /** + * @return string name of the column with quoted identifier. + */ + public function getColumnId() + { + return $this->getInfo('ColumnId'); + } + /** * @return string size of the column. */ @@ -92,7 +100,7 @@ class TDbTableColumn extends TComponent } /** - * @return integer zero-based ordinal position of the column in the table. + * @return integer one-based ordinal position of the column in the table. */ public function getColumnIndex() { @@ -139,6 +147,15 @@ class TDbTableColumn extends TComponent return $this->getInfo('NumericScale'); } + public function getMaxiumNumericConstraint() + { + if(($precision=$this->getNumericPrecision())!==null) + { + $scale=$this->getNumericScale(); + return $scale===null ? pow(10,$precision) : pow(10,$precision-$scale); + } + } + /** * @return boolean whether this column is a primary key for the table, default is false. */ @@ -170,6 +187,14 @@ class TDbTableColumn extends TComponent { return $this->getSequenceName()!==null; } + + /** + * @return boolean whether this column is excluded from insert and update. + */ + public function getIsExcluded() + { + return false; + } } ?> \ No newline at end of file diff --git a/framework/Data/DataGateway/TDataGatewayCommand.php b/framework/Data/DataGateway/TDataGatewayCommand.php index a5f729d8..8a340ee7 100644 --- a/framework/Data/DataGateway/TDataGatewayCommand.php +++ b/framework/Data/DataGateway/TDataGatewayCommand.php @@ -15,7 +15,7 @@ class TDataGatewayCommand extends TComponent /** * @return TDbTableInfo */ - protected function getTableInfo() + public function getTableInfo() { return $this->_builder->getTableInfo(); } @@ -23,7 +23,7 @@ class TDataGatewayCommand extends TComponent /** * @return TDbConnection */ - protected function getDbConnection() + public function getDbConnection() { return $this->_builder->getDbConnection(); } @@ -31,7 +31,7 @@ class TDataGatewayCommand extends TComponent /** * @return TDbCommandBuilder */ - protected function getBuilder() + public function getBuilder() { return $this->_builder; } @@ -65,6 +65,17 @@ class TDataGatewayCommand extends TComponent return $command->execute(); } + /** + * @param array update for update + * @param array primary key-value name pairs. + * @return integer number of records affected. + */ + public function updateByPk($data, $keys) + { + list($where, $parameters) = $this->getPrimaryKeyCondition((array)$keys); + return $this->update($data, new TSqlCriteria($where, $parameters)); + } + /** * Find one record matching the critera. * @param TSqlCriteria find conditions and parameters. @@ -281,11 +292,7 @@ class TDataGatewayCommand extends TComponent */ public function getLastInsertID() { - foreach($this->getTableInfo()->getColumns() as $column) - { - if($column->hasSequence()) - return $this->getDbConnection()->getLastInsertID($column->getSequenceName()); - } + return $this->getBuilder()->getLastInsertID(); } /** diff --git a/framework/Exceptions/messages.txt b/framework/Exceptions/messages.txt index ff2a6927..ca7faed1 100644 --- a/framework/Exceptions/messages.txt +++ b/framework/Exceptions/messages.txt @@ -391,4 +391,4 @@ soapserver_version_invalid = Invalid SOAP version '{0}'. It must be either '1 dbusermanager_userclass_required = TDbUserManager.UserClass is required. dbusermanager_userclass_invalid = TDbUserManager.UserClass '{0}' is not a valid user class. The class must extend TDbUser. dbusermanager_connectionid_invalid = TDbUserManager.ConnectionID '{0}' does not point to a valid TDataSourceConfig module. -dbusermanager_connectionid_required = TDbUserManager.ConnectionID is required. \ No newline at end of file +dbusermanager_connectionid_required = TDbUserManager.ConnectionID is required. diff --git a/framework/Web/Javascripts/source/prado/ratings/ratings.js b/framework/Web/Javascripts/source/prado/ratings/ratings.js index 4e6ae6c5..d84b7aa1 100644 --- a/framework/Web/Javascripts/source/prado/ratings/ratings.js +++ b/framework/Web/Javascripts/source/prado/ratings/ratings.js @@ -57,4 +57,4 @@ Prado.WebUI.TRatingList.prototype = this.caption.innerHTML = index > -1 ? this.radios[index].value : this.options.caption; } -} \ No newline at end of file +}; diff --git a/framework/Web/Javascripts/source/prado/validator/validation3.js b/framework/Web/Javascripts/source/prado/validator/validation3.js index 5fa879a5..38e11cd0 100644 --- a/framework/Web/Javascripts/source/prado/validator/validation3.js +++ b/framework/Web/Javascripts/source/prado/validator/validation3.js @@ -1223,9 +1223,9 @@ Prado.WebUI.TRangeValidator = Class.extend(Prado.WebUI.TBaseValidator, var valid = true; if(min != null) - valid = valid && value >= min; + valid = valid && (this.options.StrictComparison ? value > min : value >= min); if(max != null) - valid = valid && value <= max; + valid = valid && (this.options.StrictComparison ? value < max : value <= max); return valid; } }); diff --git a/framework/Web/UI/WebControls/TRangeValidator.php b/framework/Web/UI/WebControls/TRangeValidator.php index a5cc2807..369b887b 100644 --- a/framework/Web/UI/WebControls/TRangeValidator.php +++ b/framework/Web/UI/WebControls/TRangeValidator.php @@ -36,6 +36,9 @@ Prado::using('System.Web.UI.WebControls.TBaseValidator'); * - String A string data type. * - StringLength check for string length. * + * If {@link setStrictComparison StrictComparison} is true, then the ranges + * are compared as strictly less than the max value and/or strictly greater than the min value. + * * The TRangeValidator allows a special DataType "StringLength" that * can be used to verify minimum and maximum string length. The * {@link setCharset Charset} property can be used to force a particular @@ -93,6 +96,22 @@ class TRangeValidator extends TBaseValidator $this->setViewState('MaxValue',$value,''); } + /** + * @param boolean true to perform strict comparison (i.e. strictly less than max and/or strictly greater than min). + */ + public function setStrictComparison($value) + { + $this->setViewState('StrictComparison', TPropertyValue::ensureBoolean($value),false); + } + + /** + * @return boolean true to perform strict comparison. + */ + public function getStrictComparison() + { + return $this->getViewState('StrictComparison', false); + } + /** * @return TRangeValidationDataType the data type that the values being compared are * converted to before the comparison is made. Defaults to TRangeValidationDataType::String. @@ -184,12 +203,22 @@ class TRangeValidator extends TBaseValidator $value=intval($value); $valid=true; if($minValue!=='') - $valid=$valid && ($value>=intval($minValue)); + $valid=$valid && $this->isGreaterThan($value, intval($minValue)); if($maxValue!=='') - $valid=$valid && ($value<=intval($maxValue)); + $valid=$valid && $this->isLessThan($value,intval($maxValue)); return $valid; } + protected function isLessThan($left,$right) + { + return $this->getStrictComparison() ? $left < $right : $left <= $right; + } + + protected function isGreaterThan($left, $right) + { + return $this->getStrictComparison() ? $left > $right : $left >= $right; + } + /** * Determine if the value is within the specified float range. * @param string value to validate @@ -203,9 +232,9 @@ class TRangeValidator extends TBaseValidator $value=floatval($value); $valid=true; if($minValue!=='') - $valid=$valid && ($value>=floatval($minValue)); + $valid=$valid && $this->isGreaterThan($value,floatval($minValue)); if($maxValue!=='') - $valid=$valid && ($value<=floatval($maxValue)); + $valid=$valid && $this->isLessThan($value,floatval($maxValue)); return $valid; } @@ -228,18 +257,18 @@ class TRangeValidator extends TBaseValidator $formatter=Prado::createComponent('System.Util.TSimpleDateFormatter', $dateFormat); $value = $formatter->parse($value, $dateFormat); if($minValue!=='') - $valid=$valid && ($value>=$formatter->parse($minValue)); + $valid=$valid && $this->isGreaterThan($value,$formatter->parse($minValue)); if($maxValue!=='') - $valid=$valid && ($value<=$formatter->parse($maxValue)); + $valid=$valid && $this->isLessThan($value,$formatter->parse($maxValue)); return $valid; } else { $value=strtotime($value); if($minValue!=='') - $valid=$valid && ($value>=strtotime($minValue)); + $valid=$valid && $this->isGreaterThan($value,strtotime($minValue)); if($maxValue!=='') - $valid=$valid && ($value<=strtotime($maxValue)); + $valid=$valid && $this->isLessThan($value,strtotime($maxValue)); return $valid; } } @@ -257,9 +286,9 @@ class TRangeValidator extends TBaseValidator $valid=true; if($minValue!=='') - $valid=$valid && (strcmp($value,$minValue)>=0); + $valid=$valid && $this->isGreaterThan(strcmp($value,$minValue),0); if($maxValue!=='') - $valid=$valid && (strcmp($value,$maxValue)<=0); + $valid=$valid && $this->isLessThan(strcmp($value,$maxValue),0); return $valid; } @@ -284,9 +313,9 @@ class TRangeValidator extends TBaseValidator $length = iconv_strlen($value, $charset); if($minValue!=='') - $valid = $valid && $length >= intval($minValue); + $valid = $valid && $this->isGreaterThan($length,intval($minValue)); if($maxValue!=='') - $valid = $valid && $length <= intval($maxValue); + $valid = $valid && $this->isLessThan($length,intval($maxValue)); return $valid; } @@ -300,6 +329,7 @@ class TRangeValidator extends TBaseValidator $options['MinValue']=$this->getMinValue(); $options['MaxValue']=$this->getMaxValue(); $options['DataType']=$this->getDataType(); + $options['StrictComparison']=$this->getStrictComparison(); if(($dateFormat=$this->getDateFormat())!=='') $options['DateFormat']=$dateFormat; return $options; diff --git a/framework/prado-cli.php b/framework/prado-cli.php index 537cbf98..ca3e1461 100755 --- a/framework/prado-cli.php +++ b/framework/prado-cli.php @@ -662,9 +662,9 @@ class PradoCommandLineActiveRecordGen extends PradoCommandLineAction protected function generateActiveRecord($config, $tablename, $output) { $manager = TActiveRecordManager::getInstance(); - $inspector = $manager->getTableInspector($manager->getDbConnection()); - $meta = $inspector->getTableMetaData($tablename); - if(count($meta->getColumns()) === 0) + $gateway = $manager->getRecordGateway(); + $tableInfo = $gateway->getTableInfo($manager->getDbConnection(), $tablename); + if(count($tableInfo->getColumns()) === 0) { echo '** Unable to find table or view "'.$tablename.'" in "'.$manager->getDbConnection()->getConnectionString()."\".\n"; return false; @@ -672,7 +672,7 @@ class PradoCommandLineActiveRecordGen extends PradoCommandLineAction else { $properties = array(); - foreach($meta->getColumns() as $field=>$column) + foreach($tableInfo->getColumns() as $field=>$column) $properties[] = $this->generateProperty($field,$column); } diff --git a/tests/simple_unit/ActiveRecord/ActiveRecordDynamicCallTestCase.php b/tests/simple_unit/ActiveRecord/ActiveRecordDynamicCallTestCase.php index 85f1c944..07e810aa 100644 --- a/tests/simple_unit/ActiveRecord/ActiveRecordDynamicCallTestCase.php +++ b/tests/simple_unit/ActiveRecord/ActiveRecordDynamicCallTestCase.php @@ -40,7 +40,7 @@ class ActiveRecordDynamicCallTestCase extends UnitTestCase $rs = $finder->findByNameAndActive('Marketing'); $this->fail(); } - catch(TActiveRecordException $e) + catch(TDbException $e) { $this->pass(); } diff --git a/tests/simple_unit/ActiveRecord/SqliteTestCase.php b/tests/simple_unit/ActiveRecord/SqliteTestCase.php index b2d21ef1..94af6200 100644 --- a/tests/simple_unit/ActiveRecord/SqliteTestCase.php +++ b/tests/simple_unit/ActiveRecord/SqliteTestCase.php @@ -16,12 +16,6 @@ class SqliteTestCase extends UnitTestCase $user = $finder->findByPk('test'); $this->assertNotNull($user); } - - function test_get_pk() - { - $meta = TActiveRecordManager::getInstance()->getMetaData('SqliteUsers'); - $this->assertEqual(array('username'), $meta->PrimaryKeys); - } } ?> \ No newline at end of file diff --git a/tests/simple_unit/ActiveRecord/ViewRecordTestCase.php b/tests/simple_unit/ActiveRecord/ViewRecordTestCase.php index 29fdee49..db23f01a 100644 --- a/tests/simple_unit/ActiveRecord/ViewRecordTestCase.php +++ b/tests/simple_unit/ActiveRecord/ViewRecordTestCase.php @@ -54,7 +54,7 @@ class ViewRecordTestCase extends UnitTestCase $user = SimpleUser::finder()->findByPk('admin'); $this->fail(); } - catch(TActiveRecordException $e) + catch(TDbException $e) { $this->pass(); } @@ -67,7 +67,7 @@ class ViewRecordTestCase extends UnitTestCase SimpleUser::finder()->deleteByPk('admin'); $this->fail(); } - catch(TActiveRecordException $e) + catch(TDbException $e) { $this->pass(); } diff --git a/tests/simple_unit/DbCommon/CommandBuilderMssqlTest.php b/tests/simple_unit/DbCommon/CommandBuilderMssqlTest.php new file mode 100644 index 00000000..cc7b8b45 --- /dev/null +++ b/tests/simple_unit/DbCommon/CommandBuilderMssqlTest.php @@ -0,0 +1,46 @@ + 'SELECT username, age FROM accounts', + 'multiple' => 'select a.username, b.name from accounts a, table1 b where a.age = b.id1', + 'ordering' => 'select a.username, b.name, a.age from accounts a, table1 b where a.age = b.id1 order by age DESC, name', + 'index' => 'select a.username, b.name, a.age from accounts a, table1 b where a.age = b.id1 ORDER BY 1 DESC, 2 ASC', + //'compute' => 'SELECT username, age FROM accounts order by age compute avg(age)', + ); + + function test_limit() + { + $builder = new TMssqlCommandBuilder(); + + $sql = $builder->applyLimitOffset(self::$sql['simple'], 3); + $expect = 'SELECT TOP 3 username, age FROM accounts'; + $this->assertEqual($expect, $sql); + + + $sql = $builder->applyLimitOffset(self::$sql['simple'], 3, 2); + $expect = 'SELECT * FROM (SELECT TOP 3 * FROM (SELECT TOP 5 username, age FROM accounts) as [__inner top table__] ) as [__outer top table__] '; + $this->assertEqual($expect, $sql); + + $sql = $builder->applyLimitOffset(self::$sql['multiple'], 3, 2); + $expect = 'SELECT * FROM (SELECT TOP 3 * FROM (SELECT TOP 5 a.username, b.name from accounts a, table1 b where a.age = b.id1) as [__inner top table__] ) as [__outer top table__] '; + $this->assertEqual($sql, $expect); + + $sql = $builder->applyLimitOffset(self::$sql['ordering'], 3, 2); + $expect = 'SELECT * FROM (SELECT TOP 3 * FROM (SELECT TOP 5 a.username, b.name, a.age from accounts a, table1 b where a.age = b.id1 order by age DESC, name) as [__inner top table__] ORDER BY age ASC, name DESC) as [__outer top table__] ORDER BY age DESC, name ASC'; + $this->assertEqual($sql, $expect); + + $sql = $builder->applyLimitOffset(self::$sql['index'], 3, 2); + $expect = 'SELECT * FROM (SELECT TOP 3 * FROM (SELECT TOP 5 a.username, b.name, a.age from accounts a, table1 b where a.age = b.id1 ORDER BY 1 DESC, 2 ASC) as [__inner top table__] ORDER BY 1 ASC, 2 DESC) as [__outer top table__] ORDER BY 1 DESC, 2 ASC'; + $this->assertEqual($expect, $sql); + + // $sql = $builder->applyLimitOffset(self::$sql['compute'], 3, 2); + // var_dump($sql); + } +} + +?> \ No newline at end of file diff --git a/tests/simple_unit/DbCommon/MssqlColumnTest.php b/tests/simple_unit/DbCommon/MssqlColumnTest.php new file mode 100644 index 00000000..db2df03b --- /dev/null +++ b/tests/simple_unit/DbCommon/MssqlColumnTest.php @@ -0,0 +1,48 @@ +get_conn()); + } + + function test_insert() + { + $table = new TTableGateway('table1', $this->get_conn()); + $this->assertTrue(is_int($table->insert(array('name'=>'cool')))); + } + +/* function test_meta() + { + $result = $this->meta_data()->getTableInfo("bar"); + var_dump($result); + } +*/ + /*function test_insert() + { + $table = new TTableGateway('table1', $this->get_conn()); + //var_dump($table->insert(array('name'=>'cool'))); + //var_dump($table->getLastInsertId()); + $criteria = new TSqlCriteria(); + $criteria->Limit = 5; + $criteria->Offset = 2; + + $result = $table->findAll($criteria)->readAll(); + var_dump($result); + }*/ +} + +?> \ No newline at end of file -- cgit v1.2.3