From 7f19f76494995ec1566dbcaf228792a32aa0e3ff Mon Sep 17 00:00:00 2001 From: mark_story Date: Wed, 19 Jun 2013 22:21:37 -0400 Subject: [PATCH] Add update with join support for Postgres. Postgres requires some query fudging to make joins with updates syntactically valid. Add a query translator that moves joins around and creates a 'from' clause. --- .../Database/Dialect/PostgresDialectTrait.php | 36 +++++++++++++++++++ lib/Cake/Database/Query.php | 8 +++-- .../TestCase/Database/Driver/PostgresTest.php | 25 +++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/lib/Cake/Database/Dialect/PostgresDialectTrait.php b/lib/Cake/Database/Dialect/PostgresDialectTrait.php index e642f576f94..98fa439acde 100644 --- a/lib/Cake/Database/Dialect/PostgresDialectTrait.php +++ b/lib/Cake/Database/Dialect/PostgresDialectTrait.php @@ -80,6 +80,42 @@ 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 ($joins) { + $sql = ''; + foreach ($joins as $i => $join) { + if ($i == 0) { + $sql .= sprintf('%s %s', $join['table'], $join['alias']); + if (isset($join['conditions']) && count($join['conditions'])) { + $query->where($join['conditions']); + } + continue; + } + $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 diff --git a/lib/Cake/Database/Query.php b/lib/Cake/Database/Query.php index ebad32e3e3e..164c0640328 100644 --- a/lib/Cake/Database/Query.php +++ b/lib/Cake/Database/Query.php @@ -291,7 +291,7 @@ protected function _traverseDelete(callable $visitor) { * @return void */ protected function _traverseUpdate(callable $visitor) { - $parts = ['update', 'join', 'set', 'where']; + $parts = ['update', 'join', 'set', 'from', 'where']; foreach ($parts as $name) { call_user_func($visitor, $this->_parts[$name], $name); } @@ -597,7 +597,6 @@ public function join($tables = null, $types = [], $overwrite = false) { if (!($t['conditions']) instanceof ExpressionInterface) { $t['conditions'] = $this->newExpr()->add($t['conditions'], $types); } - $joins[] = $t + ['type' => 'INNER', 'alias' => is_string($alias) ? $alias : null]; } @@ -623,6 +622,11 @@ public function join($tables = null, $types = [], $overwrite = false) { protected function _buildJoinPart($parts) { $joins = ''; foreach ($parts as $join) { + if ($join instanceof ExpressionInterface) { + $joins .= $join->sql(); + continue; + } + $joins .= sprintf(' %s JOIN %s %s', $join['type'], $join['table'], $join['alias']); if (isset($join['conditions']) && count($join['conditions'])) { $joins .= sprintf(' ON %s', $join['conditions']); diff --git a/lib/Cake/Test/TestCase/Database/Driver/PostgresTest.php b/lib/Cake/Test/TestCase/Database/Driver/PostgresTest.php index afa31f5f7f7..e684567040e 100644 --- a/lib/Cake/Test/TestCase/Database/Driver/PostgresTest.php +++ b/lib/Cake/Test/TestCase/Database/Driver/PostgresTest.php @@ -20,6 +20,7 @@ use Cake\Core\Configure; use Cake\Database\Connection; use Cake\Database\Driver\Postgres; +use Cake\Database\Query; use \PDO; /** @@ -135,4 +136,28 @@ public function testConnectionConfigCustom() { $driver->connect(); } + public function testUpdateWithJoin() { + $driver = $this->getMock('Cake\Database\driver\Postgres', ['_connect', 'connection']); + $connection = new Connection(Configure::read('Datasource.test')); + $connection->driver($driver); + + $query = new Query($connection); + + $query->update('articles') + ->set('title', 'New title') + ->join([ + 'table' => 'authors', + 'alias' => 'a', + 'conditions' => 'author_id = a.id' + ]) + ->join('comments') + ->where(['articles.id' => 1]); + $result = $query->sql(true); + + $this->assertContains('UPDATE articles SET title = :', $result); + $this->assertContains('FROM authors a INNER JOIN comments ON 1 = 1', $result); + $this->assertContains('WHERE (articles.id = :', $result); + $this->assertContains('AND author_id = a.id)', $result); + } + }