Skip to content

Commit

Permalink
Merge pull request #1378 from markstory/3.0-postgres-fk
Browse files Browse the repository at this point in the history
3.0 postgres foreign keys
  • Loading branch information
lorenzo committed Jun 29, 2013
2 parents aa6136e + 0ebfd4e commit 0079674
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 242 deletions.
34 changes: 0 additions & 34 deletions lib/Cake/Database/Dialect/PostgresDialectTrait.php
Expand Up @@ -80,40 +80,6 @@ protected function _selectQueryTranslator($query) {
return $query;
}

/**
* Returns an update query that has been transformed for Postgres.
*
* Postgres requires joins to be defined in the FROM list instead of
* as standard joins. This translator will erase joins and replace them with
* an expression object in the FROM clause.
*
* @param Cake\Database\Query $query
* @return Cake\Database\Query
*/
protected function _updateQueryTranslator($query) {
$joins = $query->clause('join');
if (empty($joins)) {
return $query;
}
$first = array_shift($joins);
$sql = sprintf('%s %s', $first['table'], $first['alias']);
if (isset($first['conditions']) && count($first['conditions'])) {
$query->where($first['conditions']);
}
foreach ($joins as $i => $join) {
$sql .= sprintf(' %s JOIN %s %s', $join['type'], $join['table'], $join['alias']);
if (isset($join['conditions']) && count($join['conditions'])) {
$sql .= sprintf(' ON %s', $join['conditions']);
} else {
$sql .= ' ON 1 = 1';
}
}
$expr = $query->newExpr()->add($sql);
$query->join([], [], true);
$query->from([$expr]);
return $query;
}

