Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support of the SingleStoreDB #48

Merged
merged 13 commits into from
Aug 20, 2023
17 changes: 16 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
strategy:
matrix:
php: [ 8.2, 8.1 ]
database: [ mysql, mariadb, pgsql, sqlite, sqlsrv ]
database: [ mysql, mariadb, pgsql, sqlite, sqlsrv, singlestore ]
release: [ stable, lowest ]
include:
- database: mysql
Expand All @@ -22,6 +22,8 @@ jobs:
pdo: sqlite
- database: sqlsrv
pdo: sqlsrv
- database: singlestore
pdo: singlestore
- php: 8.2
release: stable
coverage: xdebug
Expand Down Expand Up @@ -59,6 +61,16 @@ jobs:
--health-cmd "echo quit | /opt/mssql-tools/bin/sqlcmd -S 127.0.0.1 -l 1 -U sa -P Password!"
ports:
- 1433
singlestore:
# check for new versions at https://github.com/singlestore-labs/singlestoredb-dev-image/pkgs/container/singlestoredb-dev/versions
image: ghcr.io/singlestore-labs/singlestoredb-dev:0.2.8
ports:
- 3306
env:
# this license key is only authorized for use in SingleStore laravel-cte tests and is heavily restricted
# if you want a free SingleStore license for your own use please visit https://www.singlestore.com/cloud-trial/
SINGLESTORE_LICENSE: BDMwMzMyOTEyNjMwYzQ1ODE5MDdjNThiYjU1MGM5YTAyAAAAAAAAAAAEAAAAAAAAAAwwNQIZAKqnuBG9UX3K2enIHyshQGHZIjQiCZpqlwIYE8t4J8VewDLm2m4+4i8KorAIZsJd8j6EAA==
ROOT_PASSWORD: "test"

steps:
- uses: actions/checkout@v3
Expand All @@ -76,6 +88,8 @@ jobs:
coverage: ${{ matrix.coverage }}
- run: docker exec sqlsrv /opt/mssql-tools/bin/sqlcmd -S 127.0.0.1 -U sa -P Password! -Q "create database [test]"
if: matrix.database == 'sqlsrv'
- run: mysql -h 127.0.0.1 -u root -ptest -P ${{ job.services.singlestore.ports[3306] }} -e "create database test"
if: matrix.database == 'singlestore'
- run: composer update --no-interaction --no-progress --prefer-dist --prefer-${{ matrix.release }}
- run: cp tests/config/database.ci.php tests/config/database.php
- run: |
Expand All @@ -87,5 +101,6 @@ jobs:
MARIADB_PORT: ${{ job.services.mariadb.ports[3306] }}
PGSQL_PORT: ${{ job.services.pgsql.ports[5432] }}
SQLSRV_PORT: ${{ job.services.sqlsrv.ports[1433] }}
SINGLESTORE_PORT: ${{ job.services.singlestore.ports[3306] }}
- run: php tests/coverage/scrutinizer.php
if: matrix.coverage == 'xdebug'
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Supports Laravel 5.5+.
- PostgreSQL 9.4+
- SQLite 3.8.3+
- SQL Server 2008+
- SingleStore 8.1+

