diff --git a/cake/libs/model/datasources/dbo/dbo_postgres.php b/cake/libs/model/datasources/dbo/dbo_postgres.php index 9edaf72cc..534d9b175 100644 --- a/cake/libs/model/datasources/dbo/dbo_postgres.php +++ b/cake/libs/model/datasources/dbo/dbo_postgres.php @@ -98,7 +98,6 @@ class DboPostgres extends DboSource { if (!empty($config['encoding'])) { $this->setEncoding($config['encoding']); } - return $this->connected; } @@ -193,7 +192,7 @@ class DboPostgres extends DboSource { $fields[$c['name']] = array( 'type' => $this->column($c['type']), 'null' => ($c['null'] == 'NO' ? false : true), - 'default' => $c['default'], + 'default' => preg_replace('/::.*/', '', $c['default']), 'length' => $length ); } @@ -364,6 +363,86 @@ class DboPostgres extends DboSource { $data = $this->fetchRow($res); return $data[0]['max']; } +/** + * Generates and executes an SQL UPDATE statement for given model, fields, and values. + * + * @param Model $model + * @param array $fields + * @param array $values + * @param mixed $conditions + * @return array + */ + function update(&$model, $fields = array(), $values = null, $conditions = null) { + if (empty($conditions)) { + return parent::update($model, $fields, $values, null); + } elseif ($conditions === true) { + $conditions = $this->conditions(true); + } else { + $idList = $model->find('all', array('fields' => $model->escapeField(), 'conditions' => $conditions)); + + if (empty($idList)) { + return false; + } + $conditions = $this->conditions(array( + $model->primaryKey => Set::extract($idList, "{n}.{$model->alias}.{$model->primaryKey}") + )); + } + if ($values == null) { + $combined = $fields; + } else { + $combined = array_combine($fields, $values); + } + $fields = join(', ', $this->_prepareUpdateFields($model, $combined, false, false)); + + $alias = $joins = null; + $table = $this->fullTableName($model); + + if (!$this->execute($this->renderStatement('update', compact('table', 'alias', 'joins', 'fields', 'conditions')))) { + $model->onError(); + return false; + } + return true; + } +/** + * Generates and executes an SQL DELETE statement + * + * @param Model $model + * @param mixed $conditions + * @return boolean Success + */ + function delete(&$model, $conditions = null) { + if (empty($conditions)) { + return parent::delete($model, null); + } elseif ($conditions === true) { + $conditions = $this->conditions(true); + } else { + $idList = $model->find('all', array('fields' => $model->escapeField(), 'conditions' => $conditions)); + + if (empty($idList)) { + return false; + } + $conditions = $this->conditions(array( + $model->primaryKey => Set::extract($idList, "{n}.{$model->alias}.{$model->primaryKey}") + )); + } + $alias = $joins = null; + $table = $this->fullTableName($model); + + if ($this->execute($this->renderStatement('delete', compact('alias', 'table', 'joins', 'conditions'))) === false) { + $model->onError(); + return false; + } + return true; + } +/** + * Prepares field names to be quoted by parent + * + * @param string $data + * @return string SQL field + */ + function name($data) { + return parent::name(str_replace('"__"', '__', $data)); + } /** * Generates the fields list of an SQL query. * @@ -392,8 +471,7 @@ class DboPostgres extends DboSource { $fields[$i] = trim(str_replace('DISTINCT', '', $fields[$i])); } - $dot = strrpos($fields[$i], '.'); - if ($dot === false) { + if (strrpos($fields[$i], '.') === false) { $fields[$i] = $prepend . $this->name($alias) . '.' . $this->name($fields[$i]) . ' AS ' . $this->name($alias . '__' . $fields[$i]); } else { $build = explode('.', $fields[$i]); @@ -588,6 +666,88 @@ class DboPostgres extends DboSource { function insertMulti($table, $fields, $values) { parent::__insertMulti($table, $fields, $values); } +/** + * Generate a Postgres-native 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) { + $name = $type = null; + $column = array_merge(array('null' => true), $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']; + + 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']; + } + if (!in_array($type, array('integer', 'binary'))) { + $out .= '(' . $length . ')'; + } + } + if (isset($column['key']) && $column['key'] == 'primary' && (isset($column['extra']) && $column['extra'] == 'auto_increment')) { + preg_match('/("\w+")/', $out, $field); + $out = $field[0] . ' serial NOT NULL'; + } elseif (isset($column['key']) && $column['key'] == 'primary') { + $out .= ' NOT NULL'; + } elseif (isset($column['default']) && isset($column['null']) && $column['null'] == false) { + $out .= ' DEFAULT ' . $this->value($column['default'], $type) . ' 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['null']) && $column['null'] == false) { + $out .= ' NOT NULL'; + } + return $out; + } +/** + * Format indexes for create table + * + * @param array $indexes + * @return string + */ + function buildIndex($indexes) { + $join = array(); + foreach ($indexes as $name => $value) { + $out = ''; + if ($name == 'PRIMARY') { + $out .= 'PRIMARY '; + $name = null; + } else { + if (!empty($value['unique'])) { + $name .= ' UNIQUE '; + } + } + if (is_array($value['column'])) { + $out .= 'CONSTRAINT '. $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_sqlite.php b/cake/libs/model/datasources/dbo/dbo_sqlite.php index a974fc59a..4791568c5 100644 --- a/cake/libs/model/datasources/dbo/dbo_sqlite.php +++ b/cake/libs/model/datasources/dbo/dbo_sqlite.php @@ -74,12 +74,12 @@ class DboSqlite extends DboSource { 'primary_key' => array('name' => 'integer primary key'), 'string' => array('name' => 'varchar', 'limit' => '255'), 'text' => array('name' => 'text'), - 'integer' => array('name' => 'integer', 'limit' => '11', 'formatter' => 'intval'), + 'integer' => array('name' => 'integer', 'limit' => null, 'formatter' => 'intval'), 'float' => array('name' => 'float', 'formatter' => 'floatval'), - 'datetime' => array('name' => 'timestamp', 'format' => 'YmdHis', 'formatter' => 'date'), - 'timestamp' => array('name' => 'timestamp', 'format' => 'YmdHis', 'formatter' => 'date'), - 'time' => array('name' => 'timestamp', 'format' => 'His', 'formatter' => 'date'), - 'date' => array('name' => 'date', 'format' => 'Ymd', 'formatter' => 'date'), + 'datetime' => array('name' => 'timestamp', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'), + 'timestamp' => array('name' => 'timestamp', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'), + 'time' => array('name' => 'timestamp', 'format' => 'H:i:s', 'formatter' => 'date'), + 'date' => array('name' => 'date', 'format' => 'Y-m-d', 'formatter' => 'date'), 'binary' => array('name' => 'blob'), 'boolean' => array('name' => 'integer', 'limit' => '1') ); @@ -173,7 +173,7 @@ class DboSqlite extends DboSource { 'default' => $column[0]['dflt_value'], 'key' => $this->index['PRI'], 'extra' => 'auto_increment', - 'length' => $this->columns['integer']['limit'] + 'length' => 11 ); } } diff --git a/cake/libs/model/datasources/dbo_source.php b/cake/libs/model/datasources/dbo_source.php index 34edf294d..e609cd5cf 100644 --- a/cake/libs/model/datasources/dbo_source.php +++ b/cake/libs/model/datasources/dbo_source.php @@ -353,8 +353,7 @@ class DboSource extends DataSource { $data = str_replace($this->startQuote . $this->startQuote, $this->startQuote, $data); if (!empty($this->endQuote) && $this->endQuote == $this->startQuote) { - $oddMatches = substr_count($data, $this->endQuote); - if ($oddMatches % 2 == 1) { + if (substr_count($data, $this->endQuote) % 2 == 1) { $data = trim($data, $this->endQuote); } } @@ -1237,16 +1236,23 @@ class DboSource extends DataSource { */ function renderStatement($type, $data) { extract($data); + $aliases = null; switch (strtolower($type)) { case 'select': return "SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$order} {$limit}"; break; case 'update': - return "UPDATE {$table} {$this->alias}{$alias} {$joins} SET {$fields} {$conditions}"; + if (!empty($alias)) { + $aliases = "{$this->alias}{$alias} {$joins}"; + } + return "UPDATE {$table} {$aliases}SET {$fields} {$conditions}"; break; case 'delete': - return "DELETE {$alias} FROM {$table} {$this->alias}{$alias} {$joins} {$conditions}"; + if (!empty($alias)) { + $aliases = "{$this->alias}{$alias} {$joins} "; + } + return "DELETE {$alias} FROM {$table} {$aliases}{$conditions}"; break; } } @@ -1285,20 +1291,55 @@ class DboSource extends DataSource { * @return array */ function update(&$model, $fields = array(), $values = null, $conditions = null) { - $updates = array(); - if ($values == null) { $combined = $fields; } else { $combined = array_combine($fields, $values); } - foreach ($combined as $field => $value) { - if ($value === null) { - $updates[] = $model->escapeField($field) . ' = NULL'; + $fields = join(', ', $this->_prepareUpdateFields($model, $combined, empty($conditions), !empty($conditions))); + $table = $this->fullTableName($model); + $alias = $this->name($model->alias); + $joins = implode(' ', $this->_getJoins($model)); + + if (empty($conditions)) { + $alias = $joins = false; + } + $conditions = $this->conditions($this->defaultConditions($model, $conditions, $alias)); + + if ($conditions === false) { + return false; + } + + if (!$this->execute($this->renderStatement('update', compact('table', 'alias', 'joins', 'fields', 'conditions')))) { + $model->onError(); + return false; + } + return true; + } +/** + * Quotes and prepares fields and values for an SQL UPDATE statement + * + * @param Model $model + * @param array $fields + * @param boolean $quoteValues If values should be quoted, or treated as SQL snippets + * @param boolean $alias Include the model alias in the field name + * @return array Fields and values, quoted and preparted + * @access protected + */ + function _prepareUpdateFields(&$model, $fields, $quoteValues, $alias) { + foreach ($fields as $field => $value) { + if ($alias) { + $field = $model->escapeField($field); } else { - $update = $model->escapeField($field) . ' = '; - if ($conditions == null) { + $field = $this->name($field); + } + + if ($value === null) { + $updates[] = $field . ' = NULL'; + } else { + $update = $field . ' = '; + if ($quoteValues) { $update .= $this->value($value, $model->getColumnType($field)); } else { $update .= $value; @@ -1306,22 +1347,7 @@ class DboSource extends DataSource { $updates[] = $update; } } - $conditions = $this->defaultConditions($model, $conditions); - - if ($conditions === false) { - return false; - } - $fields = join(', ', $updates); - $table = $this->fullTableName($model); - $conditions = $this->conditions($conditions); - $alias = $this->name($model->alias); - $joins = implode(' ', $this->_getJoins($model)); - - if (!$this->execute($this->renderStatement('update', compact('table', 'alias', 'joins', 'fields', 'conditions')))) { - $model->onError(); - return false; - } - return true; + return $updates; } /** * Generates and executes an SQL DELETE statement for given id on given model. @@ -1331,17 +1357,19 @@ class DboSource extends DataSource { * @return boolean Success */ function delete(&$model, $conditions = null) { - $query = $this->defaultConditions($model, $conditions); - - if ($query === false) { - return false; - } - $alias = $this->name($model->alias); $table = $this->fullTableName($model); - $conditions = $this->conditions($query); $joins = implode(' ', $this->_getJoins($model)); + if (empty($conditions)) { + $alias = $joins = false; + } + $conditions = $this->conditions($this->defaultConditions($model, $conditions, $alias)); + + if ($conditions === false) { + return false; + } + if ($this->execute($this->renderStatement('delete', compact('alias', 'table', 'joins', 'conditions'))) === false) { $model->onError(); return false; @@ -1387,16 +1415,22 @@ class DboSource extends DataSource { * * @param object $model * @param mixed $conditions + * @param boolean $useAlias Use model aliases rather than table names when generating conditions * @return mixed */ - function defaultConditions(&$model, $conditions) { + function defaultConditions(&$model, $conditions, $useAlias = true) { if (!empty($conditions)) { return $conditions; } if (!$model->exists()) { return false; } - return array("{$model->alias}.{$model->primaryKey}" => (array)$model->getID()); + $alias = $model->alias; + + if (!$useAlias) { + $alias = $this->fullTableName($model, false); + } + return array("{$alias}.{$model->primaryKey}" => $model->getID()); } /** * Returns a key formatted like a string Model.fieldname(i.e. Post.title, or Country.name) diff --git a/cake/tests/fixtures/join_thing_fixture.php b/cake/tests/fixtures/join_thing_fixture.php index 12cbfe11e..3a772e85c 100644 --- a/cake/tests/fixtures/join_thing_fixture.php +++ b/cake/tests/fixtures/join_thing_fixture.php @@ -38,7 +38,7 @@ class JoinThingFixture extends CakeTestFixture { 'id' => array('type' => 'integer', 'key' => 'primary', 'extra'=> 'auto_increment'), 'something_id' => array('type' => 'integer', 'length' => 10, 'null' => true), 'something_else_id' => array('type' => 'integer', 'default' => ''), - 'doomed' => array('type' => 'boolean'), + 'doomed' => array('type' => 'boolean', 'default' => '0'), 'created' => array('type' => 'datetime', 'null' => true), 'updated' => array('type' => 'datetime', 'null' => true) );