Skip to content

Commit

Permalink
Add selectConcat method to query builder (#434)
Browse files Browse the repository at this point in the history
Replaces Laravel's Grammar classes with October Rain versions, and sets the DB connections to use these new Grammars. Instead of copying over the entire grammars for the different database types, we'll just replace and amend what is necessary, so we should be safe with updates.
  • Loading branch information
bennothommo authored Oct 19, 2020
1 parent ac0215b commit d72caf0
Show file tree
Hide file tree
Showing 12 changed files with 322 additions and 6 deletions.
22 changes: 22 additions & 0 deletions src/Database/Capsule/Manager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace October\Rain\Database\Capsule;

use Illuminate\Database\DatabaseManager;
use Illuminate\Database\Capsule\Manager as BaseManager;
use October\Rain\Database\Connectors\ConnectionFactory;

class Manager extends BaseManager
{
/**
* Build the database manager instance.
*
* @return void
*/
protected function setupManager()
{
$factory = new ConnectionFactory($this->container);

$this->manager = new DatabaseManager($this->container, $factory);
}
}
2 changes: 1 addition & 1 deletion src/Database/Connections/MySqlConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use Illuminate\Database\Schema\MySqlBuilder;
use Illuminate\Database\Query\Processors\MySqlProcessor;
use Doctrine\DBAL\Driver\PDOMySql\Driver as DoctrineDriver;
use Illuminate\Database\Query\Grammars\MySqlGrammar as QueryGrammar;
use October\Rain\Database\Query\Grammars\MySqlGrammar as QueryGrammar;
use Illuminate\Database\Schema\Grammars\MySqlGrammar as SchemaGrammar;

class MySqlConnection extends Connection
Expand Down
2 changes: 1 addition & 1 deletion src/Database/Connections/PostgresConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use Illuminate\Database\Schema\PostgresBuilder;
use Doctrine\DBAL\Driver\PDOPgSql\Driver as DoctrineDriver;
use Illuminate\Database\Query\Processors\PostgresProcessor;
use Illuminate\Database\Query\Grammars\PostgresGrammar as QueryGrammar;
use October\Rain\Database\Query\Grammars\PostgresGrammar as QueryGrammar;
use Illuminate\Database\Schema\Grammars\PostgresGrammar as SchemaGrammar;

class PostgresConnection extends Connection
Expand Down
6 changes: 3 additions & 3 deletions src/Database/Connections/SQLiteConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
use Illuminate\Database\Schema\SQLiteBuilder;
use Illuminate\Database\Query\Processors\SQLiteProcessor;
use Doctrine\DBAL\Driver\PDOSqlite\Driver as DoctrineDriver;
use Illuminate\Database\Query\Grammars\SQLiteGrammar as QueryGrammar;
use October\Rain\Database\Query\Grammars\SQLiteGrammar as QueryGrammar;
use Illuminate\Database\Schema\Grammars\SQLiteGrammar as SchemaGrammar;

class SQLiteConnection extends Connection
{
/**
* Get the default query grammar instance.
*
* @return \Illuminate\Database\Query\Grammars\SQLiteGrammar
* @return \October\Rain\Database\Query\Grammars\SQLiteGrammar
*/
protected function getDefaultQueryGrammar()
{
Expand All @@ -35,7 +35,7 @@ public function getSchemaBuilder()
/**
* Get the default schema grammar instance.
*
* @return \Illuminate\Database\Schema\Grammars\SQLiteGrammar
* @return \October\Rain\Database\Query\Grammars\SQLiteGrammar
*/
protected function getDefaultSchemaGrammar()
{
Expand Down
2 changes: 1 addition & 1 deletion src/Database/Connections/SqlServerConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use Illuminate\Database\Schema\SqlServerBuilder;
use Doctrine\DBAL\Driver\PDOSqlsrv\Driver as DoctrineDriver;
use Illuminate\Database\Query\Processors\SqlServerProcessor;
use Illuminate\Database\Query\Grammars\SqlServerGrammar as QueryGrammar;
use October\Rain\Database\Query\Grammars\SqlServerGrammar as QueryGrammar;
use Illuminate\Database\Schema\Grammars\SqlServerGrammar as SchemaGrammar;

class SqlServerConnection extends Connection
Expand Down
67 changes: 67 additions & 0 deletions src/Database/Query/Grammars/Concerns/SelectConcatenations.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php namespace October\Rain\Database\Query\Grammars\Concerns;

use \Illuminate\Database\Query\Builder;
use Illuminate\Database\Query\Expression;

trait SelectConcatenations
{
/**
* Compile the "select *" portion of the query.
*
* This particular method will call the original compileColumns() method provided by the grammar, then append
* the concatenated columns to the query.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $columns
* @return string|null
*/
protected function compileColumns(Builder $query, $columns)
{
$select = parent::compileColumns($query, $columns);

if (count($query->concats)) {
$select .= $this->compileConcats($query);
}

return $select;
}

/**
* Compiles the concatenated columns and adds them to the "select" portion of the query.
*
* @param \Illuminate\Database\Query\Builder $query
* @return string
*/
protected function compileConcats(Builder $query)
{
$columns = [];

foreach ($query->concats as $as => $parts) {
$columns[] = $this->compileConcat($parts, $as);
}

return ', ' . implode(', ', $columns);
}

/**
* Compiles a single CONCAT value.
*
* @param array $parts The concatenation parts.
* @param string $as The alias to return the entire concatenation as.
* @return string
*/
protected function compileConcat(array $parts, string $as)
{
$compileParts = [];

foreach ($parts as $part) {
if (preg_match('/^[a-z_@#][a-z0-9@$#_]*$/', $part)) {
$compileParts[] = $this->wrap($part);
} else {
$compileParts[] = $this->wrap(new Expression('\'' . trim($part, '\'"') . '\''));
}
}

return 'concat(' . implode(', ', $compileParts) . ') as ' . $this->wrap($as);
}
}
9 changes: 9 additions & 0 deletions src/Database/Query/Grammars/MySqlGrammar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php namespace October\Rain\Database\Query\Grammars;

use Illuminate\Database\Query\Grammars\MySqlGrammar as BaseMysqlGrammer;
use October\Rain\Database\Query\Grammars\Concerns\SelectConcatenations;

class MySqlGrammar extends BaseMysqlGrammer
{
use SelectConcatenations;
}
9 changes: 9 additions & 0 deletions src/Database/Query/Grammars/PostgresGrammar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php namespace October\Rain\Database\Query\Grammars;

use Illuminate\Database\Query\Grammars\PostgresGrammar as BasePostgresGrammer;
use October\Rain\Database\Query\Grammars\Concerns\SelectConcatenations;

class PostgresGrammar extends BasePostgresGrammer
{
use SelectConcatenations;
}
34 changes: 34 additions & 0 deletions src/Database/Query/Grammars/SQLiteGrammar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php namespace October\Rain\Database\Query\Grammars;

use Illuminate\Database\Query\Expression;
use Illuminate\Database\Query\Grammars\SQLiteGrammar as BaseSQLiteGrammar;
use October\Rain\Database\Query\Grammars\Concerns\SelectConcatenations;

class SQLiteGrammar extends BaseSQLiteGrammar
{
use SelectConcatenations;

/**
* Compiles a single CONCAT value.
*
* SQLite uses slightly different concatenation syntax.
*
* @param array $parts The concatenation parts.
* @param string $as The alias to return the entire concatenation as.
* @return string
*/
protected function compileConcat(array $parts, string $as)
{
$compileParts = [];

foreach ($parts as $part) {
if (preg_match('/^[a-z_@#][a-z0-9@$#_]*$/', $part)) {
$compileParts[] = $this->wrap($part);
} else {
$compileParts[] = $this->wrap(new Expression('\'' . trim($part, '\'"') . '\''));
}
}

return implode(' || ', $compileParts) . ' as ' . $this->wrap($as);
}
}
9 changes: 9 additions & 0 deletions src/Database/Query/Grammars/SqlServerGrammar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php namespace October\Rain\Database\Query\Grammars;

use Illuminate\Database\Query\Grammars\SqlServerGrammar as BaseSqlServerGrammar;
use October\Rain\Database\Query\Grammars\Concerns\SelectConcatenations;

class SqlServerGrammar extends BaseSqlServerGrammar
{
use SelectConcatenations;
}
21 changes: 21 additions & 0 deletions src/Database/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ class QueryBuilder extends QueryBuilderBase
*/
protected $cachingDuplicateQueries = false;

/**
* The aliased concatenation columns.
*
* @var array
*/
public $concats = [];

/**
* Get an array with the values of a given column.
*
Expand Down Expand Up @@ -366,4 +373,18 @@ public function cachingDuplicates()
{
return $this->cachingDuplicateQueries;
}

/**
* Adds a concatenated column as an alias.
*
* @param array $parts The concatenation parts.
* @param string $as The name of the alias for the compiled concatenation.
* @return $this
*/
public function selectConcat(array $parts, string $as)
{
$this->concats[$as] = $parts;

return $this;
}
}
145 changes: 145 additions & 0 deletions tests/Database/SelectConcatTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php
use October\Rain\Database\Models\Revision;

class SelectConcatTest extends TestCase
{
public function testMySqlConcat()
{
$capsule = new October\Rain\Database\Capsule\Manager;
$capsule->addConnection([
'driver' => 'mysql',
'database' => ':memory:',
'prefix' => ''
]);
$capsule->setAsGlobal();
$capsule->bootEloquent();

$model = new Revision;

$query = $model
->newQuery()
->select(['id'])
->selectConcat(['field', ' ', 'cast'], 'full_cast')
->selectConcat(['field2', ' ', 'cast2'], 'full_cast2');

$this->assertEquals(
'select `id`, concat(`field`, \' \', `cast`) as `full_cast`, concat(`field2`, \' \', `cast2`) as `full_cast2` from `revisions`',
$query->toSql()
);

$query = $model
->newQuery()
->select(['id'])
->selectConcat(['"field"', ' ', 'cast'], 'full_cast');

$this->assertEquals(
'select `id`, concat(\'field\', \' \', `cast`) as `full_cast` from `revisions`',
$query->toSql()
);
}

public function testSQLiteConcat()
{
$capsule = new October\Rain\Database\Capsule\Manager;
$capsule->addConnection([
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => ''
]);
$capsule->setAsGlobal();
$capsule->bootEloquent();

$model = new Revision;

$query = $model
->newQuery()
->select(['id'])
->selectConcat(['field', ' ', 'cast'], 'full_cast')
->selectConcat(['field2', ' ', 'cast2'], 'full_cast2');

$this->assertEquals(
'select "id", "field" || \' \' || "cast" as "full_cast", "field2" || \' \' || "cast2" as "full_cast2" from "revisions"',
$query->toSql()
);

$query = $model
->newQuery()
->select(['id'])
->selectConcat(['"field"', ' ', 'cast'], 'full_cast');

$this->assertEquals(
'select "id", \'field\' || \' \' || "cast" as "full_cast" from "revisions"',
$query->toSql()
);
}

public function testPostgresqlConcat()
{
$capsule = new October\Rain\Database\Capsule\Manager;
$capsule->addConnection([
'driver' => 'pgsql',
'database' => ':memory:',
'prefix' => ''
]);
$capsule->setAsGlobal();
$capsule->bootEloquent();

$model = new Revision;

$query = $model
->newQuery()
->select(['id'])
->selectConcat(['field', ' ', 'cast'], 'full_cast')
->selectConcat(['field2', ' ', 'cast2'], 'full_cast2');

$this->assertEquals(
'select "id", concat("field", \' \', "cast") as "full_cast", concat("field2", \' \', "cast2") as "full_cast2" from "revisions"',
$query->toSql()
);

$query = $model
->newQuery()
->select(['id'])
->selectConcat(['"field"', ' ', 'cast'], 'full_cast');

$this->assertEquals(
'select "id", concat(\'field\', \' \', "cast") as "full_cast" from "revisions"',
$query->toSql()
);
}

public function testSqlServerConcat()
{
$capsule = new October\Rain\Database\Capsule\Manager;
$capsule->addConnection([
'driver' => 'sqlsrv',
'database' => ':memory:',
'prefix' => ''
]);
$capsule->setAsGlobal();
$capsule->bootEloquent();

$model = new Revision;

$query = $model
->newQuery()
->select(['id'])
->selectConcat(['field', ' ', 'cast'], 'full_cast')
->selectConcat(['field2', ' ', 'cast2'], 'full_cast2');

$this->assertEquals(
'select [id], concat([field], \' \', [cast]) as [full_cast], concat([field2], \' \', [cast2]) as [full_cast2] from [revisions]',
$query->toSql()
);

$query = $model
->newQuery()
->select(['id'])
->selectConcat(['"field"', ' ', 'cast'], 'full_cast');

$this->assertEquals(
'select [id], concat(\'field\', \' \', [cast]) as [full_cast] from [revisions]',
$query->toSql()
);
}
}

0 comments on commit d72caf0

Please sign in to comment.