## Installation

Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"require-dev": {
"orchestra/testbench": "^8.0",
"phpunit/phpunit": "^10.1",
"phpstan/phpstan": "^1.10"
"phpstan/phpstan": "^1.10",
"singlestoredb/singlestoredb-laravel": "^1.5.1"
},
"autoload": {
"psr-4": {
Expand Down
19 changes: 19 additions & 0 deletions src/Connections/SingleStoreConnection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Staudenmeir\LaravelCte\Connections;

use SingleStore\Laravel\Connect\Connection;
use Staudenmeir\LaravelCte\Query\SingleStoreBuilder;

class SingleStoreConnection extends Connection
{
/**
* Get a new query builder instance.
*
* @return \Illuminate\Database\Query\Builder
*/
public function query()
{
return new SingleStoreBuilder($this);
}
}
5 changes: 4 additions & 1 deletion src/Connectors/ConnectionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Staudenmeir\LaravelCte\Connections\MySqlConnection;
use Staudenmeir\LaravelCte\Connections\PostgresConnection;
use Staudenmeir\LaravelCte\Connections\SQLiteConnection;
use Staudenmeir\LaravelCte\Connections\SingleStoreConnection;
use Staudenmeir\LaravelCte\Connections\SqlServerConnection;

class ConnectionFactory extends Base
Expand All @@ -26,7 +27,7 @@ class ConnectionFactory extends Base
*/
protected function createConnection($driver, $connection, $database, $prefix = '', array $config = [])
{
if ($resolver = Connection::getResolver($driver)) {
if ($driver !== 'singlestore' && $resolver = Connection::getResolver($driver)) {
return $resolver($connection, $database, $prefix, $config); // @codeCoverageIgnore
}

Expand All @@ -39,6 +40,8 @@ protected function createConnection($driver, $connection, $database, $prefix = '
return new SQLiteConnection($connection, $database, $prefix, $config);
case 'sqlsrv':
return new SqlServerConnection($connection, $database, $prefix, $config);
case 'singlestore':
return new SingleStoreConnection($connection, $database, $prefix, $config);
}

throw new InvalidArgumentException("Unsupported driver [{$driver}]"); // @codeCoverageIgnore
Expand Down
11 changes: 11 additions & 0 deletions src/Query/Grammars/SingleStoreGrammar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Staudenmeir\LaravelCte\Query\Grammars;

use SingleStore\Laravel\Query\Grammar;
use Staudenmeir\LaravelCte\Query\Grammars\Traits\CompilesMySqlExpressions;

class SingleStoreGrammar extends Grammar
{
use CompilesMySqlExpressions;
}
3 changes: 2 additions & 1 deletion src/Query/Grammars/Traits/CompilesExpressions.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Str;
use Staudenmeir\LaravelCte\Query\Builder as CteBuilder;
use Staudenmeir\LaravelCte\Query\SingleStoreBuilder;

trait CompilesExpressions
{
Expand Down Expand Up @@ -107,7 +108,7 @@ public function compileSelect(Builder $query)
{
$sql = parent::compileSelect($query);

if ($query instanceof CteBuilder) {
if ($query instanceof CteBuilder || $query instanceof SingleStoreBuilder) {
if ($query->unionExpressions) {
$sql = $this->compileExpressions($query, $query->unionExpressions) . " $sql";
}
Expand Down
11 changes: 11 additions & 0 deletions src/Query/SingleStoreBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Staudenmeir\LaravelCte\Query;

use SingleStore\Laravel\Query\Builder;
use Staudenmeir\LaravelCte\Query\Traits\BuildsExpressionQueries;

class SingleStoreBuilder extends Builder
{
use BuildsExpressionQueries;
}
2 changes: 2 additions & 0 deletions src/Query/Traits/BuildsExpressionQueries.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use RuntimeException;
use Staudenmeir\LaravelCte\Query\Grammars\MySqlGrammar;
use Staudenmeir\LaravelCte\Query\Grammars\PostgresGrammar;
use Staudenmeir\LaravelCte\Query\Grammars\SingleStoreGrammar;
use Staudenmeir\LaravelCte\Query\Grammars\SQLiteGrammar;
use Staudenmeir\LaravelCte\Query\Grammars\SqlServerGrammar;

Expand Down Expand Up @@ -74,6 +75,7 @@ protected function getQueryGrammar(Connection $connection)
'pgsql' => new PostgresGrammar(),
'sqlite' => new SQLiteGrammar(),
'sqlsrv' => new SqlServerGrammar(),
'singlestore' => new SingleStoreGrammar(),
default => throw new RuntimeException('This database is not supported.'), // @codeCoverageIgnore
};

Expand Down
26 changes: 17 additions & 9 deletions tests/EloquentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public function testWithExpression()
$users = User::withExpression('ids', 'select 1 union all select 2', ['id'])
->whereIn('id', function (Builder $query) {
$query->from('ids');
})->get();
})->orderBy('id')->get();

$this->assertEquals([1, 2], $users->pluck('id')->all());
}
Expand All @@ -30,9 +30,10 @@ public function testWithRecursiveExpression()

$users = User::from('ancestors')
->withRecursiveExpression('ancestors', $query)
->orderBy('id')
->get();

$this->assertEquals([3, 2, 1], $users->pluck('id')->all());
$this->assertEquals([1, 2, 3], $users->pluck('id')->all());
}

public function testWithRecursiveExpressionAndCycleDetection()
Expand Down Expand Up @@ -81,7 +82,7 @@ public function testInsertUsing()
Post::withExpression('u', User::select('id')->where('id', '>', 1))
->insertUsing(['user_id'], User::from('u'));

$this->assertEquals([1, 2, 2, 3], Post::pluck('user_id')->all());
$this->assertEquals([1, 2, 2, 3], Post::orderBy('user_id')->pluck('user_id')->all());
}

public function testUpdate()
Expand Down Expand Up @@ -116,7 +117,9 @@ public function testUpdateWithJoin()

public function testUpdateWithLimit()
{
if (in_array($this->database, ['mariadb', 'sqlsrv'])) {
// SingleStore support update with limit only when it is constrained to a single partition
// https://docs.singlestore.com/cloud/reference/sql-reference/data-manipulation-language-dml/update/#update-using-limit
if (in_array($this->database, ['mariadb', 'sqlsrv', 'singlestore'])) {
$this->markTestSkipped();
}

Expand Down Expand Up @@ -164,11 +167,16 @@ public function testDeleteWithLimit()
$this->markTestSkipped();
}

Post::withExpression('u', User::where('id', '>', 0))
->whereIn('user_id', User::from('u')->select('id'))
->orderBy('id')
->limit(1)
->delete();
if ($this->database === 'singlestore') {
$query = Post::withExpression('u', User::where('id', '<', 2));
} else {
$query = Post::withExpression('u', User::where('id', '>', 0))
->orderBy('id');
}

$query->whereIn('user_id', User::from('u')->select('id'))
->limit(1)
->delete();

$this->assertEquals([2], Post::pluck('user_id')->all());
}
Expand Down
54 changes: 40 additions & 14 deletions tests/QueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,27 @@

use DateTime;
use Illuminate\Database\Connection;
use Illuminate\Database\Query\Builder as BaseBuilder;
use Illuminate\Database\Query\Processors\Processor;
use Illuminate\Support\Facades\DB;
use Staudenmeir\LaravelCte\DatabaseServiceProvider;
use Staudenmeir\LaravelCte\Query\Builder;
use Staudenmeir\LaravelCte\Query\SingleStoreBuilder;

class QueryTest extends TestCase
{
public function testWithExpression()
{
$posts = function (BaseBuilder $query) {
$query->from('posts');
};

$rows = DB::table('u')
->select('u.id')
->withExpression('u', DB::table('users'))
->withExpression('p', function (Builder $query) {
$query->from('posts');
})
->withExpression('p', $posts)
->join('p', 'p.user_id', '=', 'u.id')
->orderBy('u.id')
->get();

$this->assertEquals([1, 2], $rows->pluck('id')->all());
Expand Down Expand Up @@ -79,7 +84,14 @@ public function testWithExpressionSqlServer()

public function testWithRecursiveExpression()
{
$query = 'select 1 union all select number + 1 from numbers where number < 3';
// SingleStore doesn't support previous variant of the RCTE
// It throws the following error:
// Unsupported recursive common table expression query shape: recursive CTE select cannot be materialized.
if ($this->database === 'singlestore') {
$query = 'select 1 as number from `users` limit 1 union all select number + 1 from numbers where number < 3';
} else {
$query = 'select 1 union all select number + 1 from numbers where number < 3';
}

$rows = DB::table('numbers')
->withRecursiveExpression('numbers', $query, ['number'])
Expand Down Expand Up @@ -325,7 +337,7 @@ public function testInsertUsing()
->withExpression('u', DB::table('users')->select('id')->where('id', '>', 1))
->insertUsing(['user_id'], DB::table('u'));

$this->assertEquals([1, 2, 2, 3], DB::table('posts')->pluck('user_id')->all());
$this->assertEquals([1, 2, 2, 3], DB::table('posts')->orderBy('user_id')->pluck('user_id')->all());
}

public function testInsertUsingWithRecursionLimit()
Expand Down Expand Up @@ -373,7 +385,9 @@ public function testUpdateWithJoin()

public function testUpdateWithLimit()
{
if (in_array($this->database, ['mariadb', 'sqlsrv'])) {
// SingleStore support update with limit only when it is constrained to a single partition
// https://docs.singlestore.com/cloud/reference/sql-reference/data-manipulation-language-dml/update/#update-using-limit
if (in_array($this->database, ['mariadb', 'sqlsrv', 'singlestore'])) {
$this->markTestSkipped();
}

Expand Down Expand Up @@ -440,12 +454,18 @@ public function testDeleteWithLimit()
$this->markTestSkipped();
}

DB::table('posts')
->withExpression('u', DB::table('users')->where('id', '>', 0))
->whereIn('user_id', DB::table('u')->select('id'))
->orderBy('id')
->limit(1)
->delete();
if ($this->database === 'singlestore') {
$query = DB::table('posts')
->withExpression('u', DB::table('users')->where('id', '<', 2));
} else {
$query = DB::table('posts')
->withExpression('u', DB::table('users')->where('id', '>', 0))
->orderBy('id');
}

$query->whereIn('user_id', DB::table('u')->select('id'))
->limit(1)
->delete();

$this->assertEquals([2], DB::table('posts')->pluck('user_id')->all());
}
Expand All @@ -469,11 +489,17 @@ protected function getBuilder($database)
$grammar = 'Staudenmeir\LaravelCte\Query\Grammars\\'.$database.'Grammar';
$processor = $this->createMock(Processor::class);

return new Builder($connection, new $grammar(), $processor);
return match ($database) {
'singlestore' => new SingleStoreBuilder($connection, new $grammar(), $processor),
default => new Builder($connection, new $grammar(), $processor),
};
}

protected function getPackageProviders($app)
{
return [DatabaseServiceProvider::class];
return array_merge(
parent::getPackageProviders($app),
[DatabaseServiceProvider::class]
);
}
}
6 changes: 6 additions & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Orchestra\Testbench\TestCase as Base;
use Staudenmeir\LaravelCte\Tests\Models\Post;
use Staudenmeir\LaravelCte\Tests\Models\User;
use SingleStore\Laravel\SingleStoreProvider;

abstract class TestCase extends Base
{
Expand Down Expand Up @@ -55,4 +56,9 @@ protected function getEnvironmentSetUp($app)

$app['config']->set('database.connections.testing', $config[$this->database]);
}

protected function getPackageProviders($app)
{
return [SingleStoreProvider::class];
}
}
18 changes: 18 additions & 0 deletions tests/config/database.ci.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,22 @@
'prefix' => '',
'prefix_indexes' => true,
],
'singlestore' => [
'driver' => 'singlestore',
'host' => '127.0.0.1',
'port' => getenv('SINGLESTORE_PORT'),
'database' => 'test',
'username' => 'root',
'password' => 'test',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
PDO::ATTR_EMULATE_PREPARES => true,
]) : [],
],
];
Loading