/**
* Returns a function that will be used as a callback for a results decorator.
* this function is responsible for deleting the artificial column in results
Expand Down
5 changes: 3 additions & 2 deletions lib/Cake/Database/Query.php
Expand Up @@ -289,7 +289,7 @@ protected function _traverseDelete(callable $visitor) {
* @return void
*/
protected function _traverseUpdate(callable $visitor) {
$parts = ['update', 'join', 'set', 'from', 'where'];
$parts = ['update', 'set', 'where'];
foreach ($parts as $name) {
call_user_func($visitor, $this->_parts[$name], $name);
}
Expand Down Expand Up @@ -410,12 +410,13 @@ public function distinct($on = [], $overwrite = false) {
* @return string
*/
protected function _buildSelectPart($parts) {
$driver = $this->_connection->driver();
$select = 'SELECT %s%s';
$distinct = null;
$normalized = [];
foreach ($parts as $k => $p) {
if (!is_numeric($k)) {
$p = $p . ' AS ' . $k;
$p = $p . ' AS ' . $driver->quoteIdentifier($k);
}
$normalized[] = $p;
}
Expand Down
78 changes: 78 additions & 0 deletions lib/Cake/Database/Schema/BaseSchema.php
@@ -0,0 +1,78 @@
<?php
/**
* PHP Version 5.4
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since CakePHP(tm) v 3.0.0
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace Cake\Database\Schema;

/**
* Base class for schema implementations.
*
* This class contains methods that are common across
* the various SQL dialects.
*/
class BaseSchema {

/**
* Generate an ON clause for a foreign key.
*
* @param string|null $on The on clause
* @return string
*/
protected function _foreignOnClause($on) {
if ($on === Table::ACTION_SET_NULL) {
return 'SET NULL';
}
if ($on === Table::ACTION_CASCADE) {
return 'CASCADE';
}
if ($on === Table::ACTION_RESTRICT) {
return 'RESTRICT';
}
if ($on === Table::ACTION_NO_ACTION) {
return 'NO ACTION';
}
}

/**
* Convert string on clauses to the abstract ones.
*
* @param string $clause
* @return string|null
*/
protected function _convertOnClause($clause) {
if ($clause === 'CASCADE' || $clause === 'RESTRICT') {
return strtolower($clause);
}
if ($clause === 'NO ACTION') {
return Table::ACTION_NO_ACTION;
}
return Table::ACTION_SET_NULL;
}

/**
* Generate the SQL to drop a table.
*
* @param Cake\Database\Schema\Table $table Table instance
* @return array SQL statements to drop DROP a table.
*/
public function dropTableSql(Table $table) {
$sql = sprintf(
'DROP TABLE %s',
$this->_driver->quoteIdentifier($table->name())
);
return [$sql];
}

}
49 changes: 1 addition & 48 deletions lib/Cake/Database/Schema/MysqlSchema.php
Expand Up @@ -22,7 +22,7 @@
/**
* Schema management/reflection features for MySQL
*/
class MysqlSchema {
class MysqlSchema extends BaseSchema {

/**
* The driver instance being used.
Expand Down Expand Up @@ -244,22 +244,6 @@ public function convertForeignKey(Table $table, $row) {
$table->addConstraint($name, $data);
}

/**
* Convert MySQL on clauses to the abstract ones.
*
* @param string $clause
* @return string|null
*/
protected function _convertOnClause($clause) {
if ($clause === 'CASCADE' || $clause === 'RESTRICT') {
return strtolower($clause);
}
if ($clause === 'NO ACTION') {
return Table::ACTION_NO_ACTION;
}
return Table::ACTION_SET_NULL;
}

/**
* Generate the SQL to truncate a table.
*
Expand All @@ -270,16 +254,6 @@ public function truncateTableSql(Table $table) {
return [sprintf("TRUNCATE TABLE `%s`", $table->name())];
}

/**
* Generate the SQL to drop a table.
*
* @param Cake\Database\Schema\Table $table Table instance
* @return array DROP TABLE sql
*/
public function dropTableSql(Table $table) {
return [sprintf("DROP TABLE `%s`", $table->name())];
}

/**
* Generate the SQL to create a table.
*
Expand Down Expand Up @@ -456,25 +430,4 @@ protected function _keySql($prefix, $data) {
return $prefix . ' (' . implode(', ', $columns) . ')';
}

/**
* Generate an ON clause for a foreign key.
*
* @param string|null $on The on clause
* @return string
*/
protected function _foreignOnClause($on) {
if ($on === Table::ACTION_SET_NULL) {
return 'SET NULL';
}
if ($on === Table::ACTION_CASCADE) {
return 'CASCADE';
}
if ($on === Table::ACTION_RESTRICT) {
return 'RESTRICT';
}
if ($on === Table::ACTION_NO_ACTION) {
return 'NO ACTION';
}
}

}
100 changes: 82 additions & 18 deletions lib/Cake/Database/Schema/PostgresSchema.php
Expand Up @@ -22,7 +22,7 @@
/**
* Schema management/reflection features for Postgres.
*/
class PostgresSchema {
class PostgresSchema extends BaseSchema {

/**
* The driver instance being used.
Expand Down Expand Up @@ -187,11 +187,13 @@ public function describeIndexSql($table, $config) {
i.indisunique,
i.indisvalid,
pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS statement
FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
FROM pg_catalog.pg_class AS c,
pg_catalog.pg_class AS c2,
pg_catalog.pg_index AS i
WHERE c.oid = (
SELECT c.oid
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace
WHERE c.relname = ?
AND pg_catalog.pg_table_is_visible(c.oid)
AND n.nspname = ?
Expand Down Expand Up @@ -244,8 +246,24 @@ public function convertIndexDescription(Table $table, $row) {
*
* @return array List of sql, params
*/
public function describeForeignKeySql($table) {
return ['', []];
public function describeForeignKeySql($table, $config = []) {
$sql = "SELECT
r.conname AS name,
r.confupdtype AS update_type,
r.confdeltype AS delete_type,
pg_catalog.pg_get_constraintdef(r.oid, true) AS definition
FROM pg_catalog.pg_constraint AS r
WHERE r.conrelid = (
SELECT c.oid
FROM pg_catalog.pg_class AS c,
pg_catalog.pg_namespace AS n
WHERE c.relname = ?
AND n.nspname = ?
AND n.oid = c.relnamespace
)
AND r.contype = 'f'";
$schema = empty($config['schema']) ? 'public' : $config['schema'];
return [$sql, [$table, $schema]];
}

/**
Expand All @@ -256,6 +274,41 @@ public function describeForeignKeySql($table) {
* @return void
*/
public function convertForeignKey(Table $table, $row) {
preg_match('/REFERENCES ([^\)]+)\(([^\)]+)\)/', $row['definition'], $matches);
$tableName = $matches[1];
$column = $matches[2];

preg_match('/FOREIGN KEY \(([^\)]+)\) REFERENCES/', $row['definition'], $matches);
$columns = explode(',', $matches[1]);

$data = [
'type' => Table::CONSTRAINT_FOREIGN,
'columns' => $columns,
'references' => [$tableName, $column],
'update' => $this->_convertOnClause($row['update_type']),
'delete' => $this->_convertOnClause($row['delete_type']),
];
$name = $row['name'];
$table->addConstraint($name, $data);
}

/**
* Convert Postgres on clauses to the abstract ones.
*
* @param string $clause
* @return string|null
*/
protected function _convertOnClause($clause) {

This comment has been minimized.

Copy link
@thaJeztah

thaJeztah Aug 9, 2013

May be personal preference, but would a simple switch be cleaner (and, slightly, faster)?

This comment has been minimized.

Copy link
@markstory

markstory Aug 10, 2013

Member

Yeah a switch could have worked here. I generally use if's where possible as they are simpler to me. The performance difference if any would generally be irrelevant though.

if ($clause === 'r') {
return Table::ACTION_RESTRICT;
}
if ($clause === 'a') {
return Table::ACTION_NO_ACTION;
}
if ($clause === 'c') {
return Table::ACTION_CASCADE;
}
return Table::ACTION_SET_NULL;
}

/**
Expand Down Expand Up @@ -363,16 +416,37 @@ public function constraintSql(Table $table, $name) {
$data = $table->constraint($name);
$out = 'CONSTRAINT ' . $this->_driver->quoteIdentifier($name);
if ($data['type'] === Table::CONSTRAINT_PRIMARY) {
$out = 'PRIMARY KEY ';
$out = 'PRIMARY KEY';
}
if ($data['type'] === Table::CONSTRAINT_UNIQUE) {
$out .= ' UNIQUE ';
$out .= ' UNIQUE';
}
return $this->_keySql($out, $data);
}

/**
* Helper method for generating key SQL snippets.
*
* @param string $prefix The key prefix
* @param array $data Key data.
* @return string
*/
protected function _keySql($prefix, $data) {
$columns = array_map(
[$this->_driver, 'quoteIdentifier'],
$data['columns']
);
return $out . '(' . implode(', ', $columns) . ')';
if ($data['type'] === Table::CONSTRAINT_FOREIGN) {
return $prefix . sprintf(
' FOREIGN KEY (%s) REFERENCES %s (%s) ON UPDATE %s ON DELETE %s',
implode(', ', $columns),
$this->_driver->quoteIdentifier($data['references'][0]),
$this->_driver->quoteIdentifier($data['references'][1]),
$this->_foreignOnClause($data['update']),
$this->_foreignOnClause($data['delete'])
);
}
return $prefix . ' (' . implode(', ', $columns) . ')';
}

/**
Expand Down Expand Up @@ -406,16 +480,6 @@ public function createTableSql(Table $table, $columns, $constraints, $indexes) {
return $out;
}

/**
* Generate the SQL to drop a table.
*
* @param Cake\Database\Schema\Table $table Table instance
* @return array SQL statements to drop DROP a table.
*/
public function dropTableSql(Table $table) {
return [sprintf('DROP TABLE "%s"', $table->name())];
}

/**
* Generate the SQL to truncate a table.
*
Expand Down

0 comments on commit 0079674

Please sign in to comment.