diff --git a/src/Query/Grammars/PostgresGrammar.php b/src/Query/Grammars/PostgresGrammar.php index 431c9a5..f4e0588 100644 --- a/src/Query/Grammars/PostgresGrammar.php +++ b/src/Query/Grammars/PostgresGrammar.php @@ -100,10 +100,14 @@ public function compilePivotColumnNullValue(string $type): string */ public function compileCycleDetection(string $localKey, string $path): string { - $localKey = $this->wrap($localKey); - $path = $this->wrap($path); + $wrappedLocalKey = $this->wrap($localKey); + $wrappedPath = $this->wrap($path); - return "$localKey = any($path)"; + if ($this->model->isIntegerAttribute($localKey)) { + return "$wrappedLocalKey = any($wrappedPath)"; + } + + return "$wrappedLocalKey::varchar = any($wrappedPath)"; } /** diff --git a/tests/Graph/AncestorsTest.php b/tests/Graph/AncestorsTest.php index f1ee64e..b20b94c 100644 --- a/tests/Graph/AncestorsTest.php +++ b/tests/Graph/AncestorsTest.php @@ -30,21 +30,35 @@ public function testLazyLoading() ); } - public function testLazyLoadingWithCycleDetection() + /** + * @dataProvider cycleDetectionClassProvider + */ + public function testLazyLoadingWithCycleDetection(string $class, array $exclusions) { + if (in_array($this->database, $exclusions)) { + $this->markTestSkipped(); + } + $this->seedCycle(); - $ancestors = NodeWithCycleDetection::find(12)->ancestors; + $ancestors = $class::find(12)->ancestors; $this->assertEquals([14, 13, 12], $ancestors->pluck('id')->all()); $this->assertEquals([-1, -2, -3], $ancestors->pluck('depth')->all()); } - public function testLazyLoadingWithCycleDetectionAndStart() + /** + * @dataProvider cycleDetectionAndStartClassProvider + */ + public function testLazyLoadingWithCycleDetectionAndStart(string $class, array $exclusions) { + if (in_array($this->database, $exclusions)) { + $this->markTestSkipped(); + } + $this->seedCycle(); - $ancestors = NodeWithCycleDetectionAndStart::find(12)->ancestors; + $ancestors = $class::find(12)->ancestors; $this->assertEquals([14, 13, 12, 14], $ancestors->pluck('id')->all()); $this->assertEquals([-1, -2, -3, -4], $ancestors->pluck('depth')->all()); @@ -125,11 +139,18 @@ public function testEagerLoading() ); } - public function testEagerLoadingWithCycleDetection() + /** + * @dataProvider cycleDetectionClassProvider + */ + public function testEagerLoadingWithCycleDetection(string $class, array $exclusions) { + if (in_array($this->database, $exclusions)) { + $this->markTestSkipped(); + } + $this->seedCycle(); - $nodes = NodeWithCycleDetection::with([ + $nodes = $class::with([ 'ancestors' => fn (Ancestors $query) => $query->orderByDesc('depth'), ])->findMany([12, 13, 14]); @@ -137,11 +158,18 @@ public function testEagerLoadingWithCycleDetection() $this->assertEquals([-1, -2, -3], $nodes[0]->ancestors->pluck('depth')->all()); } - public function testEagerLoadingWithCycleDetectionAndStart() + /** + * @dataProvider cycleDetectionAndStartClassProvider + */ + public function testEagerLoadingWithCycleDetectionAndStart(string $class, array $exclusions) { + if (in_array($this->database, $exclusions)) { + $this->markTestSkipped(); + } + $this->seedCycle(); - $nodes = NodeWithCycleDetectionAndStart::with([ + $nodes = $class::with([ 'ancestors' => fn (Ancestors $query) => $query->orderByDesc('depth'), ])->findMany([12, 13, 14]); diff --git a/tests/Graph/DescendantsTest.php b/tests/Graph/DescendantsTest.php index 7d56983..122a508 100644 --- a/tests/Graph/DescendantsTest.php +++ b/tests/Graph/DescendantsTest.php @@ -30,21 +30,35 @@ public function testLazyLoading() ); } - public function testLazyLoadingWithCycleDetection() + /** + * @dataProvider cycleDetectionClassProvider + */ + public function testLazyLoadingWithCycleDetection(string $class, array $exclusions) { + if (in_array($this->database, $exclusions)) { + $this->markTestSkipped(); + } + $this->seedCycle(); - $descendants = NodeWithCycleDetection::find(12)->descendants; + $descendants = $class::find(12)->descendants; $this->assertEquals([13, 14, 12], $descendants->pluck('id')->all()); $this->assertEquals([1, 2, 3], $descendants->pluck('depth')->all()); } - public function testLazyLoadingWithCycleDetectionAndStart() + /** + * @dataProvider cycleDetectionAndStartClassProvider + */ + public function testLazyLoadingWithCycleDetectionAndStart(string $class, array $exclusions) { + if (in_array($this->database, $exclusions)) { + $this->markTestSkipped(); + } + $this->seedCycle(); - $descendants = NodeWithCycleDetectionAndStart::find(12)->descendants; + $descendants = $class::find(12)->descendants; $this->assertEquals([13, 14, 12, 13], $descendants->pluck('id')->all()); $this->assertEquals([1, 2, 3, 4], $descendants->pluck('depth')->all()); @@ -133,11 +147,18 @@ public function testEagerLoading() ); } - public function testEagerLoadingWithCycleDetection() + /** + * @dataProvider cycleDetectionClassProvider + */ + public function testEagerLoadingWithCycleDetection(string $class, array $exclusions) { + if (in_array($this->database, $exclusions)) { + $this->markTestSkipped(); + } + $this->seedCycle(); - $nodes = NodeWithCycleDetection::with([ + $nodes = $class::with([ 'descendants' => fn (Descendants $query) => $query->orderBy('depth'), ])->findMany([12, 13, 14]); @@ -145,11 +166,18 @@ public function testEagerLoadingWithCycleDetection() $this->assertEquals([1, 2, 3], $nodes[0]->descendants->pluck('depth')->all()); } - public function testEagerLoadingWithCycleDetectionAndStart() + /** + * @dataProvider cycleDetectionAndStartClassProvider + */ + public function testEagerLoadingWithCycleDetectionAndStart(string $class, array $exclusions) { + if (in_array($this->database, $exclusions)) { + $this->markTestSkipped(); + } + $this->seedCycle(); - $nodes = NodeWithCycleDetectionAndStart::with([ + $nodes = $class::with([ 'descendants' => fn (Descendants $query) => $query->orderBy('depth') ])->findMany([12, 13, 14]); diff --git a/tests/Graph/Models/Node.php b/tests/Graph/Models/Node.php index d5b84e3..7dd0713 100644 --- a/tests/Graph/Models/Node.php +++ b/tests/Graph/Models/Node.php @@ -14,6 +14,8 @@ class Node extends Model } use SoftDeletes; + protected $table = 'nodes'; + public function getPivotTableName(): string { return 'edges'; diff --git a/tests/Graph/Models/NodeWithCycleDetection.php b/tests/Graph/Models/NodeWithCycleDetection.php index 1698721..038a76e 100644 --- a/tests/Graph/Models/NodeWithCycleDetection.php +++ b/tests/Graph/Models/NodeWithCycleDetection.php @@ -4,8 +4,6 @@ class NodeWithCycleDetection extends Node { - protected $table = 'nodes'; - public function enableCycleDetection(): bool { return true; diff --git a/tests/Graph/Models/NodeWithUuid.php b/tests/Graph/Models/NodeWithUuid.php new file mode 100644 index 0000000..ac6a8a0 --- /dev/null +++ b/tests/Graph/Models/NodeWithUuid.php @@ -0,0 +1,21 @@ +id(); $table->string('slug')->unique(); + $table->uuid()->unique(); $table->timestamps(); $table->softDeletes(); } @@ -57,6 +62,8 @@ function (Blueprint $table) { function (Blueprint $table) { $table->unsignedBigInteger('parent_id'); $table->unsignedBigInteger('child_id'); + $table->uuid('parent_uuid'); + $table->uuid('child_uuid'); $table->string('label'); $table->tinyInteger('weight'); $table->timestamp('created_at'); @@ -72,23 +79,25 @@ protected function seed(): void Model::unguard(); - Node::create(['slug' => 'node-1']); - Node::create(['slug' => 'node-2']); - Node::create(['slug' => 'node-3']); - Node::create(['slug' => 'node-4']); - Node::create(['slug' => 'node-5']); - Node::create(['slug' => 'node-6']); - Node::create(['slug' => 'node-7']); - Node::create(['slug' => 'node-8']); - Node::create(['slug' => 'node-9']); - Node::create(['slug' => 'node-10']); - Node::create(['slug' => 'node-11', 'deleted_at' => Carbon::now()]); + Node::create(['slug' => 'node-1', 'uuid' => 'a0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b']); + Node::create(['slug' => 'node-2', 'uuid' => 'b0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b']); + Node::create(['slug' => 'node-3', 'uuid' => 'c0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b']); + Node::create(['slug' => 'node-4', 'uuid' => 'd0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b']); + Node::create(['slug' => 'node-5', 'uuid' => 'e0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b']); + Node::create(['slug' => 'node-6', 'uuid' => 'f0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b']); + Node::create(['slug' => 'node-7', 'uuid' => 'a1f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b']); + Node::create(['slug' => 'node-8', 'uuid' => 'b1f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b']); + Node::create(['slug' => 'node-9', 'uuid' => 'c1f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b']); + Node::create(['slug' => 'node-10', 'uuid' => 'd1f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b']); + Node::create(['slug' => 'node-11', 'uuid' => 'e1f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', 'deleted_at' => Carbon::now()]); DB::table('edges')->insert( [ [ 'parent_id' => 1, 'child_id' => 2, + 'parent_uuid' => 'a0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', + 'child_uuid' => 'b0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', 'label' => 'a', 'weight' => 1, 'created_at' => Carbon::now(), @@ -96,6 +105,8 @@ protected function seed(): void [ 'parent_id' => 1, 'child_id' => 3, + 'parent_uuid' => 'a0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', + 'child_uuid' => 'c0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', 'label' => 'b', 'weight' => 2, 'created_at' => Carbon::now(), @@ -103,6 +114,8 @@ protected function seed(): void [ 'parent_id' => 1, 'child_id' => 4, + 'parent_uuid' => 'a0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', + 'child_uuid' => 'd0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', 'label' => 'c', 'weight' => 3, 'created_at' => Carbon::now(), @@ -110,6 +123,8 @@ protected function seed(): void [ 'parent_id' => 1, 'child_id' => 5, + 'parent_uuid' => 'a0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', + 'child_uuid' => 'e0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', 'label' => 'd', 'weight' => 4, 'created_at' => Carbon::now(), @@ -117,6 +132,8 @@ protected function seed(): void [ 'parent_id' => 2, 'child_id' => 5, + 'parent_uuid' => 'b0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', + 'child_uuid' => 'e0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', 'label' => 'e', 'weight' => 5, 'created_at' => Carbon::now(), @@ -124,6 +141,8 @@ protected function seed(): void [ 'parent_id' => 3, 'child_id' => 6, + 'parent_uuid' => 'c0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', + 'child_uuid' => 'f0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', 'label' => 'f', 'weight' => 6, 'created_at' => Carbon::now(), @@ -131,6 +150,8 @@ protected function seed(): void [ 'parent_id' => 5, 'child_id' => 7, + 'parent_uuid' => 'e0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', + 'child_uuid' => 'a1f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', 'label' => 'g', 'weight' => 7, 'created_at' => Carbon::now(), @@ -138,6 +159,8 @@ protected function seed(): void [ 'parent_id' => 5, 'child_id' => 8, + 'parent_uuid' => 'e0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', + 'child_uuid' => 'b1f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', 'label' => 'h', 'weight' => 8, 'created_at' => Carbon::now(), @@ -145,6 +168,8 @@ protected function seed(): void [ 'parent_id' => 7, 'child_id' => 8, + 'parent_uuid' => 'a1f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', + 'child_uuid' => 'b1f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', 'label' => 'i', 'weight' => 9, 'created_at' => Carbon::now(), @@ -152,6 +177,8 @@ protected function seed(): void [ 'parent_id' => 9, 'child_id' => 2, + 'parent_uuid' => 'c1f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', + 'child_uuid' => 'b0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', 'label' => 'j', 'weight' => 10, 'created_at' => Carbon::now(), @@ -159,6 +186,8 @@ protected function seed(): void [ 'parent_id' => 10, 'child_id' => 5, + 'parent_uuid' => 'd1f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', + 'child_uuid' => 'e0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', 'label' => 'k', 'weight' => 11, 'created_at' => Carbon::now(), @@ -166,6 +195,8 @@ protected function seed(): void [ 'parent_id' => 11, 'child_id' => 5, + 'parent_uuid' => 'e1f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', + 'child_uuid' => 'e0f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', 'label' => 'l', 'weight' => 12, 'created_at' => Carbon::now(), @@ -180,9 +211,9 @@ protected function seedCycle(): void { Model::unguard(); - Node::create(['slug' => 'node-12']); - Node::create(['slug' => 'node-13']); - Node::create(['slug' => 'node-14']); + Node::create(['slug' => 'node-12', 'uuid' => 'f1f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b']); + Node::create(['slug' => 'node-13', 'uuid' => 'a2f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b']); + Node::create(['slug' => 'node-14', 'uuid' => 'b2f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b']); DB::table('edges')->insert( [ @@ -190,6 +221,8 @@ protected function seedCycle(): void [ 'parent_id' => 12, 'child_id' => 13, + 'parent_uuid' => 'f1f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', + 'child_uuid' => 'a2f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', 'label' => 'm', 'weight' => 13, 'created_at' => Carbon::now(), @@ -197,6 +230,8 @@ protected function seedCycle(): void [ 'parent_id' => 13, 'child_id' => 14, + 'parent_uuid' => 'a2f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', + 'child_uuid' => 'b2f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', 'label' => 'n', 'weight' => 14, 'created_at' => Carbon::now(), @@ -204,6 +239,8 @@ protected function seedCycle(): void [ 'parent_id' => 14, 'child_id' => 12, + 'parent_uuid' => 'b2f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', + 'child_uuid' => 'f1f1b2c3-d4e5-4f6a-8b9b-0c1d2e3f4a5b', 'label' => 'o', 'weight' => 15, 'created_at' => Carbon::now(), @@ -220,4 +257,20 @@ protected function getFormattedTestNow(): string return Carbon::getTestNow()->format($format); } + + public static function cycleDetectionClassProvider(): array + { + return [ + [NodeWithCycleDetection::class, []], + [NodeWithUuidAndCycleDetection::class, ['sqlsrv']], + ]; + } + + public static function cycleDetectionAndStartClassProvider(): array + { + return [ + [NodeWithCycleDetectionAndStart::class, []], + [NodeWithUuidAndCycleDetectionAndStart::class, ['sqlsrv']], + ]; + } }