Skip to content

Commit

Permalink
Support Firebird
Browse files Browse the repository at this point in the history
  • Loading branch information
staudenmeir committed Mar 8, 2024
1 parent b136d87 commit 1101205
Show file tree
Hide file tree
Showing 37 changed files with 557 additions and 368 deletions.
2 changes: 1 addition & 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.3, 8.2, 8.1 ]
database: [ mysql, mariadb, pgsql, sqlite, sqlsrv, singlestore ]
database: [ mysql, mariadb, pgsql, sqlite, sqlsrv, singlestore, firebird ]
release: [ stable, lowest ]
include:
- php: 8.3
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This Laravel Eloquent extension provides recursive relationships for [trees](#tr
- SQLite 3.8.3+
- SQL Server 2008+
- SingleStore 8.1+ (only [trees](#trees-one-parent-per-node-one-to-many))
- Firebird

## Installation

Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
"php": "^8.1",
"illuminate/database": "^10.0",
"staudenmeir/eloquent-has-many-deep-contracts": "^1.1",
"staudenmeir/laravel-cte": "^1.8"
"staudenmeir/laravel-cte": "^1.10"
},
"require-dev": {
"barryvdh/laravel-ide-helper": "^2.13",
"doctrine/dbal": "^3.5.2",
"harrygulliford/laravel-firebird": "^3.2",
"larastan/larastan": "^2.0",
"mockery/mockery": "^1.5.1",
"orchestra/testbench": "^8.15",
Expand Down
8 changes: 8 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ services:
- test
volumes:
- .docker/singlestore/init.sql:/init.sql
firebird:
image: jacobalberty/firebird:latest
environment:
FIREBIRD_DATABASE: 'test.fdb'
ISC_PASSWORD: 'password'
EnableLegacyClientAuth: 'true'
networks:
- test

networks:
test:
Expand Down
7 changes: 5 additions & 2 deletions src/Eloquent/Relations/Traits/IsOfDescendantsRelation.php
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,11 @@ protected function getInitialQuery(ExpressionGrammar $grammar, callable $constra

$depth = $grammar->wrap($model->getDepthName());

$query = $model->newModelQuery()
->select('*')
$query = $model->newModelQuery();

$table = $alias ?: $query->getQuery()->from;

$query->select("$table.*")
->selectRaw("$initialDepth as $depth");

if ($alias) {
Expand Down
5 changes: 5 additions & 0 deletions src/Eloquent/Traits/BuildsAdjacencyListQueries.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Database\PostgresConnection;
use PDO;
use RuntimeException;
use Staudenmeir\LaravelAdjacencyList\Query\Grammars\FirebirdGrammar;
use Staudenmeir\LaravelAdjacencyList\Query\Grammars\MariaDbGrammar;
use Staudenmeir\LaravelAdjacencyList\Query\Grammars\MySqlGrammar;
use Staudenmeir\LaravelAdjacencyList\Query\Grammars\PostgresGrammar;
Expand Down Expand Up @@ -104,6 +105,10 @@ public function getExpressionGrammar()
return $this->query->getConnection()->withTablePrefix(
new SingleStoreGrammar($this->model)
);
case 'firebird':
return $this->query->getConnection()->withTablePrefix(
new FirebirdGrammar($this->model)
);
}

throw new RuntimeException('This database is not supported.'); // @codeCoverageIgnore
Expand Down
9 changes: 7 additions & 2 deletions src/Eloquent/Traits/HasRecursiveRelationshipScopes.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ public function scopeWithRelationshipExpression(Builder $query, $direction, call
*/
protected function getInitialQuery(ExpressionGrammar $grammar, callable $constraint, $initialDepth, $from)
{
$table = explode(' as ', $from)[1] ?? $from;

$depth = $grammar->wrap($this->getDepthName());

$initialPath = $grammar->compileInitialPath(
Expand All @@ -192,7 +194,7 @@ protected function getInitialQuery(ExpressionGrammar $grammar, callable $constra
);

$query = $this->newModelQuery()
->select('*')
->select("$table.*")
->selectRaw($initialDepth.' as '.$depth)
->selectRaw($initialPath)
->from($from);
Expand Down Expand Up @@ -241,7 +243,10 @@ protected function getRecursiveQuery(ExpressionGrammar $grammar, $direction, $fr
];

if ($direction === 'both') {
$recursiveDepth = "$depth + (case when {$joinColumns['desc'][1]}={$joinColumns['desc'][0]} then 1 else -1 end)";
$left = $grammar->wrap($joinColumns['desc'][1]);
$right = $grammar->wrap($joinColumns['desc'][0]);

$recursiveDepth = "$depth + (case when $left=$right then 1 else -1 end)";
} else {
$recursiveDepth = $depth.' '.($direction === 'asc' ? '-' : '+').' 1';
}
Expand Down
75 changes: 75 additions & 0 deletions src/Query/Grammars/FirebirdGrammar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace Staudenmeir\LaravelAdjacencyList\Query\Grammars;

use HarryGulliford\Firebird\Query\Grammars\FirebirdGrammar as Base;
use Illuminate\Database\Query\Builder;

class FirebirdGrammar extends Base implements ExpressionGrammar
{
use OrdersByPath;

public function compileInitialPath($column, $alias)
{
return 'cast(' . $this->wrap($column) . ' as varchar(8191)) as ' . $this->wrap($alias);
}

public function compileRecursivePath($column, $alias, bool $reverse = false)
{
$wrappedColumn = $this->wrap($column);
$wrappedAlias = $this->wrap($alias);
$placeholder = 'cast(? as varchar(8191))';

return $reverse ? "($wrappedColumn || $placeholder || $wrappedAlias)" : "($wrappedAlias || $placeholder || $wrappedColumn)";
}

public function getRecursivePathBindings($separator)
{
return [$separator];
}

public function selectPathList(Builder $query, $expression, $column, $pathSeparator, $listSeparator)
{
return $query->selectRaw(
'list(' . $this->wrap($column) . ", '$listSeparator')"
)->from($expression);
}

public function compilePivotColumnNullValue(string $type, int $precision, int $scale): string
{
return 'null';
}

public function compileCycleDetection(string $localKey, string $path): string
{
$localKey = $this->wrap($localKey);
$path = $this->wrap($path);

return "position($localKey || ?, $path) > 0 or position(? || $localKey || ?, $path) > 0";
}

public function getCycleDetectionBindings(string $pathSeparator): array
{
return [$pathSeparator, $pathSeparator, $pathSeparator];
}

public function compileCycleDetectionInitialSelect(string $column): string
{
return 'false as ' . $this->wrap($column);
}

public function compileCycleDetectionRecursiveSelect(string $sql, string $column): string
{
return $sql;
}

public function compileCycleDetectionStopConstraint(string $column): string
{
return 'not ' . $this->wrap($column);
}

public function supportsUnionInRecursiveExpression(): bool
{
return false;
}
}
36 changes: 23 additions & 13 deletions tests/Graph/AncestorsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class AncestorsTest extends TestCase
{
public function testLazyLoading()
{
$ancestors = Node::find(5)->ancestors;
$ancestors = Node::find(5)->ancestors()->orderByDesc('depth')->orderBy('id')->get();

$this->assertEquals([1, 2, 10, 1, 9], $ancestors->pluck('id')->all());
$this->assertEquals([-1, -1, -1, -2, -2], $ancestors->pluck('depth')->all());
Expand Down Expand Up @@ -71,7 +71,7 @@ public function testLazyLoadingWithCycleDetectionAndStart(string $class, array $

public function testLazyLoadingAndSelf()
{
if ($this->connection === 'sqlsrv') {
if (in_array($this->connection, ['sqlsrv', 'firebird'])) {
$this->markTestSkipped();
}

Expand Down Expand Up @@ -113,7 +113,7 @@ public function testLazyLoadingAndSelf()

public function testLazyLoadingAndSelfWithCycleDetection()
{
if ($this->connection === 'sqlsrv') {
if (in_array($this->connection, ['sqlsrv', 'firebird'])) {
$this->markTestSkipped();
}

Expand All @@ -127,7 +127,7 @@ public function testLazyLoadingAndSelfWithCycleDetection()

public function testLazyLoadingAndSelfWithCycleDetectionAndStart()
{
if ($this->connection === 'sqlsrv') {
if (in_array($this->connection, ['sqlsrv', 'firebird'])) {
$this->markTestSkipped();
}

Expand Down Expand Up @@ -171,6 +171,11 @@ public function testEagerLoadingWithCycleDetection(string $class, array $exclusi
$this->markTestSkipped();
}

// TODO[L11]
if ($this->connection === 'firebird' && version_compare(phpversion(), '8.2', '<')) {
$this->markTestSkipped();
}

$this->seedCycle();

$nodes = $class::with([
Expand All @@ -188,6 +193,11 @@ public function testEagerLoadingWithCycleDetectionAndStart(string $class, array
$this->markTestSkipped();
}

// TODO[L11]
if ($this->connection === 'firebird' && version_compare(phpversion(), '8.2', '<')) {
$this->markTestSkipped();
}

$this->seedCycle();

$nodes = $class::with([
Expand All @@ -201,7 +211,7 @@ public function testEagerLoadingWithCycleDetectionAndStart(string $class, array

public function testEagerLoadingAndSelf()
{
if ($this->connection === 'sqlsrv') {
if (in_array($this->connection, ['sqlsrv', 'firebird'])) {
$this->markTestSkipped();
}

Expand Down Expand Up @@ -240,7 +250,7 @@ public function testEagerLoadingAndSelf()

public function testEagerLoadingAndSelfWithCycleDetection()
{
if ($this->connection === 'sqlsrv') {
if (in_array($this->connection, ['sqlsrv', 'firebird'])) {
$this->markTestSkipped();
}

Expand All @@ -254,7 +264,7 @@ public function testEagerLoadingAndSelfWithCycleDetection()

public function testEagerLoadingAndSelfWithCycleDetectionAndStart()
{
if ($this->connection === 'sqlsrv') {
if (in_array($this->connection, ['sqlsrv', 'firebird'])) {
$this->markTestSkipped();
}

Expand Down Expand Up @@ -293,7 +303,7 @@ public function testLazyEagerLoading()

public function testLazyEagerLoadingAndSelf()
{
if ($this->connection === 'sqlsrv') {
if (in_array($this->connection, ['sqlsrv', 'firebird'])) {
$this->markTestSkipped();
}

Expand Down Expand Up @@ -332,7 +342,7 @@ public function testLazyEagerLoadingAndSelf()

public function testExistenceQuery()
{
if (in_array($this->connection, ['mariadb', 'sqlsrv'])) {
if (in_array($this->connection, ['mariadb', 'sqlsrv', 'firebird'])) {
$this->markTestSkipped();
}

Expand All @@ -343,7 +353,7 @@ public function testExistenceQuery()

public function testExistenceQueryAndSelf()
{
if (in_array($this->connection, ['mariadb', 'sqlsrv'])) {
if (in_array($this->connection, ['mariadb', 'sqlsrv', 'firebird'])) {
$this->markTestSkipped();
}

Expand All @@ -365,7 +375,7 @@ public function testExistenceQueryForSelfRelation()

public function testExistenceQueryForSelfRelationAndSelf()
{
if (in_array($this->connection, ['mariadb', 'sqlsrv'])) {
if (in_array($this->connection, ['mariadb', 'sqlsrv', 'firebird'])) {
$this->markTestSkipped();
}

Expand All @@ -387,7 +397,7 @@ public function testWithSumForSelfRelation()

public function testWithSumForSelfRelationAndSelf()
{
if (in_array($this->connection, ['mariadb', 'sqlsrv'])) {
if (in_array($this->connection, ['mariadb', 'sqlsrv', 'firebird'])) {
$this->markTestSkipped();
}

Expand All @@ -398,7 +408,7 @@ public function testWithSumForSelfRelationAndSelf()

public function testDelete()
{
if ($this->connection === 'mariadb') {
if (in_array($this->connection, ['mariadb', 'firebird'])) {
$this->markTestSkipped();
}

Expand Down
8 changes: 4 additions & 4 deletions tests/Graph/CollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class CollectionTest extends TestCase
{
public function testToTree()
{
if ($this->connection === 'sqlsrv') {
if (in_array($this->connection, ['sqlsrv', 'firebird'])) {
$this->markTestSkipped();
}

Expand Down Expand Up @@ -41,7 +41,7 @@ public function testToTreeWithRelationship()

public function testToTreeWithCycle()
{
if ($this->connection === 'sqlsrv') {
if (in_array($this->connection, ['sqlsrv', 'firebird'])) {
$this->markTestSkipped();
}

Expand All @@ -61,7 +61,7 @@ public function testToTreeWithCycle()

public function testToTreeWithCycleAndStart()
{
if ($this->connection === 'sqlsrv') {
if (in_array($this->connection, ['sqlsrv', 'firebird'])) {
$this->markTestSkipped();
}

Expand All @@ -81,7 +81,7 @@ public function testToTreeWithCycleAndStart()

public function testToTreeWithEmptyCollection()
{
if ($this->connection === 'sqlsrv') {
if (in_array($this->connection, ['sqlsrv', 'firebird'])) {
$this->markTestSkipped();
}

Expand Down
Loading

0 comments on commit 1101205

Please sign in to comment.