From d0ae740a8e165f0ec84322a7777131cf42972ee1 Mon Sep 17 00:00:00 2001 From: Jonas Staudenmeir Date: Fri, 8 Mar 2024 13:10:35 +0100 Subject: [PATCH] Refactor tests --- tests/EloquentTest.php | 22 +-- tests/Models/Post.php | 2 + tests/Models/User.php | 2 + tests/QueryTest.php | 323 ++++++----------------------------------- tests/TestCase.php | 36 +++-- 5 files changed, 86 insertions(+), 299 deletions(-) diff --git a/tests/EloquentTest.php b/tests/EloquentTest.php index 5197d40..1a484ec 100644 --- a/tests/EloquentTest.php +++ b/tests/EloquentTest.php @@ -3,7 +3,6 @@ namespace Staudenmeir\LaravelCte\Tests; use DateTime; -use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Expression; use Staudenmeir\LaravelCte\Tests\Models\Post; use Staudenmeir\LaravelCte\Tests\Models\User; @@ -12,12 +11,12 @@ class EloquentTest extends TestCase { public function testWithExpression() { - $users = User::withExpression('ids', 'select 1 union all select 2', ['id']) - ->whereIn('id', function (Builder $query) { - $query->from('ids'); - })->orderBy('id')->get(); + $users = User::withExpression('u', User::where('id', '>', 1)) + ->from('u') + ->orderBy('id') + ->get(); - $this->assertEquals([1, 2], $users->pluck('id')->all()); + $this->assertEquals([2, 3], $users->pluck('id')->all()); } public function testWithRecursiveExpression() @@ -79,8 +78,13 @@ public function testOuterUnion() public function testInsertUsing() { - Post::withExpression('u', User::select('id')->where('id', '>', 1)) - ->insertUsing(['user_id'], User::from('u')); + $query = User::selectRaw('(select max(id) from posts) + id as id') + ->addSelect('id as post_id') + ->selectRaw('1 as views') + ->where('id', '>', 1); + + Post::withExpression('u', $query) + ->insertUsing(['id', 'user_id', 'views'], User::from('u')); $this->assertEquals([1, 2, 2, 3], Post::orderBy('user_id')->pluck('user_id')->all()); } @@ -117,8 +121,6 @@ public function testUpdateWithJoin() public function testUpdateWithLimit() { - // 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->connection, ['mariadb', 'sqlsrv', 'singlestore'])) { $this->markTestSkipped(); } diff --git a/tests/Models/Post.php b/tests/Models/Post.php index dd21c8d..5a56f3b 100644 --- a/tests/Models/Post.php +++ b/tests/Models/Post.php @@ -8,4 +8,6 @@ class Post extends Model { use QueriesExpressions; + + public $incrementing = false; } diff --git a/tests/Models/User.php b/tests/Models/User.php index 1e11524..ec1830d 100644 --- a/tests/Models/User.php +++ b/tests/Models/User.php @@ -8,4 +8,6 @@ class User extends Model { use QueriesExpressions; + + public $incrementing = false; } diff --git a/tests/QueryTest.php b/tests/QueryTest.php index a1bf409..8a31807 100644 --- a/tests/QueryTest.php +++ b/tests/QueryTest.php @@ -3,13 +3,9 @@ namespace Staudenmeir\LaravelCte\Tests; 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 { @@ -30,76 +26,8 @@ public function testWithExpression() $this->assertEquals([1, 2], $rows->pluck('id')->all()); } - public function testWithExpressionMySql() - { - $builder = $this->getBuilder('MySql'); - $builder->select('u.id') - ->from('u') - ->withExpression('u', $this->getBuilder('MySql')->from('users')) - ->withExpression('p', $this->getBuilder('MySql')->from('posts')) - ->join('p', 'p.user_id', '=', 'u.id'); - - $expected = 'with `u` as (select * from `users`), `p` as (select * from `posts`) select `u`.`id` from `u` inner join `p` on `p`.`user_id` = `u`.`id`'; - $this->assertEquals($expected, $builder->toSql()); - } - - public function testWithExpressionPostgres() - { - $builder = $this->getBuilder('Postgres'); - $builder->select('u.id') - ->from('u') - ->withExpression('u', $this->getBuilder('Postgres')->from('users')) - ->withExpression('p', $this->getBuilder('Postgres')->from('posts')) - ->join('p', 'p.user_id', '=', 'u.id'); - - $expected = 'with "u" as (select * from "users"), "p" as (select * from "posts") select "u"."id" from "u" inner join "p" on "p"."user_id" = "u"."id"'; - $this->assertEquals($expected, $builder->toSql()); - } - - public function testWithExpressionSQLite() - { - $builder = $this->getBuilder('SQLite'); - $builder->select('u.id') - ->from('u') - ->withExpression('u', $this->getBuilder('SQLite')->from('users')) - ->withExpression('p', $this->getBuilder('SQLite')->from('posts')) - ->join('p', 'p.user_id', '=', 'u.id'); - - $expected = 'with "u" as (select * from "users"), "p" as (select * from "posts") select "u"."id" from "u" inner join "p" on "p"."user_id" = "u"."id"'; - $this->assertEquals($expected, $builder->toSql()); - } - - public function testWithExpressionSqlServer() - { - $builder = $this->getBuilder('SqlServer'); - $builder->select('u.id') - ->from('u') - ->withExpression('u', $this->getBuilder('SqlServer')->from('users')) - ->withExpression('p', $this->getBuilder('SqlServer')->from('posts')) - ->join('p', 'p.user_id', '=', 'u.id'); - - $expected = 'with [u] as (select * from [users]), [p] as (select * from [posts]) select [u].[id] from [u] inner join [p] on [p].[user_id] = [u].[id]'; - $this->assertEquals($expected, $builder->toSql()); - } - - public function testWithExpressionSingleStore() - { - $builder = $this->getBuilder('SingleStore'); - $builder->select('u.id') - ->from('u') - ->withExpression('u', $this->getBuilder('SingleStore')->from('users')) - ->withExpression('p', $this->getBuilder('SingleStore')->from('posts')) - ->join('p', 'p.user_id', '=', 'u.id'); - - $expected = 'with `u` as (select * from `users`), `p` as (select * from `posts`) select `u`.`id` from `u` inner join `p` on `p`.`user_id` = `u`.`id`'; - $this->assertEquals($expected, $builder->toSql()); - } - public function testWithRecursiveExpression() { - // 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->connection === 'singlestore') { $query = 'select 1 as number from `users` limit 1 union all select number + 1 from numbers where number < 3'; } else { @@ -113,114 +41,6 @@ public function testWithRecursiveExpression() $this->assertEquals([1, 2, 3], $rows->pluck('number')->all()); } - public function testWithRecursiveExpressionMySql() - { - $query = $this->getBuilder('MySql') - ->selectRaw('1') - ->unionAll( - $this->getBuilder('MySql') - ->selectRaw('number + 1') - ->from('numbers') - ->where('number', '<', 3) - ); - $builder = $this->getBuilder('MySql'); - $builder->from('numbers') - ->withRecursiveExpression('numbers', $query, ['number']); - - $expected = 'with recursive `numbers` (`number`) as ('.$query->toSql().') select * from `numbers`'; - $this->assertEquals($expected, $builder->toSql()); - $this->assertEquals([3], $builder->getRawBindings()['expressions']); - } - - public function testWithRecursiveExpressionPostgres() - { - $query = $this->getBuilder('Postgres') - ->selectRaw('1') - ->unionAll( - $this->getBuilder('Postgres') - ->selectRaw('number + 1') - ->from('numbers') - ->where('number', '<', 3) - ); - $builder = $this->getBuilder('Postgres'); - $builder->from('numbers') - ->withRecursiveExpression('numbers', $query, ['number']); - - $expected = 'with recursive "numbers" ("number") as ('.$query->toSql().') select * from "numbers"'; - $this->assertEquals($expected, $builder->toSql()); - $this->assertEquals([3], $builder->getRawBindings()['expressions']); - } - - public function testWithRecursiveExpressionSQLite() - { - $query = $this->getBuilder('SQLite') - ->selectRaw('1') - ->unionAll( - $this->getBuilder('SQLite') - ->selectRaw('number + 1') - ->from('numbers') - ->where('number', '<', 3) - ); - $builder = $this->getBuilder('SQLite'); - $builder->from('numbers') - ->withRecursiveExpression('numbers', $query, ['number']); - - $expected = 'with recursive "numbers" ("number") as (select * from (select 1) union all select number + 1 from "numbers" where "number" < ?) select * from "numbers"'; - $this->assertEquals($expected, $builder->toSql()); - $this->assertEquals([3], $builder->getRawBindings()['expressions']); - } - - public function testUnionSQLite() - { - $builder = $this->getBuilder('SQLite') - ->from('users') - ->unionAll( - $this->getBuilder('SQLite') - ->from('posts') - ); - - $expected = 'select * from (select * from "users") union all select * from (select * from "posts")'; - $this->assertEquals($expected, $builder->toSql()); - } - - public function testWithRecursiveExpressionSqlServer() - { - $query = $this->getBuilder('SqlServer') - ->selectRaw('1') - ->unionAll( - $this->getBuilder('SqlServer') - ->selectRaw('number + 1') - ->from('numbers') - ->where('number', '<', 3) - ); - $builder = $this->getBuilder('SqlServer'); - $builder->from('numbers') - ->withRecursiveExpression('numbers', $query, ['number']); - - $expected = 'with [numbers] ([number]) as ('.$query->toSql().') select * from [numbers]'; - $this->assertEquals($expected, $builder->toSql()); - $this->assertEquals([3], $builder->getRawBindings()['expressions']); - } - - public function testWithRecursiveExpressionSingleStore() - { - $query = $this->getBuilder('SingleStore') - ->selectRaw('1') - ->unionAll( - $this->getBuilder('SingleStore') - ->selectRaw('number + 1') - ->from('numbers') - ->where('number', '<', 3) - ); - $builder = $this->getBuilder('SingleStore'); - $builder->from('numbers') - ->withRecursiveExpression('numbers', $query, ['number']); - - $expected = 'with recursive `numbers` (`number`) as ('.$query->toSql().') select * from `numbers`'; - $this->assertEquals($expected, $builder->toSql()); - $this->assertEquals([3], $builder->getRawBindings()['expressions']); - } - public function testWithRecursiveExpressionAndCycleDetection() { if (!in_array($this->connection, ['mariadb', 'pgsql'])) { @@ -258,28 +78,6 @@ public function testWithMaterializedExpression() $this->assertEquals([1, 2, 3], $rows->pluck('id')->all()); } - public function testWithMaterializedExpressionPostgres() - { - $builder = $this->getBuilder('Postgres'); - $builder->select('u.id') - ->from('u') - ->withMaterializedExpression('u', $this->getBuilder('Postgres')->from('users')); - - $expected = 'with "u" as materialized (select * from "users") select "u"."id" from "u"'; - $this->assertEquals($expected, $builder->toSql()); - } - - public function testWithMaterializedExpressionSQLite() - { - $builder = $this->getBuilder('SQLite'); - $builder->select('u.id') - ->from('u') - ->withMaterializedExpression('u', $this->getBuilder('SQLite')->from('users')); - - $expected = 'with "u" as materialized (select * from "users") select "u"."id" from "u"'; - $this->assertEquals($expected, $builder->toSql()); - } - public function testWithNonMaterializedExpression() { if (!in_array($this->connection, ['pgsql', 'sqlite'])) { @@ -294,34 +92,20 @@ public function testWithNonMaterializedExpression() $this->assertEquals([1, 2, 3], $rows->pluck('id')->all()); } - public function testWithNonMaterializedExpressionPostgres() - { - $builder = $this->getBuilder('Postgres'); - $builder->select('u.id') - ->from('u') - ->withNonMaterializedExpression('u', $this->getBuilder('Postgres')->from('users')); - - $expected = 'with "u" as not materialized (select * from "users") select "u"."id" from "u"'; - $this->assertEquals($expected, $builder->toSql()); - } - - public function testWithNonMaterializedExpressionSQLite() + public function testRecursionLimit() { - $builder = $this->getBuilder('SQLite'); - $builder->select('u.id') - ->from('u') - ->withNonMaterializedExpression('u', $this->getBuilder('SQLite')->from('users')); + if ($this->connection !== 'sqlsrv') { + $this->markTestSkipped(); + } - $expected = 'with "u" as not materialized (select * from "users") select "u"."id" from "u"'; - $this->assertEquals($expected, $builder->toSql()); - } + $query = 'select 1 union all select number + 1 from numbers where number < 102'; - public function testRecursionLimit() - { - $builder = $this->getBuilder('SqlServer'); - $builder->from('users')->recursionLimit(100); + $rows = DB::table('numbers') + ->withRecursiveExpression('numbers', $query, ['number']) + ->recursionLimit(101) + ->get(); - $this->assertEquals('select * from [users] option (maxrecursion 100)', $builder->toSql()); + $this->assertCount(102, $rows); } public function testOuterUnion() @@ -339,47 +123,40 @@ public function testOuterUnion() $this->assertEquals([1, 2], $rows->pluck('id')->all()); } - public function testOuterUnionWithRecursionLimit() - { - $builder = $this->getBuilder('SqlServer'); - $builder->from('users')->recursionLimit(100); - - $builder = $this->getBuilder('SqlServer') - ->from('u') - ->where('id', 1) - ->unionAll( - $this->getBuilder('SqlServer') - ->from('u') - ->where('id', 2) - ) - ->withExpression('u', $this->getBuilder('SqlServer')->from('users')) - ->recursionLimit(100); - - $expected = <<assertEquals($expected, $builder->toSql()); - } - public function testInsertUsing() { + $query = DB::table('users') + ->selectRaw('(select max(id) from posts) + id as id') + ->addSelect('id as post_id') + ->selectRaw('1 as views') + ->where('id', '>', 1); + DB::table('posts') - ->withExpression('u', DB::table('users')->select('id')->where('id', '>', 1)) - ->insertUsing(['user_id'], DB::table('u')); + ->withExpression('u', $query) + ->insertUsing(['id', 'user_id', 'views'], DB::table('u')); $this->assertEquals([1, 2, 2, 3], DB::table('posts')->orderBy('user_id')->pluck('user_id')->all()); } public function testInsertUsingWithRecursionLimit() { - $builder = $this->getBuilder('SqlServer'); - $query = 'insert into [posts] ([id]) select [id] from [users] option (maxrecursion 100)'; - $builder->getConnection()->expects($this->once())->method('affectingStatement')->with($query, []); + if ($this->connection !== 'sqlsrv') { + $this->markTestSkipped(); + } + + $numbers = 'select 1 union all select number + 1 from numbers where number < 102'; - $builder->from('posts') - ->recursionLimit(100) - ->insertUsing(['id'], $this->getBuilder('SqlServer')->from('users')->select('id')); + $users = DB::table('numbers') + ->selectRaw('(select max(id) from users) + number as id') + ->selectRaw('0 as followers'); + + DB::table('users') + ->withRecursiveExpression('numbers', $numbers, ['number']) + ->withExpression('u', $users) + ->recursionLimit(101) + ->insertUsing(['id', 'followers'], DB::table('u')); + + $this->assertEquals(105, DB::table('users')->count()); } public function testUpdate() @@ -416,8 +193,6 @@ public function testUpdateWithJoin() public function testUpdateWithLimit() { - // 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->connection, ['mariadb', 'sqlsrv', 'singlestore'])) { $this->markTestSkipped(); } @@ -501,29 +276,19 @@ public function testDeleteWithLimit() $this->assertEquals([2], DB::table('posts')->pluck('user_id')->all()); } - public function testOffsetSqlServer() + public function testOffset() { - $expected = 'with [p] as (select * from [posts]) select * from [users] inner join [p] on [p].[user_id] = [users].[id] order by (SELECT 0) offset 5 rows'; - - $query = $this->getBuilder('SqlServer') - ->from('users') - ->withExpression('p', $this->getBuilder('SqlServer')->from('posts')) - ->join('p', 'p.user_id', '=', 'users.id') - ->offset(5); - - $this->assertEquals($expected, $query->toSql()); - } + if ($this->connection !== 'sqlsrv') { + $this->markTestSkipped(); + } - protected function getBuilder($database) - { - $connection = $this->createMock(Connection::class); - $grammar = 'Staudenmeir\LaravelCte\Query\Grammars\\'.$database.'Grammar'; - $processor = $this->createMock(Processor::class); + $rows = DB::table('u') + ->withExpression('u', DB::table('users')) + ->limit(1) + ->offset(1) + ->get(); - return match ($database) { - 'SingleStore' => new SingleStoreBuilder($connection, new $grammar(), $processor), - default => new Builder($connection, new $grammar(), $processor), - }; + $this->assertEquals([2], $rows->pluck('id')->all()); } protected function getPackageProviders($app) diff --git a/tests/TestCase.php b/tests/TestCase.php index ebd02e7..4752ded 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Orchestra\Testbench\TestCase as Base; use Staudenmeir\LaravelCte\Tests\Models\Post; @@ -23,31 +24,46 @@ protected function setUp(): void Schema::dropAllTables(); Schema::create('users', function (Blueprint $table) { - $table->increments('id'); - $table->unsignedInteger('parent_id')->nullable(); + $table->unsignedBigInteger('id')->unique(); + $table->unsignedBigInteger('parent_id')->nullable(); $table->unsignedBigInteger('followers'); $table->timestamps(); + + if ($this->connection === 'singlestore') { + $table->shardKey('id'); + } }); Schema::create('posts', function (Blueprint $table) { - $table->increments('id'); - $table->unsignedInteger('user_id'); - $table->unsignedBigInteger('views')->default(0); + $table->unsignedBigInteger('id')->unique(); + $table->unsignedBigInteger('user_id'); + $table->unsignedBigInteger('views'); $table->timestamps(); + + if ($this->connection === 'singlestore') { + $table->shardKey('id'); + } }); Model::unguard(); - User::create(['parent_id' => null, 'followers' => 10]); - User::create(['parent_id' => 1, 'followers' => 20]); - User::create(['parent_id' => 2, 'followers' => 30]); + User::create(['id' => 1, 'parent_id' => null, 'followers' => 10]); + User::create(['id' => 2, 'parent_id' => 1, 'followers' => 20]); + User::create(['id' => 3, 'parent_id' => 2, 'followers' => 30]); - Post::create(['user_id' => 1]); - Post::create(['user_id' => 2]); + Post::create(['id' => 11, 'user_id' => 1, 'views' => 0]); + Post::create(['id' => 12, 'user_id' => 2, 'views' => 0]); Model::reguard(); } + protected function tearDown(): void + { + DB::connection()->disconnect(); + + parent::tearDown(); + } + protected function getEnvironmentSetUp($app) { $config = require __DIR__.'/config/database.php';