Browse Source

Improve support for unioned queries on MySQL.

By wrapping unioned queries with () each arm in an union can have an
order clause. I've created a specialized compiler for SQLite as it seems
to be the only rdbms that does not support () in UNION from what I could
see.

Refs #5962
mark_story 11 years ago
parent
commit
8490c9da56

+ 11 - 0
src/Database/Dialect/SqliteDialectTrait.php

@@ -18,6 +18,7 @@ use Cake\Database\Dialect\TupleComparisonTranslatorTrait;
 use Cake\Database\ExpressionInterface;
 use Cake\Database\Expression\FunctionExpression;
 use Cake\Database\SqlDialectTrait;
+use Cake\Database\SqliteCompiler;
 
 /**
  * SQLite dialect trait
@@ -185,4 +186,14 @@ trait SqliteDialectTrait
     {
         return 'PRAGMA foreign_keys = ON';
     }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return \Cake\Database\SqliteCompiler
+     */
+    public function newCompiler()
+    {
+        return new SqliteCompiler();
+    }
 }

+ 2 - 1
src/Database/QueryCompiler.php

@@ -253,7 +253,8 @@ class QueryCompiler
         $parts = array_map(function ($p) use ($generator) {
             $p['query'] = $p['query']->sql($generator);
             $p['query'] = $p['query'][0] === '(' ? trim($p['query'], '()') : $p['query'];
-            return $p['all'] ? 'ALL ' . $p['query'] : $p['query'];
+            $prefix = $p['all'] ? 'ALL' : '';
+            return sprintf('%s (%s)', $prefix, $p['query']);
         }, $parts);
         return sprintf("\nUNION %s", implode("\nUNION ", $parts));
     }

+ 48 - 0
src/Database/SqliteCompiler.php

@@ -0,0 +1,48 @@
+<?php
+/**
+ * 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         3.0.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Database;
+
+use Cake\Database\QueryCompiler;
+
+/**
+ * Responsible for compiling a Query object into its SQL representation
+ * for SQLite
+ *
+ * @internal
+ */
+class SqliteCompiler extends QueryCompiler
+{
+
+    /**
+     * Builds the SQL string for all the UNION clauses in this query, when dealing
+     * with query objects it will also transform them using their configured SQL
+     * dialect.
+     *
+     * @param array $parts list of queries to be operated with UNION
+     * @param \Cake\Database\Query $query The query that is being compiled
+     * @param \Cake\Database\ValueBinder $generator the placeholder generator to be used in expressions
+     * @return string
+     */
+    protected function _buildUnionPart($parts, $query, $generator)
+    {
+        $parts = array_map(function ($p) use ($generator) {
+            $p['query'] = $p['query']->sql($generator);
+            $p['query'] = $p['query'][0] === '(' ? trim($p['query'], '()') : $p['query'];
+            $prefix = $p['all'] ? 'ALL' : '';
+            return sprintf('%s %s', $prefix, $p['query']);
+        }, $parts);
+        return sprintf("\nUNION %s", implode("\nUNION ", $parts));
+    }
+}

+ 25 - 0
tests/TestCase/Database/QueryTest.php

@@ -1945,6 +1945,31 @@ class QueryTest extends TestCase
     }
 
     /**
+     * Tests that it is possible to run unions with order statements
+     *
+     * @return void
+     */
+    public function testUnionOrderBy()
+    {
+        $this->skipIf(
+            $this->connection->driver() instanceof \Cake\Database\Driver\Sqlite,
+            'SQLite does not support ORDER BY in UNIONed queries.'
+        );
+        $union = (new Query($this->connection))
+            ->select(['id', 'title'])
+            ->from(['a' => 'articles'])
+            ->order(['a.id' => 'asc']);
+
+        $query = new Query($this->connection);
+        $result = $query->select(['id', 'comment'])
+            ->from(['c' => 'comments'])
+            ->order(['c.id' => 'asc'])
+            ->union($union)
+            ->execute();
+        $this->assertCount(self::COMMENT_COUNT + self::ARTICLE_COUNT, $result);
+    }
+
+    /**
      * Tests that UNION ALL can be built by setting the second param of union() to true
      *
      * @return void