├── tests ├── Support │ ├── MigrationState.php │ ├── Models │ │ ├── Order.php │ │ └── User.php │ ├── Factories │ │ ├── OrderFactory.php │ │ └── UserFactory.php │ └── MigrateDatabase.php ├── ModelTest.php ├── PaginationTest.php ├── TestCase.php ├── SchemaTest.php └── QueryTest.php ├── src ├── Schema │ ├── Builder.php │ └── Grammars │ │ └── FirebirdGrammar.php ├── Query │ ├── Processors │ │ └── FirebirdProcessor.php │ ├── Builder.php │ └── Grammars │ │ └── FirebirdGrammar.php ├── FirebirdServiceProvider.php ├── FirebirdConnector.php └── FirebirdConnection.php ├── docker-compose.yml ├── .gitignore ├── phpunit.xml.dist ├── composer.json ├── .github └── workflows │ └── tests.yml └── readme.md /tests/Support/MigrationState.php: -------------------------------------------------------------------------------- 1 | belongsTo(User::class); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/Support/Models/User.php: -------------------------------------------------------------------------------- 1 | hasMany(Order::class); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Query/Processors/FirebirdProcessor.php: -------------------------------------------------------------------------------- 1 | column_name; 19 | }, $results); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/FirebirdServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->bind('db.connector.firebird', FirebirdConnector::class); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Support/Factories/OrderFactory.php: -------------------------------------------------------------------------------- 1 | self::$id++, 19 | 'user_id' => User::factory(), 20 | 'name' => $this->faker->word, 21 | 'price' => $this->faker->numberBetween(1, 200), 22 | 'quantity' => $this->faker->numberBetween(0, 8), 23 | 'created_at' => now(), 24 | 'updated_at' => now(), 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Support/Factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | self::$id++, 18 | 'name' => $this->faker->name, 19 | 'email' => $this->faker->email, 20 | 'city' => $this->faker->city, 21 | 'state' => $this->faker->state, 22 | 'post_code' => $this->faker->postcode, 23 | 'country' => $this->faker->country, 24 | 'created_at' => now(), 25 | 'updated_at' => now(), 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "harrygulliford/laravel-firebird", 3 | "description": "Firebird database driver for the Laravel Framework", 4 | "license": "MIT", 5 | "require": { 6 | "php": "^7.4|^8.0", 7 | "illuminate/support": "^8.65|^9.33|^10.0|^11.0", 8 | "illuminate/container": "^8.65|^9.33|^10.0|^11.0", 9 | "illuminate/database": "^8.65|^9.33|^10.0|^11.0", 10 | "illuminate/events": "^8.65|^9.33|^10.0|^11.0" 11 | }, 12 | "require-dev": { 13 | "mockery/mockery": "^1.4", 14 | "phpunit/phpunit": "^9.6.11|^10.0", 15 | "orchestra/testbench": "^6.19|^7.0|^8.0|^9.0", 16 | "fakerphp/faker": "^1.15" 17 | }, 18 | "archive": { 19 | "exclude": [ 20 | "/tests" 21 | ] 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "HarryGulliford\\Firebird\\": "src/" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "psr-4": { 30 | "HarryGulliford\\Firebird\\Tests\\": "tests/" 31 | } 32 | }, 33 | "extra": { 34 | "laravel": { 35 | "providers": [ 36 | "HarryGulliford\\Firebird\\FirebirdServiceProvider" 37 | ] 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Query/Builder.php: -------------------------------------------------------------------------------- 1 | 0; 17 | } 18 | 19 | /** 20 | * Add a from stored procedure clause to the query builder. 21 | * 22 | * @param string $procedure 23 | * @param array $values 24 | * @return \Illuminate\Database\Query\Builder|static 25 | */ 26 | public function fromProcedure(string $procedure, array $values = []) 27 | { 28 | $compiledProcedure = $this->grammar->compileProcedure($this, $procedure, $values); 29 | 30 | // Remove any expressions from the values array, as they will have 31 | // already been evaluated by the grammar's parameterize() function. 32 | $values = array_filter($values, function ($value) { 33 | return ! $this->grammar->isExpression($value); 34 | }); 35 | 36 | $this->fromRaw($compiledProcedure, array_values($values)); 37 | 38 | return $this; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/ModelTest.php: -------------------------------------------------------------------------------- 1 | $id = UserFactory::$id++, // Firebird < 3 does not support auto-incrementing columns. 18 | 'name' => 'Anna', 19 | 'email' => 'anna@example.com', 20 | 'city' => 'Sydney', 21 | 'state' => 'New South Wales', 22 | 'post_code' => '2000', 23 | 'country' => 'Australia', 24 | 'created_at' => now()->toDateTimeString(), 25 | 'updated_at' => now()->toDateTimeString(), 26 | ]); 27 | 28 | $user = User::find($id); 29 | 30 | $this->assertInstanceOf(User::class, $user); 31 | 32 | // Check all fields have been persisted the model. 33 | foreach ($fields as $key => $value) { 34 | $this->assertEquals($value, $user->{$key}); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/FirebirdConnector.php: -------------------------------------------------------------------------------- 1 | createConnection( 19 | $this->getDsn($config), 20 | $config, 21 | $this->getOptions($config) 22 | ); 23 | } 24 | 25 | /** 26 | * Create a DSN string from the configuration. 27 | * 28 | * @param array $config 29 | * @return string 30 | */ 31 | protected function getDsn(array $config) 32 | { 33 | extract($config); 34 | 35 | if (! isset($host) || ! isset($database)) { 36 | trigger_error('Cannot connect to Firebird Database, no host or database supplied'); 37 | } 38 | 39 | $dsn = "firebird:dbname={$host}"; 40 | 41 | if (isset($port)) { 42 | $dsn .= "/{$port}"; 43 | } 44 | 45 | $dsn .= ":{$database};"; 46 | 47 | if (isset($role)) { 48 | $dsn .= "role={$role};"; 49 | } 50 | 51 | if (isset($charset)) { 52 | $dsn .= "charset={$charset};"; 53 | } 54 | 55 | return $dsn; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/PaginationTest.php: -------------------------------------------------------------------------------- 1 | count(10)->create(['price' => 50]); 17 | 18 | $paginator = DB::table('orders')->paginate(3, ['id', 'price'], 'orders'); 19 | $this->assertCount(3, $paginator); 20 | $this->assertEquals(10, $paginator->total()); 21 | $this->assertTrue($paginator->hasMorePages()); 22 | 23 | $paginator = DB::table('orders')->paginate(3, ['id', 'price'], 'orders', 2); 24 | $this->assertCount(3, $paginator); 25 | $this->assertEquals(10, $paginator->total()); 26 | $this->assertTrue($paginator->hasMorePages()); 27 | } 28 | 29 | /** @test */ 30 | public function it_can_simple_paginate_results() 31 | { 32 | Order::factory()->count(10)->create(['price' => 50]); 33 | 34 | $paginator = DB::table('orders')->simplePaginate(3); 35 | $this->assertCount(3, $paginator); 36 | $this->assertTrue($paginator->hasMorePages()); 37 | 38 | $paginator = DB::table('orders')->simplePaginate(3, ['id', 'price'], 'orders', 2); 39 | $this->assertCount(3, $paginator); 40 | $this->assertTrue($paginator->hasMorePages()); 41 | } 42 | 43 | /** @test */ 44 | public function it_can_cursor_paginate_results() 45 | { 46 | Order::factory()->count(10)->create(['price' => 50]); 47 | 48 | $paginator = DB::table('orders')->orderBy('id')->cursorPaginate(3, ['id', 'price'], 'orders'); 49 | $this->assertCount(3, $paginator); 50 | 51 | $paginator = DB::table('orders')->orderBy('id')->cursorPaginate(3, ['id', 'price'], 'orders', $paginator->nextCursor()); 52 | $this->assertCount(3, $paginator); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | set('database.default', 'firebird'); 42 | config()->set('database.connections.firebird', [ 43 | 'driver' => 'firebird', 44 | 'host' => env('DB_HOST', 'localhost'), 45 | 'port' => env('DB_PORT', '3050'), 46 | 'database' => env('DB_DATABASE', '/firebird/data/database.fdb'), 47 | 'username' => env('DB_USERNAME', 'sysdba'), 48 | 'password' => env('DB_PASSWORD', 'masterkey'), 49 | 'charset' => env('DB_CHARSET', 'UTF8'), 50 | ]); 51 | } 52 | 53 | /** 54 | * Determine the Firebird engine version of the current database connection. 55 | * 56 | * @return float 57 | */ 58 | public function getDatabaseEngineVersion(): float 59 | { 60 | return (float) DB::selectOne('SELECT rdb$get_context(\'SYSTEM\', \'ENGINE_VERSION\') as "version" from rdb$database')->version; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | [push, pull_request] 5 | 6 | jobs: 7 | test: 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest] 12 | php: [8.3, 8.2, 8.1, 8.0, 7.4] 13 | laravel: [11.*, 10.*, 9.*, 8.*] 14 | stability: [prefer-lowest, prefer-stable] 15 | firebird: [v4.0, v3.0, v2.5.9-sc] 16 | include: 17 | - laravel: 8.* 18 | testbench: ^6.19 19 | - laravel: 9.* 20 | testbench: ^7.0 21 | - laravel: 10.* 22 | testbench: ^8.0 23 | - laravel: 11.* 24 | testbench: ^9.0 25 | exclude: 26 | - laravel: 8.* 27 | php: 8.3 28 | - laravel: 8.* 29 | php: 8.2 30 | - laravel: 9.* 31 | php: 7.4 32 | - laravel: 10.* 33 | php: 8.0 34 | - laravel: 10.* 35 | php: 7.4 36 | - laravel: 11.* 37 | php: 8.1 38 | - laravel: 11.* 39 | php: 8.0 40 | - laravel: 11.* 41 | php: 7.4 42 | fail-fast: false 43 | 44 | name: PHP ${{ matrix.php }}, Laravel ${{ matrix.laravel }}, Firebird ${{ matrix.firebird }}, ${{ matrix.stability }} 45 | 46 | services: 47 | firebird: 48 | image: jacobalberty/firebird:${{ matrix.firebird }} 49 | env: 50 | FIREBIRD_DATABASE: 'database.fdb' 51 | ISC_PASSWORD: 'masterkey' 52 | EnableLegacyClientAuth: 'true' 53 | ports: 54 | - 3050:3050 55 | 56 | steps: 57 | - name: Checkout code 58 | uses: actions/checkout@v4 59 | 60 | - name: Setup PHP 61 | uses: shivammathur/setup-php@v2 62 | with: 63 | php-version: ${{ matrix.php }} 64 | extensions: pdo_firebird 65 | tools: composer:v2 66 | coverage: none 67 | 68 | - name: Install dependencies 69 | run: | 70 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update 71 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction 72 | 73 | - name: Execute tests 74 | run: vendor/bin/phpunit 75 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Firebird for Laravel 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/harrygulliford/laravel-firebird/v/stable)](https://packagist.org/packages/harrygulliford/laravel-firebird) 4 | [![Total Downloads](https://poser.pugx.org/harrygulliford/laravel-firebird/downloads)](https://packagist.org/packages/harrygulliford/laravel-firebird) 5 | [![Tests](https://github.com/harrygulliford/laravel-firebird/actions/workflows/tests.yml/badge.svg)](https://github.com/harrygulliford/laravel-firebird/actions/workflows/tests.yml) 6 | [![License](https://poser.pugx.org/harrygulliford/laravel-firebird/license)](https://packagist.org/packages/harrygulliford/laravel-firebird) 7 | 8 | This package adds support for the Firebird PDO Database Driver in Laravel applications. 9 | 10 | ## Version Support 11 | 12 | - **PHP:** 7.4, 8.0, 8.1, 8.2, 8.3 13 | - **Laravel:** 8.x, 9.x, 10.x, 11.x 14 | - **Firebird:** 2.5, 3.0, 4.0 15 | 16 | ## Installation 17 | 18 | You can install the package via composer: 19 | 20 | ```bash 21 | composer require harrygulliford/laravel-firebird 22 | ``` 23 | 24 | _The package will automatically register itself._ 25 | 26 | Declare the connection within your `config/database.php` file by using `firebird` as the 27 | driver: 28 | ```php 29 | 'connections' => [ 30 | 31 | 'firebird' => [ 32 | 'driver' => 'firebird', 33 | 'host' => env('DB_HOST', 'localhost'), 34 | 'port' => env('DB_PORT', '3050'), 35 | 'database' => env('DB_DATABASE', '/path_to/database.fdb'), 36 | 'username' => env('DB_USERNAME', 'sysdba'), 37 | 'password' => env('DB_PASSWORD', 'masterkey'), 38 | 'charset' => env('DB_CHARSET', 'UTF8'), 39 | 'role' => null, 40 | ], 41 | 42 | ], 43 | ``` 44 | 45 | To register this package in Lumen, you'll also need to add the following line to the service providers in your `config/app.php` file: 46 | `$app->register(\HarryGulliford\Firebird\FirebirdServiceProvider::class);` 47 | 48 | ## Limitations 49 | This package does not intend to support database migrations and it should not be used for this use case. 50 | 51 | ## Credits 52 | - [Harry Gulliford](https://github.com/harrygulliford) 53 | - [Jacques van Zuydam](https://github.com/jacquestvanzuydam/laravel-firebird) 54 | - [Simonov Denis](https://github.com/sim1984/laravel-firebird) 55 | - [All Contributors](https://github.com/harrygulliford/laravel-firebird/graphs/contributors) 56 | 57 | ## License 58 | Licensed under the [MIT](https://choosealicense.com/licenses/mit/) license. 59 | -------------------------------------------------------------------------------- /src/FirebirdConnection.php: -------------------------------------------------------------------------------- 1 | schemaGrammar)) { 42 | $this->useDefaultSchemaGrammar(); 43 | } 44 | 45 | return new FirebirdSchemaBuilder($this); 46 | } 47 | 48 | /** 49 | * Get the default schema grammar instance. 50 | * 51 | * @return \Firebird\Schema\Grammars\FirebirdGrammar 52 | */ 53 | protected function getDefaultSchemaGrammar() 54 | { 55 | return $this->withTablePrefix(new FirebirdSchemaGrammar); 56 | } 57 | 58 | /** 59 | * Get a new query builder instance. 60 | * 61 | * @return \Firebird\Query\Builder 62 | */ 63 | public function query() 64 | { 65 | return new FirebirdQueryBuilder( 66 | $this, $this->getQueryGrammar(), $this->getPostProcessor() 67 | ); 68 | } 69 | 70 | /** 71 | * Execute a stored procedure. 72 | * 73 | * @param string $procedure 74 | * @param array $values 75 | * @return \Illuminate\Support\Collection 76 | */ 77 | public function executeProcedure($procedure, array $values = []) 78 | { 79 | return $this->query()->fromProcedure($procedure, $values)->get(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/SchemaTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(Schema::hasTable('users')); 18 | $this->assertFalse(Schema::hasTable('foo')); 19 | } 20 | 21 | /** @test */ 22 | public function it_has_column() 23 | { 24 | $this->assertTrue(Schema::hasColumn('users', 'id')); 25 | $this->assertFalse(Schema::hasColumn('users', 'foo')); 26 | } 27 | 28 | /** @test */ 29 | public function it_has_view(): void 30 | { 31 | if (version_compare($this->app->version(), '10.34.0', '<')) { 32 | $this->markTestSkipped('The hasView method is only available in Laravel 10.34.0 and above.'); 33 | } 34 | 35 | $this->createViews(); 36 | 37 | $this->assertTrue(Schema::hasView('view_all_users')); 38 | 39 | $this->assertFalse(Schema::hasView(uniqid('view_'))); 40 | } 41 | 42 | /** @test */ 43 | public function it_has_columns() 44 | { 45 | $this->assertTrue(Schema::hasColumns('users', ['id', 'country'])); 46 | $this->assertFalse(Schema::hasColumns('users', ['id', 'foo'])); 47 | } 48 | 49 | /** @test */ 50 | public function it_can_create_a_table() 51 | { 52 | Schema::dropIfExists('foo'); 53 | 54 | $this->assertFalse(Schema::hasTable('foo')); 55 | 56 | Schema::create('foo', function (Blueprint $table) { 57 | $table->string('bar'); 58 | }); 59 | 60 | $this->assertTrue(Schema::hasTable('foo')); 61 | 62 | // Clean up... 63 | Schema::drop('foo'); 64 | } 65 | 66 | /** @test */ 67 | public function it_throws_an_exception_for_creating_temporary_tables() 68 | { 69 | Schema::dropIfExists('foo'); 70 | 71 | $this->expectException(\LogicException::class); 72 | $this->expectExceptionMessage('This database driver does not support temporary tables.'); 73 | 74 | $this->assertFalse(Schema::hasTable('foo')); 75 | 76 | Schema::create('foo', function (Blueprint $table) { 77 | $table->temporary(); 78 | 79 | $table->string('bar'); 80 | }); 81 | 82 | $this->assertFalse(Schema::hasTable('foo')); 83 | } 84 | 85 | /** @test */ 86 | public function it_can_drop_table() 87 | { 88 | DB::select('RECREATE TABLE "foo" ("id" INTEGER NOT NULL)'); 89 | 90 | $this->assertTrue(Schema::hasTable('foo')); 91 | 92 | Schema::drop('foo'); 93 | 94 | $this->assertFalse(Schema::hasTable('foo')); 95 | } 96 | 97 | /** @test */ 98 | public function it_can_drop_table_if_exists() 99 | { 100 | DB::select('RECREATE TABLE "foo" ("id" INTEGER NOT NULL)'); 101 | 102 | $this->assertTrue(Schema::hasTable('foo')); 103 | 104 | Schema::dropIfExists('foo'); 105 | 106 | $this->assertFalse(Schema::hasTable('foo')); 107 | 108 | // Run again to check exists = false. 109 | 110 | Schema::dropIfExists('foo'); 111 | 112 | $this->assertFalse(Schema::hasTable('foo')); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tests/Support/MigrateDatabase.php: -------------------------------------------------------------------------------- 1 | dropViews(); 19 | 20 | $this->dropTables(); 21 | $this->createTables(); 22 | 23 | $this->dropProcedure(); 24 | $this->createProcedure(); 25 | 26 | MigrationState::$migrated = true; 27 | } 28 | } 29 | 30 | public function tearDown(): void 31 | { 32 | DB::select('DELETE FROM "orders"'); 33 | DB::select('DELETE FROM "users"'); 34 | 35 | // Reset the static ids on the factory, as Firebird <= 3 does not 36 | // support auto-incrementing ids. 37 | // TODO: Like to figure out a way of using auto-incrementing ids for 38 | // newer versions of Firebird, but not ready to drop v2.5 support yet. 39 | UserFactory::$id = 1; 40 | OrderFactory::$id = 1; 41 | 42 | parent::tearDown(); 43 | } 44 | 45 | public function createTables(): void 46 | { 47 | DB::select('CREATE TABLE "users" ("id" INTEGER NOT NULL, "name" VARCHAR(255) NOT NULL, "email" VARCHAR(255) NOT NULL, "city" VARCHAR(255), "state" VARCHAR(255), "post_code" VARCHAR(255), "country" VARCHAR(255), "created_at" TIMESTAMP, "updated_at" TIMESTAMP, "deleted_at" TIMESTAMP)'); 48 | DB::select('ALTER TABLE "users" ADD PRIMARY KEY ("id")'); 49 | 50 | DB::select('CREATE TABLE "orders" ("id" INTEGER NOT NULL, "user_id" INTEGER NOT NULL, "name" VARCHAR(255) NOT NULL, "price" INTEGER NOT NULL, "quantity" INTEGER NOT NULL, "created_at" TIMESTAMP, "updated_at" TIMESTAMP, "deleted_at" TIMESTAMP)'); 51 | DB::select('ALTER TABLE "orders" ADD CONSTRAINT orders_user_id_foreign FOREIGN KEY ("user_id") REFERENCES "users" ("id")'); 52 | DB::select('ALTER TABLE "orders" ADD PRIMARY KEY ("id")'); 53 | } 54 | 55 | public function dropTables(): void 56 | { 57 | try { 58 | DB::select('DROP TABLE "orders"'); 59 | } catch (QueryException $e) { 60 | // Suppress the "table does not exist" exception, as we want to 61 | // replicate dropIfExists() functionality without using the Schema 62 | // class. 63 | if (! Str::contains($e->getMessage(), 'does not exist')) { 64 | throw $e; 65 | } 66 | } 67 | 68 | try { 69 | DB::select('DROP TABLE "users"'); 70 | } catch (QueryException $e) { 71 | // Suppress the "table does not exist" exception, as we want to 72 | // replicate dropIfExists() functionality without using the Schema 73 | // class. 74 | if (! Str::contains($e->getMessage(), 'does not exist')) { 75 | throw $e; 76 | } 77 | } 78 | } 79 | 80 | public function createProcedure() 81 | { 82 | DB::select( 83 | 'CREATE PROCEDURE MULTIPLY (a INTEGER, b INTEGER) 84 | RETURNS (result INTEGER) 85 | AS BEGIN 86 | result = a * b; 87 | SUSPEND; 88 | END' 89 | ); 90 | } 91 | 92 | public function dropProcedure() 93 | { 94 | try { 95 | DB::select('DROP PROCEDURE MULTIPLY'); 96 | } catch (QueryException $e) { 97 | // Suppress the "procedure not found" exception, as we want to 98 | // replicate dropIfExists() functionality without using the Schema 99 | // class. 100 | if (! Str::contains($e->getMessage(), 'not found')) { 101 | throw $e; 102 | } 103 | } 104 | } 105 | 106 | public function createViews(): void 107 | { 108 | DB::select('CREATE VIEW "view_all_users" AS SELECT * FROM "users"'); 109 | } 110 | 111 | public function dropViews(): void 112 | { 113 | try { 114 | DB::select('DROP VIEW "view_all_users"'); 115 | } catch (QueryException $e) { 116 | // Suppress the "view does not exist" exception, as we want to 117 | // replicate dropIfExists() functionality without using the Schema 118 | // class. 119 | if (! Str::contains($e->getMessage(), 'does not exist')) { 120 | throw $e; 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Query/Grammars/FirebirdGrammar.php: -------------------------------------------------------------------------------- 1 | ', '<=', '>=', '<>', '!=', 39 | 'like', 'not like', 'between', 'not between', 40 | 'containing', 'not containing', 'starting with', 'not starting with', 41 | 'similar to', 'not similar to', 'is distinct from', 'is not distinct from', 42 | ]; 43 | 44 | /** 45 | * @param Builder $query 46 | * @param array $columns 47 | * @return string|null 48 | */ 49 | protected function compileColumns(Builder $query, $columns) 50 | { 51 | // See superclass. 52 | if (! is_null($query->aggregate)) { 53 | return; 54 | } 55 | 56 | // In Firebird, the correct syntax for limiting and offsetting rows is 57 | // "select first [num_rows] skip [start_row] * from table". Laravel does 58 | // not support adding components between the "select" keyword and the 59 | // column names, so compile the limit and offset components here. Note 60 | // that they are commented out in the $selectComponents class variable. 61 | // Reference: http://mc-computing.com/Databases/Firebird/SQL.html 62 | 63 | $select = 'select '; 64 | 65 | if ($query->limit) { 66 | $select .= $this->compileLimit($query, $query->limit).' '; 67 | } 68 | 69 | if ($query->offset) { 70 | $select .= $this->compileOffset($query, $query->offset).' '; 71 | } 72 | 73 | if ($query->distinct) { 74 | $select .= 'distinct '; 75 | } 76 | 77 | return $select.$this->columnize($columns); 78 | } 79 | 80 | /** 81 | * Compile the "limit" portions of the query. 82 | * 83 | * @param \Illuminate\Database\Query\Builder $query 84 | * @param int $limit 85 | * @return string 86 | */ 87 | protected function compileLimit(Builder $query, $limit) 88 | { 89 | return 'first '.(int) $limit; 90 | } 91 | 92 | /** 93 | * Compile the "offset" portions of the query. 94 | * 95 | * @param \Illuminate\Database\Query\Builder $query 96 | * @param int $offset 97 | * @return string 98 | */ 99 | protected function compileOffset(Builder $query, $offset) 100 | { 101 | return 'skip '.(int) $offset; 102 | } 103 | 104 | /** 105 | * Compile the random statement into SQL. 106 | * 107 | * @param string $seed 108 | * @return string 109 | */ 110 | public function compileRandom($seed) 111 | { 112 | return 'RAND()'; 113 | } 114 | 115 | /** 116 | * Wrap a union subquery in parentheses. 117 | * 118 | * @param string $sql 119 | * @return string 120 | */ 121 | protected function wrapUnion($sql) 122 | { 123 | return $sql; 124 | } 125 | 126 | /** 127 | * Compile a date based where clause. 128 | * 129 | * @param string $type 130 | * @param \Illuminate\Database\Query\Builder $query 131 | * @param array $where 132 | * @return string 133 | */ 134 | protected function dateBasedWhere($type, Builder $query, $where) 135 | { 136 | $value = $this->parameter($where['value']); 137 | 138 | return 'EXTRACT('.$type.' FROM '.$this->wrap($where['column']).') '.$where['operator'].' '.$value; 139 | } 140 | 141 | /** 142 | * Compile SQL statement for a stored procedure. 143 | * 144 | * @param \Illuminate\Database\Query\Builder $query 145 | * @param string $procedure 146 | * @param array $values 147 | * @return string 148 | */ 149 | public function compileProcedure(Builder $query, $procedure, array $values = null) 150 | { 151 | $procedure = $this->wrap($procedure); 152 | 153 | return $procedure.' ('.$this->parameterize($values).')'; 154 | } 155 | 156 | /** 157 | * Compile an aggregated select clause. 158 | * 159 | * @param \Illuminate\Database\Query\Builder $query 160 | * @param array $aggregate 161 | * @return string 162 | */ 163 | protected function compileAggregate(Builder $query, $aggregate) 164 | { 165 | // Wrap `aggregate` in double quotes to ensure the resultset returns the 166 | // column name as a lowercase string. This resolves compatibility with 167 | // the framework's paginator. 168 | return Str::replaceLast( 169 | 'as aggregate', 'as "aggregate"', parent::compileAggregate($query, $aggregate) 170 | ); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/Schema/Grammars/FirebirdGrammar.php: -------------------------------------------------------------------------------- 1 | quoteString($table).' ' 74 | .'order by rdb$relation_name'; 75 | } 76 | 77 | /** 78 | * Compile the query to determine the list of columns. 79 | * 80 | * @param string $table 81 | * @return string 82 | */ 83 | public function compileColumnListing($table) 84 | { 85 | return "select trim(rdb\$field_name) as \"column_name\" from rdb\$relation_fields where rdb\$relation_name = '$table'"; 86 | } 87 | 88 | /** 89 | * Compile a create table command. 90 | * 91 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 92 | * @param \Illuminate\Support\Fluent $command 93 | * @return string 94 | */ 95 | public function compileCreate(Blueprint $blueprint, Fluent $command) 96 | { 97 | if ($blueprint->temporary) { 98 | throw new \LogicException('This database driver does not support temporary tables.'); 99 | } 100 | 101 | $columns = implode(', ', $this->getColumns($blueprint)); 102 | 103 | $sql = 'create table '.$this->wrapTable($blueprint)." ($columns)"; 104 | 105 | return $sql; 106 | } 107 | 108 | /** 109 | * Compile a drop table command. 110 | * 111 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 112 | * @param \Illuminate\Support\Fluent $command 113 | * @return string 114 | */ 115 | public function compileDrop(Blueprint $blueprint, Fluent $command) 116 | { 117 | return 'drop table '.$this->wrapTable($blueprint); 118 | } 119 | 120 | /** 121 | * Compile a drop table (if exists) command. 122 | * 123 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 124 | * @param \Illuminate\Support\Fluent $command 125 | * @return string 126 | */ 127 | public function compileDropIfExists(Blueprint $blueprint, Fluent $command) 128 | { 129 | // Replace the double quotes with single quotes. 130 | $table = str_replace('"', "'", $this->wrapTable($blueprint)); 131 | 132 | return sprintf( 133 | "execute block as begin if (exists(%s)) then execute statement '%s'; end", 134 | str_replace('?', $table, $this->compileTableExists()), // Replace the ? character with the table name. 135 | $this->compileDrop($blueprint, $command) 136 | ); 137 | } 138 | 139 | /** 140 | * Compile a column addition command. 141 | * 142 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 143 | * @param \Illuminate\Support\Fluent $command 144 | * @return string 145 | */ 146 | public function compileAdd(Blueprint $blueprint, Fluent $command) 147 | { 148 | $table = $this->wrapTable($blueprint); 149 | 150 | $columns = $this->prefixArray('ADD', $this->getColumns($blueprint)); 151 | 152 | return 'ALTER TABLE '.$table.' '.implode(', ', $columns); 153 | } 154 | 155 | /** 156 | * Compile a primary key command. 157 | * 158 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 159 | * @param \Illuminate\Support\Fluent $command 160 | * @return string 161 | */ 162 | public function compilePrimary(Blueprint $blueprint, Fluent $command) 163 | { 164 | $columns = $this->columnize($command->columns); 165 | 166 | return 'ALTER TABLE '.$this->wrapTable($blueprint)." ADD PRIMARY KEY ({$columns})"; 167 | } 168 | 169 | /** 170 | * Compile a unique key command. 171 | * 172 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 173 | * @param \Illuminate\Support\Fluent $command 174 | * @return string 175 | */ 176 | public function compileUnique(Blueprint $blueprint, Fluent $command) 177 | { 178 | $table = $this->wrapTable($blueprint); 179 | 180 | $index = $this->wrap(substr($command->index, 0, 31)); 181 | 182 | $columns = $this->columnize($command->columns); 183 | 184 | return "ALTER TABLE {$table} ADD CONSTRAINT {$index} UNIQUE ({$columns})"; 185 | } 186 | 187 | /** 188 | * Compile a plain index key command. 189 | * 190 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 191 | * @param \Illuminate\Support\Fluent $command 192 | * @return string 193 | */ 194 | public function compileIndex(Blueprint $blueprint, Fluent $command) 195 | { 196 | $columns = $this->columnize($command->columns); 197 | 198 | $index = $this->wrap(substr($command->index, 0, 31)); 199 | 200 | $table = $this->wrapTable($blueprint); 201 | 202 | return "CREATE INDEX {$index} ON {$table} ($columns)"; 203 | } 204 | 205 | /** 206 | * Compile a foreign key command. 207 | * 208 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 209 | * @param \Illuminate\Support\Fluent $command 210 | * @return string 211 | */ 212 | public function compileForeign(Blueprint $blueprint, Fluent $command) 213 | { 214 | $table = $this->wrapTable($blueprint); 215 | 216 | $on = $this->wrapTable($command->on); 217 | 218 | // We need to prepare several of the elements of the foreign key definition 219 | // before we can create the SQL, such as wrapping the tables and convert 220 | // an array of columns to comma-delimited strings for the SQL queries. 221 | $columns = $this->columnize($command->columns); 222 | 223 | $onColumns = $this->columnize((array) $command->references); 224 | 225 | $fkName = substr($command->index, 0, 31); 226 | 227 | $sql = "ALTER TABLE {$table} ADD CONSTRAINT {$fkName} "; 228 | 229 | $sql .= "FOREIGN KEY ({$columns}) REFERENCES {$on} ({$onColumns})"; 230 | 231 | // Once we have the basic foreign key creation statement constructed we can 232 | // build out the syntax for what should happen on an update or delete of 233 | // the affected columns, which will get something like "cascade", etc. 234 | if (! is_null($command->onDelete)) { 235 | $sql .= " ON DELETE {$command->onDelete}"; 236 | } 237 | 238 | if (! is_null($command->onUpdate)) { 239 | $sql .= " ON UPDATE {$command->onUpdate}"; 240 | } 241 | 242 | return $sql; 243 | } 244 | 245 | /** 246 | * Compile a drop foreign key command. 247 | * 248 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 249 | * @param \Illuminate\Support\Fluent $command 250 | * @return string 251 | */ 252 | public function compileDropForeign(Blueprint $blueprint, Fluent $command) 253 | { 254 | $table = $this->wrapTable($blueprint); 255 | 256 | return "ALTER TABLE {$table} DROP CONSTRAINT {$command->index}"; 257 | } 258 | 259 | /** 260 | * Get the SQL for a character set column modifier. 261 | * 262 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 263 | * @param \Illuminate\Support\Fluent $column 264 | * @return string|null 265 | */ 266 | protected function modifyCharset(Blueprint $blueprint, Fluent $column) 267 | { 268 | if (! is_null($column->charset)) { 269 | return ' CHARACTER SET '.$column->charset; 270 | } 271 | } 272 | 273 | /** 274 | * Get the SQL for a collation column modifier. 275 | * 276 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 277 | * @param \Illuminate\Support\Fluent $column 278 | * @return string|null 279 | */ 280 | protected function modifyCollate(Blueprint $blueprint, Fluent $column) 281 | { 282 | if (! is_null($column->collation)) { 283 | return ' COLLATE '.$column->collation; 284 | } 285 | } 286 | 287 | /** 288 | * Get the SQL for a nullable column modifier. 289 | * 290 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 291 | * @param \Illuminate\Support\Fluent $column 292 | * @return string|null 293 | */ 294 | protected function modifyNullable(Blueprint $blueprint, Fluent $column) 295 | { 296 | return $column->nullable ? '' : ' NOT NULL'; 297 | } 298 | 299 | /** 300 | * Get the SQL for a default column modifier. 301 | * 302 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 303 | * @param \Illuminate\Support\Fluent $column 304 | * @return string|null 305 | */ 306 | protected function modifyDefault(Blueprint $blueprint, Fluent $column) 307 | { 308 | if (! is_null($column->default)) { 309 | return ' DEFAULT '.$this->getDefaultValue($column->default); 310 | } 311 | } 312 | 313 | /** 314 | * Create the column definition for a char type. 315 | * 316 | * @param \Illuminate\Support\Fluent $column 317 | * @return string 318 | */ 319 | protected function typeChar(Fluent $column) 320 | { 321 | return "CHAR({$column->length})"; 322 | } 323 | 324 | /** 325 | * Create the column definition for a string type. 326 | * 327 | * @param \Illuminate\Support\Fluent $column 328 | * @return string 329 | */ 330 | protected function typeString(Fluent $column) 331 | { 332 | return "VARCHAR({$column->length})"; 333 | } 334 | 335 | /** 336 | * Create the column definition for a text type. 337 | * 338 | * @param \Illuminate\Support\Fluent $column 339 | * @return string 340 | */ 341 | protected function typeText(Fluent $column) 342 | { 343 | return 'BLOB SUB_TYPE TEXT'; 344 | } 345 | 346 | /** 347 | * Create the column definition for a medium text type. 348 | * 349 | * @param \Illuminate\Support\Fluent $column 350 | * @return string 351 | */ 352 | protected function typeMediumText(Fluent $column) 353 | { 354 | return 'BLOB SUB_TYPE TEXT'; 355 | } 356 | 357 | /** 358 | * Create the column definition for a long text type. 359 | * 360 | * @param \Illuminate\Support\Fluent $column 361 | * @return string 362 | */ 363 | protected function typeLongText(Fluent $column) 364 | { 365 | return 'BLOB SUB_TYPE TEXT'; 366 | } 367 | 368 | /** 369 | * Create the column definition for a integer type. 370 | * 371 | * @param \Illuminate\Support\Fluent $column 372 | * @return string 373 | */ 374 | protected function typeInteger(Fluent $column) 375 | { 376 | return 'INTEGER'; 377 | } 378 | 379 | /** 380 | * Create the column definition for a big integer type. 381 | * 382 | * @param \Illuminate\Support\Fluent $column 383 | * @return string 384 | */ 385 | protected function typeBigInteger(Fluent $column) 386 | { 387 | return 'BIGINT'; 388 | } 389 | 390 | /** 391 | * Create the column definition for a medium integer type. 392 | * 393 | * @param \Illuminate\Support\Fluent $column 394 | * @return string 395 | */ 396 | protected function typeMediumInteger(Fluent $column) 397 | { 398 | return 'INTEGER'; 399 | } 400 | 401 | /** 402 | * Create the column definition for a tiny integer type. 403 | * 404 | * @param \Illuminate\Support\Fluent $column 405 | * @return string 406 | */ 407 | protected function typeTinyInteger(Fluent $column) 408 | { 409 | return 'SMALLINT'; 410 | } 411 | 412 | /** 413 | * Create the column definition for a small integer type. 414 | * 415 | * @param \Illuminate\Support\Fluent $column 416 | * @return string 417 | */ 418 | protected function typeSmallInteger(Fluent $column) 419 | { 420 | return 'SMALLINT'; 421 | } 422 | 423 | /** 424 | * Create the column definition for a float type. 425 | * 426 | * @param \Illuminate\Support\Fluent $column 427 | * @return string 428 | */ 429 | protected function typeFloat(Fluent $column) 430 | { 431 | return 'FLOAT'; 432 | } 433 | 434 | /** 435 | * Create the column definition for a double type. 436 | * 437 | * @param \Illuminate\Support\Fluent $column 438 | * @return string 439 | */ 440 | protected function typeDouble(Fluent $column) 441 | { 442 | return 'DOUBLE PRECISION'; 443 | } 444 | 445 | /** 446 | * Create the column definition for a decimal type. 447 | * 448 | * @param \Illuminate\Support\Fluent $column 449 | * @return string 450 | */ 451 | protected function typeDecimal(Fluent $column) 452 | { 453 | return "DECIMAL({$column->total}, {$column->places})"; 454 | } 455 | 456 | /** 457 | * Create the column definition for a boolean type. 458 | * 459 | * @param \Illuminate\Support\Fluent $column 460 | * @return string 461 | */ 462 | protected function typeBoolean(Fluent $column) 463 | { 464 | return 'CHAR(1)'; 465 | } 466 | 467 | /** 468 | * Create the column definition for an enum type. 469 | * 470 | * @param \Illuminate\Support\Fluent $column 471 | * @return string 472 | */ 473 | protected function typeEnum(Fluent $column) 474 | { 475 | $allowed = array_map(function ($a) { 476 | return "'".$a."'"; 477 | }, $column->allowed); 478 | 479 | return "VARCHAR(255) CHECK (\"{$column->name}\" IN (".implode(', ', $allowed).'))'; 480 | } 481 | 482 | /** 483 | * Create the column definition for a json type. 484 | * 485 | * @param \Illuminate\Support\Fluent $column 486 | * @return string 487 | */ 488 | protected function typeJson(Fluent $column) 489 | { 490 | return 'VARCHAR(8191)'; 491 | } 492 | 493 | /** 494 | * Create the column definition for a jsonb type. 495 | * 496 | * @param \Illuminate\Support\Fluent $column 497 | * @return string 498 | */ 499 | protected function typeJsonb(Fluent $column) 500 | { 501 | return 'VARCHAR(8191) CHARACTER SET OCTETS'; 502 | } 503 | 504 | /** 505 | * Create the column definition for a date type. 506 | * 507 | * @param \Illuminate\Support\Fluent $column 508 | * @return string 509 | */ 510 | protected function typeDate(Fluent $column) 511 | { 512 | return 'DATE'; 513 | } 514 | 515 | /** 516 | * Create the column definition for a date-time type. 517 | * 518 | * @param \Illuminate\Support\Fluent $column 519 | * @return string 520 | */ 521 | protected function typeDateTime(Fluent $column) 522 | { 523 | return 'TIMESTAMP'; 524 | } 525 | 526 | /** 527 | * Create the column definition for a date-time type. 528 | * 529 | * @param \Illuminate\Support\Fluent $column 530 | * @return string 531 | */ 532 | protected function typeDateTimeTz(Fluent $column) 533 | { 534 | // No timezone support, default to plain date time 535 | return $this->typeDateTime($column); 536 | } 537 | 538 | /** 539 | * Create the column definition for a time type. 540 | * 541 | * @param \Illuminate\Support\Fluent $column 542 | * @return string 543 | */ 544 | protected function typeTime(Fluent $column) 545 | { 546 | return 'TIME'; 547 | } 548 | 549 | /** 550 | * Create the column definition for a time type. 551 | * 552 | * @param \Illuminate\Support\Fluent $column 553 | * @return string 554 | */ 555 | protected function typeTimeTz(Fluent $column) 556 | { 557 | // No timezone support, default to plain time 558 | return $this->typeTime($column); 559 | } 560 | 561 | /** 562 | * Create the column definition for a timestamp type. 563 | * 564 | * @param \Illuminate\Support\Fluent $column 565 | * @return string 566 | */ 567 | protected function typeTimestamp(Fluent $column) 568 | { 569 | if ($column->useCurrent) { 570 | return 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP'; 571 | } 572 | 573 | return 'TIMESTAMP'; 574 | } 575 | 576 | /** 577 | * Create the column definition for a timestamp type. 578 | * 579 | * @param \Illuminate\Support\Fluent $column 580 | * @return string 581 | */ 582 | protected function typeTimestampTz(Fluent $column) 583 | { 584 | // No timezone support, default to plain timestamp 585 | return $this->typeTimestamp($column); 586 | } 587 | 588 | /** 589 | * Create the column definition for a binary type. 590 | * 591 | * @param \Illuminate\Support\Fluent $column 592 | * @return string 593 | */ 594 | protected function typeBinary(Fluent $column) 595 | { 596 | return 'BLOB SUB_TYPE BINARY'; 597 | } 598 | 599 | /** 600 | * Create the column definition for a uuid type. 601 | * 602 | * @param \Illuminate\Support\Fluent $column 603 | * @return string 604 | */ 605 | protected function typeUuid(Fluent $column) 606 | { 607 | return 'CHAR(36)'; 608 | } 609 | 610 | /** 611 | * Create the column definition for an IP address type. 612 | * 613 | * @param \Illuminate\Support\Fluent $column 614 | * @return string 615 | */ 616 | protected function typeIpAddress(Fluent $column) 617 | { 618 | return 'VARCHAR(45)'; 619 | } 620 | 621 | /** 622 | * Create the column definition for a MAC address type. 623 | * 624 | * @param \Illuminate\Support\Fluent $column 625 | * @return string 626 | */ 627 | protected function typeMacAddress(Fluent $column) 628 | { 629 | return 'VARCHAR(17)'; 630 | } 631 | } 632 | -------------------------------------------------------------------------------- /tests/QueryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('firebird', DB::getDefaultConnection()); 20 | } 21 | 22 | /** @test */ 23 | public function it_can_get() 24 | { 25 | Order::factory()->count(3)->create(); 26 | 27 | $users = DB::table('users')->get(); 28 | 29 | $this->assertCount(3, $users); 30 | $this->assertInstanceOf(Collection::class, $users); 31 | $this->assertIsObject($users->first()); 32 | $this->assertIsArray($users->toArray()); 33 | 34 | $orders = DB::table('orders')->get(); 35 | 36 | $this->assertCount(3, $orders); 37 | $this->assertInstanceOf(Collection::class, $orders); 38 | $this->assertIsObject($orders->first()); 39 | $this->assertIsArray($orders->toArray()); 40 | } 41 | 42 | /** @test */ 43 | public function it_can_select() 44 | { 45 | User::factory()->create([ 46 | 'name' => 'Anna', 47 | 'city' => 'Sydney', 48 | 'country' => 'Australia', 49 | ]); 50 | 51 | $result = DB::table('users') 52 | ->select(['name', 'city', 'country']) 53 | ->first(); 54 | 55 | $this->assertCount(3, (array) $result); 56 | 57 | $this->assertObjectHasProperty('name', $result); 58 | $this->assertObjectHasProperty('city', $result); 59 | $this->assertObjectHasProperty('country', $result); 60 | 61 | $this->assertEquals('Anna', $result->name); 62 | $this->assertEquals('Sydney', $result->city); 63 | $this->assertEquals('Australia', $result->country); 64 | } 65 | 66 | /** @test */ 67 | public function it_can_select_with_aliases() 68 | { 69 | User::factory()->create([ 70 | 'name' => 'Anna', 71 | 'city' => 'Sydney', 72 | 'country' => 'Australia', 73 | ]); 74 | 75 | $result = DB::table('users') 76 | ->select([ 77 | 'name as USER_NAME', 78 | 'city as user_city', 79 | 'country as User_Country', 80 | ]) 81 | ->first(); 82 | 83 | $this->assertCount(3, (array) $result); 84 | 85 | $this->assertObjectHasProperty('USER_NAME', $result); 86 | $this->assertObjectHasProperty('user_city', $result); 87 | $this->assertObjectHasProperty('User_Country', $result); 88 | 89 | $this->assertEquals('Anna', $result->USER_NAME); 90 | $this->assertEquals('Sydney', $result->user_city); 91 | $this->assertEquals('Australia', $result->User_Country); 92 | } 93 | 94 | /** @test */ 95 | public function it_can_select_distinct() 96 | { 97 | Order::factory()->count(1)->create(['price' => 10]); 98 | Order::factory()->count(10)->create(['price' => 50]); 99 | Order::factory()->count(5)->create(['price' => 100]); 100 | 101 | $results = DB::table('orders')->select('price')->distinct()->get(); 102 | 103 | $this->assertCount(3, $results); 104 | } 105 | 106 | /** @test */ 107 | public function it_can_filter_where_with_results() 108 | { 109 | User::factory()->count(5)->create(['name' => 'Frank']); 110 | User::factory()->count(2)->create(['name' => 'Inigo']); 111 | User::factory()->count(7)->create(['name' => 'Ashley']); 112 | 113 | $results = DB::table('users') 114 | ->where('name', 'Frank') 115 | ->get(); 116 | 117 | $this->assertCount(5, $results); 118 | $this->assertCount(1, $results->pluck('name')->unique()); 119 | $this->assertEquals('Frank', $results->random()->name); 120 | } 121 | 122 | /** @test */ 123 | public function it_can_filter_where_without_results() 124 | { 125 | User::factory()->count(25)->create(); 126 | 127 | $results = DB::table('users') 128 | ->where('id', 26) 129 | ->get(); 130 | 131 | $this->assertCount(0, $results); 132 | $this->assertInstanceOf(Collection::class, $results); 133 | $this->assertEquals([], $results->toArray()); 134 | $this->assertNull($results->first()); 135 | } 136 | 137 | /** @test */ 138 | public function it_can_filter_where_gt() 139 | { 140 | Order::factory() 141 | ->count(8) 142 | ->state(new Sequence( 143 | ['price' => 5], 144 | ['price' => 25], 145 | ['price' => 50], 146 | ['price' => 99], 147 | ['price' => 100], 148 | ['price' => 101], 149 | ['price' => 150], 150 | ['price' => 200], 151 | )) 152 | ->create(); 153 | 154 | $results = DB::table('orders') 155 | ->where('price', '>', 100) 156 | ->get(); 157 | 158 | $this->assertCount(3, $results); 159 | } 160 | 161 | /** @test */ 162 | public function it_can_filter_where_gte() 163 | { 164 | Order::factory() 165 | ->count(8) 166 | ->state(new Sequence( 167 | ['price' => 5], 168 | ['price' => 25], 169 | ['price' => 50], 170 | ['price' => 99], 171 | ['price' => 100], 172 | ['price' => 101], 173 | ['price' => 150], 174 | ['price' => 200], 175 | )) 176 | ->create(); 177 | 178 | $results = DB::table('orders') 179 | ->where('price', '>=', 100) 180 | ->get(); 181 | 182 | $this->assertCount(4, $results); 183 | } 184 | 185 | /** @test */ 186 | public function it_can_filter_where_lt() 187 | { 188 | Order::factory() 189 | ->count(8) 190 | ->state(new Sequence( 191 | ['price' => 5], 192 | ['price' => 25], 193 | ['price' => 50], 194 | ['price' => 99], 195 | ['price' => 100], 196 | ['price' => 101], 197 | ['price' => 150], 198 | ['price' => 200], 199 | )) 200 | ->create(); 201 | 202 | $results = DB::table('orders') 203 | ->where('price', '<', 100) 204 | ->get(); 205 | 206 | $this->assertCount(4, $results); 207 | } 208 | 209 | /** @test */ 210 | public function it_can_filter_where_lte() 211 | { 212 | Order::factory() 213 | ->count(8) 214 | ->state(new Sequence( 215 | ['price' => 5], 216 | ['price' => 25], 217 | ['price' => 50], 218 | ['price' => 99], 219 | ['price' => 100], 220 | ['price' => 101], 221 | ['price' => 150], 222 | ['price' => 200], 223 | )) 224 | ->create(); 225 | 226 | $results = DB::table('orders') 227 | ->where('price', '<=', 100) 228 | ->get(); 229 | 230 | $this->assertCount(5, $results); 231 | } 232 | 233 | /** @test */ 234 | public function it_can_filter_where_not_equal() 235 | { 236 | Order::factory() 237 | ->count(8) 238 | ->state(new Sequence( 239 | ['price' => 5], 240 | ['price' => 25], 241 | ['price' => 50], 242 | ['price' => 99], 243 | ['price' => 100], 244 | ['price' => 101], 245 | ['price' => 150], 246 | ['price' => 200], 247 | )) 248 | ->create(); 249 | 250 | $results = DB::table('orders') 251 | ->where('price', '!=', 100) 252 | ->get(); 253 | 254 | $this->assertCount(7, $results); 255 | 256 | $results = DB::table('orders') 257 | ->where('price', '<>', 100) 258 | ->get(); 259 | 260 | $this->assertCount(7, $results); 261 | } 262 | 263 | /** @test */ 264 | public function it_can_filter_where_like() 265 | { 266 | // "Like" is case-sensitive. For case-insensitive, use "containing". 267 | 268 | Order::factory()->create(['name' => 'Pants Small']); 269 | Order::factory()->create(['name' => 'Pants Large']); 270 | Order::factory()->create(['name' => 'Shirt Small']); 271 | Order::factory()->create(['name' => 'Shirt Medium']); 272 | Order::factory()->create(['name' => 'Shirt Large']); 273 | 274 | $results = DB::table('orders') 275 | ->where('name', 'like', 'Shirt%') 276 | ->get(); 277 | 278 | $this->assertCount(3, $results); 279 | 280 | $results = DB::table('orders') 281 | ->where('name', 'like', '%Small') 282 | ->get(); 283 | 284 | $this->assertCount(2, $results); 285 | } 286 | 287 | /** @test */ 288 | public function it_can_filter_where_not_like() 289 | { 290 | Order::factory()->create(['name' => 'Pants Small']); 291 | Order::factory()->create(['name' => 'Pants Large']); 292 | Order::factory()->create(['name' => 'Shirt Small']); 293 | Order::factory()->create(['name' => 'Shirt Medium']); 294 | Order::factory()->create(['name' => 'Shirt Large']); 295 | 296 | $results = DB::table('orders') 297 | ->where('name', 'not like', 'Shirt%') 298 | ->get(); 299 | 300 | $this->assertCount(2, $results); 301 | } 302 | 303 | /** @test */ 304 | public function it_can_filter_where_array() 305 | { 306 | Order::factory()->create(['name' => 'Pants Small', 'price' => 60]); 307 | Order::factory()->create(['name' => 'Pants Large', 'price' => 80]); 308 | Order::factory()->create(['name' => 'Shirt Small', 'price' => 50]); 309 | Order::factory()->create(['name' => 'Shirt Medium', 'price' => 60]); 310 | Order::factory()->create(['name' => 'Shirt Large', 'price' => 70]); 311 | 312 | $results = DB::table('orders') 313 | ->where([ 314 | ['price', '>=', 60], 315 | ['name', 'like', '%Large%'], 316 | ['name', 'not like', '%Pants%'], 317 | ]) 318 | ->get(); 319 | 320 | $this->assertCount(1, $results); 321 | } 322 | 323 | /** @test */ 324 | public function it_can_filter_or_where() 325 | { 326 | Order::factory() 327 | ->count(8) 328 | ->state(new Sequence( 329 | ['price' => 5], 330 | ['price' => 25], 331 | ['price' => 50], 332 | ['price' => 99], 333 | ['price' => 100], 334 | ['price' => 100], 335 | ['price' => 150], 336 | ['price' => 200], 337 | )) 338 | ->create(); 339 | 340 | $results = DB::table('orders') 341 | ->where('price', 100) 342 | ->orWhere('price', 5) 343 | ->get(); 344 | 345 | $this->assertCount(3, $results); 346 | } 347 | 348 | /** @test */ 349 | public function it_can_filter_grouped_or_where() 350 | { 351 | Order::factory()->count(2)->create(['price' => 100]); 352 | Order::factory()->create(['price' => 25, 'quantity' => 1]); 353 | Order::factory()->create(['price' => 30, 'quantity' => 3]); 354 | 355 | $results = DB::table('orders') 356 | ->where('price', '>=', 100) 357 | ->orWhere(function ($query) { 358 | $query->where('price', '>', 10) 359 | ->where('quantity', '>', 2); 360 | }) 361 | ->get(); 362 | 363 | $this->assertCount(3, $results); 364 | } 365 | 366 | /** @test */ 367 | public function it_can_filter_where_in() 368 | { 369 | Order::factory()->count(1)->create(['price' => 75]); 370 | Order::factory()->count(3)->create(['price' => 100]); 371 | Order::factory()->count(5)->create(['price' => 125]); 372 | 373 | $results = DB::table('orders') 374 | ->whereIn('price', [100, 125]) 375 | ->get(); 376 | 377 | $this->assertCount(8, $results); 378 | } 379 | 380 | /** @test */ 381 | public function it_can_filter_where_in_exceeds_firebird_2_limit() 382 | { 383 | Order::factory() 384 | ->count(1505) 385 | ->for(User::factory()) 386 | ->create(['price' => 100]); 387 | 388 | $results = DB::table('orders') 389 | ->whereIn('price', [100]) 390 | ->count(); 391 | 392 | $this->assertEquals(1505, $results); 393 | } 394 | 395 | /** @test */ 396 | public function it_can_filter_where_not_in() 397 | { 398 | Order::factory()->count(1)->create(['price' => 75]); 399 | Order::factory()->count(3)->create(['price' => 100]); 400 | Order::factory()->count(5)->create(['price' => 125]); 401 | 402 | $results = DB::table('orders') 403 | ->whereNotIn('price', [100, 125]) 404 | ->get(); 405 | 406 | $this->assertCount(1, $results); 407 | } 408 | 409 | /** @test */ 410 | public function it_can_filter_where_between() 411 | { 412 | Order::factory()->create(['price' => 10]); 413 | Order::factory()->create(['price' => 20]); 414 | Order::factory()->create(['price' => 30]); 415 | Order::factory()->create(['price' => 40]); 416 | Order::factory()->create(['price' => 50]); 417 | 418 | $results = DB::table('orders') 419 | ->whereBetween('price', [30, 60]) 420 | ->get(); 421 | 422 | $this->assertCount(3, $results); 423 | } 424 | 425 | /** @test */ 426 | public function it_can_filter_where_not_between() 427 | { 428 | Order::factory()->create(['price' => 10]); 429 | Order::factory()->create(['price' => 20]); 430 | Order::factory()->create(['price' => 30]); 431 | Order::factory()->create(['price' => 40]); 432 | Order::factory()->create(['price' => 50]); 433 | 434 | $results = DB::table('orders') 435 | ->whereNotBetween('price', [30, 60]) 436 | ->get(); 437 | 438 | $this->assertCount(2, $results); 439 | } 440 | 441 | /** @test */ 442 | public function it_can_filter_where_null() 443 | { 444 | Order::factory()->count(10)->create(); 445 | 446 | $results = DB::table('orders') 447 | ->whereNull('deleted_at') 448 | ->get(); 449 | 450 | $this->assertCount(10, $results); 451 | } 452 | 453 | /** @test */ 454 | public function it_can_filter_where_not_null() 455 | { 456 | Order::factory()->count(10)->create(); 457 | 458 | $results = DB::table('orders') 459 | ->whereNotNull('created_at') 460 | ->get(); 461 | 462 | $this->assertCount(10, $results); 463 | } 464 | 465 | /** @test */ 466 | public function it_can_filter_where_date() 467 | { 468 | $this->markTestSkipped('The necessary grammar for whereDate() has not been implemented.'); 469 | 470 | $results = DB::table('orders') 471 | ->whereDate('created_at', now()) 472 | ->get(); 473 | 474 | $this->assertCount(10, $results); 475 | } 476 | 477 | /** @test */ 478 | public function it_can_filter_where_time() 479 | { 480 | $this->markTestSkipped('The necessary grammar for whereTime() has not been implemented.'); 481 | 482 | $results = DB::table('orders') 483 | ->whereTime('created_at', now()) 484 | ->get(); 485 | 486 | $this->assertCount(10, $results); 487 | } 488 | 489 | /** @test */ 490 | public function it_can_filter_where_day() 491 | { 492 | Order::factory()->count(3)->create(['created_at' => now()]); 493 | Order::factory()->count(5)->create(['created_at' => now()->subDay()]); 494 | 495 | $results = DB::table('orders') 496 | ->whereDay('created_at', now()) 497 | ->get(); 498 | 499 | $this->assertCount(3, $results); 500 | } 501 | 502 | /** @test */ 503 | public function it_can_filter_where_month() 504 | { 505 | Order::factory()->count(3)->create(['created_at' => now()]); 506 | Order::factory()->count(5)->create(['created_at' => now()->subMonth()]); 507 | 508 | $results = DB::table('orders') 509 | ->whereMonth('created_at', now()) 510 | ->get(); 511 | 512 | $this->assertCount(3, $results); 513 | } 514 | 515 | /** @test */ 516 | public function it_can_filter_where_year() 517 | { 518 | Order::factory()->count(3)->create(['created_at' => now()]); 519 | Order::factory()->count(5)->create(['created_at' => now()->subYear()]); 520 | 521 | $results = DB::table('orders') 522 | ->whereYear('created_at', now()) 523 | ->get(); 524 | 525 | $this->assertCount(3, $results); 526 | } 527 | 528 | /** @test */ 529 | public function it_can_filter_where_containing() 530 | { 531 | // "Containing" is a case-insensitive alternative to "like". Also, the 532 | // % wildcard operators are not required. 533 | 534 | Order::factory()->create(['name' => 'Pants Small']); 535 | Order::factory()->create(['name' => 'Pants Large']); 536 | Order::factory()->create(['name' => 'Shirt Small']); 537 | Order::factory()->create(['name' => 'Shirt Medium']); 538 | Order::factory()->create(['name' => 'Shirt Large']); 539 | 540 | $results = DB::table('orders') 541 | ->where('name', 'containing', 'shirt') 542 | ->get(); 543 | 544 | $this->assertCount(3, $results); 545 | 546 | $results = DB::table('orders') 547 | ->where('name', 'containing', 'small') 548 | ->get(); 549 | 550 | $this->assertCount(2, $results); 551 | } 552 | 553 | /** @test */ 554 | public function it_can_filter_where_not_containing() 555 | { 556 | // "Containing" is a case-insensitive alternative to "like". Also, the 557 | // % wildcard operators are not required. 558 | 559 | Order::factory()->create(['name' => 'Pants Small']); 560 | Order::factory()->create(['name' => 'Pants Large']); 561 | Order::factory()->create(['name' => 'Shirt Small']); 562 | Order::factory()->create(['name' => 'Shirt Medium']); 563 | Order::factory()->create(['name' => 'Shirt Large']); 564 | 565 | $results = DB::table('orders') 566 | ->where('name', 'not containing', 'shirt') 567 | ->get(); 568 | 569 | $this->assertCount(2, $results); 570 | 571 | $results = DB::table('orders') 572 | ->where('name', 'not containing', 'small') 573 | ->get(); 574 | 575 | $this->assertCount(3, $results); 576 | } 577 | 578 | /** @test */ 579 | public function it_can_filter_where_starting_with() 580 | { 581 | Order::factory()->create(['name' => 'Pants Small']); 582 | Order::factory()->create(['name' => 'Pants Large']); 583 | Order::factory()->create(['name' => 'Shirt Small']); 584 | Order::factory()->create(['name' => 'Shirt Medium']); 585 | Order::factory()->create(['name' => 'Shirt Large']); 586 | 587 | $results = DB::table('orders') 588 | ->where('name', 'starting with', 'Shirt') 589 | ->get(); 590 | 591 | $this->assertCount(3, $results); 592 | 593 | $results = DB::table('orders') 594 | ->where('name', 'starting with', 'Pants') 595 | ->get(); 596 | 597 | $this->assertCount(2, $results); 598 | } 599 | 600 | /** @test */ 601 | public function it_can_filter_where_not_starting_with() 602 | { 603 | Order::factory()->create(['name' => 'Pants Small']); 604 | Order::factory()->create(['name' => 'Pants Large']); 605 | Order::factory()->create(['name' => 'Shirt Small']); 606 | Order::factory()->create(['name' => 'Shirt Medium']); 607 | Order::factory()->create(['name' => 'Shirt Large']); 608 | 609 | $results = DB::table('orders') 610 | ->where('name', 'not starting with', 'Shirt') 611 | ->get(); 612 | 613 | $this->assertCount(2, $results); 614 | 615 | $results = DB::table('orders') 616 | ->where('name', 'not starting with', 'Pants') 617 | ->get(); 618 | 619 | $this->assertCount(3, $results); 620 | } 621 | 622 | /** @test */ 623 | public function it_can_filter_where_similar_to() 624 | { 625 | Order::factory()->create(['name' => 'Pants Small']); 626 | Order::factory()->create(['name' => 'Pants Large']); 627 | Order::factory()->create(['name' => 'Shirt Small']); 628 | Order::factory()->create(['name' => 'Shirt Medium']); 629 | Order::factory()->create(['name' => 'Shirt Large']); 630 | 631 | $results = DB::table('orders') 632 | ->where('name', 'similar to', 'Pants (Medium|Large)') 633 | ->get(); 634 | 635 | $this->assertCount(1, $results); 636 | 637 | $results = DB::table('orders') 638 | ->where('name', 'similar to', 'Shirt (Medium|Large)') 639 | ->get(); 640 | 641 | $this->assertCount(2, $results); 642 | } 643 | 644 | /** @test */ 645 | public function it_can_filter_where_not_similar_to() 646 | { 647 | Order::factory()->create(['name' => 'Pants Small']); 648 | Order::factory()->create(['name' => 'Pants Large']); 649 | Order::factory()->create(['name' => 'Shirt Small']); 650 | Order::factory()->create(['name' => 'Shirt Medium']); 651 | Order::factory()->create(['name' => 'Shirt Large']); 652 | 653 | $results = DB::table('orders') 654 | ->where('name', 'not similar to', 'Pants (Medium|Large)') 655 | ->get(); 656 | 657 | $this->assertCount(4, $results); 658 | 659 | $results = DB::table('orders') 660 | ->where('name', 'not similar to', 'Shirt (Medium|Large)') 661 | ->get(); 662 | 663 | $this->assertCount(3, $results); 664 | } 665 | 666 | /** @test */ 667 | public function it_can_filter_where_is_distinct_from() 668 | { 669 | User::factory()->create(['state' => null]); 670 | User::factory()->create(['state' => 'NY']); 671 | User::factory()->create(['state' => 'AK']); 672 | 673 | $results = DB::table('users') 674 | ->where('state', 'is distinct from', 'NY') 675 | ->get(); 676 | 677 | $this->assertCount(2, $results); 678 | 679 | $results = DB::table('users') 680 | ->where('state', 'is distinct from', null) 681 | ->get(); 682 | 683 | $this->assertCount(2, $results); 684 | } 685 | 686 | /** @test */ 687 | public function it_can_filter_where_is_not_distinct_from() 688 | { 689 | User::factory()->create(['state' => null]); 690 | User::factory()->create(['state' => 'NY']); 691 | User::factory()->create(['state' => 'AK']); 692 | 693 | $results = DB::table('users') 694 | ->where('state', 'is not distinct from', 'NY') 695 | ->get(); 696 | 697 | $this->assertCount(1, $results); 698 | 699 | $results = DB::table('users') 700 | ->where('state', 'is not distinct from', null) 701 | ->get(); 702 | 703 | $this->assertCount(2, $results); 704 | } 705 | 706 | /** @test */ 707 | public function it_can_filter_where_exists() 708 | { 709 | Order::factory()->count(2)->create(['price' => 120]); 710 | Order::factory()->count(3)->create(['price' => 80]); 711 | 712 | $results = DB::table('users') 713 | ->whereExists(function ($query) { 714 | $query->select(DB::raw(1)) 715 | ->from('orders') 716 | ->whereColumn('orders.user_id', 'users.id') 717 | ->where('price', '>', 100); 718 | }) 719 | ->get(); 720 | 721 | $this->assertCount(2, $results); 722 | } 723 | 724 | /** @test */ 725 | public function it_can_filter_subquery_where() 726 | { 727 | Order::factory()->count(2)->create(['price' => 100]); 728 | Order::factory()->count(3)->create(['price' => 80]); 729 | Order::factory()->count(6)->create(['price' => 120]); 730 | 731 | $results = DB::table('users') 732 | ->where(function ($query) { 733 | $query->select('price') 734 | ->from('orders') 735 | ->whereColumn('orders.user_id', 'users.id') 736 | ->limit(1); 737 | }, 100) 738 | ->get(); 739 | 740 | $this->assertCount(2, $results); 741 | } 742 | 743 | /** @test */ 744 | public function it_can_order_by_asc() 745 | { 746 | Order::factory()->create(['price' => 100]); 747 | Order::factory()->create(['price' => 200]); 748 | Order::factory()->create(['price' => 300]); 749 | 750 | $results = DB::table('orders')->orderBy('price')->get(); 751 | 752 | $this->assertEquals(100, $results->first()->price); 753 | $this->assertEquals(300, $results->last()->price); 754 | } 755 | 756 | /** @test */ 757 | public function it_can_order_by_desc() 758 | { 759 | Order::factory()->create(['price' => 100]); 760 | Order::factory()->create(['price' => 200]); 761 | Order::factory()->create(['price' => 300]); 762 | 763 | $results = DB::table('orders')->orderByDesc('price')->get(); 764 | 765 | $this->assertEquals(300, $results->first()->price); 766 | $this->assertEquals(100, $results->last()->price); 767 | } 768 | 769 | /** @test */ 770 | public function it_can_order_latest() 771 | { 772 | Order::factory()->create(['price' => 100, 'created_at' => now()]); 773 | Order::factory()->create(['price' => 200, 'created_at' => now()->subMonths(1)]); 774 | Order::factory()->create(['price' => 300, 'created_at' => now()->subMonths(2)]); 775 | 776 | $results = DB::table('orders')->latest()->get(); 777 | 778 | $this->assertEquals(100, $results->first()->price); 779 | $this->assertEquals(300, $results->last()->price); 780 | } 781 | 782 | /** @test */ 783 | public function it_can_order_oldest() 784 | { 785 | Order::factory()->create(['price' => 100, 'created_at' => now()]); 786 | Order::factory()->create(['price' => 200, 'created_at' => now()->subMonths(1)]); 787 | Order::factory()->create(['price' => 300, 'created_at' => now()->subMonths(2)]); 788 | 789 | $results = DB::table('orders')->oldest()->get(); 790 | 791 | $this->assertEquals(300, $results->first()->price); 792 | $this->assertEquals(100, $results->last()->price); 793 | } 794 | 795 | /** @test */ 796 | public function it_can_return_random_order() 797 | { 798 | User::factory()->count(25)->create(); 799 | 800 | $resultsA = DB::table('users')->inRandomOrder()->get(); 801 | $resultsB = DB::table('users')->inRandomOrder()->get(); 802 | $resultsC = DB::table('users')->inRandomOrder()->get(); 803 | 804 | $this->assertNotEquals($resultsA, $resultsB); 805 | $this->assertNotEquals($resultsA, $resultsC); 806 | $this->assertNotEquals($resultsB, $resultsC); 807 | } 808 | 809 | /** @test */ 810 | public function it_can_remove_existing_orderings() 811 | { 812 | Order::factory()->count(10)->create(); 813 | 814 | $query = DB::table('orders')->orderByDesc('id'); 815 | 816 | $results = $query->get(); 817 | 818 | $this->assertEquals(10, $results->first()->id); 819 | $this->assertEquals(1, $results->last()->id); 820 | 821 | $results = $query->reorder()->get(); 822 | 823 | $this->assertEquals(1, $results->first()->id); 824 | $this->assertEquals(10, $results->last()->id); 825 | } 826 | 827 | /** @test */ 828 | public function it_can_pluck() 829 | { 830 | Order::factory()->count(10)->create(); 831 | 832 | $results = DB::table('orders')->pluck('id'); 833 | 834 | $this->assertCount(10, $results); 835 | foreach (range(1, 10) as $expectedId) { 836 | $this->assertContains($expectedId, $results); 837 | } 838 | } 839 | 840 | /** @test */ 841 | public function it_can_count() 842 | { 843 | Order::factory()->count(10)->create(); 844 | 845 | $count = DB::table('orders')->count(); 846 | 847 | $this->assertEquals(10, $count); 848 | } 849 | 850 | /** @test */ 851 | public function it_can_aggregate_max() 852 | { 853 | Order::factory() 854 | ->count(5) 855 | ->state(new Sequence( 856 | ['price' => 68], 857 | ['price' => 92], 858 | ['price' => 12], 859 | ['price' => 37], 860 | ['price' => 54], 861 | )) 862 | ->create(); 863 | 864 | $price = DB::table('orders')->max('price'); 865 | 866 | $this->assertEquals(92, $price); 867 | } 868 | 869 | /** @test */ 870 | public function it_can_aggregate_min() 871 | { 872 | Order::factory() 873 | ->count(5) 874 | ->state(new Sequence( 875 | ['price' => 68], 876 | ['price' => 92], 877 | ['price' => 12], 878 | ['price' => 37], 879 | ['price' => 54], 880 | )) 881 | ->create(); 882 | 883 | $price = DB::table('orders')->min('price'); 884 | 885 | $this->assertEquals(12, $price); 886 | } 887 | 888 | /** @test */ 889 | public function it_can_aggregate_average() 890 | { 891 | Order::factory() 892 | ->count(5) 893 | ->state(new Sequence( 894 | ['price' => 68], 895 | ['price' => 92], 896 | ['price' => 12], 897 | ['price' => 37], 898 | ['price' => 54], 899 | )) 900 | ->create(); 901 | 902 | $price = DB::table('orders')->avg('price'); 903 | 904 | $this->assertEquals((int) 52.6, $price); 905 | } 906 | 907 | /** @test */ 908 | public function it_can_aggregate_sum() 909 | { 910 | Order::factory() 911 | ->count(5) 912 | ->state(new Sequence( 913 | ['price' => 68], 914 | ['price' => 92], 915 | ['price' => 12], 916 | ['price' => 37], 917 | ['price' => 54], 918 | )) 919 | ->create(); 920 | 921 | $price = DB::table('orders')->sum('price'); 922 | 923 | $this->assertEquals(263, $price); 924 | } 925 | 926 | /** @test */ 927 | public function it_can_check_exists() 928 | { 929 | User::factory()->create(); 930 | 931 | $this->assertTrue(DB::table('users')->where('id', 1)->exists()); 932 | $this->assertFalse(DB::table('users')->where('id', null)->exists()); 933 | } 934 | 935 | /** @test */ 936 | public function it_can_execute_raw_expressions() 937 | { 938 | Order::factory() 939 | ->count(6) 940 | ->state(new Sequence( 941 | ['price' => 50], 942 | ['price' => 50], 943 | ['price' => 70], 944 | ['price' => 90], 945 | ['price' => 90], 946 | ['price' => 90], 947 | )) 948 | ->create(); 949 | 950 | $results = DB::table('orders') 951 | ->select(DB::raw('count(*) as "price_count", "price"')) 952 | ->groupBy('price') 953 | ->get(); 954 | 955 | $this->assertCount(3, $results); 956 | $this->assertEquals(2, $results->where('price', 50)->first()->price_count); 957 | $this->assertEquals(1, $results->where('price', 70)->first()->price_count); 958 | $this->assertEquals(3, $results->where('price', 90)->first()->price_count); 959 | } 960 | 961 | /** @test */ 962 | public function it_can_execute_raw_select_containing_arithmetic() 963 | { 964 | if ($this->getDatabaseEngineVersion() >= 4.0) { 965 | // Ref: https://github.com/FirebirdSQL/php-firebird/issues/26 966 | $this->markTestSkipped('Skipped due to an issue with DECIMAL or NUMERIC types in the PHP Firebird PDO extension for database engine version 4.0+'); 967 | } 968 | 969 | Order::factory() 970 | ->count(3) 971 | ->state(new Sequence( 972 | ['price' => 50], 973 | ['price' => 70], 974 | ['price' => 90], 975 | )) 976 | ->create(); 977 | 978 | $results = DB::table('orders') 979 | ->selectRaw('"price", "price" * 1.1 as "price_with_tax"') 980 | ->get(); 981 | 982 | foreach ($results as $result) { 983 | $this->assertEquals(round($result->price * 1.1), $result->price_with_tax); 984 | } 985 | } 986 | 987 | /** @test */ 988 | public function it_can_execute_raw_select_containing_sum() 989 | { 990 | Order::factory() 991 | ->count(3) 992 | ->state(new Sequence( 993 | ['price' => 50], 994 | ['price' => 70], 995 | ['price' => 90], 996 | )) 997 | ->create(); 998 | 999 | $result = DB::table('orders') 1000 | ->selectRaw('SUM("price") as "price"') 1001 | ->get() 1002 | ->first(); 1003 | 1004 | $this->assertEquals(210, $result->price); 1005 | } 1006 | 1007 | /** @test */ 1008 | public function it_can_execute_raw_where() 1009 | { 1010 | User::factory()->count(3)->create(); 1011 | User::factory()->create(['city' => null]); 1012 | 1013 | $results = DB::table('users') 1014 | ->whereRaw('"city" is not null') 1015 | ->get(); 1016 | 1017 | $this->assertCount(3, $results); 1018 | } 1019 | 1020 | /** @test */ 1021 | public function it_can_execute_raw_order_by() 1022 | { 1023 | Order::factory() 1024 | ->count(3) 1025 | ->state(new Sequence( 1026 | ['price' => 50, 'quantity' => 10], 1027 | ['price' => 70, 'quantity' => 5], 1028 | ['price' => 90, 'quantity' => 1], 1029 | )) 1030 | ->create(); 1031 | 1032 | $results = DB::table('orders') 1033 | ->orderByRaw('"price" * "quantity" desc') 1034 | ->get(); 1035 | 1036 | $max = $results->first()->price * $results->first()->quantity; 1037 | $min = $results->last()->price * $results->last()->quantity; 1038 | 1039 | $this->assertEquals(500, $max); 1040 | $this->assertEquals(90, $min); 1041 | } 1042 | 1043 | /** @test */ 1044 | public function it_can_add_inner_join() 1045 | { 1046 | Order::factory()->count(10)->create(); 1047 | 1048 | $results = DB::table('orders') 1049 | ->join('users', 'users.id', '=', 'orders.user_id') 1050 | ->get(); 1051 | 1052 | $this->assertCount(10, $results); 1053 | $this->assertObjectHasProperty('name', $results->first()); 1054 | $this->assertObjectHasProperty('email', $results->first()); 1055 | $this->assertObjectHasProperty('state', $results->first()); 1056 | $this->assertObjectHasProperty('price', $results->first()); 1057 | $this->assertObjectHasProperty('quantity', $results->first()); 1058 | } 1059 | 1060 | /** @test */ 1061 | public function it_can_add_inner_join_where() 1062 | { 1063 | Order::factory()->count(2)->create(['price' => 100]); 1064 | Order::factory()->count(3)->create(['price' => 50]); 1065 | 1066 | $results = DB::table('orders') 1067 | ->join('users', function ($join) { 1068 | $join->on('users.id', '=', 'orders.user_id') 1069 | ->where('orders.price', 100); 1070 | }) 1071 | ->get(); 1072 | 1073 | $this->assertCount(2, $results); 1074 | $this->assertEquals(100, $results->first()->price); 1075 | 1076 | $this->assertObjectHasProperty('name', $results->first()); 1077 | $this->assertObjectHasProperty('email', $results->first()); 1078 | $this->assertObjectHasProperty('state', $results->first()); 1079 | $this->assertObjectHasProperty('price', $results->first()); 1080 | $this->assertObjectHasProperty('quantity', $results->first()); 1081 | } 1082 | 1083 | /** @test */ 1084 | public function it_can_add_left_join() 1085 | { 1086 | Order::factory()->count(2)->create(['price' => 100]); 1087 | Order::factory()->count(3)->create(['price' => 50]); 1088 | 1089 | $results = DB::table('orders') 1090 | ->leftJoin('users', function ($join) { 1091 | $join->on('users.id', '=', 'orders.user_id') 1092 | ->where('orders.price', 100); 1093 | }) 1094 | ->get(); 1095 | 1096 | $this->assertCount(5, $results); 1097 | $this->assertCount(0, $results->whereNull('price')); 1098 | $this->assertCount(3, $results->whereNull('email')); 1099 | } 1100 | 1101 | /** @test */ 1102 | public function it_can_add_right_join() 1103 | { 1104 | Order::factory()->count(2)->create(['price' => 100]); 1105 | Order::factory()->count(3)->create(['price' => 50]); 1106 | 1107 | $results = DB::table('orders') 1108 | ->rightJoin('users', function ($join) { 1109 | $join->on('users.id', '=', 'orders.user_id') 1110 | ->where('orders.price', 100); 1111 | }) 1112 | ->get(); 1113 | 1114 | $this->assertCount(5, $results); 1115 | $this->assertCount(3, $results->whereNull('price')); 1116 | $this->assertCount(0, $results->whereNull('email')); 1117 | } 1118 | 1119 | /** @test */ 1120 | public function it_can_add_subquery_join() 1121 | { 1122 | Order::factory()->create(); 1123 | 1124 | $latestOrder = DB::table('orders') 1125 | ->select('user_id', DB::raw('MAX("created_at") as "last_order_created_at"')) 1126 | ->groupBy('user_id'); 1127 | 1128 | $user = DB::table('users') 1129 | ->joinSub($latestOrder, 'latest_order', function ($join) { 1130 | $join->on('users.id', '=', 'latest_order.user_id'); 1131 | })->first(); 1132 | 1133 | $this->assertNotNull($user->last_order_created_at); 1134 | } 1135 | 1136 | /** @test */ 1137 | public function it_can_union_queries() 1138 | { 1139 | Order::factory() 1140 | ->count(5) 1141 | ->state(new Sequence( 1142 | ['price' => 110], 1143 | ['price' => 100], 1144 | ['price' => 100], 1145 | ['price' => 80], 1146 | ['price' => 16], 1147 | )) 1148 | ->create(); 1149 | 1150 | $first = DB::table('orders') 1151 | ->where('price', 100); 1152 | 1153 | $orders = DB::table('orders') 1154 | ->where('price', 16) 1155 | ->union($first) 1156 | ->get(); 1157 | 1158 | $this->assertCount(3, $orders); 1159 | } 1160 | 1161 | /** @test */ 1162 | public function it_can_group_having() 1163 | { 1164 | User::factory()->count(5)->create(['country' => 'Australia']); 1165 | User::factory()->count(3)->create(['country' => 'New Zealand']); 1166 | User::factory()->count(2)->create(['country' => 'England']); 1167 | 1168 | $results = DB::table('users') 1169 | ->selectRaw('count("id") as "count", "country"') 1170 | ->groupBy('country') 1171 | ->having('country', '!=', 'England') 1172 | ->get(); 1173 | 1174 | $this->assertCount(2, $results); 1175 | $results = $results->mapWithKeys(fn ($result) => [$result->country => $result->count]); 1176 | $this->assertEquals(5, $results['Australia']); 1177 | $this->assertEquals(3, $results['New Zealand']); 1178 | } 1179 | 1180 | /** @test */ 1181 | public function it_can_group_having_raw() 1182 | { 1183 | User::factory()->count(5)->create(['country' => 'Australia']); 1184 | User::factory()->count(3)->create(['country' => 'New Zealand']); 1185 | User::factory()->count(2)->create(['country' => 'England']); 1186 | 1187 | $results = DB::table('users') 1188 | ->selectRaw('count("id") as "count", "country"') 1189 | ->groupBy('country') 1190 | ->havingRaw('count("id") > 2') 1191 | ->get(); 1192 | 1193 | $this->assertCount(2, $results); 1194 | $results = $results->mapWithKeys(fn ($result) => [$result->country => $result->count]); 1195 | $this->assertEquals(5, $results['Australia']); 1196 | $this->assertEquals(3, $results['New Zealand']); 1197 | } 1198 | 1199 | /** @test */ 1200 | public function it_can_offset_results() 1201 | { 1202 | User::factory()->count(10)->create(); 1203 | 1204 | $results = DB::table('users') 1205 | ->offset(3) 1206 | ->get(); 1207 | 1208 | $this->assertCount(7, $results); 1209 | $this->assertEquals(4, $results->first()->id); 1210 | $this->assertEquals(10, $results->last()->id); 1211 | } 1212 | 1213 | /** @test */ 1214 | public function it_can_offset_and_limit_results() 1215 | { 1216 | User::factory()->count(10)->create(); 1217 | 1218 | $results = DB::table('users') 1219 | ->offset(3) 1220 | ->limit(3) 1221 | ->get(); 1222 | 1223 | $this->assertCount(3, $results); 1224 | $this->assertEquals(4, $results->first()->id); 1225 | $this->assertEquals(6, $results->last()->id); 1226 | } 1227 | 1228 | /** @test */ 1229 | public function it_can_limit_results() 1230 | { 1231 | User::factory()->count(10)->create(); 1232 | 1233 | $results = DB::table('users') 1234 | ->limit(3) 1235 | ->get(); 1236 | 1237 | $this->assertCount(3, $results); 1238 | $this->assertEquals(1, $results->first()->id); 1239 | $this->assertEquals(3, $results->last()->id); 1240 | } 1241 | 1242 | // /** @test */ 1243 | // public function it_can_insert_returning_id() 1244 | // { 1245 | // $id = DB::table('users') 1246 | // ->insertGetId([ 1247 | // 'name' => 'Anna', 1248 | // 'city' => 'Sydney', 1249 | // 'country' => 'Australia', 1250 | // ]); 1251 | 1252 | // dd($id); 1253 | // } 1254 | 1255 | /** @test */ 1256 | public function it_can_execute_stored_procedures() 1257 | { 1258 | $firstNumber = random_int(1, 10); 1259 | $secondNumber = random_int(1, 10); 1260 | 1261 | $result = DB::query() 1262 | ->fromProcedure('MULTIPLY', [ 1263 | $firstNumber, $secondNumber, 1264 | ]) 1265 | ->first() 1266 | ->RESULT; 1267 | 1268 | $this->assertEquals($firstNumber * $secondNumber, $result); 1269 | } 1270 | } 1271 | --------------------------------------------------------------------------------