From 8bb67d9f6a1cbad9662ef70e73b2fe46eb5e22db Mon Sep 17 00:00:00 2001 From: gwoo Date: Tue, 21 Aug 2007 21:46:59 +0000 Subject: [PATCH] Adding Schema support, updated fixtures, deprecated loadInfo, changed Dbo::describe in all dbos, fixed tests, git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@5563 3807eeeb-6ff5-0310-8944-8be069107fe0 --- cake/console/libs/schema.php | 223 ++++++++++ cake/console/libs/tasks/model.php | 34 +- cake/libs/error.php | 10 +- cake/libs/model/datasources/datasource.php | 374 ++++++++++++----- cake/libs/model/datasources/dbo/dbo_adodb.php | 5 +- cake/libs/model/datasources/dbo/dbo_db2.php | 3 +- .../model/datasources/dbo/dbo_firebird.php | 10 +- cake/libs/model/datasources/dbo/dbo_mssql.php | 11 +- cake/libs/model/datasources/dbo/dbo_mysql.php | 194 +++++++-- .../libs/model/datasources/dbo/dbo_mysqli.php | 104 +---- cake/libs/model/datasources/dbo/dbo_odbc.php | 39 +- .../libs/model/datasources/dbo/dbo_oracle.php | 55 +-- .../model/datasources/dbo/dbo_postgres.php | 56 +-- .../libs/model/datasources/dbo/dbo_sqlite.php | 5 +- .../libs/model/datasources/dbo/dbo_sybase.php | 21 +- cake/libs/model/datasources/dbo_source.php | 212 ++++------ cake/libs/model/model.php | 50 ++- cake/libs/model/schema.php | 390 ++++++++++++++++++ .../libs/controller/components/acl.test.php | 1 + .../libs/controller/components/auth.test.php | 31 +- .../cases/libs/model/behaviors/tree.test.php | 6 +- cake/tests/cases/libs/model/db_acl.test.php | 40 ++ cake/tests/cases/libs/model/model.test.php | 37 +- cake/tests/cases/libs/model/schema.test.php | 185 +++++++++ cake/tests/fixtures/aco_fixture.php | 9 + .../article_featureds_tags_fixture.php | 2 +- cake/tests/fixtures/articles_tag_fixture.php | 2 +- cake/tests/lib/cake_test_case.php | 11 +- cake/tests/lib/cake_test_fixture.php | 91 ++-- 29 files changed, 1514 insertions(+), 697 deletions(-) create mode 100644 cake/console/libs/schema.php create mode 100644 cake/libs/model/schema.php create mode 100644 cake/tests/cases/libs/model/schema.test.php diff --git a/cake/console/libs/schema.php b/cake/console/libs/schema.php new file mode 100644 index 000000000..80642390a --- /dev/null +++ b/cake/console/libs/schema.php @@ -0,0 +1,223 @@ + + * Copyright 2005-2007, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2005-2007, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project + * @package cake + * @subpackage cake.cake.console.libs + * @since CakePHP(tm) v 1.2.0.5550 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +uses('file', 'model' . DS . 'schema'); +/** + * Schema is a command-line database management utility for automating programmer chores. + * + * @package cake + * @subpackage cake.cake.console.libs + */ +class SchemaShell extends Shell { +/** + * Override initialize + * + * @access public + * @return void + */ + function initialize() { + $this->out('Cake Schema Shell'); + $this->hr(); + } +/** + * Override startup + * + * @access public + * @return void + */ + function startup() { + $this->Schema =& new CakeSchema(array('path'=> CONFIGS .'sql')); + } +/** + * Read and output contents od schema object + * path to read as second arg + * + * @access public + * @return void + */ + function view() { + $path = TMP; + if (!empty($this->args[0])) { + $path = $this->args[0]; + } + $File = new File($path . DS .'schema.php'); + if ($File->exists()) { + $this->out($File->read()); + exit(); + } else { + $this->err(__('Schema could not be found', true)); + exit(); + } + } +/** + * Read database and Write schema object + * accepts a connection as first arg or path to save as second arg + * + * @access public + * @return void + */ + function generate() { + $this->out('Generating Schema...'); + if (!empty($this->args[0])) { + $this->Schema->connection = $this->args[0]; + } + if (!empty($this->args[1])) { + $this->Schema->path = $this->args[1]; + } + + $content = $this->Schema->read(); + if ($this->Schema->write($content)) { + $this->out(__('Schema file created.', true)); + exit(); + } else { + $this->err(__('Schema could not be created', true)); + exit(); + } + } +/** + * Dump Schema object to sql file + * if first arg == write, file will be written to sql file + * or it will output sql + * + * @access public + * @return void + */ + function dump() { + $write = false; + if (!empty($this->args[0])) { + if($this->args[0] == 'write') { + $write = true; + } + } + if (!empty($this->args[1])) { + $this->Schema->path = $this->args[1]; + } + $Schema = $this->Schema->load(); + $db =& ConnectionManager::getDataSource($this->Schema->connection); + $contents = $db->dropSchema($Schema) . $db->createSchema($Schema); + if($write === true) { + $File = new File($this->Schema->path . DS . Inflector::underscore($this->Schema->name) .'.sql', true); + if($File->write($contents)) { + $this->out(__('SQL dump file created in '. $File->pwd(), true)); + exit(); + } else { + $this->err(__('SQL dump could not be created', true)); + exit(); + } + } + $this->out($contents); + return $contents; + } +/** + * Create database from Schema object + * + * @access public + * @return void + */ + function create() { + $Schema = $this->Schema->load(); + $db =& ConnectionManager::getDataSource($this->Schema->connection); + $drop = $db->dropSchema($Schema); + $this->out($drop); + if('y' == $this->in('Are you sure you want to drop tables and create your database?', array('y', 'n'), 'n')) { + $contents = $db->createSchema($Schema); + $this->out('Updating Database...'); + if ($db->_execute($contents)) { + $this->out(__('Database created', true)); + exit(); + } else { + $this->err(__('Database could not be created', true)); + $this->err($db->lastError()); + exit(); + } + } + } +/** + * Update database with Schema object + * + * @access public + * @return void + */ + function update() { + $this->out('Comparing Database to Schema...'); + if (!empty($this->args[0])) { + $this->Schema->connection = $this->args[0]; + } + if (!empty($this->args[1])) { + $this->Schema->path = $this->args[1]; + } + $Old = $this->Schema->read(); + $Schema = $this->Schema->load(); + $compare = $this->Schema->compare($Old, $Schema); + + $db =& ConnectionManager::getDataSource($this->Schema->connection); + $db->fullDebug = true; + Configure::write('debug', 2); + $contents = $db->alterSchema($compare); + if(empty($contents)) { + $this->out(__('Current database is up to date.', true)); + exit(); + } else { + $this->out($contents); + } + if('y' == $this->in('Are you sure you want to update your database?', array('y', 'n'), 'n')) { + $this->out('Updating Database...'); + if ($db->_execute($contents)) { + $this->out(__('Database updated', true)); + exit(); + } else { + $this->err(__('Database could not be updated', true)); + $this->err($db->lastError()); + exit(); + } + } + } +/** + * Displays help contents + * + * @return void + */ + function help() { + $this->out('The Schema Shell generates a schema object from the database and updates the database from the schema.'); + $this->hr(); + $this->out("Usage: cake schema ..."); + $this->hr(); + $this->out('Commands:'); + $this->out("\n\tschema help\n\t\tshows this help message."); + $this->out("\n\tschema view \n\t\tread and output contents of schema file"); + $this->out("\n\tschema generate \n\t\treads from 'connection' writes to 'path'"); + $this->out("\n\tschema dump 'write' \n\t\tdump database sql based on schema file"); + $this->out("\n\tschema create \n\t\tdrop tables and create database based on schema file"); + $this->out("\n\tschema update \n\t\tmodify database based on schema file"); + $this->out(""); + exit(); + } + +} +?> \ No newline at end of file diff --git a/cake/console/libs/tasks/model.php b/cake/console/libs/tasks/model.php index 0c8ac3882..e27eb5853 100644 --- a/cake/console/libs/tasks/model.php +++ b/cake/console/libs/tasks/model.php @@ -112,9 +112,9 @@ class ModelTask extends Shell { $validate = array(); if (array_search($useTable, $this->__tables) !== false && (low($wannaDoValidation) == 'y' || low($wannaDoValidation) == 'yes')) { - foreach ($modelFields as $field) { + foreach ($modelFields as $fieldName => $field) { $this->out(''); - $prompt = 'Name: ' . $field['name'] . "\n"; + $prompt = 'Name: ' . $fieldName . "\n"; $prompt .= 'Type: ' . $field['type'] . "\n"; $prompt .= '---------------------------------------------------------------'."\n"; $prompt .= 'Please select one of the following validation options:'."\n"; @@ -126,7 +126,7 @@ class ModelTask extends Shell { $prompt .= "5- Do not do any validation on this field.\n\n"; $prompt .= "... or enter in a valid regex validation string.\n\n"; - if ($field['null'] == 1 || $field['name'] == $primaryKey || $field['name'] == 'created' || $field['name'] == 'modified') { + if ($field['null'] == 1 || $fieldName == $primaryKey || $fieldName == 'created' || $fieldName == 'modified') { $validation = $this->in($prompt, null, '5'); } else { $validation = $this->in($prompt, null, '1'); @@ -134,21 +134,21 @@ class ModelTask extends Shell { switch ($validation) { case '1': - $validate[$field['name']] = 'VALID_NOT_EMPTY'; + $validate[$fieldName] = 'VALID_NOT_EMPTY'; break; case '2': - $validate[$field['name']] = 'VALID_EMAIL'; + $validate[$fieldName] = 'VALID_EMAIL'; break; case '3': - $validate[$field['name']] = 'VALID_NUMBER'; + $validate[$fieldName] = 'VALID_NUMBER'; break; case '4': - $validate[$field['name']] = 'VALID_YEAR'; + $validate[$fieldName] = 'VALID_YEAR'; break; case '5': break; default: - $validate[$field['name']] = $validation; + $validate[$fieldName] = $validation; break; } } @@ -161,13 +161,13 @@ class ModelTask extends Shell { $possibleKeys = array(); //Look for belongsTo $i = 0; - foreach ($modelFields as $field) { - $offset = strpos($field['name'], '_id'); - if ($field['name'] != $primaryKey && $offset !== false) { - $tmpModelName = $this->_modelNameFromKey($field['name']); + foreach ($modelFields as $fieldName => $field) { + $offset = strpos($fieldName, '_id'); + if ($fieldName != $primaryKey && $offset !== false) { + $tmpModelName = $this->_modelNameFromKey($fieldName); $associations['belongsTo'][$i]['alias'] = $tmpModelName; $associations['belongsTo'][$i]['className'] = $tmpModelName; - $associations['belongsTo'][$i]['foreignKey'] = $field['name']; + $associations['belongsTo'][$i]['foreignKey'] = $fieldName; $i++; } } @@ -179,17 +179,17 @@ class ModelTask extends Shell { $modelFieldsTemp = $db->describe($tempOtherModel); foreach ($modelFieldsTemp as $field) { if ($field['type'] == 'integer' || $field['type'] == 'string') { - $possibleKeys[$otherTable][] = $field['name']; + $possibleKeys[$otherTable][] = $fieldName; } - if ($field['name'] != $primaryKey && $field['name'] == $this->_modelKey($currentModelName)) { + if ($fieldName != $primaryKey && $fieldName == $this->_modelKey($currentModelName)) { $tmpModelName = $this->_modelName($otherTable); $associations['hasOne'][$j]['alias'] = $tmpModelName; $associations['hasOne'][$j]['className'] = $tmpModelName; - $associations['hasOne'][$j]['foreignKey'] = $field['name']; + $associations['hasOne'][$j]['foreignKey'] = $fieldName; $associations['hasMany'][$j]['alias'] = $tmpModelName; $associations['hasMany'][$j]['className'] = $tmpModelName; - $associations['hasMany'][$j]['foreignKey'] = $field['name']; + $associations['hasMany'][$j]['foreignKey'] = $fieldName; $j++; } } diff --git a/cake/libs/error.php b/cake/libs/error.php index 2ff2d0f2d..8f9d9cdd2 100644 --- a/cake/libs/error.php +++ b/cake/libs/error.php @@ -64,14 +64,12 @@ class ErrorHandler extends Object{ require CAKE . 'dispatcher.php'; } $this->__dispatch =& new Dispatcher(); - + if (!class_exists('appcontroller')) { + loadController(null); + } if ($__previousError != array($method, $messages)) { $__previousError = array($method, $messages); - if (!class_exists('AppController')) { - loadController(null); - } - $this->controller =& new AppController(); if (!empty($this->controller->uses)) { $this->controller->constructClasses(); @@ -84,7 +82,7 @@ class ErrorHandler extends Object{ return $this->controller->appError($method, $messages); } } else { - $this->controller =& new Controller(); + $this->controller =& new AppController(); $this->controller->cacheAction = false; } if (Configure::read() > 0 || $method == 'error') { diff --git a/cake/libs/model/datasources/datasource.php b/cake/libs/model/datasources/datasource.php index 73dc75e21..2592b985e 100644 --- a/cake/libs/model/datasources/datasource.php +++ b/cake/libs/model/datasources/datasource.php @@ -192,6 +192,270 @@ class DataSource extends Object { $this->setConfig(func_get_arg(0)); } } + +/** + * Datsrouce Query abstraction + * + * @return resource Result resource identifier + */ + function query() { + $args = func_get_args(); + $fields = null; + $order = null; + $limit = null; + $page = null; + $recursive = null; + + if (count($args) == 1) { + return $this->fetchAll($args[0]); + + } elseif (count($args) > 1 && (strpos(low($args[0]), 'findby') === 0 || strpos(low($args[0]), 'findallby') === 0)) { + $params = $args[1]; + + if (strpos(strtolower($args[0]), 'findby') === 0) { + $all = false; + $field = Inflector::underscore(preg_replace('/findBy/i', '', $args[0])); + } else { + $all = true; + $field = Inflector::underscore(preg_replace('/findAllBy/i', '', $args[0])); + } + + $or = (strpos($field, '_or_') !== false); + if ($or) { + $field = explode('_or_', $field); + } else { + $field = explode('_and_', $field); + } + $off = count($field) - 1; + + if (isset($params[1 + $off])) { + $fields = $params[1 + $off]; + } + + if (isset($params[2 + $off])) { + $order = $params[2 + $off]; + } + + if (!array_key_exists(0, $params)) { + return false; + } + + $c = 0; + $query = array(); + foreach ($field as $f) { + if (!is_array($params[$c]) && !empty($params[$c]) && $params[$c] !== true && $params[$c] !== false) { + $query[$args[2]->name . '.' . $f] = '= ' . $params[$c]; + } else { + $query[$args[2]->name . '.' . $f] = $params[$c]; + } + $c++; + } + + if ($or) { + $query = array('OR' => $query); + } + + if ($all) { + + if (isset($params[3 + $off])) { + $limit = $params[3 + $off]; + } + + if (isset($params[4 + $off])) { + $page = $params[4 + $off]; + } + + if (isset($params[5 + $off])) { + $recursive = $params[5 + $off]; + } + return $args[2]->findAll($query, $fields, $order, $limit, $page, $recursive); + } else { + if (isset($params[3 + $off])) { + $recursive = $params[3 + $off]; + } + return $args[2]->find($query, $fields, $order, $recursive); + } + } else { + if (isset($args[1]) && $args[1] === true) { + return $this->fetchAll($args[0], true); + } + return $this->fetchAll($args[0], false); + } + } +/** + * Returns an array of all result rows for a given SQL query. + * Returns false if no rows matched. + * + * @param string $sql SQL statement + * @param boolean $cache Enables returning/storing cached query results + * @return array Array of resultset rows, or false if no rows matched + */ + function fetchAll($sql, $cache = true, $modelName = null) { + if ($cache && isset($this->_queryCache[$sql])) { + if (preg_match('/^\s*select/i', $sql)) { + return $this->_queryCache[$sql]; + } + } + + if ($this->execute($sql)) { + $out = array(); + + while ($item = $this->fetchRow()) { + $out[] = $item; + } + + if ($cache) { + if (strpos(trim(strtolower($sql)), 'select') !== false) { + $this->_queryCache[$sql] = $out; + } + } + return $out; + + } else { + return false; + } + } +/** + * Caches/returns cached results for child instances + * + * @return array + */ + function listSources($data = null) { + if ($this->cacheSources === false) { + return null; + } + if ($this->_sources != null) { + return $this->_sources; + } + + if (Configure::read() > 0) { + $expires = "+30 seconds"; + } else { + $expires = "+999 days"; + } + + if ($data != null) { + $data = serialize($data); + } + $filename = ConnectionManager::getSourceName($this) . '_' . preg_replace("/[^A-Za-z0-9_-]/", "_", $this->config['database']) . '_list'; + $new = cache('models' . DS . $filename, $data, $expires); + + if ($new != null) { + $new = unserialize($new); + $this->_sources = $new; + } + return $new; + } +/** + * Convenience method for DboSource::listSources(). Returns source names in lowercase. + * + * @return array + */ + function sources() { + $return = array_map('strtolower', $this->listSources()); + return $return; + } +/** + * Returns a Model description (metadata) or null if none found. + * + * @param Model $model + * @return mixed + */ + function describe($model) { + if ($this->cacheSources === false) { + return null; + } + + if (isset($this->__descriptions[$model->tablePrefix.$model->table])) { + return $this->__descriptions[$model->tablePrefix.$model->table]; + } + $cache = $this->__cacheDescription($model->tablePrefix.$model->table); + + if ($cache !== null) { + $this->__descriptions[$model->tablePrefix.$model->table] =& $cache; + return $cache; + } + return null; + } +/** + * Converts column types to basic types + * + * @param string $real Real column type (i.e. "varchar(255)") + * @return string Abstract column type (i.e. "string") + */ + function column($real) { + return false; + } +/** + * To-be-overridden in subclasses. + * + * @param unknown_type $model + * @param unknown_type $fields + * @param unknown_type $values + * @return unknown + */ + function create(&$model, $fields = null, $values = null) { + return false; + } +/** + * To-be-overridden in subclasses. + * + * @param unknown_type $model + * @param unknown_type $queryData + * @return unknown + */ + function read(&$model, $queryData = array()) { + return false; + } +/** + * To-be-overridden in subclasses. + * + * @param unknown_type $model + * @param unknown_type $fields + * @param unknown_type $values + * @return unknown + */ + function update(&$model, $fields = null, $values = null) { + return false; + } +/** + * To-be-overridden in subclasses. + * + * @param unknown_type $model + * @param unknown_type $id + */ + function delete(&$model, $id = null) { + if ($id == null) { + $id = $model->id; + } + } +/** + * Returns the ID generated from the previous INSERT operation. + * + * @param unknown_type $source + * @return in + */ + function lastInsertId($source = null) { + return false; + } +/** + * Returns the ID generated from the previous INSERT operation. + * + * @param unknown_type $source + * @return in + */ + function lastNumRows($source = null) { + return false; + } +/** + * Returns the ID generated from the previous INSERT operation. + * + * @param unknown_type $source + * @return in + */ + function lastAffected($source = null) { + return false; + } /** * Returns true if the DataSource supports the given interface (method) * @@ -249,116 +513,6 @@ class DataSource extends Object { } return $new; } -/** - * To-be-overridden in subclasses. - * - * @return string - */ - function conditions($conditions) { - return $conditions; - } -/** - * To-be-overridden in subclasses. - * - * @param unknown_type $name - * @return unknown - */ - function name($name) { - return $name; - } -/** - * To-be-overridden in subclasses. - * - * @param unknown_type $value - * @return unknown - */ - function value($value) { - return $value; - } -/** - * Returns a Model description (metadata) or null if none found. - * - * @param Model $model - * @return mixed - */ - function describe($model) { - if ($this->cacheSources === false) { - return null; - } - - if (isset($this->__descriptions[$model->tablePrefix.$model->table])) { - return $this->__descriptions[$model->tablePrefix.$model->table]; - } - $cache = $this->__cacheDescription($model->tablePrefix.$model->table); - - if ($cache !== null) { - $this->__descriptions[$model->tablePrefix.$model->table] =& $cache; - return $cache; - } - return null; - } -/** - * To-be-overridden in subclasses. - * - * @param unknown_type $model - * @param unknown_type $fields - * @param unknown_type $values - * @return unknown - */ - function create(&$model, $fields = null, $values = null) { - return false; - } -/** - * To-be-overridden in subclasses. - * - * @param unknown_type $model - * @param unknown_type $queryData - * @return unknown - */ - function read(&$model, $queryData = array()) { - return false; - } -/** - * To-be-overridden in subclasses. - * - * @param unknown_type $model - * @param unknown_type $fields - * @param unknown_type $values - * @return unknown - */ - function update(&$model, $fields = null, $values = null) { - return false; - } -/** - * To-be-overridden in subclasses. - * - * @param unknown_type $model - * @param unknown_type $id - */ - function delete(&$model, $id = null) { - if ($id == null) { - $id = $model->id; - } - } -/** - * To-be-overridden in subclasses. - * - * @param mixed $fields - * @return mixed - */ - function fields($fields) { - return $fields; - } -/** - * To-be-overridden in subclasses. - * - * @param Model $model - * @param unknown_type $fields - * @return unknown - */ - function getColumnType(&$model, $fields) { - return false; - } /** * Enter description here... * diff --git a/cake/libs/model/datasources/dbo/dbo_adodb.php b/cake/libs/model/datasources/dbo/dbo_adodb.php index 13b1a248f..bc0a8a66d 100644 --- a/cake/libs/model/datasources/dbo/dbo_adodb.php +++ b/cake/libs/model/datasources/dbo/dbo_adodb.php @@ -221,8 +221,9 @@ class DboAdodb extends DboSource { $cols = $this->_adodb->MetaColumns($this->fullTableName($model, false)); foreach ($cols as $column) { - $fields[] = array('name' => $column->name, - 'type' => $this->column($column->type)); + $fields[$column->name] = array( + 'type' => $this->column($column->type) + ); } $this->__cacheDescription($this->fullTableName($model, false), $fields); diff --git a/cake/libs/model/datasources/dbo/dbo_db2.php b/cake/libs/model/datasources/dbo/dbo_db2.php index 2a3f2f131..bd85b57ad 100644 --- a/cake/libs/model/datasources/dbo/dbo_db2.php +++ b/cake/libs/model/datasources/dbo/dbo_db2.php @@ -216,8 +216,7 @@ class DboDb2 extends DboSource { $result = db2_columns($this->connection, '', '', strtoupper($this->fullTableName($model))); while (db2_fetch_row($result)) { - $fields[] = array( - 'name' => strtolower(db2_result($result, 'COLUMN_NAME')), + $fields[strtolower(db2_result($result, 'COLUMN_NAME'))] = array( 'type' => db2_result($result, 'TYPE_NAME'), 'null' => db2_result($result, 'NULLABLE'), 'default' => db2_result($result, 'COLUMN_DEF')); diff --git a/cake/libs/model/datasources/dbo/dbo_firebird.php b/cake/libs/model/datasources/dbo/dbo_firebird.php index 56fcfbadc..a0e3ff5d6 100644 --- a/cake/libs/model/datasources/dbo/dbo_firebird.php +++ b/cake/libs/model/datasources/dbo/dbo_firebird.php @@ -205,11 +205,11 @@ class DboFirebird extends DboSource { $col_info = ibase_field_info($rs, $i); $col_info['type'] = $this->column($col_info['type']); - $fields[] = array( - 'name' => strtolower($col_info['name']), - 'type' => $col_info['type'], - 'null' => '', - 'length' => $col_info['length']); + $fields[strtolower($col_info['name'])] = array( + 'type' => $col_info['type'], + 'null' => '', + 'length' => $col_info['length'] + ); } $this->__cacheDescription($model->tablePrefix . $model->table, $fields); return $fields; diff --git a/cake/libs/model/datasources/dbo/dbo_mssql.php b/cake/libs/model/datasources/dbo/dbo_mssql.php index 569a6ceac..c4ebe1e9f 100644 --- a/cake/libs/model/datasources/dbo/dbo_mssql.php +++ b/cake/libs/model/datasources/dbo/dbo_mssql.php @@ -575,16 +575,6 @@ class DboMssql extends DboSource { return false; } } - - function buildSchemaQuery($schema) { - $search = array('{AUTOINCREMENT}', '{PRIMARY}', '{UNSIGNED}', '{FULLTEXT}', '{BOOLEAN}', '{UTF_8}'); - - $replace = array('int(11) not null auto_increment', 'primary key', 'unsigned', 'FULLTEXT', - 'enum (\'true\', \'false\') NOT NULL default \'true\'', '/*!40100 CHARACTER SET utf8 COLLATE utf8_unicode_ci */'); - - $query = trim(r($search, $replace, $schema)); - return $query; - } /** * Inserts multiple values into a join table * @@ -598,5 +588,6 @@ class DboMssql extends DboSource { $this->query("INSERT INTO {$table} ({$fields}) VALUES {$values[$x]}"); } } + } ?> \ No newline at end of file diff --git a/cake/libs/model/datasources/dbo/dbo_mysql.php b/cake/libs/model/datasources/dbo/dbo_mysql.php index b91138cd6..089b16f58 100644 --- a/cake/libs/model/datasources/dbo/dbo_mysql.php +++ b/cake/libs/model/datasources/dbo/dbo_mysql.php @@ -163,12 +163,10 @@ class DboMysql extends DboSource { * @return array Fields in table. Keys are name and type */ function describe(&$model) { - $cache = parent::describe($model); if ($cache != null) { return $cache; } - $fields = false; $cols = $this->query('DESCRIBE ' . $this->fullTableName($model)); @@ -178,16 +176,20 @@ class DboMysql extends DboSource { $column[0] = $column[$colKey[0]]; } if (isset($column[0])) { - $fields[] = array( - 'name' => $column[0]['Field'], + $fields[$column[0]['Field']] = array( 'type' => $this->column($column[0]['Type']), 'null' => ($column[0]['Null'] == 'YES' ? true : false), 'default' => $column[0]['Default'], - 'length' => $this->length($column[0]['Type']) + 'length' => $this->length($column[0]['Type']), ); + if(!empty($column[0]['Key']) && isset($this->index[$column[0]['Key']])) { + $fields[$column[0]['Field']]['key'] = $this->index[$column[0]['Key']]; + } + if(!empty($column[0]['Extra'])) { + $fields[$column[0]['Field']]['extra'] = $column[0]['Extra']; + } } } - $this->__cacheDescription($this->fullTableName($model, false), $fields); return $fields; } @@ -451,6 +453,33 @@ class DboMysql extends DboSource { function getEncoding() { return mysql_client_encoding($this->connection); } +/** + * Returns an array of the indexes in given table name. + * + * @param string $model Name of model to inspect + * @return array Fields in table. Keys are column and unique + */ + function index($model) { + $index = array(); + $table = $this->fullTableName($model, false); + if($table) { + $indexes = $this->query('SHOW INDEX FROM ' . $table); + $keys = Set::extract($indexes, '{n}.STATISTICS'); + foreach ($keys as $i => $key) { + if(!isset($index[$key['Key_name']])) { + $index[$key['Key_name']]['column'] = $key['Column_name']; + $index[$key['Key_name']]['unique'] = ife($key['Non_unique'] == 0, 1, 0); + } else { + if(!is_array($index[$key['Key_name']]['column'])) { + $col[] = $index[$key['Key_name']]['column']; + } + $col[] = $key['Column_name']; + $index[$key['Key_name']]['column'] = $col; + } + } + } + return $index; + } /** * Generate a MySQL schema for the given Schema object * @@ -459,31 +488,110 @@ class DboMysql extends DboSource { * Otherwise, all tables defined in the schema are generated. * @return string */ - function generateSchema($schema, $table = null) { + function createSchema($schema, $table = null) { if (!is_a($schema, 'CakeSchema')) { trigger_error(__('Invalid schema object', true), E_USER_WARNING); return null; } $out = ''; - foreach ($schema->tables as $curTable => $columns) { - if (empty($table) || $table == $curTable) { + if (!$table || $table == $curTable) { $out .= 'CREATE TABLE ' . $this->fullTableName($curTable) . " (\n"; - $colList = array(); + $cols = $colList = $index = array(); $primary = null; - - foreach ($columns as $col) { - if (isset($col['key']) && $col['key'] == 'primary') { - $primary = $col; + foreach ($columns as $name => $col) { + if (is_string($col)) { + $col = array('type' => $col); } - $colList[] = $this->generateColumnSchema($col); + if (isset($col['key']) && $col['key'] == 'primary') { + $primary = $name; + } + if($name !== 'indexes') { + $col['name'] = $name; + if(!isset($col['type'])) { + $col['type'] = 'string'; + } + $cols[] = $this->buildColumn($col); + } else { + $index[] = $this->buildIndex($col); + } + } - if (empty($primary)) { - $primary = array('id', 'integer', 'key' => 'primary'); - array_unshift($colList, $this->generateColumnSchema($primary)); + if (empty($index) && empty($primary)) { + $primary = 'id'; + $col = array('type'=>'integer', 'key' => 'primary'); + array_unshift($cols, $this->buildColumn($col)); } - $colList[] = 'PRIMARY KEY (' . $this->name($primary[0]) . ')'; - $out .= "\t" . join(",\n\t", $colList) . "\n);\n\n"; + if(empty($index) && !empty($primary)) { + $col = array('PRIMARY'=> array('column'=> $primary, 'unique' => 1)); + $index[] = $this->buildIndex($col); + } + $out .= "\t" . join(",\n\t", $cols) . ",\n\t". join(",\n\t", $index) . "\n);\n\n"; + } + } + return $out; + } +/** + * Generate a MySQL Alter Table syntax for the given Schema comparison + * + * @param unknown_type $schema + * @return unknown + */ + function alterSchema($compare) { + if(!is_array($compare)) { + return false; + } + $out = ''; + $colList = array(); + foreach($compare as $table => $types) { + $out .= 'ALTER TABLE ' . $this->fullTableName($table) . " \n"; + foreach($types as $type => $column) { + switch($type) { + case 'add': + foreach($column as $field => $col) { + $col['name'] = $field; + $alter = 'ADD '.$this->buildColumn($col); + if(isset($col['after'])) { + $alter .= ' AFTER '. $this->name($col['after']); + } + $colList[] = $alter; + } + break; + case 'drop': + foreach($column as $field => $col) { + $col['name'] = $field; + $colList[] = 'DROP '.$this->name($field); + } + break; + case 'change': + foreach($column as $field => $col) { + $col['name'] = $field; + $colList[] = 'CHANGE '. $this->name($field).' '.$this->buildColumn($col); + } + break; + } + } + $out .= "\t" . join(",\n\t", $colList) . ";\n\n"; + } + return $out; + } +/** + * Generate a MySQL Drop table for the given Schema object + * + * @param object $schema An instance of a subclass of CakeSchema + * @param string $table Optional. If specified only the table name given will be generated. + * Otherwise, all tables defined in the schema are generated. + * @return string + */ + function dropSchema($schema, $table = null) { + if (!is_a($schema, 'CakeSchema')) { + trigger_error(__('Invalid schema object', true), E_USER_WARNING); + return null; + } + $out = ''; + foreach ($schema->tables as $curTable => $columns) { + if (!$table || $table == $curTable) { + $out .= 'DROP TABLE ' . $this->fullTableName($curTable) . ";\n"; } } return $out; @@ -491,23 +599,25 @@ class DboMysql extends DboSource { /** * Generate a MySQL-native column schema string * - * @param array $column An array structured like the following: array('name', 'type'[, options]), + * @param array $column An array structured like the following: array('name'=>'value', 'type'=>'value'[, options]), * where options can be 'default', 'length', or 'key'. * @return string */ - function generateColumnSchema($column) { + function buildColumn($column) { $name = $type = null; $column = am(array('null' => true), $column); - list($name, $type) = $column; + extract($column); if (empty($name) || empty($type)) { trigger_error('Column name or type not defined in schema', E_USER_WARNING); return null; } + if (!isset($this->columns[$type])) { trigger_error("Column type {$type} does not exist", E_USER_WARNING); return null; } + $real = $this->columns[$type]; $out = $this->name($name) . ' ' . $real['name']; @@ -523,9 +633,10 @@ class DboMysql extends DboSource { } $out .= '(' . $length . ')'; } - - if (isset($column['key']) && $column['key'] == 'primary') { + if (isset($column['key']) && $column['key'] == 'primary' && (!isset($column['extra']) || (isset($column['extra']) && $column['extra'] == 'auto_increment'))) { $out .= ' NOT NULL AUTO_INCREMENT'; + } elseif (isset($column['key']) && $column['key'] == 'primary') { + $out .= ' NOT NULL'; } elseif (isset($column['default'])) { $out .= ' DEFAULT ' . $this->value($column['default'], $type); } elseif (isset($column['null']) && $column['null'] == true) { @@ -538,19 +649,30 @@ class DboMysql extends DboSource { return $out; } /** - * Enter description here... + * Format indexes for create table * - * @param unknown_type $schema - * @return unknown + * @param array $indexes + * @return string */ - function buildSchemaQuery($schema) { - $search = array('{AUTOINCREMENT}', '{PRIMARY}', '{UNSIGNED}', '{FULLTEXT}', - '{FULLTEXT_MYSQL}', '{BOOLEAN}', '{UTF_8}'); - $replace = array('int(11) not null auto_increment', 'primary key', 'unsigned', - 'FULLTEXT', 'FULLTEXT', 'enum (\'true\', \'false\') NOT NULL default \'true\'', - '/*!40100 CHARACTER SET utf8 COLLATE utf8_unicode_ci */'); - $query = trim(r($search, $replace, $schema)); - return $query; + function buildIndex($indexes) { + $join = array(); + foreach ($indexes as $name => $value) { + $out = null; + if ($name == 'PRIMARY') { + $out .= 'PRIMARY KEY (' . $this->name($value['column']) . ')'; + } else { + if (!empty($value['unique'])) { + $out .= 'UNIQUE '; + } + if (is_array($value['column'])) { + $out .= 'KEY '. $name .' (' . join(', ', array_map(array(&$this, 'name'), $value['column'])) . ')'; + } else { + $out .= 'KEY '. $name .' (' . $this->name($value['column']) . ')'; + } + } + $join[] = $out; + } + return join(",\n\t", $join); } } ?> \ No newline at end of file diff --git a/cake/libs/model/datasources/dbo/dbo_mysqli.php b/cake/libs/model/datasources/dbo/dbo_mysqli.php index 2caed6564..18e690bd0 100644 --- a/cake/libs/model/datasources/dbo/dbo_mysqli.php +++ b/cake/libs/model/datasources/dbo/dbo_mysqli.php @@ -167,8 +167,7 @@ class DboMysqli extends DboSource { $column[0] = $column[$colKey[0]]; } if (isset($column[0])) { - $fields[] = array( - 'name' => $column[0]['Field'], + $fields[$column[0]['Field']] = array( 'type' => $this->column($column[0]['Type']), 'null' => ($column[0]['Null'] == 'YES' ? true : false), 'default' => $column[0]['Default'], @@ -438,106 +437,5 @@ class DboMysqli extends DboSource { function getEncoding() { return mysqli_client_encoding($this->connection); } -/** - * Generate a MySQL schema for the given Schema object - * - * @param object $schema An instance of a subclass of CakeSchema - * @param string $table Optional. If specified only the table name given will be generated. - * Otherwise, all tables defined in the schema are generated. - * @return string - */ - function generateSchema($schema, $table = null) { - if (!is_a($schema, 'CakeSchema')) { - trigger_error(__('Invalid schema object', true), E_USER_WARNING); - return null; - } - $out = ''; - - foreach ($schema->tables as $curTable => $columns) { - if (empty($table) || $table == $curTable) { - $out .= 'CREATE TABLE ' . $this->fullTableName($curTable) . " (\n"; - $colList = array(); - $primary = null; - - foreach ($columns as $col) { - if (isset($col['key']) && $col['key'] == 'primary') { - $primary = $col; - } - $colList[] = $this->generateColumnSchema($col); - } - if (empty($primary)) { - $primary = array('id', 'integer', 'key' => 'primary'); - array_unshift($colList, $this->generateColumnSchema($primary)); - } - $colList[] = 'PRIMARY KEY (' . $this->name($primary[0]) . ')'; - $out .= "\t" . join(",\n\t", $colList) . "\n);\n\n"; - } - } - return $out; - } -/** - * Generate a MySQL-native column schema string - * - * @param array $column An array structured like the following: array('name', 'type'[, options]), - * where options can be 'default', 'length', or 'key'. - * @return string - */ - function generateColumnSchema($column) { - $name = $type = null; - $column = am(array('null' => true), $column); - list($name, $type) = $column; - - if (empty($name) || empty($type)) { - trigger_error('Column name or type not defined in schema', E_USER_WARNING); - return null; - } - if (!isset($this->columns[$type])) { - trigger_error("Column type {$type} does not exist", E_USER_WARNING); - return null; - } - $real = $this->columns[$type]; - $out = $this->name($name) . ' ' . $real['name']; - - if (isset($real['limit']) || isset($real['length']) || isset($column['limit']) || isset($column['length'])) { - if (isset($column['length'])) { - $length = $column['length']; - } elseif (isset($column['limit'])) { - $length = $column['limit']; - } elseif (isset($real['length'])) { - $length = $real['length']; - } else { - $length = $real['limit']; - } - $out .= '(' . $length . ')'; - } - - if (isset($column['key']) && $column['key'] == 'primary') { - $out .= ' NOT NULL AUTO_INCREMENT'; - } elseif (isset($column['default'])) { - $out .= ' DEFAULT ' . $this->value($column['default'], $type); - } elseif (isset($column['null']) && $column['null'] == true) { - $out .= ' DEFAULT NULL'; - } elseif (isset($column['default']) && isset($column['null']) && $column['null'] == false) { - $out .= ' DEFAULT ' . $this->value($column['default'], $type) . ' NOT NULL'; - } elseif (isset($column['null']) && $column['null'] == false) { - $out .= ' NOT NULL'; - } - return $out; - } -/** - * Enter description here... - * - * @param unknown_type $schema - * @return unknown - */ - function buildSchemaQuery($schema) { - $search = array('{AUTOINCREMENT}', '{PRIMARY}', '{UNSIGNED}', '{FULLTEXT}', - '{FULLTEXT_MYSQL}', '{BOOLEAN}', '{UTF_8}'); - $replace = array('int(11) not null auto_increment', 'primary key', 'unsigned', - 'FULLTEXT', 'FULLTEXT', 'enum (\'true\', \'false\') NOT NULL default \'true\'', - '/*!40100 CHARACTER SET utf8 COLLATE utf8_unicode_ci */'); - $query = trim(r($search, $replace, $schema)); - return $query; - } } ?> \ No newline at end of file diff --git a/cake/libs/model/datasources/dbo/dbo_odbc.php b/cake/libs/model/datasources/dbo/dbo_odbc.php index 9c108a615..40a737035 100644 --- a/cake/libs/model/datasources/dbo/dbo_odbc.php +++ b/cake/libs/model/datasources/dbo/dbo_odbc.php @@ -174,23 +174,19 @@ class DboOdbc extends DboSource{ return $cache; } - $fields=array(); - $sql='SELECT * FROM ' . $this->fullTableName($model) . ' LIMIT 1'; - $result=odbc_exec($this->connection, $sql); + $fields = array(); + $sql = 'SELECT * FROM ' . $this->fullTableName($model) . ' LIMIT 1'; + $result = odbc_exec($this->connection, $sql); - $count=odbc_num_fields($result); + $count = odbc_num_fields($result); for ($i = 1; $i <= $count; $i++) { $cols[$i - 1] = odbc_field_name($result, $i); } foreach ($cols as $column) { - $type - = odbc_field_type( - odbc_exec($this->connection, "SELECT " . $column . " FROM " . $this->fullTableName($model)), - 1); - array_push($fields, array('name' => $column, - 'type' => $type)); + $type = odbc_field_type(odbc_exec($this->connection, "SELECT " . $column . " FROM " . $this->fullTableName($model)), 1); + $fields[$column] array('type' => $type)); } $this->__cacheDescription($model->tablePrefix . $model->table, $fields); @@ -202,7 +198,7 @@ class DboOdbc extends DboSource{ return '*'; } - $pos=strpos($data, '`'); + $pos = strpos($data, '`'); if ($pos === false) { $data = '' . str_replace('.', '.', $data) . ''; @@ -441,26 +437,5 @@ class DboOdbc extends DboSource{ return false; } } - - function buildSchemaQuery($schema) { - $search=array('{AUTOINCREMENT}', - '{PRIMARY}', - '{UNSIGNED}', - '{FULLTEXT}', - '{FULLTEXT_MYSQL}', - '{BOOLEAN}', - '{UTF_8}'); - - $replace=array('int(11) not null auto_increment', - 'primary key', - 'unsigned', - 'FULLTEXT', - 'FULLTEXT', - 'enum (\'true\', \'false\') NOT NULL default \'true\'', - '/*!40100 CHARACTER SET utf8 COLLATE utf8_unicode_ci */'); - - $query=trim(str_replace($search, $replace, $schema)); - return $query; - } } ?> \ No newline at end of file diff --git a/cake/libs/model/datasources/dbo/dbo_oracle.php b/cake/libs/model/datasources/dbo/dbo_oracle.php index 119eab106..0e086cb75 100644 --- a/cake/libs/model/datasources/dbo/dbo_oracle.php +++ b/cake/libs/model/datasources/dbo/dbo_oracle.php @@ -424,9 +424,8 @@ class DboOracle extends DboSource { $fields = array(); for($i=0; $row = $this->fetchRow(); $i++) { - $fields[$i]['name'] = strtolower($row[0]['COLUMN_NAME']); - $fields[$i]['length'] = $row[0]['DATA_LENGTH']; - $fields[$i]['type'] = $this->column($row[0]['DATA_TYPE']); + $fields[strtolower($row[0]['COLUMN_NAME'])] = array('type'=> $this->column($row[0]['DATA_TYPE']), + 'length'=> $row[0]['DATA_LENGTH']); } $this->__cacheDescription($this->fullTableName($model, false), $fields); return $fields; @@ -617,56 +616,6 @@ class DboOracle extends DboSource { function lastAffected() { return $this->_statementId ? ocirowcount($this->_statementId): false; } - -/** - * Generate a Oracle-native column schema string - * - * @param array $column An array structured like the following: array('name', 'type'[, options]), - * where options can be 'default', 'length', or 'key'. - * @return string - */ - function generateColumnSchema($column) { - $name = $type = null; - $column = am(array('null' => true), $column); - list($name, $type) = $column; - - if (empty($name) || empty($type)) { - trigger_error('Column name or type not defined in schema', E_USER_WARNING); - return null; - } - if (!isset($this->columns[$type])) { - trigger_error("Column type {$type} does not exist", E_USER_WARNING); - return null; - } - $real = $this->columns[$type]; - $out = $this->name($name) . ' ' . $real['name']; - - if (isset($real['limit']) || isset($real['length']) || isset($column['limit']) || isset($column['length'])) { - if (isset($column['length'])) { - $length = $column['length']; - } elseif (isset($column['limit'])) { - $length = $column['limit']; - } elseif (isset($real['length'])) { - $length = $real['length']; - } else { - $length = $real['limit']; - } - $out .= '(' . $length . ')'; - } - - if (isset($column['key']) && $column['key'] == 'primary') { - $out .= ' NOT NULL '; - } elseif (isset($column['default'])) { - $out .= ' DEFAULT ' . $this->value($column['default'], $type); - } elseif (isset($column['null']) && $column['null'] == true) { - $out .= ' DEFAULT NULL'; - } elseif (isset($column['default']) && isset($column['null']) && $column['null'] == false) { - $out .= ' DEFAULT ' . $this->value($column['default'], $type) . ' NOT NULL'; - } elseif (isset($column['null']) && $column['null'] == false) { - $out .= ' NOT NULL'; - } - return $out; - } /** * Inserts multiple values into a join table * diff --git a/cake/libs/model/datasources/dbo/dbo_postgres.php b/cake/libs/model/datasources/dbo/dbo_postgres.php index 09e890079..bc50b6a34 100644 --- a/cake/libs/model/datasources/dbo/dbo_postgres.php +++ b/cake/libs/model/datasources/dbo/dbo_postgres.php @@ -190,8 +190,7 @@ class DboPostgres extends DboSource { } else { $length = $this->length($c['type']); } - $fields[] = array( - 'name' => $c['name'], + $fields[$c['name']] = array( 'type' => $this->column($c['type']), 'null' => ($c['null'] == 'NO' ? false : true), 'default' => $c['default'], @@ -579,59 +578,6 @@ class DboPostgres extends DboSource { function getEncoding() { return pg_client_encoding($this->connection); } -/** - * Generate a PostgreSQL-native column schema string - * - * @param array $column An array structured like the following: array('name', 'type'[, options]), - * where options can be 'default', 'length', or 'key'. - * @return string - */ - function generateColumnSchema($column) { - $name = $type = $out = null; - $column = am(array('null' => true), $column); - list($name, $type) = $column; - - if (empty($name) || empty($type)) { - trigger_error('Column name or type not defined in schema', E_USER_WARNING); - return null; - } - if (!isset($this->columns[$type])) { - trigger_error("Column type {$type} does not exist", E_USER_WARNING); - return null; - } - $out = "\t" . $this->name($name) . ' '; - - if (!isset($column['key']) || $column['key'] != 'primary') { - $real = $this->columns[$type]; - $out .= $real['name']; - - if (isset($real['limit']) || isset($real['length']) || isset($column['limit']) || isset($column['length'])) { - if (isset($column['length'])) { - $length = $column['length']; - } elseif (isset($column['limit'])) { - $length = $column['limit']; - } elseif (isset($real['length'])) { - $length = $real['length']; - } else { - $length = $real['limit']; - } - $out .= '(' . $length . ')'; - } - } - - if (isset($column['key']) && $column['key'] == 'primary') { - $out .= $this->columns['primary_key']['name']; - } elseif (isset($column['default'])) { - $out .= ' DEFAULT ' . $this->value($column['default'], $type); - } elseif (isset($column['null']) && $column['null'] == true) { - $out .= ' DEFAULT NULL'; - } elseif (isset($column['default']) && isset($column['null']) && $column['null'] == false) { - $out .= ' DEFAULT ' . $this->value($column['default'], $type) . ' NOT NULL'; - } elseif (isset($column['null']) && $column['null'] == false) { - $out .= ' NOT NULL'; - } - return $out; - } } ?> \ No newline at end of file diff --git a/cake/libs/model/datasources/dbo/dbo_sqlite.php b/cake/libs/model/datasources/dbo/dbo_sqlite.php index ea74a6f81..2099e2250 100644 --- a/cake/libs/model/datasources/dbo/dbo_sqlite.php +++ b/cake/libs/model/datasources/dbo/dbo_sqlite.php @@ -160,8 +160,7 @@ class DboSqlite extends DboSource { $result = $this->fetchAll('PRAGMA table_info(' . $model->tablePrefix . $model->table . ')'); foreach ($result as $column) { - $fields[] = array( - 'name' => $column[0]['name'], + $fields[$column[0]['name']] = array( 'type' => $this->column($column[0]['type']), 'null' => ! $column[0]['notnull'], 'default' => $column[0]['dflt_value'] @@ -405,6 +404,6 @@ class DboSqlite extends DboSource { for ($x = 0; $x < $count; $x++) { $this->query("INSERT INTO {$table} ({$fields}) VALUES {$values[$x]}"); } - } + } } ?> \ No newline at end of file diff --git a/cake/libs/model/datasources/dbo/dbo_sybase.php b/cake/libs/model/datasources/dbo/dbo_sybase.php index 82d47444e..ad2eee63a 100644 --- a/cake/libs/model/datasources/dbo/dbo_sybase.php +++ b/cake/libs/model/datasources/dbo/dbo_sybase.php @@ -170,7 +170,9 @@ class DboSybase extends DboSource { $column[0] = $column[$colKey[0]]; } if (isset($column[0])) { - $fields[] = array('name' => $column[0]['Field'], 'type' => $this->column($column[0]['Type']), 'null' => $column[0]['Null']); + $fields[$column[0]['Field']] = array('type' => $this->column($column[0]['Type']), + 'null' => $column[0]['Null'] + ); } } @@ -377,21 +379,6 @@ class DboSybase extends DboSource { return false; } } -/** - * Enter description here... - * - * @param unknown_type $schema - * @return unknown - */ - function buildSchemaQuery($schema) { - $search = array('{AUTOINCREMENT}', '{PRIMARY}', '{UNSIGNED}', '{FULLTEXT}', - '{FULLTEXT_SYBASE}', '{BOOLEAN}', '{UTF_8}'); - $replace = array('int(11) not null auto_increment', 'primary key', 'unsigned', - 'FULLTEXT', 'FULLTEXT', 'enum (\'true\', \'false\') NOT NULL default \'true\'', - '/*!40100 CHARACTER SET utf8 COLLATE utf8_unicode_ci */'); - $query = trim(r($search, $replace, $schema)); - return $query; - } /** * Inserts multiple values into a join table * @@ -404,6 +391,6 @@ class DboSybase extends DboSource { for ($x = 0; $x < $count; $x++) { $this->query("INSERT INTO {$table} ({$fields}) VALUES {$values[$x]}"); } - } + } } ?> \ No newline at end of file diff --git a/cake/libs/model/datasources/dbo_source.php b/cake/libs/model/datasources/dbo_source.php index ce47fe680..e408ecdb9 100644 --- a/cake/libs/model/datasources/dbo_source.php +++ b/cake/libs/model/datasources/dbo_source.php @@ -43,11 +43,11 @@ class DboSource extends DataSource { */ var $description = "Database Data Source"; /** - * Enter description here... + * index definition, standard cake, primary, index, unique * - * @var unknown_type + * @var array */ - var $__bypass = false; + var $index = array('PRI'=> 'primary', 'MUL'=> 'index', 'UNI'=>'unique'); /** * Enter description here... * @@ -66,18 +66,24 @@ class DboSource extends DataSource { * @var unknown_type */ var $alias = 'AS '; -/** - * The set of valid SQL operations usable in a WHERE statement - * - * @var array - */ - var $__sqlOps = array('like', 'ilike', 'or', 'not', 'in', 'between', 'regexp', 'similar to'); /** * Enter description here... * * @var unknown_type */ var $goofyLimit = false; +/** + * Enter description here... + * + * @var unknown_type + */ + var $__bypass = false; +/** + * The set of valid SQL operations usable in a WHERE statement + * + * @var array + */ + var $__sqlOps = array('like', 'ilike', 'or', 'not', 'in', 'between', 'regexp', 'similar to'); /** * Constructor */ @@ -126,135 +132,6 @@ class DboSource extends DataSource { return null; } } -/** - * Caches/returns cached results for child instances - * - * @return array - */ - function listSources($data = null) { - if ($this->cacheSources === false) { - return null; - } - if ($this->_sources != null) { - return $this->_sources; - } - - if (Configure::read() > 0) { - $expires = "+30 seconds"; - } else { - $expires = "+999 days"; - } - - if ($data != null) { - $data = serialize($data); - } - $filename = ConnectionManager::getSourceName($this) . '_' . preg_replace("/[^A-Za-z0-9_-]/", "_", $this->config['database']) . '_list'; - $new = cache('models' . DS . $filename, $data, $expires); - - if ($new != null) { - $new = unserialize($new); - $this->_sources = $new; - } - return $new; - } -/** - * Convenience method for DboSource::listSources(). Returns source names in lowercase. - * - * @return array - */ - function sources() { - $return = array_map('strtolower', $this->listSources()); - return $return; - } -/** - * SQL Query abstraction - * - * @return resource Result resource identifier - */ - function query() { - $args = func_get_args(); - $fields = null; - $order = null; - $limit = null; - $page = null; - $recursive = null; - - if (count($args) == 1) { - return $this->fetchAll($args[0]); - - } elseif (count($args) > 1 && (strpos(low($args[0]), 'findby') === 0 || strpos(low($args[0]), 'findallby') === 0)) { - $params = $args[1]; - - if (strpos(strtolower($args[0]), 'findby') === 0) { - $all = false; - $field = Inflector::underscore(preg_replace('/findBy/i', '', $args[0])); - } else { - $all = true; - $field = Inflector::underscore(preg_replace('/findAllBy/i', '', $args[0])); - } - - $or = (strpos($field, '_or_') !== false); - if ($or) { - $field = explode('_or_', $field); - } else { - $field = explode('_and_', $field); - } - $off = count($field) - 1; - - if (isset($params[1 + $off])) { - $fields = $params[1 + $off]; - } - - if (isset($params[2 + $off])) { - $order = $params[2 + $off]; - } - - if (!array_key_exists(0, $params)) { - return false; - } - - $c = 0; - $query = array(); - foreach ($field as $f) { - if (!is_array($params[$c]) && !empty($params[$c]) && $params[$c] !== true && $params[$c] !== false) { - $query[$args[2]->name . '.' . $f] = '= ' . $params[$c]; - } else { - $query[$args[2]->name . '.' . $f] = $params[$c]; - } - $c++; - } - - if ($or) { - $query = array('OR' => $query); - } - - if ($all) { - - if (isset($params[3 + $off])) { - $limit = $params[3 + $off]; - } - - if (isset($params[4 + $off])) { - $page = $params[4 + $off]; - } - - if (isset($params[5 + $off])) { - $recursive = $params[5 + $off]; - } - return $args[2]->findAll($query, $fields, $order, $limit, $page, $recursive); - } else { - if (isset($params[3 + $off])) { - $recursive = $params[3 + $off]; - } - return $args[2]->find($query, $fields, $order, $recursive); - } - } else { - if (isset($args[1]) && $args[1] === true) { - return $this->fetchAll($args[0], true); - } - return $this->fetchAll($args[0], false); - } - } /** * Executes given SQL statement. * @@ -1829,5 +1706,64 @@ class DboSource extends DataSource { $values = implode(', ', $values); $this->query("INSERT INTO {$table} ({$fields}) VALUES {$values}"); } +/** + * Returns an array of the indexes in given datasource name. + * + * @param string $model Name of model to inspect + * @return array Fields in table. Keys are column and unique + */ + function index($model) { + return false; + } +/** + * Generate a create syntax from CakeSchema + * + * @param object $schema An instance of a subclass of CakeSchema + * @param string $table Optional. If specified only the table name given will be generated. + * Otherwise, all tables defined in the schema are generated. + * @return string + */ + function createSchema($schema, $table = null) { + return false; + } +/** + * Generate a alter syntax from CakeSchema::compare() + * + * @param unknown_type $schema + * @return unknown + */ + function alterSchema($compare) { + return false; + } +/** + * Generate a drop syntax from CakeSchema + * + * @param object $schema An instance of a subclass of CakeSchema + * @param string $table Optional. If specified only the table name given will be generated. + * Otherwise, all tables defined in the schema are generated. + * @return string + */ + function dropSchema($schema, $table = null) { + return false; + } +/** + * Generate a column schema string + * + * @param array $column An array structured like the following: array('name'=>'value', 'type'=>'value'[, options]), + * where options can be 'default', 'length', or 'key'. + * @return string + */ + function buildColumn($column) { + return false; + } +/** + * Format indexes for create table + * + * @param array $indexes + * @return string + */ + function buildIndex($indexes) { + return false; + } } ?> \ No newline at end of file diff --git a/cake/libs/model/model.php b/cake/libs/model/model.php index f431a9137..a5ee109ff 100644 --- a/cake/libs/model/model.php +++ b/cake/libs/model/model.php @@ -106,7 +106,12 @@ class Model extends Overloadable { * Table metadata * * @var array - * @access private + * @access protected + */ + var $_schema = null; +/** + * + * @deprecated see $_schema */ var $_tableInfo = null; /** @@ -706,12 +711,14 @@ class Model extends Overloadable { $this->{$type}[$assocKey]['joinTable'] = $this->{$with}->table; } elseif ($type == 'hasAndBelongsToMany') { $joinClass = Inflector::camelize($this->name . $assocKey); - $this->{$type}[$assocKey]['_with'] = $joinClass; - $this->{$joinClass} = new AppModel(array( - 'name' => $joinClass, - 'table' => $this->{$type}[$assocKey]['joinTable'], - 'ds' => $this->useDbConfig - )); + if(!class_exists(low($joinClass))) { + $this->{$type}[$assocKey]['_with'] = $joinClass; + $this->{$joinClass} = new AppModel(array( + 'name' => $joinClass, + 'table' => $this->{$type}[$assocKey]['joinTable'], + 'ds' => $this->useDbConfig + )); + } } } } @@ -724,7 +731,6 @@ class Model extends Overloadable { $this->setDataSource($this->useDbConfig); $db =& ConnectionManager::getDataSource($this->useDbConfig); $db->cacheSources = $this->cacheSources; - if ($db->isInterfaceSupported('listSources')) { $sources = $db->listSources(); if (is_array($sources) && !in_array(low($this->tablePrefix . $tableName), array_map('low', $sources))) { @@ -733,13 +739,13 @@ class Model extends Overloadable { 'table' => $this->tablePrefix . $tableName ))); } else { - $this->table = $tableName; + $this->table = $this->useTable = $tableName; $this->tableToModel[$this->table] = $this->name; $this->_tableInfo = null; $this->loadInfo(); } } else { - $this->table = $tableName; + $this->table = $this->useTable = $tableName; $this->tableToModel[$this->table] = $this->name; $this->loadInfo(); } @@ -792,20 +798,34 @@ class Model extends Overloadable { } /** * Returns an array of table metadata (column names and types) from the database. + * $field => keys(type, null, default, key, length, extra) * * @return array Array of table metadata */ - function loadInfo($clear = false) { - if (!is_object($this->_tableInfo) || $clear) { + function schema($clear = false) { + if (!is_object($this->_schema) || $clear) { $db =& ConnectionManager::getDataSource($this->useDbConfig); $db->cacheSources = $this->cacheSources; - if ($db->isInterfaceSupported('describe') && $this->useTable !== false) { - $this->_tableInfo = new Set($db->describe($this, $clear)); + $this->_schema = new Set($db->describe($this, $clear)); } elseif ($this->useTable === false) { - $this->_tableInfo = new Set(); + $this->_schema = new Set(); } } + return $this->_schema; + } +/** + * See Model::schema + * + * @deprecated + */ + function loadInfo($clear = false) { + $info = $this->schema($clear); + foreach($info->value as $field => $value) { + $fields[] = am(array('name'=> $field), $value); + } + unset($info); + $this->_tableInfo = new Set($fields); return $this->_tableInfo; } /** diff --git a/cake/libs/model/schema.php b/cake/libs/model/schema.php new file mode 100644 index 000000000..1cd06f6f4 --- /dev/null +++ b/cake/libs/model/schema.php @@ -0,0 +1,390 @@ + + * Copyright 2005-2007, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2005-2007, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project + * @package cake + * @subpackage cake.cake.libs + * @since CakePHP(tm) v 1.2.0.5550 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +if (!class_exists('connectionmanager')) { + uses('model' . DS . 'connection_manager'); +} +/** + * Base Class for Schema management + * + * @package cake.libs + * @subpackage cake.libs + */ +class CakeSchema extends Object { +/** + * name of the App Schema + * + * @var string + * @access public + */ + var $name = null; +/** + * path to write location + * + * @var string + * @access public + */ + var $path = TMP; +/** + * connection used for read + * + * @var string + * @access public + */ + var $connection = 'default'; +/** + * array of tables + * + * @var array + * @access public + */ + var $tables = array(); +/** + * Constructor + * + * @param array $data optional load object properties + * @access private + */ + function __construct($data = array()) { + $this->path = CONFIGS . 'sql'; + $data = am(get_object_vars($this), $data); + + $this->_build($data); + + if (empty($this->name)) { + $this->name = preg_replace('/schema$/i', '', get_class($this)); + } + parent::__construct(); + } +/** + * Builds schema object properties + * + * @param array $data loaded object properties + * @access protected + */ + function _build($data) { + foreach ($data as $key => $val) { + if (!in_array($key, array('name', 'path', 'connection', 'tables', '_log'))) { + $this->tables[$key] = $val; + unset($this->{$key}); + } elseif ($key != 'tables' && !empty($val)) { + $this->{$key} = $val; + } + } + } +/** + * Reads database and creates schema tables + * + * @param array $options schema object properties + * @access public + * @return array $name, $tables + */ + function load($options = array()) { + if (is_string($options)) { + $options = array('path'=> $options); + } + if (!isset($options['name'])) { + $options['name'] = Inflector::camelize(Configure::read('App.dir')); + } + $options = am( + get_object_vars($this), $options + ); + extract($options); + if (file_exists($path . DS . 'schema.php')) { + require_once($path . DS . 'schema.php'); + $class = $name .'Schema'; + if(class_exists($class)) { + $Schema =& new $class(); + $this->_build($options); + return $Schema; + } + } + return false; + } +/** + * Reads database and creates schema tables + * + * @param array $options schema object properties + * @access public + * @return array $name, $tables + */ + function read($options = array()) { + extract(am( + array( + 'connection' => $this->connection, + 'name' => Inflector::camelize(Configure::read('App.dir')), + ), + $options + )); + $db =& ConnectionManager::getDataSource($connection); + + if (empty($models)) { + $models = Configure::listObjects('model'); + } + $tables = array(); + foreach ($models as $model) { + if (!class_exists($model)) { + loadModel($model); + } + $Object =& new $model(); + $Object->setDataSource($connection); + if (is_object($Object)) { + if(empty($tables[$Object->table])) { + $tables[$Object->table] = $this->__columns($Object); + $tables[$Object->table]['indexes'] = $db->index($Object); + if(!empty($Object->hasAndBelongsToMany)) { + foreach($Object->hasAndBelongsToMany as $Assoc => $assocData) { + $tables[$Object->$Assoc->table] = $this->__columns($Object->$Assoc); + $tables[$Object->$Assoc->table]['indexes'] = $db->index($Object->$Assoc); + } + } + } + } else { + trigger_error('Schema generation error: model class ' . $class . ' not found', E_USER_WARNING); + } + + } + return compact('name', 'tables'); + } +/** + * Writes schema file from object or options + * + * @param mixed $object schema object or options array + * @param array $options schema object properties to override object + * @access public + * @return mixed false or string written to file + */ + function write($object, $options = array()) { + if (is_object($object)) { + $object = get_object_vars($object); + $this->_build($object); + } + + if (is_array($object)) { + $options = $object; + unset($object); + } + + extract(am( + get_object_vars($this), $options + )); + + $out = "\n\nclass {$name}Schema extends CakeSchema {\n\n"; + + $out .= "\tvar \$name = '{$name}';\n\n"; + + if ($path !== $this->path) { + $out .= "\tvar \$path = '{$path}';\n\n"; + } + + if ($connection !== 'default') { + $out .= "\tvar \$connection = '{$connection}';\n\n"; + } + + if(empty($tables)) { + $this->read(); + } + + foreach ($tables as $table => $fields) { + if(!is_numeric($table)) { + $out .= "\tvar \${$table} = array(\n"; + if (is_array($fields)) { + $cols = array(); + foreach ($fields as $field => $value) { + if($field != 'indexes') { + if (is_string($value)) { + $type = $value; + $value = array('type'=> $type); + } + $col = "\t\t\t'{$field}' => array('type'=>'" . $value['type'] . "', "; + unset($value['type']); + $col .= join(', ', $this->__values($value)); + } else { + $col = "\t\t\t'indexes' => array("; + $props = array(); + foreach ($value as $key => $index) { + $props[] = "'{$key}' => array(".join(', ', $this->__values($index)).")"; + } + $col .= join(', ', $props); + } + $col .= ")"; + $cols[] = $col; + } + $out .= join(",\n", $cols); + } + $out .= "\n\t\t);\n"; + $out .="\n"; + } + } + $out .= "\n\tfunction setup(\$version) {\n\t}\n\n\tfunction teardown(\$version) {\n"; + $out .= "\t}\n}\n\n"; + + $File =& new File($path . DS . 'schema.php', true); + $content = "*/\n{$out}?>"; + if ($File->write($content)) { + return $content; + } + return false; + } +/** + * Writes schema file from object or options + * + * @param mixed $old schema object or array + * @param mixed $new schema object or array + * @access public + * @return array $add, $drop, $change + */ + function compare($old, $new = null) { + if (empty($new)) { + $new = $this; + } + if (is_array($new)) { + if (isset($new['tables'])) { + $new = $new['tables']; + } + } else { + $new = $new->tables; + } + + if (is_array($old)) { + if (isset($old['tables'])) { + $old = $old['tables']; + } + } else { + $old = $old->tables; + } + $tables = array(); + foreach ($new as $table => $fields) { + if (!array_key_exists($table, $old)) { + $tables[$table]['add'] = $fields; + } else { + $diff = array_diff_assoc($fields, $old[$table]); + if (!empty($diff)) { + $tables[$table]['add'] = $diff; + } + $diff = array_diff_assoc($old[$table], $fields); + if (!empty($diff)) { + $tables[$table]['drop'] = $diff; + } + } + foreach ($fields as $field => $value) { + if (isset($old[$table][$field])) { + $diff = array_diff($value, $old[$table][$field]); + if (!empty($diff)) { + $tables[$table]['change'][$field] = am($old[$table][$field], $diff); + } + } + + if (isset($add[$table][$field])) { + $wrapper = array_keys($fields); + if ($column = array_search($field, $wrapper)) { + if (isset($wrapper[$column - 1])) { + $tables[$table]['add'][$field]['after'] = $wrapper[$column - 1]; + } + } + } + } + } + return $tables; + } +/** + * Formats Schema columns from Model Object + * + * @param array $value options keys(type, null, default, key, length, extra) + * @access public + * @return array $name, $tables + */ + function __values($values) { + $vals = array(); + if(is_array($values)) { + foreach ($values as $key => $val) { + if(is_array($val)) { + $vals[] = "'{$key}' => array('".join("', '", $val)."')"; + } else if (!is_numeric($key)) { + $prop = "'{$key}' => "; + if (is_bool($val)) { + $prop .= $val ? 'true' : 'false'; + } elseif (is_numeric($val)) { + $prop .= $val; + } elseif ($val === null) { + $prop .= 'null'; + } else { + $prop .= "'{$val}'"; + } + $vals[] = $prop; + } + } + } + return $vals; + } +/** + * Formats Schema columns from Model Object + * + * @param array $Obj model object + * @access public + * @return array $name, $tables + */ + function __columns(&$Obj) { + $db =& ConnectionManager::getDataSource($Obj->useDbConfig); + $fields = $Obj->schema(true); + $columns = $props = array(); + foreach ($fields->value as $name => $value) { + + if ($Obj->primaryKey == $name) { + $value['key'] = 'primary'; + } + if (!isset($db->columns[$value['type']])) { + trigger_error('Schema generation error: invalid column type ' . $value['type'] . ' does not exist in DBO', E_USER_WARNING); + continue; + } else { + $defaultCol = $db->columns[$value['type']]; + if (isset($defaultCol['limit']) && $defaultCol['limit'] == $value['length']) { + unset($value['length']); + } elseif (isset($defaultCol['length']) && $defaultCol['length'] == $value['length']) { + unset($value['length']); + } + unset($value['limit']); + } + + if (empty($value['default']) && $value['null'] == true) { + unset($value['default']); + } + if (empty($value['length'])) { + unset($value['length']); + } + if (empty($value['key'])) { + unset($value['key']); + } + if (empty($value['extra'])) { + unset($value['extra']); + } + $columns[$name] = $value; + } + + return $columns; + } +} +?> \ No newline at end of file diff --git a/cake/tests/cases/libs/controller/components/acl.test.php b/cake/tests/cases/libs/controller/components/acl.test.php index 28e1200f3..8475eb04b 100644 --- a/cake/tests/cases/libs/controller/components/acl.test.php +++ b/cake/tests/cases/libs/controller/components/acl.test.php @@ -39,6 +39,7 @@ uses('controller'.DS.'components'.DS.'acl', 'model'.DS.'db_acl'); if(!class_exists('aclnodetestbase')) { class AclNodeTestBase extends AclNode { var $useDbConfig = 'test_suite'; + var $cacheSources = false; } } diff --git a/cake/tests/cases/libs/controller/components/auth.test.php b/cake/tests/cases/libs/controller/components/auth.test.php index 851e49a5d..6d1ece7c9 100644 --- a/cake/tests/cases/libs/controller/components/auth.test.php +++ b/cake/tests/cases/libs/controller/components/auth.test.php @@ -39,6 +39,7 @@ uses('controller'.DS.'components'.DS.'acl', 'model'.DS.'db_acl'); if(!class_exists('aclnodetestbase')) { class AclNodeTestBase extends AclNode { var $useDbConfig = 'test_suite'; + var $cacheSources = false; } } @@ -329,41 +330,49 @@ class AuthTest extends CakeTestCase { $this->Controller->Session->del('Auth'); - //$this->Controller->Acl->Aro->execute('truncate aros;'); - //$this->Controller->Acl->Aro->execute('truncate acos;'); - //$this->Controller->Acl->Aro->execute('truncate aros_acos;'); } function testLoginRedirect() { $backup = $_SERVER['HTTP_REFERER']; $_SERVER['HTTP_REFERER'] = false; - $this->Controller->data = array(); - $this->Controller->Auth->loginRedirect = array('controller' => 'pages', 'action' => 'display', 'welcome'); - + + $this->Controller->Session->write('Auth', array('AuthUser' => array('id'=>'1', 'username'=>'nate'))); + $this->Controller->params['url']['url'] = 'users/login'; $this->Controller->Auth->initialize($this->Controller); + + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->loginRedirect = array('controller' => 'pages', 'action' => 'display', 'welcome'); $this->Controller->Auth->startup($this->Controller); $expected = $this->Controller->Auth->_normalizeURL($this->Controller->Auth->loginRedirect); $this->assertEqual($expected, $this->Controller->Auth->redirect()); - $this->Controller->Session->del('Auth'); + $this->Controller->Session->del('Auth'); + $this->Controller->params['url']['url'] = 'admin/'; $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->loginRedirect = null; $this->Controller->Auth->startup($this->Controller); $expected = $this->Controller->Auth->_normalizeURL('admin/'); $this->assertEqual($expected, $this->Controller->Auth->redirect()); - $this->Controller->Session->del('Auth'); - + + $this->Controller->Session->del('Auth'); + $_SERVER['HTTP_REFERER'] = '/admin/'; + $this->Controller->Session->write('Auth', array('AuthUser' => array('id'=>'1', 'username'=>'nate'))); + $this->Controller->params['url']['url'] = 'users/login'; $this->Controller->Auth->initialize($this->Controller); + $this->Controller->Auth->userModel = 'AuthUser'; + $this->Controller->Auth->loginRedirect = null; $this->Controller->Auth->startup($this->Controller); - $expected = '/admin/'; + $expected = $this->Controller->Auth->_normalizeURL('admin'); $this->assertEqual($expected, $this->Controller->Auth->redirect()); - $this->Controller->Session->del('Auth'); $_SERVER['HTTP_REFERER'] = $backup; + $this->Controller->Session->del('Auth'); } function testEmptyUsernameOrPassword() { diff --git a/cake/tests/cases/libs/model/behaviors/tree.test.php b/cake/tests/cases/libs/model/behaviors/tree.test.php index b7d2e98ad..f84c05ca4 100644 --- a/cake/tests/cases/libs/model/behaviors/tree.test.php +++ b/cake/tests/cases/libs/model/behaviors/tree.test.php @@ -69,7 +69,7 @@ class NumberTree extends CakeTestModel { } class NumberTreeCase extends CakeTestCase { - var $fixtures = array('number_tree'); + var $fixtures = array('core.number_tree'); function testInitialize() { $this->NumberTree = & new NumberTree(); @@ -721,5 +721,9 @@ class NumberTreeCase extends CakeTestCase { array('NumberTree' => array('name' => '1.2.2'))); $this->assertIdentical($result, $expects); } + + function tearDown() { + unset($this->NumberTree); + } } ?> diff --git a/cake/tests/cases/libs/model/db_acl.test.php b/cake/tests/cases/libs/model/db_acl.test.php index 17b79950d..9bc87b35b 100644 --- a/cake/tests/cases/libs/model/db_acl.test.php +++ b/cake/tests/cases/libs/model/db_acl.test.php @@ -41,6 +41,7 @@ uses('controller'.DS.'components'.DS.'acl', 'model'.DS.'db_acl'); if(!class_exists('aclnodetestbase')) { class AclNodeTestBase extends AclNode { var $useDbConfig = 'test_suite'; + var $cacheSources = false; } } @@ -131,6 +132,45 @@ if(!class_exists('db_acl_test')) { function testNodeNesting() { } + function testNode(){ + $aco = new AcoTest(); + $result = Set::extract($aco->node('Controller1'), '{n}.AcoTest.id'); + $expected = array(2, 1); + $this->assertEqual($result, $expected); + + $result = Set::extract($aco->node('Controller1/action1'), '{n}.AcoTest.id'); + $expected = array(3, 2, 1); + $this->assertEqual($result, $expected); + + $result = Set::extract($aco->node('Controller2/action1'), '{n}.AcoTest.id'); + $expected = array(7, 6, 1); + $this->assertEqual($result, $expected); + + $result = Set::extract($aco->node('Controller1/action2'), '{n}.AcoTest.id'); + $expected = array(5, 2, 1); + $this->assertEqual($result, $expected); + + $result = Set::extract($aco->node('Controller1/action1/record1'), '{n}.AcoTest.id'); + $expected = array(4, 3, 2, 1); + $this->assertEqual($result, $expected); + + $result = Set::extract($aco->node('Controller2/action1/record1'), '{n}.AcoTest.id'); + $expected = array(8, 7, 6, 1); + $this->assertEqual($result, $expected); + + //action3 is an action with no ACO entry + //the default returned ACOs should be its parents + $result = Set::extract($aco->node('Controller2/action3'), '{n}.AcoTest.id'); + $expected = array(6, 1); + $this->assertEqual($result, $expected); + + //action3 and record5 have none ACO entry + //the default returned ACOs should be their parents ACO + $result = Set::extract($aco->node('Controller2/action3/record5'), '{n}.AcoTest.id'); + $expected = array(6, 1); + $this->assertEqual($result, $expected); + + } } ?> \ No newline at end of file diff --git a/cake/tests/cases/libs/model/model.test.php b/cake/tests/cases/libs/model/model.test.php index 902011fee..3b06d2a84 100644 --- a/cake/tests/cases/libs/model/model.test.php +++ b/cake/tests/cases/libs/model/model.test.php @@ -48,7 +48,7 @@ class Test extends Model { function loadInfo() { return new Set(array( - array('name' => 'id', 'type' => 'integer', 'null' => '', 'default' => '1', 'length' => '8'), + array('name' => 'id', 'type' => 'integer', 'null' => '', 'default' => '1', 'length' => '8', 'key'=>'primary'), array('name' => 'name', 'type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), array('name' => 'email', 'type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), array('name' => 'notes', 'type' => 'text', 'null' => '1', 'default' => 'write some notes here', 'length' => ''), @@ -56,6 +56,17 @@ class Test extends Model { array('name' => 'updated', 'type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) )); } + + function schema() { + return new Set(array( + 'id'=> array('type' => 'integer', 'null' => '', 'default' => '1', 'length' => '8', 'key'=>'primary'), + 'name'=> array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'), + 'email'=> array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'), + 'notes'=> array('type' => 'text', 'null' => '1', 'default' => 'write some notes here', 'length' => ''), + 'created'=> array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''), + 'updated'=> array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null) + )); + } } /** @@ -174,6 +185,24 @@ class Featured extends CakeTestModel { class Tag extends CakeTestModel { var $name = 'Tag'; } +/** + * Short description for class. + * + * @package cake.tests + * @subpackage cake.tests.cases.libs.model + */ +class ArticleTag extends CakeTestModel { + var $name = 'ArticleTag'; +} +/** + * Short description for class. + * + * @package cake.tests + * @subpackage cake.tests.cases.libs.model + */ +class ArticleFeaturedTag extends CakeTestModel { + var $name = 'ArticleFeaturedTag'; +} /** * Short description for class. * @@ -354,7 +383,7 @@ class ModelTest extends CakeTestCase { var $fixtures = array( 'core.category', 'core.category_thread', 'core.user', 'core.article', 'core.featured', 'core.article_featureds_tags', - 'core.article_featured', 'core.tag', 'core.articles_tag', 'core.comment', 'core.attachment', + 'core.article_featured', 'core.articles', 'core.tag', 'core.articles_tag', 'core.comment', 'core.attachment', 'core.apple', 'core.sample', 'core.another_article', 'core.advertisement', 'core.home', 'core.post', 'core.author', 'core.project', 'core.thread', 'core.message', 'core.bid' ); @@ -430,9 +459,7 @@ class ModelTest extends CakeTestCase { 'Thread' => array() ) ); - $this->assertEqual($result, $expected); - unset($this->Project); } @@ -521,7 +548,7 @@ class ModelTest extends CakeTestCase { } $expected = array( - array('name' => 'id', 'type' => 'integer', 'null' => false, 'default' => null, 'length' => $intLength), + array('name' => 'id', 'type' => 'integer', 'null' => false, 'default' => null, 'length' => $intLength, 'key' => 'primary', 'extra' => 'auto_increment'), array('name' => 'user', 'type' => 'string', 'null' => false, 'default' => '', 'length' => 255), array('name' => 'password', 'type' => 'string', 'null' => false, 'default' => '', 'length' => 255), array('name' => 'created', 'type' => 'datetime', 'null' => true, 'default' => null, 'length' => null), diff --git a/cake/tests/cases/libs/model/schema.test.php b/cake/tests/cases/libs/model/schema.test.php new file mode 100644 index 000000000..dcb4077ab --- /dev/null +++ b/cake/tests/cases/libs/model/schema.test.php @@ -0,0 +1,185 @@ + + * Copyright 2005-2007, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2005-2007, Cake Software Foundation, Inc. + * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests + * @package cake.tests + * @subpackage cake.tests.cases.libs + * @since CakePHP(tm) v 1.2.0.5550 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +uses('model' . DS .'schema'); +/** + * Test for Schema database management + * + * @package cake.tests + * @subpackage cake.tests.cases.libs + */ +class MyAppSchema extends CakeSchema { + + var $name = 'MyApp'; + + var $connection = 'test_suite'; + + var $posts = array( + 'id' => array('type'=>'integer', 'null' => false, 'default' => null, 'key' => 'primary', 'extra'=> 'auto_increment'), + 'author_id' => array('type'=>'integer', 'null' => false, 'default' => ''), + 'title' => array('type'=>'string', 'null' => false, 'default' => 'Title'), + 'summary' => array('type'=>'text', 'null' => true), + 'body' => array('type'=>'text', 'null' => true), + 'published' => array('type'=>'string', 'null' => true, 'default' => 'Y', 'length' => 1), + 'created' => array('type'=>'datetime', 'null' => true), + 'updated' => array('type'=>'datetime', 'null' => true), + 'indexes' => array('PRIMARY'=>array('column'=>'id', 'unique' => true)), + + ); + + var $comments = array( + 'id' => array('type'=>'integer', 'null' => false, 'default' => null, 'key' => 'primary', 'extra'=> 'auto_increment'), + 'post_id' => array('type'=>'integer', 'null' => false, 'default' => ''), + 'user_id' => array('type'=>'integer', 'null' => false, 'default' => ''), + 'title' => array('type'=>'string', 'null' => false, 'length' => 100), + 'comment' => array('type'=>'text', 'null' => false), + 'published' => array('type'=>'string', 'null' => true, 'default' => 'N', 'length' => 1), + 'created' => array('type'=>'datetime', 'null' => true), + 'updated' => array('type'=>'datetime', 'null' => true), + 'indexes' => array('PRIMARY'=>array('column'=>'id', 'unique' => true)), + ); + + function setup($version) { + } + + function teardown($version) { + } +} +class TestAppSchema extends CakeSchema { + + var $name = 'MyApp'; + + var $posts = array( + 'id' => array('type'=>'integer', 'null' => false, 'default' => null, 'key' => 'primary', 'extra'=> 'auto_increment'), + 'author_id' => array('type'=>'integer', 'null' => false, 'default' => ''), + 'title' => array('type'=>'string', 'null' => false, 'default' => ''), + 'body' => array('type'=>'text', 'null' => true), + 'published' => array('type'=>'string', 'null' => true, 'default' => 'N', 'length' => 1), + 'created' => array('type'=>'datetime', 'null' => true), + 'updated' => array('type'=>'datetime', 'null' => true), + 'indexes' => array('PRIMARY'=>array('column'=>'id', 'unique' => true)), + ); + var $comments = array( + 'id' => array('type'=>'integer', 'null' => false, 'default' => null, 'key' => 'primary', 'extra'=> 'auto_increment'), + 'article_id' => array('type'=>'integer', 'null' => false, 'default' => ''), + 'user_id' => array('type'=>'integer', 'null' => false, 'default' => ''), + 'comment' => array('type'=>'text', 'null' => true), + 'published' => array('type'=>'string', 'null' => true, 'default' => 'N', 'length' => 1), + 'created' => array('type'=>'datetime', 'null' => true), + 'updated' => array('type'=>'datetime', 'null' => true), + 'indexes' => array('PRIMARY'=>array('column'=>'id', 'unique' => true)), + ); + + + function setup($version) { + } + + function teardown($version) { + } +} +/** + * Short description for class. + * + * @package cake.tests + * @subpackage cake.tests.cases.libs.model + */ +class SchemaPost extends CakeTestModel { + var $name = 'SchemaPost'; + //var $useTable = 'posts'; + var $hasMany = array('SchemaComment'); +} +/** + * Short description for class. + * + * @package cake.tests + * @subpackage cake.tests.cases.libs.model + */ +class SchemaComment extends CakeTestModel { + var $name = 'SchemaComment'; + //var $useTable = 'comments'; + var $belongsTo = array('SchemaPost'); +} +/** + * Short description for class. + * + * @package cake.tests + * @subpackage cake.tests.cases.libs + */ +class CakeSchemaTest extends CakeTestCase { + + var $fixtures = array('core.post', 'core.comment', 'core.author'); + + function setUp() { + $this->Schema = new TestAppSchema(); + } + + + function testSchemaGeneration() { + + $read = $this->Schema->read(array('connection'=>'test_suite', 'name'=>'TestApp', 'models'=>array('post', 'comment'))); + $this->assertEqual($read['tables'], $this->Schema->tables); + + $write = $this->Schema->write(array('name'=>'MyOtherApp', 'tables'=> $read['tables'], 'path'=> TMP . 'tests')); + $file = file_get_contents(TMP . 'tests' . DS .'schema.php'); + $this->assertEqual($write, $file); + + require_once( TMP . 'tests' . DS .'schema.php'); + $OtherSchema = new MyOtherAppSchema(); + $this->assertEqual($read['tables'], $OtherSchema->tables); + } + + function testSchemaComparison() { + $New = new MyAppSchema(); + $compare = $New->compare($this->Schema); + $expected = array( + 'posts'=> array( + 'add'=> array('summary'=>array('type'=> 'text', 'null'=> 1)), + 'change'=> array('title'=>array('type'=>'string', 'null'=> false, 'default'=> 'Title'), 'published'=>array('type'=>'string', 'null'=> true, 'default'=>'Y', 'length'=> '1')), + ), + 'comments'=> array( + 'add'=>array('post_id'=>array('type'=> 'integer', 'null'=> false, 'default'=>''), 'title'=>array('type'=> 'string', 'null'=> false, 'length'=> 100)), + 'drop'=>array('article_id'=>array('type'=> 'integer', 'null'=> false, 'default'=>'')), + 'change'=>array('comment'=>array('type'=>'text', 'null'=> false)) + + ), + ); + + $this->assertEqual($expected, $compare); + } + + function testSchemaLoading() { + $Other = $this->Schema->load(array('name'=>'MyOtherApp', 'path'=> TMP . 'tests')); + + $this->assertEqual($Other->name, 'MyOtherApp'); + $this->assertEqual($Other->tables, $this->Schema->tables); + } + + function tearDown() { + unset($this->Schema); + } +} +?> \ No newline at end of file diff --git a/cake/tests/fixtures/aco_fixture.php b/cake/tests/fixtures/aco_fixture.php index 1b127a150..a7361db64 100644 --- a/cake/tests/fixtures/aco_fixture.php +++ b/cake/tests/fixtures/aco_fixture.php @@ -44,6 +44,15 @@ class AcoFixture extends CakeTestFixture { 'rght' => array('type' => 'integer', 'length' => 10, 'null' => true) ); var $records = array( + array ('id' => 1, 'parent_id' => null, 'model' => '', 'foreign_key' => '', 'alias' => 'ROOT', 'lft' => 1, 'rght' => 18), + array ('id' => 2, 'parent_id' => 1, 'model' => '', 'foreign_key' => '', 'alias' => 'Controller1', 'lft' => 2, 'rght' => 9), + array ('id' => 3, 'parent_id' => 2, 'model' => '', 'foreign_key' => '', 'alias' => 'action1', 'lft' => 3, 'rght' => 6), + array ('id' => 4, 'parent_id' => 3, 'model' => '', 'foreign_key' => '', 'alias' => 'record1', 'lft' => 4, 'rght' => 5), + array ('id' => 5, 'parent_id' => 2, 'model' => '', 'foreign_key' => '', 'alias' => 'action2', 'lft' => 7, 'rght' => 8), + array ('id' => 6, 'parent_id' => 1, 'model' => '', 'foreign_key' => '', 'alias' => 'Controller2', 'lft' => 10, 'rght' => 17), + array ('id' => 7, 'parent_id' => 6, 'model' => '', 'foreign_key' => '', 'alias' => 'action1', 'lft' => 11, 'rght' => 14), + array ('id' => 8, 'parent_id' => 7, 'model' => '', 'foreign_key' => '', 'alias' => 'record1', 'lft' => 12, 'rght' => 13), + array ('id' => 9, 'parent_id' => 6, 'model' => '', 'foreign_key' => '', 'alias' => 'action2', 'lft' => 15, 'rght' => 16), ); } diff --git a/cake/tests/fixtures/article_featureds_tags_fixture.php b/cake/tests/fixtures/article_featureds_tags_fixture.php index fde9bdd60..669fa13b8 100644 --- a/cake/tests/fixtures/article_featureds_tags_fixture.php +++ b/cake/tests/fixtures/article_featureds_tags_fixture.php @@ -34,11 +34,11 @@ */ class ArticleFeaturedsTagsFixture extends CakeTestFixture { var $name = 'ArticleFeaturedsTags'; - var $primaryKey = array('article_featured_id', 'tag_id'); var $fields = array( 'article_featured_id' => array('type' => 'integer', 'null' => false), 'tag_id' => array('type' => 'integer', 'null' => false), + 'indexes' => array('UNIQUE_FEATURED' => array('column'=> array('article_featured_id', 'tag_id'), 'unique'=> 1)) ); } diff --git a/cake/tests/fixtures/articles_tag_fixture.php b/cake/tests/fixtures/articles_tag_fixture.php index a0a6d2f15..8dbdcc851 100644 --- a/cake/tests/fixtures/articles_tag_fixture.php +++ b/cake/tests/fixtures/articles_tag_fixture.php @@ -37,8 +37,8 @@ class ArticlesTagFixture extends CakeTestFixture { var $fields = array( 'article_id' => array('type' => 'integer', 'null' => false), 'tag_id' => array('type' => 'integer', 'null' => false), + 'indexes' => array('UNIQUE_TAG' => array('column'=> array('article_id', 'tag_id'), 'unique'=>1)) ); - var $primaryKey = array('article_id', 'tag_id'); var $records = array( array('article_id' => 1, 'tag_id' => 1), array('article_id' => 1, 'tag_id' => 2), diff --git a/cake/tests/lib/cake_test_case.php b/cake/tests/lib/cake_test_case.php index 0a04e6c77..6f7d8dd68 100644 --- a/cake/tests/lib/cake_test_case.php +++ b/cake/tests/lib/cake_test_case.php @@ -181,10 +181,9 @@ class CakeTestCase extends UnitTestCase { foreach ($models as $model) { $object =& $classRegistry->getObject($model['key']); - if ($object !== false) { - $object->useDbConfig = 'test_suite'; - $object->setDataSource(); + $object->setDataSource('test_suite'); + $object->cacheSources = false; } } } @@ -336,7 +335,6 @@ class CakeTestCase extends UnitTestCase { if (isset($this->_fixtures) && isset($this->db)) { foreach ($this->_fixtures as $fixture) { $query = $fixture->create(); - if (isset($query) && $query !== false) { $this->db->_execute($query); } @@ -352,7 +350,6 @@ class CakeTestCase extends UnitTestCase { if (isset($this->_fixtures) && isset($this->db)) { foreach (array_reverse($this->_fixtures) as $fixture) { $query = $fixture->drop(); - if (isset($query) && $query !== false) { $this->db->_execute($query); } @@ -370,7 +367,6 @@ class CakeTestCase extends UnitTestCase { if (isset($this->_fixtures) && isset($this->db) && !in_array(low($method), array('start', 'end'))) { foreach ($this->_fixtures as $fixture) { $query = $fixture->truncate(); - if (isset($query) && $query !== false) { $this->db->_execute($query); } @@ -393,9 +389,7 @@ class CakeTestCase extends UnitTestCase { */ function getTests() { $methods = array_diff(parent::getTests(), array('testAction', 'testaction')); - $methods = am(am(array('start', 'startCase'), $methods), array('endCase', 'end')); - return $methods; } /** @@ -433,6 +427,7 @@ class CakeTestCase extends UnitTestCase { // Get db connection $this->db =& ConnectionManager::getDataSource('test_suite'); + $this->db->cacheSources = false; $this->db->fullDebug = false; } /** diff --git a/cake/tests/lib/cake_test_fixture.php b/cake/tests/lib/cake_test_fixture.php index f2d6f4343..143fe25aa 100644 --- a/cake/tests/lib/cake_test_fixture.php +++ b/cake/tests/lib/cake_test_fixture.php @@ -44,12 +44,17 @@ class CakeTestFixture extends Object { function __construct(&$db) { $this->db =& $db; $this->init(); + if(!class_exists('cakeschema')) { + uses('model' . DS .'schema'); + } + $this->Schema = new CakeSchema(array('name'=>'TestSuite', 'connection'=>'test_suite')); } /** * Initialize the fixture. * */ function init() { + if (isset($this->import) && (is_string($this->import) || is_array($this->import))) { $import = array(); @@ -63,33 +68,29 @@ class CakeTestFixture extends Object { if (isset($import['model']) && (class_exists($import['model']) || loadModel($import['model']))) { $model =& new $import['model']; - $modelDb =& ConnectionManager::getDataSource($model->useDbConfig); - - $info = $model->loadInfo(); - - $this->fields = array_combine(Set::extract($info->value, '{n}.name'), $info->value); + $db =& ConnectionManager::getDataSource($model->useDbConfig); + $db->cacheSources = false; + $this->table = $this->useTable; + $schema = $model->schema(true); + $this->fields = $schema->value; $this->fields[$model->primaryKey]['key'] = 'primary'; - - $this->primaryKey = array( $model->primaryKey ); } elseif (isset($import['table'])) { - $model =& new stdClass(); - $modelDb =& ConnectionManager::getDataSource($import['connection']); - + $model =& new Model(null, $import['table'], $import['connection']); + $db =& ConnectionManager::getDataSource($import['connection']); + $db->cacheSources = false; $model->name = Inflector::camelize(Inflector::singularize($import['table'])); $model->table = $import['table']; - $model->tablePrefix = $modelDb->config['prefix']; - - $info = $modelDb->describe($model); - - $this->fields = array_combine(Set::extract($info, '{n}.name'), $info); + $model->tablePrefix = $db->config['prefix']; + $schema = $model->schema(true); + $this->fields = $schema->value; } - if ($import['records'] !== false && isset($model) && isset($modelDb)) { + if ($import['records'] !== false && isset($model) && isset($db)) { $this->records = array(); $query = array( - 'fields' => Set::extract($this->fields, '{n}.name'), - 'table' => $modelDb->name($model->table), + 'fields' => array_keys($this->fields), + 'table' => $db->name($model->table), 'alias' => $model->name, 'conditions' => array(), 'order' => null, @@ -97,10 +98,10 @@ class CakeTestFixture extends Object { ); foreach ($query['fields'] as $index => $field) { - $query['fields'][$index] = $modelDb->name($query['alias']) . '.' . $modelDb->name($field); + $query['fields'][$index] = $db->name($query['alias']) . '.' . $db->name($field); } - $records = $modelDb->fetchAll($modelDb->buildStatement($query, $model), false, $model->name); + $records = $db->fetchAll($db->buildStatement($query, $model), false, $model->name); if ($records !== false && !empty($records)) { $this->records = Set::extract($records, '{n}.' . $model->name); @@ -116,14 +117,6 @@ class CakeTestFixture extends Object { $this->primaryKey = 'id'; } - if (isset($this->primaryKey) && !is_array($this->primaryKey)) { - $this->primaryKey = array( $this->primaryKey ); - } - - if (isset($this->primaryKey) && isset($this->fields[$this->primaryKey[0]])) { - $this->fields[$this->primaryKey[0]]['key'] = 'primary'; - } - if (isset($this->fields)) { foreach ($this->fields as $index => $field) { if (empty($field['default'])) { @@ -144,43 +137,9 @@ class CakeTestFixture extends Object { if (!isset($this->fields) || empty($this->fields)) { return null; } - - $create = 'CREATE TABLE ' . $this->db->name($this->db->config['prefix'] . $this->table) . ' (' . "\n"; - - foreach ($this->fields as $field => $attributes) { - if (!is_array($attributes)) { - $attributes = array('type' => $attributes); - } elseif (isset($attributes['key']) && low($attributes['key']) == 'primary' && !isset($this->primaryKey)) { - $this->primaryKey = array ( $field ); - } - - $column = array($field, $attributes['type']); - unset($attributes['type']); - - if (!empty($attributes)) { - $column = array_merge($column, $attributes); - } - - $create .= $this->db->generateColumnSchema($column) . ',' . "\n"; - } - - if (isset($this->primaryKey)) { - foreach ($this->primaryKey as $index => $field) { - $this->primaryKey[$index] = $this->db->name($field); - } - } - - if (!isset($this->primaryKey)) { - $create = substr($create, 0, -1); - } else { - $create .= 'PRIMARY KEY(' . implode(', ', $this->primaryKey) . ')' . "\n"; - } - - $create .= ')'; - - $this->_create = $create; + $this->Schema->_build(array($this->table => $this->fields)); + $this->_create = $this->db->createSchema($this->Schema); } - return $this->_create; } /** @@ -192,9 +151,9 @@ class CakeTestFixture extends Object { */ function drop() { if (!isset($this->_drop)) { - $this->_drop = 'DROP TABLE ' . $this->db->name($this->db->config['prefix'] . $this->table); + $this->Schema->_build(array($this->table => $this->fields)); + $this->_drop = $this->db->dropSchema($this->Schema); } - return $this->_drop; } /**