Skip to content

Commit

Permalink
Added support of the SingleStoreDB (#48)
Browse files Browse the repository at this point in the history
* Added SingleStore support

* Fixed sqlite test

* Updated singlestoredb laravel dependency

* Fixed test config

* Created database

* removed unused dependency

* Formatting and refactoring

* Fix refactoring

* Moved singlestore dependency to dev and deleted singlestore provider

* Refactoring

* Refactor tests

---------

Co-authored-by: Jonas Staudenmeir <mail@jonas-staudenmeir.de>
  • Loading branch information
AdalbertMemSQL and staudenmeir committed Aug 20, 2023
1 parent 8582245 commit a68eff1
Show file tree
Hide file tree
Showing 14 changed files with 167 additions and 27 deletions.
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

0 comments on commit a68eff1

Please sign in to comment.