├── .envrc ├── .github ├── cover.png └── workflows │ └── run_tests.yml ├── .gitignore ├── .php-cs-fixer.php ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── docker-compose.yml ├── flake.lock ├── flake.nix ├── phpunit.xml ├── src ├── Database │ ├── LibsqlConnection.php │ ├── LibsqlConnectionFactory.php │ ├── LibsqlConnector.php │ ├── LibsqlDatabase.php │ ├── LibsqlQueryBuilder.php │ ├── LibsqlQueryGrammar.php │ ├── LibsqlQueryProcessor.php │ ├── LibsqlSchemaBuilder.php │ ├── LibsqlSchemaGrammar.php │ ├── LibsqlSchemaState.php │ └── LibsqlStatement.php ├── Exceptions │ ├── ConfigurationIsNotFound.php │ └── FeatureNotSupportedException.php ├── LibsqlConnection.php ├── LibsqlManager.php ├── LibsqlSchemaGrammar.php ├── LibsqlServiceProvider.php ├── Vector │ └── VectorMacro.php ├── VectorCast.php └── helpers.php └── tests ├── ArchTest.php ├── Feature ├── DataTypes │ ├── BlobDataTest.php │ ├── BooleanDataTest.php │ ├── DateDataTest.php │ ├── DateTimeDataTest.php │ ├── DoubleDataTest.php │ ├── FloatDataTest.php │ ├── IntegerDataTest.php │ ├── StringDataTest.php │ ├── TextDataTest.php │ ├── TimestampDataTest.php │ ├── VectorDataTest.php │ └── YearDataTest.php ├── DatabaseTransactionsTest.php ├── EloquentAttributeCasting │ ├── ArrayCastingTest.php │ ├── AsStringableCastingTest.php │ ├── BooleanCastingTest.php │ ├── CastingModels │ │ ├── ArrayCastingModel.php │ │ ├── BooleanCastingModel.php │ │ ├── CollectionCastingModel.php │ │ ├── DateCastingModel.php │ │ ├── DatetimeCastingModel.php │ │ ├── DoubleCastingModel.php │ │ ├── EnumCastingModel.php │ │ ├── FloatCastingModel.php │ │ ├── IntegerCastingModel.php │ │ ├── StringableCastingModel.php │ │ └── TimestampCastingModel.php │ ├── CollectionCastingTest.php │ ├── DateCastingTest.php │ ├── DatetimeCastingTest.php │ ├── DoubleCastingTest.php │ ├── EnumCastingTest.php │ ├── Enums │ │ └── Status.php │ ├── FloatCastingTest.php │ ├── IntegerCastingTest.php │ └── TimestampCastingTest.php ├── EloquentCollectionTest.php ├── EloquentDeleteTest.php ├── EloquentRelationship │ ├── HasManyThroughTest.php │ ├── ManyToManyTest.php │ ├── OneToManyTest.php │ └── OneToOneTest.php ├── EloquentSoftDeleteTest.php ├── EmbebdedReplicaTest.php ├── LibsqlPDOStatementTest.php ├── LibsqlPDOTest.php ├── LibsqlSchemaBuilderTest.php ├── MulticonnectionTest.php └── QueryBuilder │ ├── DatabaseQueriesTest.php │ ├── DeleteStatementsTest.php │ ├── InsertStatementsTest.php │ ├── RawExpressionsTest.php │ ├── SelectStatementsTest.php │ └── UpdateStatementsTest.php ├── Fixtures ├── Factories │ ├── CommentFactory.php │ ├── DeploymentFactory.php │ ├── EnvironmentFactory.php │ ├── PhoneFactory.php │ ├── PostFactory.php │ ├── ProjectFactory.php │ ├── RoleFactory.php │ └── UserFactory.php ├── Migrations │ ├── create_comments_table.php │ ├── create_deployments_table.php │ ├── create_environments_table.php │ ├── create_phones_table.php │ ├── create_posts_table.php │ ├── create_projects_table.php │ ├── create_roles_table.php │ ├── create_user_roles_table.php │ └── create_users_table.php └── Models │ ├── Comment.php │ ├── Deployment.php │ ├── Environment.php │ ├── Phone.php │ ├── Post.php │ ├── Project.php │ ├── Role.php │ └── User.php ├── Pest.php ├── TestCase.php ├── Unit └── Database │ ├── LibsqlConnectionTest.php │ ├── LibsqlPDOTest.php │ └── LibsqlSchemaBuilderTest.php └── database └── .gitkeep /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.github/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tursodatabase/libsql-laravel/df8b8d3a1d43d924945041e9dd49049713d9f0ca/.github/cover.png -------------------------------------------------------------------------------- /.github/workflows/run_tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: 4 | push: 5 | paths: 6 | - "**.php" 7 | - ".github/workflows/run_tests.yml" 8 | - "phpunit.xml.dist" 9 | - "composer.json" 10 | - "composer.lock" 11 | pull_request: 12 | branches: [main] 13 | 14 | jobs: 15 | test: 16 | runs-on: ${{ matrix.os }} 17 | timeout-minutes: 5 18 | strategy: 19 | fail-fast: true 20 | matrix: 21 | os: [ubuntu-latest] 22 | php: [8.4, 8.3] 23 | laravel: [11.*, 12.*] 24 | stability: [prefer-lowest, prefer-stable] 25 | include: 26 | - laravel: 11.* 27 | testbench: ^9.0 28 | carbon: ^2.63 29 | - laravel: 12.* 30 | testbench: 10.* 31 | 32 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} 33 | 34 | steps: 35 | - name: Checkout code 36 | uses: actions/checkout@v4 37 | 38 | - name: Run Turso Local Database 39 | run: docker compose up -d 40 | 41 | - name: Setup PHP 42 | uses: shivammathur/setup-php@v2 43 | with: 44 | php-version: ${{ matrix.php }} 45 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo 46 | coverage: none 47 | 48 | - name: Setup problem matchers 49 | run: | 50 | echo "::add-matcher::${{ runner.tool_cache }}/php.json" 51 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 52 | 53 | - name: Install dependencies 54 | run: | 55 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "nesbot/carbon:${{ matrix.carbon }}" --no-interaction --no-update 56 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction 57 | 58 | - name: List Installed Dependencies 59 | run: composer show -D 60 | 61 | - name: Execute tests 62 | run: vendor/bin/pest --ci 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .direnv 2 | .php-cs-fixer.cache 3 | .phpactor.json 4 | vendor/ 5 | *.cache 6 | .env* 7 | .DS_Store 8 | *.db 9 | tests/database/* 10 | !tests/database/.gitkeep 11 | -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | in(__DIR__) 5 | ->exclude('vendor') 6 | ->exclude('storage') 7 | ->exclude('bootstrap/cache') 8 | ->exclude('lib'); 9 | 10 | return (new PhpCsFixer\Config()) 11 | ->setRules([ 12 | '@PSR12' => true, 13 | 'array_syntax' => ['syntax' => 'short'], 14 | ]) 15 | ->setFinder($finder); 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributor Manual 2 | 3 | Turso welcomes contributions from the community. This manual provides guidelines for contributing to `libsql-laravel` SDK. 4 | 5 | Make sure to [join us on Discord](https://tur.so/discord-laravel) — (`#libsql-php` channel) to discuss your ideas and get help. 6 | 7 | ## Prerequisites 8 | 9 | - PHP 8.3 or higher 10 | - Composer 11 | 12 | ## Development 13 | 14 | 1. Fork and clone the repository 15 | 2. Install dependencies using `composer install` 16 | 3. Create a new branch for your feature or bug fix: `git switch -c my-new-feature` 17 | 4. Make changes and commit them with a clear commit message 18 | 5. Push your changes to your fork `git push origin my-new-feature` 19 | 6. Open a Pull Request on the main repository 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Turso (libSQL) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | libSQL Laravel Adapter 5 | 6 | 7 |

libSQL Laravel

8 |

9 | 10 |

11 | Databases for all Laravel Apps. 12 |

13 | 14 |

15 | Turso · 16 | Docs · 17 | Quickstart · 18 | Blog & Tutorials 19 |

20 | 21 |

22 | 23 | 24 | MIT License 25 | 26 | 27 | 28 | 29 | Discord 30 | 31 | 32 | 33 | 34 | Contributors 35 | 36 | 37 | 38 | 39 | Total downloads 40 | 41 | 42 | 43 | 44 | Examples 45 | 46 | 47 |

48 | 49 | ## Features 50 | 51 | - 🔌 Works offline with [Embedded Replicas](https://docs.turso.tech/features/embedded-replicas/introduction) 52 | - 🌎 Works with remote Turso databases (on Fly) 53 | - ✨ Works with Turso [AI & Vector Search](https://docs.turso.tech/features/ai-and-embeddings) 54 | - 🐘 Works with Laravel's Eloquent ORM 55 | 56 | > [!WARNING] 57 | > This SDK is currently in technical preview. Join us in Discord to report any issues. 58 | 59 | ## Install 60 | 61 | ```bash 62 | composer require turso/libsql-laravel 63 | ``` 64 | 65 | ## Quickstart 66 | 67 | Inside your Laravel application’s `config/database.php`, configure the `default` and `libsql` connections: 68 | 69 | ```php 70 | env("DB_CONNECTION", "libsql"), 76 | 77 | "connections" => [ 78 | "libsql" => [ 79 | "driver" => env("DB_CONNECTION", "libsql"), 80 | "database" => database_path("dev.db"), 81 | ], 82 | 83 | // ... 84 | ], 85 | ]; 86 | ``` 87 | 88 | ## Documentation 89 | 90 | Visit our [official documentation](https://docs.turso.tech/sdk/php/guides/laravel). 91 | 92 | ## Support 93 | 94 | Join us [on Discord](https://tur.so/discord-laravel) to get help using this SDK. Report security issues [via email](mailto:security@turso.tech). 95 | 96 | ## Contributors 97 | 98 | See the [contributing guide](CONTRIBUTING.md) to learn how to get involved. 99 | 100 | ![Contributors](https://contrib.nn.ci/api?repo=tursodatabase/libsql-laravel) 101 | 102 | 103 | 104 | good first issue 105 | 106 | 107 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "turso/libsql-laravel", 3 | "type": "library", 4 | "autoload": { 5 | "psr-4": { 6 | "Libsql\\Laravel\\": "src/", 7 | "Libsql\\Laravel\\Tests\\": "tests/" 8 | }, 9 | "files": [ 10 | "src/helpers.php" 11 | ] 12 | }, 13 | "require": { 14 | "turso/libsql": "dev-master", 15 | "illuminate/database": "^11.0|^12.0", 16 | "spatie/laravel-package-tools": "^1.16" 17 | }, 18 | "require-dev": { 19 | "friendsofphp/php-cs-fixer": "^3.64", 20 | "phpstan/phpstan": "^2.1", 21 | "pestphp/pest": "^3.7", 22 | "orchestra/testbench": "^9.9||^10.0" 23 | }, 24 | "authors": [ 25 | { 26 | "name": "Levy A.", 27 | "email": "levyddsa@gmail.com" 28 | } 29 | ], 30 | "scripts": { 31 | "test": [ 32 | "@php vendor/bin/pest" 33 | ], 34 | "test-feature": [ 35 | "@php vendor/bin/pest --testsuite=Feature" 36 | ], 37 | "test-unit": [ 38 | "@php vendor/bin/pest --testsuite=Unit" 39 | ] 40 | }, 41 | "extra": { 42 | "laravel": { 43 | "providers": [ 44 | "Libsql\\Laravel\\LibsqlServiceProvider" 45 | ] 46 | } 47 | }, 48 | "config": { 49 | "allow-plugins": { 50 | "pestphp/pest-plugin": true 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | turso: 3 | platform: linux/amd64 4 | container_name: turso 5 | restart: unless-stopped 6 | image: "richan/turso-dev:latest" 7 | environment: 8 | - TURSO_DB_FILE=/var/lib/turso/turso.sqlite 9 | ports: 10 | - "8081:8080" 11 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1734649271, 24 | "narHash": "sha256-4EVBRhOjMDuGtMaofAIqzJbg4Ql7Ai0PSeuVZTHjyKQ=", 25 | "owner": "nixos", 26 | "repo": "nixpkgs", 27 | "rev": "d70bd19e0a38ad4790d3913bf08fcbfc9eeca507", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "nixos", 32 | "ref": "nixos-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs" 41 | } 42 | }, 43 | "systems": { 44 | "locked": { 45 | "lastModified": 1681028828, 46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 | "owner": "nix-systems", 48 | "repo": "default", 49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nix-systems", 54 | "repo": "default", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A very basic flake"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; 6 | flake-utils = { 7 | url = "github:numtide/flake-utils"; 8 | }; 9 | }; 10 | outputs = { self, nixpkgs, flake-utils }: 11 | flake-utils.lib.eachDefaultSystem (system: 12 | let 13 | pkgs = import nixpkgs { 14 | inherit system; 15 | overlays = [ 16 | (final: prev: { 17 | php = prev.php83.withExtensions ({ enabled, all }: enabled ++ [ all.ffi ]); 18 | }) 19 | ]; 20 | }; 21 | in 22 | { 23 | devShells.default = 24 | with pkgs; 25 | mkShell { 26 | nativeBuildInputs = [ 27 | rustPlatform.bindgenHook 28 | ]; 29 | 30 | buildInputs = [ 31 | php.packages.composer 32 | php 33 | turso-cli 34 | ] ++ lib.optionals stdenv.isDarwin [ iconv ]; 35 | }; 36 | }); 37 | } 38 | 39 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests 10 | 11 | 12 | 13 | 14 | ./app 15 | ./src 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Database/LibsqlConnection.php: -------------------------------------------------------------------------------- 1 | db = $db; 37 | $this->schemaGrammar = $this->getDefaultSchemaGrammar(); 38 | } 39 | 40 | public function sync(): void 41 | { 42 | $this->db->sync(); 43 | } 44 | 45 | public function getConnectionMode(): string 46 | { 47 | return $this->db->getConnectionMode(); 48 | } 49 | 50 | public function inTransaction(): bool 51 | { 52 | return $this->db->inTransaction(); 53 | } 54 | 55 | public function setFetchMode(int $mode, mixed ...$args): bool 56 | { 57 | $this->mode = $mode; 58 | 59 | return true; 60 | } 61 | 62 | public function getServerVersion(): string 63 | { 64 | return $this->db->version(); 65 | } 66 | 67 | public function getPdo(): LibsqlDatabase 68 | { 69 | return $this->db; 70 | } 71 | 72 | /** 73 | * Set the active PDO connection used for reads. 74 | * 75 | * @param LibsqlDatabase|\Closure $pdo 76 | * @return \Libsql\Laravel\Database\LibsqlConnection 77 | */ 78 | public function setReadPdo($pdo): self 79 | { 80 | $this->readPdo = $pdo; 81 | 82 | return $this; 83 | } 84 | 85 | public function createReadPdo(array $config): ?LibsqlDatabase 86 | { 87 | $db = function () use ($config) { 88 | return new LibsqlDatabase($config); 89 | }; 90 | $this->setReadPdo($db); 91 | 92 | return $db(); 93 | } 94 | 95 | public function selectOne($query, $bindings = [], $useReadPdo = true) 96 | { 97 | $records = $this->select($query, $bindings, $useReadPdo); 98 | 99 | return array_shift($records); 100 | } 101 | 102 | public function select($query, $bindings = [], $useReadPdo = true) 103 | { 104 | $bindings = array_map(function ($binding) { 105 | return is_bool($binding) ? (int) $binding : $binding; 106 | }, $bindings); 107 | 108 | $data = $this->run($query, $bindings, function ($query, $bindings) { 109 | if ($this->pretending()) { 110 | return []; 111 | } 112 | 113 | $statement = $this->getPdo()->prepare($query); 114 | $results = (array) $statement->query($bindings); 115 | 116 | $decodedResults = array_map(function ($row) { 117 | return decodeBlobs($row); 118 | }, $results); 119 | 120 | return $decodedResults; 121 | }); 122 | 123 | $rowValues = array_values($data); 124 | 125 | return match ($this->mode) { 126 | \PDO::FETCH_BOTH => array_merge($data, $rowValues), 127 | \PDO::FETCH_ASSOC, \PDO::FETCH_NAMED => $data, 128 | \PDO::FETCH_NUM => $rowValues, 129 | \PDO::FETCH_OBJ => arrayToStdClass($data), 130 | default => throw new \PDOException('Unsupported fetch mode.'), 131 | }; 132 | } 133 | 134 | public function insert($query, $bindings = []): bool 135 | { 136 | return $this->affectingStatement($query, $bindings) > 0; 137 | } 138 | 139 | public function update($query, $bindings = []) 140 | { 141 | return $this->affectingStatement($query, $bindings); 142 | } 143 | 144 | public function delete($query, $bindings = []) 145 | { 146 | return $this->affectingStatement($query, $bindings); 147 | } 148 | 149 | public function affectingStatement($query, $bindings = []) 150 | { 151 | $bindings = array_map(function ($binding) { 152 | return is_bool($binding) ? (int) $binding : $binding; 153 | }, $bindings); 154 | 155 | return $this->run($query, $bindings, function ($query, $bindings) { 156 | if ($this->pretending()) { 157 | return 0; 158 | } 159 | 160 | $statement = $this->getPdo()->prepare($query); 161 | 162 | foreach ($bindings as $key => $value) { 163 | $type = is_resource($value) ? \PDO::PARAM_LOB : \PDO::PARAM_STR; 164 | $statement->bindValue($key, $value, $type); 165 | } 166 | 167 | $statement->execute(); 168 | 169 | $this->recordsHaveBeenModified(($count = $statement->rowCount()) > 0); 170 | 171 | return $count; 172 | }); 173 | } 174 | 175 | #[\ReturnTypeWillChange] 176 | protected function getDefaultSchemaGrammar(): LibsqlSchemaGrammar 177 | { 178 | ($grammar = new LibsqlSchemaGrammar)->setConnection($this); 179 | return $this->withTablePrefix($grammar); 180 | } 181 | 182 | public function getSchemaBuilder(): LibsqlSchemaBuilder 183 | { 184 | if ($this->schemaGrammar === null) { 185 | $this->useDefaultSchemaGrammar(); 186 | } 187 | 188 | return new LibsqlSchemaBuilder($this->db, $this); 189 | } 190 | 191 | public function getDefaultPostProcessor(): LibsqlQueryProcessor 192 | { 193 | return new LibsqlQueryProcessor; 194 | } 195 | 196 | public function useDefaultPostProcessor() 197 | { 198 | $this->postProcessor = $this->getDefaultPostProcessor(); 199 | } 200 | 201 | protected function getDefaultQueryGrammar() 202 | { 203 | ($grammar = new LibsqlQueryGrammar)->setConnection($this); 204 | $this->withTablePrefix($grammar); 205 | 206 | return $grammar; 207 | } 208 | 209 | public function useDefaultQueryGrammar() 210 | { 211 | $this->queryGrammar = $this->getDefaultQueryGrammar(); 212 | } 213 | 214 | public function query() 215 | { 216 | $grammar = $this->getQueryGrammar(); 217 | $processor = $this->getPostProcessor(); 218 | 219 | return new LibsqlQueryBuilder( 220 | $this, 221 | $grammar, 222 | $processor 223 | ); 224 | } 225 | 226 | public function getSchemaState(?Filesystem $files = null, ?callable $processFactory = null): LibsqlSchemaState 227 | { 228 | return new LibSQLSchemaState($this, $files, $processFactory); 229 | } 230 | 231 | public function isUniqueConstraintError(\Exception $exception): bool 232 | { 233 | return (bool) preg_match('#(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)#i', $exception->getMessage()); 234 | } 235 | 236 | public function escapeString($input) 237 | { 238 | if ($input === null) { 239 | return 'NULL'; 240 | } 241 | 242 | return \SQLite3::escapeString($input); 243 | } 244 | 245 | public function quote($input) 246 | { 247 | if ($input === null) { 248 | return 'NULL'; 249 | } 250 | 251 | if (is_string($input)) { 252 | return "'" . $this->escapeString($input) . "'"; 253 | } 254 | 255 | if (is_resource($input)) { 256 | return $this->escapeBinary(stream_get_contents($input)); 257 | } 258 | 259 | return $this->escapeBinary($input); 260 | } 261 | 262 | } 263 | -------------------------------------------------------------------------------- /src/Database/LibsqlConnectionFactory.php: -------------------------------------------------------------------------------- 1 | createConfig($config); 29 | $connectionMode = $this->detectConnectionMode($config); 30 | 31 | $this->db = $this->buildConnection($connectionMode, $config); 32 | $this->in_transaction = false; 33 | } 34 | 35 | private function createConfig(array $config): array 36 | { 37 | return [ 38 | 'path' => $config['database'] ?? null, 39 | 'url' => $config['url'] ?? null, 40 | 'authToken' => $config['password'] ?? null, 41 | 'encryptionKey' => $config['encryptionKey'] ?? null, 42 | 'syncInterval' => $config['syncInterval'] ?? 0, 43 | 'disable_read_your_writes' => $config['read_your_writes'] ?? true, 44 | 'webpki' => $config['webpki'] ?? false, 45 | ]; 46 | } 47 | 48 | private function buildConnection(string $mode, array $config): Connection 49 | { 50 | $db = match ($mode) { 51 | 'local' => new Database(path: $config['path']), 52 | 'remote' => new Database(url: $config['url'], authToken: $config['authToken']), 53 | 'remote_replica' => new Database( 54 | path: $config['path'], 55 | url: $config['url'], 56 | authToken: $config['authToken'], 57 | syncInterval: $config['syncInterval'], 58 | readYourWrites: $config['disable_read_your_writes'], 59 | webpki: $config['webpki'] 60 | ), 61 | default => new Database(':memory:') 62 | }; 63 | 64 | return $db->connect(); 65 | } 66 | 67 | private function detectConnectionMode(array $config): string 68 | { 69 | $database = $config['path']; 70 | $url = $config['url']; 71 | $authToken = $config['authToken']; 72 | 73 | $mode = 'unknown'; 74 | 75 | if ($database === ':memory:') { 76 | $mode = 'memory'; 77 | } 78 | 79 | if (empty($database) && !empty($url) && !empty($authToken)) { 80 | $mode = 'remote'; 81 | } 82 | 83 | if (!empty($database) && $database !== ':memory:' && empty($url) && empty($authToken) && empty($url)) { 84 | $mode = 'local'; 85 | } 86 | 87 | if (!empty($database) && $database !== ':memory:' && !empty($authToken) && !empty($url)) { 88 | $mode = 'remote_replica'; 89 | } 90 | 91 | $this->connection_mode = $mode; 92 | 93 | return $mode; 94 | } 95 | 96 | public function version(): string 97 | { 98 | // TODO: Need to return an actual version from libSQL binary 99 | return '0.0.1'; 100 | } 101 | 102 | public function inTransaction(): bool 103 | { 104 | return $this->in_transaction; 105 | } 106 | 107 | public function sync(): void 108 | { 109 | if ($this->connection_mode !== 'remote_replica') { 110 | throw new \Exception("[Libsql:{$this->connection_mode}] Sync is only available for Remote Replica Connection.", 1); 111 | } 112 | $this->conn->sync(); 113 | } 114 | 115 | public function getConnectionMode(): string 116 | { 117 | return $this->connection_mode; 118 | } 119 | 120 | public function setFetchMode(int $mode, mixed ...$args): bool 121 | { 122 | $this->mode = $mode; 123 | 124 | return true; 125 | } 126 | 127 | public function beginTransaction(): bool 128 | { 129 | if ($this->inTransaction()) { 130 | throw new \PDOException('Already in a transaction'); 131 | } 132 | 133 | $this->in_transaction = true; 134 | $this->tx = $this->db->transaction(); 135 | 136 | return true; 137 | } 138 | 139 | public function prepare(string $sql): LibsqlStatement 140 | { 141 | return new LibsqlStatement( 142 | ($this->inTransaction() ? $this->tx : $this->db)->prepare($sql), 143 | $sql 144 | ); 145 | } 146 | 147 | public function exec(string $queryStatement): int 148 | { 149 | $statement = $this->prepare($queryStatement); 150 | $statement->execute(); 151 | 152 | return $statement->rowCount(); 153 | } 154 | 155 | public function query(string $sql, array $params = []) 156 | { 157 | $results = $this->db->query($sql, $params)->fetchArray(); 158 | $rowValues = array_values($results); 159 | 160 | return match ($this->mode) { 161 | \PDO::FETCH_BOTH => array_merge($results, $rowValues), 162 | \PDO::FETCH_ASSOC, \PDO::FETCH_NAMED => $results, 163 | \PDO::FETCH_NUM => $rowValues, 164 | \PDO::FETCH_OBJ => $results, 165 | default => throw new \PDOException('Unsupported fetch mode.'), 166 | }; 167 | } 168 | 169 | public function setLastInsertId(?string $name = null, ?int $value = null): void 170 | { 171 | if ($name === null) { 172 | $name = 'id'; 173 | } 174 | 175 | $this->lastInsertIds[$name] = $value; 176 | } 177 | 178 | public function lastInsertId(?string $name = null): int|string 179 | { 180 | if ($name === null) { 181 | $name = 'id'; 182 | } 183 | 184 | return isset($this->lastInsertIds[$name]) 185 | ? (string) $this->lastInsertIds[$name] 186 | : $this->db->lastInsertId(); 187 | 188 | } 189 | 190 | public function escapeString($input) 191 | { 192 | if ($input === null) { 193 | return 'NULL'; 194 | } 195 | 196 | return \SQLite3::escapeString($input); 197 | } 198 | 199 | public function quote($input) 200 | { 201 | if ($input === null) { 202 | return 'NULL'; 203 | } 204 | 205 | return "'" . $this->escapeString($input) . "'"; 206 | } 207 | 208 | public function commit(): bool 209 | { 210 | if (!$this->inTransaction()) { 211 | throw new \PDOException('No active transaction'); 212 | } 213 | 214 | $this->tx->commit(); 215 | $this->in_transaction = false; 216 | 217 | return true; 218 | } 219 | 220 | public function rollBack(): bool 221 | { 222 | if (!$this->inTransaction()) { 223 | throw new \PDOException('No active transaction'); 224 | } 225 | 226 | $this->tx->rollback(); 227 | $this->in_transaction = false; 228 | 229 | return true; 230 | } 231 | 232 | } 233 | -------------------------------------------------------------------------------- /src/Database/LibsqlQueryBuilder.php: -------------------------------------------------------------------------------- 1 | applyBeforeQueryCallbacks(); 14 | 15 | $results = $this->connection->select( 16 | $this->grammar->compileExists($this), 17 | $this->getBindings(), 18 | !$this->useWritePdo 19 | ); 20 | 21 | $results = (array) $results; 22 | if (isset($results[0])) { 23 | $results = (array) $results[0]; 24 | 25 | return (bool) ($results['exists'] ?? false); 26 | } 27 | 28 | return false; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Database/LibsqlQueryGrammar.php: -------------------------------------------------------------------------------- 1 | $result->name, 24 | 'schema' => $result->schema ?? null, // PostgreSQL and SQL Server 25 | 'size' => isset($result->size) ? (int) $result->size : null, 26 | 'comment' => $result->comment ?? null, // MySQL and PostgreSQL 27 | 'collation' => $result->collation ?? null, // MySQL only 28 | 'engine' => $result->engine ?? null, // MySQL only 29 | ]; 30 | }, $results); 31 | } 32 | 33 | public function processViews($results) 34 | { 35 | return array_map(function ($result) { 36 | $result = (object) $result; 37 | 38 | return [ 39 | 'name' => $result->name, 40 | 'schema' => $result->schema ?? null, // PostgreSQL and SQL Server 41 | 'definition' => $result->definition, 42 | ]; 43 | }, $results); 44 | } 45 | 46 | public function processSelect(Builder $query, $results) 47 | { 48 | return $results; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Database/LibsqlSchemaBuilder.php: -------------------------------------------------------------------------------- 1 | db->prepare($this->grammar()->compileDropAllIndexes()); 32 | $results = $statement->query(); 33 | 34 | collect($results)->each(function (array $query) { 35 | $query = array_values($query)[0]; 36 | $this->db->query($query); 37 | }); 38 | } 39 | 40 | public function dropAllTables(): void 41 | { 42 | $this->dropAllTriggers(); 43 | $this->dropAllIndexes(); 44 | 45 | $this->db->exec($this->grammar()->compileDisableForeignKeyConstraints()); 46 | 47 | $statement = $this->db->prepare($this->grammar()->compileDropAllTables()); 48 | $results = $statement->query(); 49 | 50 | collect($results)->each(function (array $query) { 51 | $query = array_values($query)[0]; 52 | $this->db->query($query); 53 | }); 54 | 55 | $this->db->exec($this->grammar()->compileEnableForeignKeyConstraints()); 56 | } 57 | 58 | protected function dropAllTriggers(): void 59 | { 60 | $statement = $this->db->prepare($this->grammar()->compileDropAllTriggers()); 61 | $results = $statement->query(); 62 | 63 | collect($results)->each(function (array $query) { 64 | $query = array_values($query)[0]; 65 | $this->db->query($query); 66 | }); 67 | } 68 | 69 | public function dropAllViews(): void 70 | { 71 | $statement = $this->db->prepare($this->grammar()->compileDropAllViews()); 72 | $results = $statement->query(); 73 | 74 | collect($results)->each(function (array $query) { 75 | $query = array_values($query)[0]; 76 | $this->db->query($query); 77 | }); 78 | } 79 | 80 | public function getColumns($table) 81 | { 82 | $table = $this->connection->getTablePrefix() . $table; 83 | 84 | $data = $this->connection->select("PRAGMA table_xinfo('{$table}')"); 85 | 86 | $columns = $this->connection->selectOne("SELECT sql FROM sqlite_master WHERE type='table' AND name='{$table}'"); 87 | 88 | $pattern = '/(?:\(|,)\s*[\'"`]?([a-zA-Z_][a-zA-Z0-9_]*)[\'"`]?\s+[a-zA-Z]+/i'; 89 | preg_match_all($pattern, $columns->sql, $matches); 90 | $columnMatches = $matches[1] ?? []; 91 | 92 | $delctypes = stdClassToArray($data); 93 | foreach ($delctypes as $key => $value) { 94 | 95 | if (isset($delctypes[$key]['name'])) { 96 | $delctypes[$key]['name'] = $columnMatches[$key]; 97 | } 98 | 99 | if (isset($delctypes[$key]['type'])) { 100 | $type = strtolower($delctypes[$key]['type']); 101 | $delctypes[$key]['type'] = $type; 102 | $delctypes[$key]['type_name'] = $type; 103 | } 104 | 105 | if (isset($delctypes[$key]['notnull'])) { 106 | $delctypes[$key]['nullable'] = $delctypes[$key]['notnull'] == 1 ? false : true; 107 | } 108 | 109 | if (isset($delctypes[$key]['dflt_value'])) { 110 | $delctypes[$key]['default'] = $delctypes[$key]['dflt_value'] == 'NULL' ? null : new Expression(Str::wrap($delctypes[$key]['dflt_value'], '(', ')')); 111 | } 112 | 113 | if (isset($delctypes[$key]['pk'])) { 114 | $delctypes[$key]['auto_increment'] = $delctypes[$key]['pk'] == 1 ? true : false; 115 | } 116 | 117 | $delctypes[$key]['collation'] = null; 118 | $delctypes[$key]['comment'] = null; 119 | $delctypes[$key]['generation'] = null; 120 | } 121 | 122 | $keyOrder = ['name', 'type_name', 'type', 'collation', 'nullable', 'default', 'auto_increment', 'comment', 'generation', 'pk', 'notnull', 'dflt_value', 'cid', 'hidden']; 123 | $delctypes = reorderArrayKeys($delctypes, $keyOrder); 124 | 125 | return $delctypes; 126 | } 127 | 128 | protected function grammar(): LibsqlSchemaGrammar 129 | { 130 | $grammar = new LibsqlSchemaGrammar; 131 | 132 | return $grammar; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/Database/LibsqlSchemaGrammar.php: -------------------------------------------------------------------------------- 1 | dimensions) && $column->dimensions !== '') { 40 | return "F32_BLOB({$column->dimensions})"; 41 | } 42 | 43 | throw new \RuntimeException('Dimension must be set for vector embedding'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Database/LibsqlSchemaState.php: -------------------------------------------------------------------------------- 1 | mode = $mode; 31 | 32 | return true; 33 | } 34 | 35 | public function bindValue($parameter, $value = null, $type = \PDO::PARAM_STR): self 36 | { 37 | if (is_int($parameter)) { 38 | $this->bindings[$parameter] = $value; 39 | } elseif (is_string($parameter)) { 40 | $this->bindings[$parameter] = $value; 41 | } else { 42 | throw new \InvalidArgumentException('Parameter must be an integer or string.'); 43 | } 44 | 45 | $this->bindings = $this->parameterCasting($this->bindings); 46 | 47 | return $this; 48 | } 49 | 50 | public function prepare(string $query): self 51 | { 52 | return new self($this->statement, $query); 53 | } 54 | 55 | public function query(array $parameters = []): mixed 56 | { 57 | if (empty($parameters)) { 58 | $parameters = $this->parameterCasting($this->bindings); 59 | 60 | foreach ($parameters as $key => $value) { 61 | $this->statement->bind([$key => $value]); 62 | } 63 | 64 | $results = $this->statement->query()->fetchArray(); 65 | $rows = decodeDoubleBase64($results); 66 | $rowValues = array_values($rows); 67 | 68 | return match ($this->mode) { 69 | \PDO::FETCH_BOTH => array_merge($rows, $rowValues), 70 | \PDO::FETCH_ASSOC, \PDO::FETCH_NAMED => $rows, 71 | \PDO::FETCH_NUM => $rowValues, 72 | \PDO::FETCH_OBJ => (object) $rows, 73 | default => throw new \PDOException('Unsupported fetch mode.'), 74 | }; 75 | } 76 | 77 | $parameters = $this->parameterCasting($parameters); 78 | foreach ($parameters as $key => $value) { 79 | $this->statement->bind([$key => $value]); 80 | } 81 | $result = $this->statement->query()->fetchArray(); 82 | $rows = decodeDoubleBase64($result); 83 | 84 | return match ($this->mode) { 85 | \PDO::FETCH_ASSOC => collect($rows), 86 | \PDO::FETCH_OBJ => (object) $rows, 87 | \PDO::FETCH_NUM => array_values($rows), 88 | default => collect($rows) 89 | }; 90 | } 91 | 92 | public function execute(array $parameters = []): bool 93 | { 94 | try { 95 | 96 | if (empty($parameters)) { 97 | $parameters = $this->bindings; 98 | } 99 | 100 | foreach ($parameters as $key => $value) { 101 | $this->statement->bind([$key => $value]); 102 | } 103 | 104 | if (str_starts_with(strtolower($this->query), 'select')) { 105 | $queryRows = $this->statement->query()->fetchArray(); 106 | $this->affectedRows = count($queryRows); 107 | } else { 108 | $this->affectedRows = $this->statement->execute(); 109 | } 110 | 111 | return true; 112 | } catch (\Exception $e) { 113 | return false; 114 | } 115 | } 116 | 117 | #[\ReturnTypeWillChange] 118 | public function fetch(int $mode = \PDO::FETCH_DEFAULT, int $cursorOrientation = \PDO::FETCH_ORI_NEXT, int $cursorOffset = 0): array|false 119 | { 120 | if ($mode === \PDO::FETCH_DEFAULT) { 121 | $mode = $this->mode; 122 | } 123 | 124 | $parameters = $this->bindings; 125 | $parameters = $this->parameterCasting($parameters); 126 | foreach ($parameters as $key => $value) { 127 | $this->statement->bind([$key => $value]); 128 | } 129 | 130 | $result = $this->statement->query(); 131 | $rows = $result->fetchArray(); 132 | 133 | $row = $rows[$cursorOffset]; 134 | $mode = \PDO::FETCH_ASSOC; 135 | 136 | if ($this->response === $row) { 137 | return false; 138 | } 139 | $this->response = $row; 140 | 141 | $rowValues = array_values($row); 142 | 143 | $response = match ($mode) { 144 | \PDO::FETCH_BOTH => array_merge($row, $rowValues), 145 | \PDO::FETCH_ASSOC, \PDO::FETCH_NAMED => $row, 146 | \PDO::FETCH_NUM => $rowValues, 147 | \PDO::FETCH_OBJ => (object) $row, 148 | default => throw new \PDOException('Unsupported fetch mode.'), 149 | }; 150 | 151 | return $response; 152 | } 153 | 154 | #[\ReturnTypeWillChange] 155 | public function fetchAll(int $mode = \PDO::FETCH_DEFAULT, ...$args): array 156 | { 157 | if ($mode === \PDO::FETCH_DEFAULT) { 158 | $mode = $this->mode; 159 | } 160 | 161 | $parameters = $this->parameterCasting($this->bindings); 162 | foreach ($parameters as $key => $value) { 163 | $this->statement->bind([$key => $value]); 164 | } 165 | 166 | $result = $this->statement->query(); 167 | $rows = $result->fetchArray(); 168 | 169 | $allRows = $rows; 170 | $decodedRows = $this->parameterCasting($allRows); 171 | $rowValues = \array_map('array_values', $decodedRows); 172 | 173 | $data = match ($mode) { 174 | \PDO::FETCH_BOTH => array_merge($allRows, $rowValues), 175 | \PDO::FETCH_ASSOC, \PDO::FETCH_NAMED => $allRows, 176 | \PDO::FETCH_NUM => $rowValues, 177 | \PDO::FETCH_OBJ => (object) $allRows, 178 | default => throw new \PDOException('Unsupported fetch mode.'), 179 | }; 180 | 181 | return $data; 182 | } 183 | 184 | public function getAffectedRows(): int 185 | { 186 | return $this->affectedRows; 187 | } 188 | 189 | public function nextRowset(): bool 190 | { 191 | // TFIDK: database is support for multiple rowset. 192 | return false; 193 | } 194 | 195 | public function rowCount(): int 196 | { 197 | return $this->affectedRows; 198 | } 199 | 200 | public function closeCursor(): void 201 | { 202 | $this->statement->reset(); 203 | } 204 | 205 | private function parameterCasting(array $parameters): array 206 | { 207 | $parameters = collect(array_values($parameters))->map(function ($value) { 208 | $type = match (true) { 209 | is_string($value) && (!ctype_print($value) || !mb_check_encoding($value, 'UTF-8')) => 'blob', 210 | is_float($value) || is_float($value) => 'float', 211 | is_int($value) => 'integer', 212 | is_bool($value) => 'boolean', 213 | $value === null => 'null', 214 | $value instanceof Carbon => 'datetime', 215 | is_vector($value) => 'vector', 216 | default => 'text', 217 | }; 218 | 219 | if ($type === 'blob') { 220 | $value = base64_encode(base64_encode($value)); 221 | } 222 | 223 | if ($type === 'boolean') { 224 | $value = (int) $value; 225 | } 226 | 227 | if ($type === 'datetime') { 228 | $value = $value->toDateTimeString(); 229 | } 230 | 231 | if ($type === 'vector') { 232 | $value = json_encode($value); 233 | } 234 | 235 | return $value; 236 | })->toArray(); 237 | 238 | return $parameters; 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/Exceptions/ConfigurationIsNotFound.php: -------------------------------------------------------------------------------- 1 | setConnection($this); 15 | return $this->withTablePrefix($grammar); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/LibsqlManager.php: -------------------------------------------------------------------------------- 1 | config = new Collection($config); 20 | $this->client = new LibSQLDatabase($config); 21 | } 22 | 23 | public function __call(string $method, array $arguments = []): mixed 24 | { 25 | if (!method_exists($this->client, $method)) { 26 | throw new BadMethodCallException('Call to undefined method ' . static::class . '::' . $method . '()'); 27 | } 28 | 29 | return $this->client->$method(...$arguments); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/LibsqlSchemaGrammar.php: -------------------------------------------------------------------------------- 1 | dimensions) && $column->dimensions !== '') { 15 | return "F32_BLOB({$column->dimensions})"; 16 | } 17 | 18 | throw new RuntimeException('Dimension must be set for vector embedding'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/LibsqlServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('libsql-laravel'); 29 | } 30 | 31 | public function register(): void 32 | { 33 | parent::register(); 34 | 35 | VectorMacro::create(); 36 | 37 | $this->app->singleton('db.factory', function ($app) { 38 | return new LibsqlConnectionFactory($app); 39 | }); 40 | 41 | $this->app->scoped(LibsqlManager::class, function ($app) { 42 | return new LibsqlManager(config('database.connections.libsql')); 43 | }); 44 | 45 | $this->app->resolving('db', function (DatabaseManager $db) { 46 | $db->extend('libsql', function ($config, $name) { 47 | $config = config('database.connections.libsql'); 48 | $config['name'] = $name; 49 | if (!isset($config['driver'])) { 50 | $config['driver'] = 'libsql'; 51 | } 52 | 53 | $connector = new LibsqlConnector(); 54 | $db = $connector->connect($config); 55 | 56 | $connection = new LibsqlConnection($db, $config['database'] ?? ':memory:', $config['prefix'], $config); 57 | app()->instance(LibsqlConnection::class, $connection); 58 | 59 | $connection->createReadPdo($config); 60 | 61 | return $connection; 62 | }); 63 | }); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Vector/VectorMacro.php: -------------------------------------------------------------------------------- 1 | table}(libsql_vector_idx({$column}))"); 17 | }); 18 | 19 | Builder::macro('nearest', function ($index_name, $vector, $limit = 10) { 20 | /** @var Builder $this */ 21 | return $this->joinSub( 22 | DB::table(DB::raw("vector_top_k('$index_name', '[" . implode(',', $vector) . "]', $limit)")), 23 | 'v', 24 | "{$this->from}.rowid", 25 | '=', 26 | 'v.id' 27 | ); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/VectorCast.php: -------------------------------------------------------------------------------- 1 | $value) { 11 | if (is_array($value) && !is_vector($value)) { 12 | // Encode only the nested array as a JSON string 13 | $formattedItem[$key] = json_encode($value); 14 | } else { 15 | $formattedItem[$key] = $value; 16 | } 17 | } 18 | 19 | // Convert the formatted item to a stdClass 20 | $result[] = (object) $formattedItem; 21 | } 22 | 23 | return $result; 24 | } 25 | 26 | function stdClassToArray(\stdClass|array $object): array 27 | { 28 | if (is_array($object)) { 29 | return array_map('stdClassToArray', $object); 30 | } 31 | 32 | if (!$object instanceof \stdClass) { 33 | return $object; 34 | } 35 | 36 | $array = []; 37 | 38 | foreach (get_object_vars($object) as $key => $value) { 39 | $array[$key] = (is_array($value) || $value instanceof \stdClass) 40 | ? stdClassToArray($value) 41 | : $value; 42 | } 43 | 44 | return $array; 45 | } 46 | 47 | function reorderArrayKeys(array $data, array $keyOrder): array 48 | { 49 | return array_map(function ($item) use ($keyOrder) { 50 | $ordered = array_fill_keys($keyOrder, null); 51 | 52 | return array_merge($ordered, $item); 53 | }, $data); 54 | } 55 | 56 | function is_vector($value): bool 57 | { 58 | if (!is_array($value)) { 59 | return false; 60 | } 61 | 62 | foreach ($value as $element) { 63 | if (!is_numeric($element)) { 64 | return false; 65 | } 66 | } 67 | 68 | return array_keys($value) === range(0, count($value) - 1); 69 | } 70 | 71 | function decodeBlobs(array $row): array 72 | { 73 | return array_map(function ($value) { 74 | return is_resource($value) ? stream_get_contents($value) : $value; 75 | }, $row); 76 | } 77 | 78 | function decodeDoubleBase64(array $result): array 79 | { 80 | if (isset($result) && is_array($result)) { 81 | foreach ($result as &$row) { 82 | foreach ($row as $key => &$value) { 83 | if (is_string($value) && isValidDateOrTimestamp($value)) { 84 | continue; 85 | } 86 | 87 | if (is_string($value) && $decoded = json_decode($value, true)) { 88 | $value = $decoded; 89 | } 90 | 91 | if (is_string($value) && isValidBlob($value)) { 92 | $value = base64_decode(base64_decode($value)); 93 | } 94 | } 95 | } 96 | } 97 | 98 | return $result; 99 | } 100 | 101 | function isValidBlob(mixed $value): bool 102 | { 103 | return (bool) preg_match('/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/', $value); 104 | } 105 | 106 | function isValidDateOrTimestamp($string, $format = null): bool 107 | { 108 | if (is_numeric($string) && (int) $string > 0 && (int) $string <= PHP_INT_MAX) { 109 | return true; 110 | } 111 | 112 | if (is_numeric($string) && strlen($string) === 4 && (int) $string >= 1000 && (int) $string <= 9999) { 113 | return true; 114 | } 115 | 116 | $formats = $format ? [$format] : ['Y-m-d H:i:s', 'Y-m-d']; 117 | 118 | foreach ($formats as $fmt) { 119 | $dateTime = \DateTime::createFromFormat($fmt, $string); 120 | if ($dateTime && $dateTime->format($fmt) === $string) { 121 | return true; 122 | } 123 | } 124 | 125 | return false; 126 | } 127 | -------------------------------------------------------------------------------- /tests/ArchTest.php: -------------------------------------------------------------------------------- 1 | expect([ 5 | // 'dd', 6 | 'debug_backtrace', 7 | 'die', 8 | // 'dump', 9 | 'echo', 10 | 'eval', 11 | 'exec', 12 | 'exit', 13 | 'passthru', 14 | 'phpinfo', 15 | 'print_r', 16 | 'proc_open', 17 | 'ray', 18 | 'shell_exec', 19 | 'system', 20 | 'var_dump', 21 | ]) 22 | ->each->not->toBeUsed(); 23 | 24 | arch('it should implement strict types') 25 | ->expect('Libsql\\Laravel') 26 | ->toUseStrictTypes(); 27 | 28 | arch('test fixtures should implement strict types') 29 | ->expect('Libsql\\Laravel\\Tests\\Fixtures') 30 | ->toUseStrictTypes(); 31 | -------------------------------------------------------------------------------- /tests/Feature/DataTypes/BlobDataTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->binary('blob'); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('blob_table'); 15 | }); 16 | 17 | test('it can insert a new blob data', function () { 18 | $data = random_bytes(50); 19 | 20 | $result = DB::table('blob_table')->insert([ 21 | 'blob' => $data, 22 | ]); 23 | 24 | $newData = DB::table('blob_table')->first(); 25 | 26 | expect($result)->toBeTrue() 27 | ->and(DB::table('blob_table')->count())->toBe(1) 28 | ->and($newData->blob)->toBe($data); 29 | })->group('BlobDataTest', 'DataTypes', 'FeatureTest'); 30 | 31 | test('it can update an existing blob data', function () { 32 | $data = random_bytes(50); 33 | 34 | DB::table('blob_table')->insert([ 35 | 'blob' => $data, 36 | ]); 37 | 38 | $newData = random_bytes(50); 39 | 40 | $result = DB::table('blob_table')->update([ 41 | 'blob' => $newData, 42 | ]); 43 | 44 | $updatedData = DB::table('blob_table')->first(); 45 | 46 | expect($result)->toBe(1) 47 | ->and($updatedData->blob)->toBe($newData); 48 | })->group('BlobDataTest', 'DataTypes', 'FeatureTest'); 49 | -------------------------------------------------------------------------------- /tests/Feature/DataTypes/BooleanDataTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->boolean('confirmed'); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('boolean_table'); 15 | }); 16 | 17 | test('it can insert a new boolean data, and the value will be saved as an integer', function () { 18 | $result = DB::table('boolean_table')->insert([ 19 | 'confirmed' => true, 20 | ]); 21 | 22 | $newData = DB::table('boolean_table')->first(); 23 | 24 | expect($result)->toBeTrue() 25 | ->and(DB::table('boolean_table')->count())->toBe(1) 26 | ->and($newData->confirmed)->toBe(1); 27 | })->group('BooleanDataTest', 'DataTypes', 'FeatureTest'); 28 | 29 | test('it can update an existing boolean data, and the retrieved value will be an integer', function () { 30 | DB::table('boolean_table')->insert([ 31 | 'confirmed' => true, 32 | ]); 33 | 34 | $result = DB::table('boolean_table')->update([ 35 | 'confirmed' => false, 36 | ]); 37 | 38 | $updatedData = DB::table('boolean_table')->first(); 39 | 40 | expect($result)->toBe(1) 41 | ->and($updatedData->confirmed)->toBe(0); 42 | })->group('BooleanDataTest', 'DataTypes', 'FeatureTest'); 43 | 44 | test('it can find the saved record', function () { 45 | DB::table('boolean_table')->insert([ 46 | 'confirmed' => true, 47 | ]); 48 | DB::table('boolean_table')->insert([ 49 | 'confirmed' => false, 50 | ]); 51 | 52 | $found = DB::table('boolean_table')->where('confirmed', false)->first(); 53 | 54 | expect($found->id)->toBe(2) 55 | ->and($found->confirmed)->toBe(0); 56 | })->group('BooleanDataTest', 'DataTypes', 'FeatureTest'); 57 | -------------------------------------------------------------------------------- /tests/Feature/DataTypes/DateDataTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->date('started_at'); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('date_table'); 15 | }); 16 | 17 | test('it can insert a new date data', function () { 18 | $date = '2021-01-01'; 19 | 20 | $result = DB::table('date_table')->insert([ 21 | 'started_at' => $date, 22 | ]); 23 | 24 | $newData = DB::table('date_table')->first(); 25 | 26 | expect($result)->toBeTrue() 27 | ->and(DB::table('date_table')->count())->toBe(1) 28 | ->and($newData->started_at)->toBe($date); 29 | })->group('DateDataTest', 'DataTypes', 'FeatureTest'); 30 | 31 | test('it can update an existing date data', function () { 32 | $date = '2021-01-01'; 33 | 34 | DB::table('date_table')->insert([ 35 | 'started_at' => $date, 36 | ]); 37 | 38 | $newDate = '2021-02-01'; 39 | 40 | $result = DB::table('date_table')->update([ 41 | 'started_at' => $newDate, 42 | ]); 43 | 44 | $updatedData = DB::table('date_table')->first(); 45 | 46 | expect($result)->toBe(1) 47 | ->and($updatedData->started_at)->toBe($newDate); 48 | })->group('DateDataTest', 'DataTypes', 'FeatureTest'); 49 | 50 | test('it can find the saved record', function () { 51 | $date = '2021-01-01'; 52 | 53 | DB::table('date_table')->insert([ 54 | 'started_at' => '2021-02-01', 55 | ]); 56 | DB::table('date_table')->insert([ 57 | 'started_at' => $date, 58 | ]); 59 | 60 | $found = DB::table('date_table')->where('started_at', $date)->first(); 61 | 62 | expect($found->id)->toBe(2) 63 | ->and($found->started_at)->toBe($date); 64 | })->group('DateDataTest', 'DataTypes', 'FeatureTest'); 65 | -------------------------------------------------------------------------------- /tests/Feature/DataTypes/DateTimeDataTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->dateTime('published_at'); 10 | $table->timestamps(); 11 | }); 12 | }); 13 | 14 | afterEach(function () { 15 | Schema::dropIfExists('datetime_table'); 16 | }); 17 | 18 | test('it can insert a new datetime data', function () { 19 | $publishedAt = now(); 20 | 21 | $result = DB::table('datetime_table')->insert([ 22 | 'published_at' => $publishedAt, 23 | ]); 24 | 25 | $newData = DB::table('datetime_table')->first(); 26 | 27 | expect($result)->toBeTrue() 28 | ->and(DB::table('datetime_table')->count())->toBe(1) 29 | ->and($newData->published_at)->toBe($publishedAt->format('Y-m-d H:i:s')); 30 | })->group('DateTimeDataTest', 'DataTypes', 'FeatureTest'); 31 | 32 | test('it can update an existing datetime data', function () { 33 | $publishedAt = now(); 34 | 35 | DB::table('datetime_table')->insert([ 36 | 'published_at' => $publishedAt, 37 | ]); 38 | 39 | $newPublishedAt = now()->subDay(); 40 | 41 | $result = DB::table('datetime_table')->update([ 42 | 'published_at' => $newPublishedAt, 43 | ]); 44 | 45 | $updatedData = DB::table('datetime_table')->first(); 46 | 47 | expect($result)->toBe(1) 48 | ->and($updatedData->published_at)->toBe($newPublishedAt->format('Y-m-d H:i:s')); 49 | })->group('DateTimeDataTest', 'DataTypes', 'FeatureTest'); 50 | 51 | test('it can find the saved record', function () { 52 | $publishedAt = now(); 53 | 54 | DB::table('datetime_table')->insert([ 55 | 'published_at' => now()->subDay(), 56 | ]); 57 | DB::table('datetime_table')->insert([ 58 | 'published_at' => $publishedAt, 59 | ]); 60 | 61 | $found = DB::table('datetime_table')->where('published_at', '>=', $publishedAt->format('Y-m-d H:i:s'))->first(); 62 | 63 | expect($found->id)->toBe(2) 64 | ->and($found->published_at)->toBe($publishedAt->format('Y-m-d H:i:s')); 65 | })->group('DateTimeDataTest', 'DataTypes', 'FeatureTest'); 66 | -------------------------------------------------------------------------------- /tests/Feature/DataTypes/DoubleDataTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->double('amount'); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('double_table'); 15 | }); 16 | 17 | test('it can insert a new double data', function () { 18 | $amount = 123.45; 19 | 20 | $result = DB::table('double_table')->insert([ 21 | 'amount' => $amount, 22 | ]); 23 | 24 | $newData = DB::table('double_table')->first(); 25 | 26 | expect($result)->toBeTrue() 27 | ->and(DB::table('double_table')->count())->toBe(1) 28 | ->and($newData->amount)->toBe($amount); 29 | })->group('DoubleDataTest', 'DataTypes', 'FeatureTest'); 30 | 31 | test('it can update an existing double data', function () { 32 | $amount = 123.45; 33 | 34 | DB::table('double_table')->insert([ 35 | 'amount' => $amount, 36 | ]); 37 | 38 | $newAmount = 543.21; 39 | 40 | $result = DB::table('double_table')->update([ 41 | 'amount' => $newAmount, 42 | ]); 43 | 44 | $updatedData = DB::table('double_table')->first(); 45 | 46 | expect($result)->toBe(1) 47 | ->and($updatedData->amount)->toBe($newAmount); 48 | })->group('DoubleDataTest', 'DataTypes', 'FeatureTest'); 49 | 50 | test('it can find the saved record', function () { 51 | $amount = 123.45; 52 | 53 | DB::table('double_table')->insert([ 54 | 'amount' => 543.21, 55 | ]); 56 | DB::table('double_table')->insert([ 57 | 'amount' => $amount, 58 | ]); 59 | 60 | $found = DB::table('double_table')->where('amount', $amount)->first(); 61 | 62 | expect($found->id)->toBe(2) 63 | ->and($found->amount)->toBe($amount); 64 | })->group('DoubleDataTest', 'DataTypes', 'FeatureTest'); 65 | -------------------------------------------------------------------------------- /tests/Feature/DataTypes/FloatDataTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->float('amount', precision: 53); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('float_table'); 15 | }); 16 | 17 | test('it can insert a new float data', function () { 18 | $amount = 123.45; 19 | 20 | $result = DB::table('float_table')->insert([ 21 | 'amount' => $amount, 22 | ]); 23 | 24 | $newData = DB::table('float_table')->first(); 25 | 26 | expect($result)->toBeTrue() 27 | ->and(DB::table('float_table')->count())->toBe(1) 28 | ->and($newData->amount)->toBe($amount); 29 | })->group('FloatDataTest', 'DataTypes', 'FeatureTest'); 30 | 31 | test('it can update an existing float data', function () { 32 | $amount = 123.45; 33 | 34 | DB::table('float_table')->insert([ 35 | 'amount' => $amount, 36 | ]); 37 | 38 | $newAmount = 543.21; 39 | 40 | $result = DB::table('float_table')->update([ 41 | 'amount' => $newAmount, 42 | ]); 43 | 44 | $updatedData = DB::table('float_table')->first(); 45 | 46 | expect($result)->toBe(1) 47 | ->and($updatedData->amount)->toBe($newAmount); 48 | })->group('FloatDataTest', 'DataTypes', 'FeatureTest'); 49 | 50 | test('it can find the saved record', function () { 51 | $amount = 123.45; 52 | 53 | DB::table('float_table')->insert([ 54 | 'amount' => 543.21, 55 | ]); 56 | DB::table('float_table')->insert([ 57 | 'amount' => $amount, 58 | ]); 59 | 60 | $found = DB::table('float_table')->where('amount', $amount)->first(); 61 | 62 | expect($found->id)->toBe(2) 63 | ->and($found->amount)->toBe($amount); 64 | })->group('FloatDataTest', 'DataTypes', 'FeatureTest'); 65 | -------------------------------------------------------------------------------- /tests/Feature/DataTypes/IntegerDataTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->tinyInteger('tiny_integer'); 10 | $table->smallInteger('small_integer'); 11 | $table->mediumInteger('medium_integer'); 12 | $table->integer('integer'); 13 | $table->bigInteger('big_integer'); 14 | 15 | $table->unsignedTinyInteger('unsigned_tiny_integer'); 16 | $table->unsignedSmallInteger('unsigned_small_integer'); 17 | $table->unsignedMediumInteger('unsigned_medium_integer'); 18 | $table->unsignedInteger('unsigned_integer'); 19 | $table->unsignedBigInteger('unsigned_big_integer'); 20 | }); 21 | }); 22 | 23 | afterEach(function () { 24 | Schema::dropIfExists('integer_table'); 25 | }); 26 | 27 | test('it can insert a new integer data', function () { 28 | $tinyInteger = 127; 29 | $smallInteger = 32767; 30 | $mediumInteger = 8388607; 31 | $integer = 2147483647; 32 | $bigInteger = 9223372036854775807; 33 | 34 | $unsignedTinyInteger = 255; 35 | $unsignedSmallInteger = 65535; 36 | $unsignedMediumInteger = 16777215; 37 | $unsignedInteger = 4294967295; 38 | $unsignedBigInteger = 9223372036854775807; 39 | 40 | $result = DB::table('integer_table')->insert([ 41 | 'tiny_integer' => $tinyInteger, 42 | 'small_integer' => $smallInteger, 43 | 'medium_integer' => $mediumInteger, 44 | 'integer' => $integer, 45 | 'big_integer' => $bigInteger, 46 | 47 | 'unsigned_tiny_integer' => $unsignedTinyInteger, 48 | 'unsigned_small_integer' => $unsignedSmallInteger, 49 | 'unsigned_medium_integer' => $unsignedMediumInteger, 50 | 'unsigned_integer' => $unsignedInteger, 51 | 'unsigned_big_integer' => $unsignedBigInteger, 52 | ]); 53 | 54 | $newData = DB::table('integer_table')->first(); 55 | 56 | expect($result)->toBeTrue() 57 | ->and(DB::table('integer_table')->count())->toBe(1) 58 | ->and($newData->tiny_integer)->toBe($tinyInteger) 59 | ->and($newData->small_integer)->toBe($smallInteger) 60 | ->and($newData->medium_integer)->toBe($mediumInteger) 61 | ->and($newData->integer)->toBe($integer) 62 | ->and($newData->big_integer)->toBe($bigInteger) 63 | 64 | ->and($newData->unsigned_tiny_integer)->toBe($unsignedTinyInteger) 65 | ->and($newData->unsigned_small_integer)->toBe($unsignedSmallInteger) 66 | ->and($newData->unsigned_medium_integer)->toBe($unsignedMediumInteger) 67 | ->and($newData->unsigned_integer)->toBe($unsignedInteger) 68 | ->and($newData->unsigned_big_integer)->toBe($unsignedBigInteger); 69 | })->group('IntegerDataTest', 'DataTypes', 'FeatureTest'); 70 | 71 | test('it can update an existing integer data', function () { 72 | $tinyInteger = 127; 73 | $smallInteger = 32767; 74 | $mediumInteger = 8388607; 75 | $integer = 2147483647; 76 | $bigInteger = 9223372036854775807; 77 | 78 | $unsignedTinyInteger = 255; 79 | $unsignedSmallInteger = 65535; 80 | $unsignedMediumInteger = 16777215; 81 | $unsignedInteger = 4294967295; 82 | $unsignedBigInteger = 9223372036854775807; 83 | 84 | DB::table('integer_table')->insert([ 85 | 'tiny_integer' => $tinyInteger, 86 | 'small_integer' => $smallInteger, 87 | 'medium_integer' => $mediumInteger, 88 | 'integer' => $integer, 89 | 'big_integer' => $bigInteger, 90 | 91 | 'unsigned_tiny_integer' => $unsignedTinyInteger, 92 | 'unsigned_small_integer' => $unsignedSmallInteger, 93 | 'unsigned_medium_integer' => $unsignedMediumInteger, 94 | 'unsigned_integer' => $unsignedInteger, 95 | 'unsigned_big_integer' => $unsignedBigInteger, 96 | ]); 97 | 98 | $newTinyInteger = 63; 99 | $newSmallInteger = 16383; 100 | $newMediumInteger = 4194303; 101 | $newInteger = 1073741823; 102 | $newBigInteger = 4611686018427387903; 103 | 104 | $newUnsignedTinyInteger = 127; 105 | $newUnsignedSmallInteger = 32767; 106 | $newUnsignedMediumInteger = 8388607; 107 | $newUnsignedInteger = 2147483647; 108 | $newUnsignedBigInteger = 9223372036854775807; 109 | 110 | $result = DB::table('integer_table')->update([ 111 | 'tiny_integer' => $newTinyInteger, 112 | 'small_integer' => $newSmallInteger, 113 | 'medium_integer' => $newMediumInteger, 114 | 'integer' => $newInteger, 115 | 'big_integer' => $newBigInteger, 116 | 117 | 'unsigned_tiny_integer' => $newUnsignedTinyInteger, 118 | 'unsigned_small_integer' => $newUnsignedSmallInteger, 119 | 'unsigned_medium_integer' => $newUnsignedMediumInteger, 120 | 'unsigned_integer' => $newUnsignedInteger, 121 | 'unsigned_big_integer' => $newUnsignedBigInteger, 122 | ]); 123 | 124 | $updatedData = DB::table('integer_table')->first(); 125 | 126 | expect($result)->toBe(1) 127 | ->and($updatedData->tiny_integer)->toBe($newTinyInteger) 128 | ->and($updatedData->small_integer)->toBe($newSmallInteger) 129 | ->and($updatedData->medium_integer)->toBe($newMediumInteger) 130 | ->and($updatedData->integer)->toBe($newInteger) 131 | ->and($updatedData->big_integer)->toBe($newBigInteger) 132 | 133 | ->and($updatedData->unsigned_tiny_integer)->toBe($newUnsignedTinyInteger) 134 | ->and($updatedData->unsigned_small_integer)->toBe($newUnsignedSmallInteger) 135 | ->and($updatedData->unsigned_medium_integer)->toBe($newUnsignedMediumInteger) 136 | ->and($updatedData->unsigned_integer)->toBe($newUnsignedInteger) 137 | ->and($updatedData->unsigned_big_integer)->toBe($newUnsignedBigInteger); 138 | })->group('IntegerDataTest', 'DataTypes', 'FeatureTest'); 139 | 140 | test('it can find the saved record', function () { 141 | $tinyInteger = 127; 142 | $smallInteger = 32767; 143 | $mediumInteger = 8388607; 144 | $integer = 2147483647; 145 | $bigInteger = 9223372036854775807; 146 | 147 | $unsignedTinyInteger = 255; 148 | $unsignedSmallInteger = 65535; 149 | $unsignedMediumInteger = 16777215; 150 | $unsignedInteger = 4294967295; 151 | $unsignedBigInteger = 9223372036854775807; 152 | 153 | DB::table('integer_table')->insert([ 154 | 'tiny_integer' => 63, 155 | 'small_integer' => 16383, 156 | 'medium_integer' => 4194303, 157 | 'integer' => 1073741823, 158 | 'big_integer' => 4611686018427387903, 159 | 160 | 'unsigned_tiny_integer' => 127, 161 | 'unsigned_small_integer' => 32767, 162 | 'unsigned_medium_integer' => 8388607, 163 | 'unsigned_integer' => 2147483647, 164 | 'unsigned_big_integer' => 9223372036854775807, 165 | ]); 166 | DB::table('integer_table')->insert([ 167 | 'tiny_integer' => $tinyInteger, 168 | 'small_integer' => $smallInteger, 169 | 'medium_integer' => $mediumInteger, 170 | 'integer' => $integer, 171 | 'big_integer' => $bigInteger, 172 | 173 | 'unsigned_tiny_integer' => $unsignedTinyInteger, 174 | 'unsigned_small_integer' => $unsignedSmallInteger, 175 | 'unsigned_medium_integer' => $unsignedMediumInteger, 176 | 'unsigned_integer' => $unsignedInteger, 177 | 'unsigned_big_integer' => $unsignedBigInteger, 178 | ]); 179 | 180 | $found = DB::table('integer_table')->where('tiny_integer', $tinyInteger)->first(); 181 | 182 | expect($found->id)->toBe(2) 183 | ->and($found->tiny_integer)->toBe($tinyInteger) 184 | ->and($found->small_integer)->toBe($smallInteger) 185 | ->and($found->medium_integer)->toBe($mediumInteger) 186 | ->and($found->integer)->toBe($integer) 187 | ->and($found->big_integer)->toBe($bigInteger) 188 | 189 | ->and($found->unsigned_tiny_integer)->toBe($unsignedTinyInteger) 190 | ->and($found->unsigned_small_integer)->toBe($unsignedSmallInteger) 191 | ->and($found->unsigned_medium_integer)->toBe($unsignedMediumInteger) 192 | ->and($found->unsigned_integer)->toBe($unsignedInteger) 193 | ->and($found->unsigned_big_integer)->toBe($unsignedBigInteger); 194 | })->group('IntegerDataTest', 'DataTypes', 'FeatureTest'); 195 | 196 | test('it handles maximum unsigned big integer value', function () { 197 | $maxUnsignedBigInteger = 18446744073709551615; 198 | 199 | $result = DB::table('integer_table')->insert([ 200 | 'tiny_integer' => 127, 201 | 'small_integer' => 32767, 202 | 'medium_integer' => 8388607, 203 | 'integer' => 2147483647, 204 | 'big_integer' => 9223372036854775807, 205 | 206 | 'unsigned_tiny_integer' => 255, 207 | 'unsigned_small_integer' => 65535, 208 | 'unsigned_medium_integer' => 16777215, 209 | 'unsigned_integer' => 4294967295, 210 | 'unsigned_big_integer' => $maxUnsignedBigInteger, 211 | ]); 212 | 213 | $newData = DB::table('integer_table')->first(); 214 | 215 | // Assertions 216 | expect($result)->toBeTrue() 217 | ->and($newData->unsigned_big_integer)->toBe($maxUnsignedBigInteger); 218 | })->group('IntegerDataTest', 'EdgeCases', 'FeatureTest'); 219 | -------------------------------------------------------------------------------- /tests/Feature/DataTypes/StringDataTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->string('name', length: 100); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('string_table'); 15 | }); 16 | 17 | test('it can insert a new string data', function () { 18 | $name = 'John Doe'; 19 | 20 | $result = DB::table('string_table')->insert([ 21 | 'name' => $name, 22 | ]); 23 | 24 | $newData = DB::table('string_table')->first(); 25 | 26 | expect($result)->toBeTrue() 27 | ->and(DB::table('string_table')->count())->toBe(1) 28 | ->and($newData->name)->toBe($name); 29 | })->group('StringDataTest', 'DataTypes', 'FeatureTest'); 30 | 31 | test('it can update an existing string data', function () { 32 | $name = 'John Doe'; 33 | 34 | DB::table('string_table')->insert([ 35 | 'name' => $name, 36 | ]); 37 | 38 | $newName = 'Jane Doe'; 39 | 40 | $result = DB::table('string_table')->update([ 41 | 'name' => $newName, 42 | ]); 43 | 44 | $updatedData = DB::table('string_table')->first(); 45 | 46 | expect($result)->toBe(1) 47 | ->and($updatedData->name)->toBe($newName); 48 | })->group('StringDataTest', 'DataTypes', 'FeatureTest'); 49 | 50 | test('it can find the saved record', function () { 51 | $name = 'John Doe'; 52 | 53 | DB::table('string_table')->insert([ 54 | 'name' => 'Jane Doe', 55 | ]); 56 | DB::table('string_table')->insert([ 57 | 'name' => $name, 58 | ]); 59 | 60 | $found = DB::table('string_table')->where('name', $name)->first(); 61 | 62 | expect($found->id)->toBe(2) 63 | ->and($found->name)->toBe($name); 64 | })->group('StringDataTest', 'DataTypes', 'FeatureTest'); 65 | -------------------------------------------------------------------------------- /tests/Feature/DataTypes/TextDataTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->string('description'); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('text_table'); 15 | }); 16 | 17 | test('it can insert a new text data', function () { 18 | $description = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; 19 | 20 | $result = DB::table('text_table')->insert([ 21 | 'description' => $description, 22 | ]); 23 | 24 | $newData = DB::table('text_table')->first(); 25 | 26 | expect($result)->toBeTrue() 27 | ->and(DB::table('text_table')->count())->toBe(1) 28 | ->and($newData->description)->toBe($description); 29 | })->group('TextDataTest', 'DataTypes', 'FeatureTest'); 30 | 31 | test('it can update an existing text data', function () { 32 | $description = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; 33 | 34 | DB::table('text_table')->insert([ 35 | 'description' => $description, 36 | ]); 37 | 38 | $newDescription = 'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; 39 | 40 | $result = DB::table('text_table')->update([ 41 | 'description' => $newDescription, 42 | ]); 43 | 44 | $updatedData = DB::table('text_table')->first(); 45 | 46 | expect($result)->toBe(1) 47 | ->and($updatedData->description)->toBe($newDescription); 48 | })->group('TextDataTest', 'DataTypes', 'FeatureTest'); 49 | 50 | test('it can find the saved record', function () { 51 | $description = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; 52 | 53 | DB::table('text_table')->insert([ 54 | 'description' => 'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', 55 | ]); 56 | DB::table('text_table')->insert([ 57 | 'description' => $description, 58 | ]); 59 | 60 | $found = DB::table('text_table')->where('description', $description)->first(); 61 | 62 | expect($found->id)->toBe(2) 63 | ->and($found->description)->toBe($description); 64 | })->group('TextDataTest', 'DataTypes', 'FeatureTest'); 65 | -------------------------------------------------------------------------------- /tests/Feature/DataTypes/TimestampDataTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->timestamp('added_at', precision: 0); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('timestamp_table'); 15 | }); 16 | 17 | test('it can insert a new timestamp data', function () { 18 | $timestamp = '2021-01-01 12:34:56'; 19 | 20 | $result = DB::table('timestamp_table')->insert([ 21 | 'added_at' => $timestamp, 22 | ]); 23 | 24 | $newData = DB::table('timestamp_table')->first(); 25 | 26 | expect($result)->toBeTrue() 27 | ->and(DB::table('timestamp_table')->count())->toBe(1) 28 | ->and($newData->added_at)->toBe($timestamp); 29 | })->group('TimestampDataTest', 'DataTypes', 'FeatureTest'); 30 | 31 | test('it can update an existing timestamp data', function () { 32 | $timestamp = '2021-01-01 12:34:56'; 33 | 34 | DB::table('timestamp_table')->insert([ 35 | 'added_at' => $timestamp, 36 | ]); 37 | 38 | $newTimestamp = '2021-02-01 23:45:01'; 39 | 40 | $result = DB::table('timestamp_table')->update([ 41 | 'added_at' => $newTimestamp, 42 | ]); 43 | 44 | $updatedData = DB::table('timestamp_table')->first(); 45 | 46 | expect($result)->toBe(1) 47 | ->and($updatedData->added_at)->toBe($newTimestamp); 48 | })->group('TimestampDataTest', 'DataTypes', 'FeatureTest'); 49 | 50 | test('it can find the saved record', function () { 51 | $timestamp = '2021-01-01 12:34:56'; 52 | 53 | DB::table('timestamp_table')->insert([ 54 | 'added_at' => '2021-02-01 23:45:01', 55 | ]); 56 | DB::table('timestamp_table')->insert([ 57 | 'added_at' => $timestamp, 58 | ]); 59 | 60 | $found = DB::table('timestamp_table')->where('added_at', $timestamp)->first(); 61 | 62 | expect($found->added_at)->toBe($timestamp); 63 | })->group('TimestampDataTest', 'DataTypes', 'FeatureTest'); 64 | -------------------------------------------------------------------------------- /tests/Feature/DataTypes/VectorDataTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->string('title'); 10 | $table->string('genre'); 11 | $table->integer('release_year'); 12 | $table->vector('plot_embedding', 5); // 5-dimensional vector 13 | $table->timestamps(); 14 | }); 15 | 16 | Schema::table('movies', function ($table) { 17 | $table->vectorIndex('plot_embedding', 'movies_plot_embedding_idx'); 18 | }); 19 | }); 20 | 21 | afterEach(function () { 22 | Schema::dropAllTables(); 23 | }); 24 | 25 | test('it can insert a new vector data', function () { 26 | $embedding = [0.1, 0.2, 0.3, 0.4, 0.5]; 27 | 28 | DB::table('movies')->insert([ 29 | 'title' => 'The Matrix', 30 | 'genre' => 'Action', 31 | 'release_year' => 1999, 32 | 'plot_embedding' => $embedding, 33 | ]); 34 | 35 | $movie = DB::table('movies')->first(); 36 | 37 | expect($movie->plot_embedding)->toBe($embedding); 38 | })->group('VectorDataTest', 'FeatureTest'); 39 | 40 | test('it can find nearest vector data', function () { 41 | 42 | DB::table('movies')->insert([ 43 | [ 44 | 'title' => 'The Matrix', 45 | 'genre' => 'Action', 46 | 'release_year' => 1999, 47 | 'plot_embedding' => [0.1, 0.2, 0.3, 0.4, 0.5], 48 | ], 49 | [ 50 | 'title' => 'Inception', 51 | 'genre' => 'Sci-Fi', 52 | 'release_year' => 2010, 53 | 'plot_embedding' => [0.15, 0.25, 0.35, 0.45, 0.55], 54 | ], 55 | [ 56 | 'title' => 'Interstellar', 57 | 'genre' => 'Sci-Fi', 58 | 'release_year' => 2014, 59 | 'plot_embedding' => [0.2, 0.3, 0.4, 0.5, 0.6], 60 | ], 61 | ]); 62 | 63 | $queryVector = [0.15, 0.25, 0.35, 0.45, 0.55]; 64 | $result = DB::table('movies') 65 | ->nearest('movies_plot_embedding_idx', $queryVector, 5) 66 | ->get(); 67 | 68 | expect($result->count())->toBe(3); 69 | })->group('VectorDataTest', 'FeatureTest'); 70 | -------------------------------------------------------------------------------- /tests/Feature/DataTypes/YearDataTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->year('birth_year'); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('year_table'); 15 | }); 16 | 17 | test('it can insert a new year data', function () { 18 | $year = 2021; 19 | 20 | $result = DB::table('year_table')->insert([ 21 | 'birth_year' => $year, 22 | ]); 23 | 24 | $newData = DB::table('year_table')->first(); 25 | 26 | expect($result)->toBeTrue() 27 | ->and(DB::table('year_table')->count())->toBe(1) 28 | ->and($newData->birth_year)->toBe($year); 29 | })->group('YearDataTest', 'DataTypes', 'FeatureTest'); 30 | 31 | test('it can update an existing year data', function () { 32 | $year = 2021; 33 | 34 | DB::table('year_table')->insert([ 35 | 'birth_year' => $year, 36 | ]); 37 | 38 | $newYear = 2022; 39 | 40 | $result = DB::table('year_table')->update([ 41 | 'birth_year' => $newYear, 42 | ]); 43 | 44 | $updatedData = DB::table('year_table')->first(); 45 | 46 | expect($result)->toBe(1) 47 | ->and($updatedData->birth_year)->toBe($newYear); 48 | })->group('YearDataTest', 'DataTypes', 'FeatureTest'); 49 | 50 | test('it can find the saved record', function () { 51 | $year = 2021; 52 | 53 | DB::table('year_table')->insert([ 54 | 'birth_year' => 2022, 55 | ]); 56 | DB::table('year_table')->insert([ 57 | 'birth_year' => $year, 58 | ]); 59 | 60 | $found = DB::table('year_table')->where('birth_year', $year)->first(); 61 | 62 | expect($found->birth_year)->toBe($year); 63 | })->group('YearDataTest', 'DataTypes', 'FeatureTest'); 64 | -------------------------------------------------------------------------------- /tests/Feature/DatabaseTransactionsTest.php: -------------------------------------------------------------------------------- 1 | user = User::factory()->create(); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropAllTables(); 15 | }); 16 | 17 | test('it can rollback the transaction', function () { 18 | $this->user->name = 'John Doe'; 19 | $this->user->save(); 20 | 21 | expect(User::count())->toBe(1); 22 | 23 | DB::transaction(function () { 24 | $this->user->name = 'Jane Doe'; 25 | $this->user->save(); 26 | 27 | expect(User::first()->name)->toBe('Jane Doe'); 28 | 29 | DB::rollBack(); 30 | }); 31 | 32 | expect(User::count())->toBe(1); 33 | expect(User::first()->name)->toBe('John Doe'); 34 | })->group('DatabaseTransactionsTest', 'FeatureTest'); 35 | 36 | test('it can rollback the transaction by manually using the transactions', function () { 37 | $this->user->name = 'John Doe'; 38 | $this->user->save(); 39 | 40 | expect(User::count())->toBe(1); 41 | 42 | DB::beginTransaction(); 43 | 44 | $this->user->name = 'Jane Doe'; 45 | $this->user->save(); 46 | 47 | expect(User::first()->name)->toBe('Jane Doe'); 48 | 49 | DB::rollBack(); 50 | 51 | expect(User::count())->toBe(1); 52 | expect(User::first()->name)->toBe('John Doe'); 53 | })->group('DatabaseTransactionsTest', 'FeatureTest'); 54 | 55 | test('it can commit the transaction', function () { 56 | $this->user->name = 'John Doe'; 57 | $this->user->save(); 58 | 59 | expect(User::count())->toBe(1); 60 | 61 | DB::transaction(function () { 62 | $this->user->name = 'Jane Doe'; 63 | $this->user->save(); 64 | 65 | expect(User::first()->name)->toBe('Jane Doe'); 66 | }); 67 | 68 | expect(User::count())->toBe(1); 69 | expect(User::first()->name)->toBe('Jane Doe'); 70 | })->group('DatabaseTransactionsTest', 'FeatureTest'); 71 | 72 | test('it can commit the transaction by manually using the transactions', function () { 73 | $this->user->name = 'John Doe'; 74 | $this->user->save(); 75 | 76 | expect(User::count())->toBe(1); 77 | 78 | DB::beginTransaction(); 79 | 80 | $this->user->name = 'Jane Doe'; 81 | $this->user->save(); 82 | 83 | expect(User::first()->name)->toBe('Jane Doe'); 84 | 85 | DB::commit(); 86 | 87 | expect(User::count())->toBe(1); 88 | expect(User::first()->name)->toBe('Jane Doe'); 89 | })->group('DatabaseTransactionsTest', 'FeatureTest'); 90 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/ArrayCastingTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->json('data'); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('array_casting_table'); 15 | }); 16 | 17 | test('it can insert a new record using Eloquent ORM', function () { 18 | $data = ['name' => 'John Doe', 'city' => 'New York']; 19 | 20 | ArrayCastingModel::create([ 21 | 'data' => $data, 22 | ]); 23 | 24 | $result = ArrayCastingModel::first(); 25 | 26 | expect($result->id)->toBe(1) 27 | ->and(gettype($result->data))->toBe('array') 28 | ->and($result->data)->toBe($data); 29 | })->group('ArrayCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 30 | 31 | test('it can update an existing record using Eloquent ORM', function () { 32 | $data = ['name' => 'John Doe', 'city' => 'New York']; 33 | 34 | ArrayCastingModel::create([ 35 | 'data' => $data, 36 | ]); 37 | 38 | $newData = ['name' => 'Jane Doe', 'city' => 'Los Angeles']; 39 | 40 | ArrayCastingModel::first()->update([ 41 | 'data' => $newData, 42 | ]); 43 | 44 | $result = ArrayCastingModel::first(); 45 | 46 | expect($result->id)->toBe(1) 47 | ->and(gettype($result->data))->toBe('array') 48 | ->and($result->data)->toBe($newData); 49 | })->group('ArrayCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 50 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/AsStringableCastingTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->string('data'); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('stringable_casting_table'); 15 | }); 16 | 17 | test('it can insert a new record using Eloquent ORM', function () { 18 | $data = 'John Doe'; 19 | 20 | StringableCastingModel::create([ 21 | 'data' => $data, 22 | ]); 23 | 24 | $result = StringableCastingModel::first(); 25 | 26 | expect($result->id)->toBe(1) 27 | ->and(get_class($result->data))->toBe('Illuminate\Support\Stringable') 28 | ->and($result->data->toString())->toBe($data); 29 | })->group('AsStringableCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 30 | 31 | test('it can update an existing record using Eloquent ORM', function () { 32 | $data = 'John Doe'; 33 | 34 | StringableCastingModel::create([ 35 | 'data' => $data, 36 | ]); 37 | 38 | $newData = 'Jane Doe'; 39 | 40 | StringableCastingModel::first()->update([ 41 | 'data' => $newData, 42 | ]); 43 | 44 | $result = StringableCastingModel::first(); 45 | 46 | expect($result->id)->toBe(1) 47 | ->and(get_class($result->data))->toBe('Illuminate\Support\Stringable') 48 | ->and($result->data->toString())->toBe($newData); 49 | }); 50 | 51 | test('it can find the saved record', function () { 52 | $data = 'John Doe'; 53 | 54 | StringableCastingModel::create([ 55 | 'data' => 'Jane Doe', 56 | ]); 57 | StringableCastingModel::create([ 58 | 'data' => $data, 59 | ]); 60 | 61 | $found = StringableCastingModel::where('data', $data)->first(); 62 | 63 | expect($found->data->toString())->toBe($data); 64 | })->group('AsStringableCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 65 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/BooleanCastingTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->boolean('confirmed'); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('boolean_casting_table'); 15 | }); 16 | 17 | test('it can insert a new record using Eloquent ORM', function () { 18 | $confirmed = true; 19 | 20 | BooleanCastingModel::create([ 21 | 'confirmed' => $confirmed, 22 | ]); 23 | 24 | $result = BooleanCastingModel::first(); 25 | 26 | expect($result->id)->toBe(1) 27 | ->and(gettype($result->confirmed))->toBe('boolean') 28 | ->and($result->confirmed)->toBe($confirmed); 29 | })->group('BooleanCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 30 | 31 | test('it can update an existing record using Eloquent ORM', function () { 32 | $confirmed = true; 33 | 34 | BooleanCastingModel::create([ 35 | 'confirmed' => $confirmed, 36 | ]); 37 | 38 | $newConfirmed = false; 39 | 40 | BooleanCastingModel::first()->update([ 41 | 'confirmed' => $newConfirmed, 42 | ]); 43 | 44 | $result = BooleanCastingModel::first(); 45 | 46 | expect($result->id)->toBe(1) 47 | ->and(gettype($result->confirmed))->toBe('boolean') 48 | ->and($result->confirmed)->toBe($newConfirmed); 49 | })->group('BooleanCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 50 | 51 | test('it can find the saved record using Eloquent ORM', function () { 52 | $confirmed = true; 53 | 54 | BooleanCastingModel::create([ 55 | 'confirmed' => false, 56 | ]); 57 | BooleanCastingModel::create([ 58 | 'confirmed' => $confirmed, 59 | ]); 60 | 61 | $found = BooleanCastingModel::where('confirmed', $confirmed)->first(); 62 | 63 | expect($found->id)->toBe(2) 64 | ->and(gettype($found->confirmed))->toBe('boolean') 65 | ->and($found->confirmed)->toBe($confirmed); 66 | })->group('BooleanCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 67 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/CastingModels/ArrayCastingModel.php: -------------------------------------------------------------------------------- 1 | 'array', 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/CastingModels/BooleanCastingModel.php: -------------------------------------------------------------------------------- 1 | 'boolean', 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/CastingModels/CollectionCastingModel.php: -------------------------------------------------------------------------------- 1 | 'collection', 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/CastingModels/DateCastingModel.php: -------------------------------------------------------------------------------- 1 | 'date', 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/CastingModels/DatetimeCastingModel.php: -------------------------------------------------------------------------------- 1 | 'datetime', 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/CastingModels/DoubleCastingModel.php: -------------------------------------------------------------------------------- 1 | 'double', 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/CastingModels/EnumCastingModel.php: -------------------------------------------------------------------------------- 1 | Status::class, 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/CastingModels/FloatCastingModel.php: -------------------------------------------------------------------------------- 1 | 'float', 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/CastingModels/IntegerCastingModel.php: -------------------------------------------------------------------------------- 1 | 'integer', 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/CastingModels/StringableCastingModel.php: -------------------------------------------------------------------------------- 1 | AsStringable::class, 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/CastingModels/TimestampCastingModel.php: -------------------------------------------------------------------------------- 1 | 'timestamp', 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/CollectionCastingTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->json('data'); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('collection_casting_table'); 15 | }); 16 | 17 | test('it can insert a new record using Eloquent ORM', function () { 18 | $data = collect(['name' => 'John Doe', 'city' => 'New York']); 19 | 20 | CollectionCastingModel::create([ 21 | 'data' => $data, 22 | ]); 23 | 24 | $result = CollectionCastingModel::first(); 25 | 26 | expect($result->id)->toBe(1) 27 | ->and(get_class($result->data))->toBe('Illuminate\Support\Collection') 28 | ->and($result->data->toArray())->toBe($data->toArray()) 29 | ->and($result->data->get('name'))->toBe('John Doe') 30 | ->and($result->data->get('city'))->toBe('New York'); 31 | })->group('CollectionCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 32 | 33 | test('it can update an existing record using Eloquent ORM', function () { 34 | $data = collect(['name' => 'John Doe', 'city' => 'New York']); 35 | 36 | CollectionCastingModel::create([ 37 | 'data' => $data, 38 | ]); 39 | 40 | $newData = collect(['name' => 'Jane Doe', 'city' => 'Los Angeles']); 41 | 42 | CollectionCastingModel::first()->update([ 43 | 'data' => $newData, 44 | ]); 45 | 46 | $result = CollectionCastingModel::first(); 47 | 48 | expect($result->id)->toBe(1) 49 | ->and(get_class($result->data))->toBe('Illuminate\Support\Collection') 50 | ->and($result->data->toArray())->toBe($newData->toArray()) 51 | ->and($result->data->get('name'))->toBe('Jane Doe') 52 | ->and($result->data->get('city'))->toBe('Los Angeles'); 53 | })->group('CollectionCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 54 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/DateCastingTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->date('birthdate'); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('date_casting_table'); 15 | }); 16 | 17 | test('it can insert a new record using Eloquent ORM', function () { 18 | $birthdate = '1990-01-01'; 19 | 20 | DateCastingModel::create([ 21 | 'birthdate' => $birthdate, 22 | ]); 23 | 24 | $result = DateCastingModel::first(); 25 | 26 | expect($result->id)->toBe(1) 27 | ->and(get_class($result->birthdate))->toBe('Illuminate\Support\Carbon') 28 | ->and($result->birthdate->format('Y-m-d'))->toBe($birthdate); 29 | })->group('DateCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 30 | 31 | test('it can update an existing record using Eloquent ORM', function () { 32 | $birthdate = '1990-01-01'; 33 | 34 | DateCastingModel::create([ 35 | 'birthdate' => $birthdate, 36 | ]); 37 | 38 | $newBirthdate = '1995-01-01'; 39 | 40 | DateCastingModel::first()->update([ 41 | 'birthdate' => $newBirthdate, 42 | ]); 43 | 44 | $result = DateCastingModel::first(); 45 | 46 | expect($result->id)->toBe(1) 47 | ->and(get_class($result->birthdate))->toBe('Illuminate\Support\Carbon') 48 | ->and($result->birthdate->format('Y-m-d'))->toBe($newBirthdate); 49 | }); 50 | 51 | test('it can insert a new record using Eloquent ORM with Carbon instance', function () { 52 | $birthdate = '1990-01-01'; 53 | 54 | DateCastingModel::create([ 55 | 'birthdate' => new \Illuminate\Support\Carbon($birthdate), 56 | ]); 57 | 58 | $result = DateCastingModel::first(); 59 | 60 | expect($result->id)->toBe(1) 61 | ->and(get_class($result->birthdate))->toBe('Illuminate\Support\Carbon') 62 | ->and($result->birthdate->format('Y-m-d'))->toBe($birthdate); 63 | })->group('DateCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 64 | 65 | test('it can update an existing record using Eloquent ORM with Carbon instance', function () { 66 | $birthdate = '1990-01-01'; 67 | 68 | DateCastingModel::create([ 69 | 'birthdate' => $birthdate, 70 | ]); 71 | 72 | $newBirthdate = '1995-01-01'; 73 | 74 | DateCastingModel::first()->update([ 75 | 'birthdate' => new \Illuminate\Support\Carbon($newBirthdate), 76 | ]); 77 | 78 | $result = DateCastingModel::first(); 79 | 80 | expect($result->id)->toBe(1) 81 | ->and(get_class($result->birthdate))->toBe('Illuminate\Support\Carbon') 82 | ->and($result->birthdate->format('Y-m-d'))->toBe($newBirthdate); 83 | }); 84 | 85 | test('it can find the saved record using Eloquent ORM', function () { 86 | $birthdate = '1990-01-01'; 87 | 88 | DateCastingModel::create([ 89 | 'birthdate' => '1995-01-01', 90 | ]); 91 | DateCastingModel::create([ 92 | 'birthdate' => $birthdate, 93 | ]); 94 | 95 | $found = DateCastingModel::whereRaw('date("birthdate") = date(?)', [$birthdate])->first(); 96 | 97 | expect($found->id)->toBe(2) 98 | ->and(get_class($found->birthdate))->toBe('Illuminate\Support\Carbon') 99 | ->and($found->birthdate->format('Y-m-d'))->toBe($birthdate); 100 | })->group('DateCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 101 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/DatetimeCastingTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->dateTime('started_at'); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('datetime_casting_table'); 15 | }); 16 | 17 | test('it can insert a new record using Eloquent ORM', function () { 18 | $startedAt = '2021-01-01 12:00:00'; 19 | 20 | DatetimeCastingModel::create([ 21 | 'started_at' => $startedAt, 22 | ]); 23 | 24 | $result = DatetimeCastingModel::first(); 25 | 26 | expect($result->id)->toBe(1) 27 | ->and(get_class($result->started_at))->toBe('Illuminate\Support\Carbon') 28 | ->and($result->started_at->format('Y-m-d H:i:s'))->toBe($startedAt); 29 | })->group('DatetimeCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 30 | 31 | test('it can update an existing record using Eloquent ORM', function () { 32 | $startedAt = '2021-01-01 12:00:00'; 33 | 34 | DatetimeCastingModel::create([ 35 | 'started_at' => $startedAt, 36 | ]); 37 | 38 | $newStartedAt = '2021-01-01 13:00:00'; 39 | 40 | DatetimeCastingModel::first()->update([ 41 | 'started_at' => $newStartedAt, 42 | ]); 43 | 44 | $result = DatetimeCastingModel::first(); 45 | 46 | expect($result->id)->toBe(1) 47 | ->and(get_class($result->started_at))->toBe('Illuminate\Support\Carbon') 48 | ->and($result->started_at->format('Y-m-d H:i:s'))->toBe($newStartedAt); 49 | })->group('DatetimeCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 50 | 51 | test('it can insert a new record using Eloquent ORM with Carbon instance', function () { 52 | $startedAt = now(); 53 | 54 | DatetimeCastingModel::create([ 55 | 'started_at' => $startedAt, 56 | ]); 57 | 58 | $result = DatetimeCastingModel::first(); 59 | 60 | expect($result->id)->toBe(1) 61 | ->and(get_class($result->started_at))->toBe('Illuminate\Support\Carbon') 62 | ->and($result->started_at->format('Y-m-d H:i:s'))->toBe($startedAt->format('Y-m-d H:i:s')); 63 | })->group('DatetimeCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 64 | 65 | test('it can update an existing record using Eloquent ORM with Carbon instance', function () { 66 | $startedAt = now(); 67 | 68 | DatetimeCastingModel::create([ 69 | 'started_at' => $startedAt, 70 | ]); 71 | 72 | $newStartedAt = now()->addHour(); 73 | 74 | DatetimeCastingModel::first()->update([ 75 | 'started_at' => $newStartedAt, 76 | ]); 77 | 78 | $result = DatetimeCastingModel::first(); 79 | 80 | expect($result->id)->toBe(1) 81 | ->and(get_class($result->started_at))->toBe('Illuminate\Support\Carbon') 82 | ->and($result->started_at->format('Y-m-d H:i:s'))->toBe($newStartedAt->format('Y-m-d H:i:s')); 83 | })->group('DatetimeCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 84 | 85 | test('it can find the saved record using Eloquent ORM', function () { 86 | $startedAt = now(); 87 | 88 | DatetimeCastingModel::create([ 89 | 'started_at' => now()->subHour(), 90 | ]); 91 | DatetimeCastingModel::create([ 92 | 'started_at' => $startedAt, 93 | ]); 94 | 95 | $found = DatetimeCastingModel::where('started_at', $startedAt)->first(); 96 | 97 | expect($found->id)->toBe(2) 98 | ->and(get_class($found->started_at))->toBe('Illuminate\Support\Carbon') 99 | ->and($found->started_at->format('Y-m-d H:i:s'))->toBe($startedAt->format('Y-m-d H:i:s')); 100 | })->group('DatetimeCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 101 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/DoubleCastingTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->double('amount'); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('double_casting_table'); 15 | }); 16 | 17 | test('it can insert a new record using Eloquent ORM', function () { 18 | $amount = 100.50; 19 | 20 | DoubleCastingModel::create([ 21 | 'amount' => $amount, 22 | ]); 23 | 24 | $result = DoubleCastingModel::first(); 25 | 26 | expect($result->id)->toBe(1) 27 | ->and(gettype($result->amount))->toBe('double') 28 | ->and($result->amount)->toBe($amount); 29 | })->group('DoubleCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 30 | 31 | test('it can update an existing record using Eloquent ORM', function () { 32 | $amount = 100.50; 33 | 34 | DoubleCastingModel::create([ 35 | 'amount' => $amount, 36 | ]); 37 | 38 | $newAmount = 200.75; 39 | 40 | DoubleCastingModel::first()->update([ 41 | 'amount' => $newAmount, 42 | ]); 43 | 44 | $result = DoubleCastingModel::first(); 45 | 46 | expect($result->id)->toBe(1) 47 | ->and(gettype($result->amount))->toBe('double') 48 | ->and($result->amount)->toBe($newAmount); 49 | })->group('DoubleCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 50 | 51 | test('it can find the saved record using Eloquent ORM', function () { 52 | $amount = 100.50; 53 | 54 | DoubleCastingModel::create([ 55 | 'amount' => $amount, 56 | ]); 57 | 58 | $result = DoubleCastingModel::where('amount', $amount)->first(); 59 | 60 | expect($result->id)->toBe(1) 61 | ->and(gettype($result->amount))->toBe('double') 62 | ->and($result->amount)->toBe($amount); 63 | })->group('DoubleCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 64 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/EnumCastingTest.php: -------------------------------------------------------------------------------- 1 | id(); 10 | $table->tinyInteger('status'); 11 | }); 12 | }); 13 | 14 | afterEach(function () { 15 | Schema::dropIfExists('enum_casting_table'); 16 | }); 17 | 18 | test('it can insert a new record using Eloquent ORM', function () { 19 | $status = Status::Approved; 20 | 21 | EnumCastingModel::create([ 22 | 'status' => $status->value, 23 | ]); 24 | 25 | $result = EnumCastingModel::first(); 26 | 27 | expect($result->id)->toBe(1) 28 | ->and(gettype($result->status))->toBe('object') 29 | ->and(get_class($result->status))->toBe(Status::class) 30 | ->and($result->status)->toBe($status); 31 | })->group('EnumCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 32 | 33 | test('it can update an existing record using Eloquent ORM', function () { 34 | $status = Status::Approved; 35 | 36 | EnumCastingModel::create([ 37 | 'status' => $status->value, 38 | ]); 39 | 40 | $newStatus = Status::Rejected; 41 | 42 | EnumCastingModel::first()->update([ 43 | 'status' => $newStatus->value, 44 | ]); 45 | 46 | $result = EnumCastingModel::first(); 47 | 48 | expect($result->id)->toBe(1) 49 | ->and(gettype($result->status))->toBe('object') 50 | ->and(get_class($result->status))->toBe(Status::class) 51 | ->and($result->status)->toBe($newStatus); 52 | })->group('EnumCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 53 | 54 | test('it can find the saved record using Eloquent ORM', function () { 55 | $status = Status::Approved; 56 | 57 | EnumCastingModel::create([ 58 | 'status' => $status, 59 | ]); 60 | 61 | $result = EnumCastingModel::where('status', $status->value)->first(); 62 | 63 | expect($result->id)->toBe(1) 64 | ->and(gettype($result->status))->toBe('object') 65 | ->and(get_class($result->status))->toBe(Status::class) 66 | ->and($result->status)->toBe($status); 67 | })->group('EnumCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 68 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/Enums/Status.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->float('amount'); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('float_casting_table'); 15 | }); 16 | 17 | test('it can insert a new record using Eloquent ORM', function () { 18 | $amount = 100.50; 19 | 20 | FloatCastingModel::create([ 21 | 'amount' => $amount, 22 | ]); 23 | 24 | $result = FloatCastingModel::first(); 25 | 26 | expect($result->id)->toBe(1) 27 | ->and(gettype($result->amount))->toBe('double') 28 | ->and($result->amount)->toBe($amount); 29 | })->group('FloatCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 30 | 31 | test('it can update an existing record using Eloquent ORM', function () { 32 | $amount = 100.50; 33 | 34 | FloatCastingModel::create([ 35 | 'amount' => $amount, 36 | ]); 37 | 38 | $newAmount = 200.75; 39 | 40 | FloatCastingModel::first()->update([ 41 | 'amount' => $newAmount, 42 | ]); 43 | 44 | $result = FloatCastingModel::first(); 45 | 46 | expect($result->id)->toBe(1) 47 | ->and(gettype($result->amount))->toBe('double') 48 | ->and($result->amount)->toBe($newAmount); 49 | })->group('FloatCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 50 | 51 | test('it can find the saved record using Eloquent ORM', function () { 52 | $amount = 100.50; 53 | 54 | FloatCastingModel::create([ 55 | 'amount' => $amount, 56 | ]); 57 | 58 | $result = FloatCastingModel::where('amount', $amount)->first(); 59 | 60 | expect($result->id)->toBe(1) 61 | ->and(gettype($result->amount))->toBe('double') 62 | ->and($result->amount)->toBe($amount); 63 | })->group('FloatCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 64 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/IntegerCastingTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->integer('amount'); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('integer_casting_table'); 15 | }); 16 | 17 | test('it can insert a new record using Eloquent ORM', function () { 18 | $amount = 100; 19 | 20 | IntegerCastingModel::create([ 21 | 'amount' => $amount, 22 | ]); 23 | 24 | $result = IntegerCastingModel::first(); 25 | 26 | expect($result->id)->toBe(1) 27 | ->and(gettype($result->amount))->toBe('integer') 28 | ->and($result->amount)->toBe($amount); 29 | })->group('IntegerCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 30 | 31 | test('it can update an existing record using Eloquent ORM', function () { 32 | $amount = 100; 33 | 34 | IntegerCastingModel::create([ 35 | 'amount' => $amount, 36 | ]); 37 | 38 | $newAmount = 200; 39 | 40 | IntegerCastingModel::first()->update([ 41 | 'amount' => $newAmount, 42 | ]); 43 | 44 | $result = IntegerCastingModel::first(); 45 | 46 | expect($result->id)->toBe(1) 47 | ->and(gettype($result->amount))->toBe('integer') 48 | ->and($result->amount)->toBe($newAmount); 49 | })->group('IntegerCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 50 | 51 | test('it can find a record using Eloquent ORM', function () { 52 | $amount = 100; 53 | 54 | IntegerCastingModel::create([ 55 | 'amount' => $amount, 56 | ]); 57 | 58 | $result = IntegerCastingModel::where('amount', $amount)->first(); 59 | 60 | expect($result->id)->toBe(1) 61 | ->and(gettype($result->amount))->toBe('integer') 62 | ->and($result->amount)->toBe($amount); 63 | })->group('IntegerCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 64 | -------------------------------------------------------------------------------- /tests/Feature/EloquentAttributeCasting/TimestampCastingTest.php: -------------------------------------------------------------------------------- 1 | id(); 9 | $table->timestamp('added_at'); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropIfExists('timestamp_casting_table'); 15 | }); 16 | 17 | test('it can insert a new record using Eloquent ORM', function () { 18 | $addedAt = now(); 19 | 20 | TimestampCastingModel::create([ 21 | 'added_at' => $addedAt, 22 | ]); 23 | 24 | $result = TimestampCastingModel::first(); 25 | 26 | expect($result->id)->toBe(1) 27 | ->and(gettype($result->added_at))->toBe('integer') 28 | ->and($result->added_at)->toBe($addedAt->timestamp); 29 | })->group('TimestampCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 30 | 31 | test('it can update an existing record using Eloquent ORM', function () { 32 | $addedAt = now(); 33 | 34 | TimestampCastingModel::create([ 35 | 'added_at' => $addedAt, 36 | ]); 37 | 38 | $newAddedAt = now()->addHour(); 39 | 40 | TimestampCastingModel::first()->update([ 41 | 'added_at' => $newAddedAt, 42 | ]); 43 | 44 | $result = TimestampCastingModel::first(); 45 | 46 | expect($result->id)->toBe(1) 47 | ->and(gettype($result->added_at))->toBe('integer') 48 | ->and($result->added_at)->toBe($newAddedAt->timestamp); 49 | })->group('TimestampCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 50 | 51 | test('it can retrieve a record using Eloquent ORM', function () { 52 | $addedAt = now(); 53 | 54 | TimestampCastingModel::create([ 55 | 'added_at' => $addedAt, 56 | ]); 57 | 58 | $result = TimestampCastingModel::where('added_at', $addedAt)->first(); 59 | 60 | expect($result->id)->toBe(1) 61 | ->and(gettype($result->added_at))->toBe('integer') 62 | ->and($result->added_at)->toBe($addedAt->timestamp); 63 | })->group('TimestampCastingTest', 'EloquentAttributeCastings', 'FeatureTest'); 64 | -------------------------------------------------------------------------------- /tests/Feature/EloquentCollectionTest.php: -------------------------------------------------------------------------------- 1 | count(3)->create(); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropAllTables(); 15 | }); 16 | 17 | test('it can perform the fresh() method', function () { 18 | $collection = Project::all(); 19 | 20 | $collection->first()->delete(); 21 | 22 | $freshCollection = $collection->fresh(); 23 | 24 | expect($freshCollection)->toBeInstanceOf(Collection::class) 25 | ->and($freshCollection->count())->toBe($collection->count() - 1); 26 | })->group('EloquentCollectionTest', 'FeatureTest'); 27 | -------------------------------------------------------------------------------- /tests/Feature/EloquentDeleteTest.php: -------------------------------------------------------------------------------- 1 | project1 = Project::create(['name' => 'Project 1']); 10 | $this->project2 = Project::create(['name' => 'Project 2']); 11 | $this->project3 = Project::create(['name' => 'Project 3']); 12 | }); 13 | 14 | afterEach(function () { 15 | Schema::dropAllTables(); 16 | }); 17 | 18 | test('it can delete a single record', function () { 19 | $this->project2->delete(); 20 | 21 | expect(Project::count())->toBe(2) 22 | ->and(Project::find($this->project2->getKey()))->toBeNull(); 23 | })->group('EloquentDeleteTest', 'FeatureTest'); 24 | 25 | test('it can delete multiple records using query', function () { 26 | Project::whereIn('id', [$this->project1->getKey(), $this->project3->getKey()])->delete(); 27 | 28 | expect(Project::count())->toBe(1) 29 | ->and(Project::find($this->project1->getKey()))->toBeNull() 30 | ->and(Project::find($this->project3->getKey()))->toBeNull(); 31 | })->group('EloquentDeleteTest', 'FeatureTest'); 32 | 33 | test('it can delete multiple records using destroy method', function () { 34 | Project::destroy([$this->project1->getKey(), $this->project3->getKey()]); 35 | 36 | expect(Project::count())->toBe(1) 37 | ->and(Project::find($this->project1->getKey()))->toBeNull() 38 | ->and(Project::find($this->project3->getKey()))->toBeNull(); 39 | })->group('EloquentDeleteTest', 'FeatureTest'); 40 | 41 | test('it can truncate the whole table', function () { 42 | Project::truncate(); 43 | 44 | expect(Project::count())->toBe(0) 45 | ->and(Project::find($this->project1->getKey()))->toBeNull() 46 | ->and(Project::find($this->project2->getKey()))->toBeNull() 47 | ->and(Project::find($this->project3->getKey()))->toBeNull(); 48 | })->group('EloquentDeleteTest', 'FeatureTest'); 49 | -------------------------------------------------------------------------------- /tests/Feature/EloquentRelationship/HasManyThroughTest.php: -------------------------------------------------------------------------------- 1 | project = Project::factory()->create(); 13 | $this->environment = Environment::factory()->create([ 14 | 'project_id' => $this->project->getKey(), 15 | ]); 16 | 17 | $this->deployment1 = Deployment::factory()->create([ 18 | 'environment_id' => $this->environment->getKey(), 19 | ]); 20 | $this->deployment2 = Deployment::factory()->create([ 21 | 'environment_id' => $this->environment->getKey(), 22 | ]); 23 | $this->deployment3 = Deployment::factory()->create([ 24 | 'environment_id' => $this->environment->getKey(), 25 | ]); 26 | }); 27 | 28 | afterEach(function () { 29 | Schema::dropAllTables(); 30 | }); 31 | 32 | test('it can retrieve the related model in has many through relationship', function () { 33 | $project = Project::findOrFail($this->project->getKey()); 34 | $deployments = $project->deployments; 35 | 36 | expect($deployments)->not->toBeEmpty() 37 | ->and($deployments)->toBeInstanceOf(Collection::class) 38 | ->and($deployments->count())->toBe(3) 39 | ->and($deployments->first()->getKey())->toBe($this->deployment1->getKey()) 40 | ->and($deployments->last()->getKey())->toBe($this->deployment3->getKey()) 41 | ->and($deployments->first()->environment->getKey())->toBe($this->environment->getKey()) 42 | ->and($deployments->last()->environment->getKey())->toBe($this->environment->getKey()) 43 | ->and($deployments->first()->environment->project->getKey())->toBe($this->project->getKey()) 44 | ->and($deployments->last()->environment->project->getKey())->toBe($this->project->getKey()); 45 | })->group('HasManyThroughTest', 'EloquentRelationship', 'FeatureTest'); 46 | 47 | test('it can retrieve the related model in has many through relationship using eager loading', function () { 48 | $project = Project::with('deployments')->findOrFail($this->project->getKey()); 49 | $deployments = $project->deployments; 50 | 51 | expect($deployments)->not->toBeEmpty() 52 | ->and($deployments)->toBeInstanceOf(Collection::class) 53 | ->and($deployments->count())->toBe(3) 54 | ->and($deployments->first()->getKey())->toBe($this->deployment1->getKey()) 55 | ->and($deployments->last()->getKey())->toBe($this->deployment3->getKey()) 56 | ->and($deployments->first()->environment->getKey())->toBe($this->environment->getKey()) 57 | ->and($deployments->last()->environment->getKey())->toBe($this->environment->getKey()) 58 | ->and($deployments->first()->environment->project->getKey())->toBe($this->project->getKey()) 59 | ->and($deployments->last()->environment->project->getKey())->toBe($this->project->getKey()); 60 | })->group('HasManyThroughTest', 'EloquentRelationship', 'FeatureTest'); 61 | -------------------------------------------------------------------------------- /tests/Feature/EloquentRelationship/ManyToManyTest.php: -------------------------------------------------------------------------------- 1 | user = User::factory()->create(); 12 | $this->role1 = Role::factory()->create(); 13 | $this->role2 = Role::factory()->create(); 14 | $this->role3 = Role::factory()->create(); 15 | 16 | $this->user->roles()->attach($this->role1->getKey()); 17 | $this->user->roles()->attach($this->role3->getKey()); 18 | }); 19 | 20 | afterEach(function () { 21 | Schema::dropAllTables(); 22 | }); 23 | 24 | test('it can retrieve the related model in many to many relationship', function () { 25 | $user = User::findOrFail($this->user->getKey()); 26 | $roles = $user->roles; 27 | 28 | expect($roles)->not->toBeEmpty() 29 | ->and($roles->count())->toBe(2) 30 | ->and($roles->first()->getKey())->toBe($this->role1->getKey()) 31 | ->and($roles->last()->getKey())->toBe($this->role3->getKey()); 32 | })->group('ManyToManyTest', 'EloquentRelationship', 'FeatureTest'); 33 | 34 | test('it can retrieve the related model in many to many relationship using eager loading', function () { 35 | $user = User::with('roles')->findOrFail($this->user->getKey()); 36 | $roles = $user->roles; 37 | 38 | expect($roles)->not->toBeEmpty() 39 | ->and($roles->count())->toBe(2) 40 | ->and($roles->first()->getKey())->toBe($this->role1->getKey()) 41 | ->and($roles->last()->getKey())->toBe($this->role3->getKey()); 42 | })->group('ManyToManyTest', 'EloquentRelationship', 'FeatureTest'); 43 | 44 | test('it can retrieve the related model in inverted way of many to many relationship', function () { 45 | $role = Role::findOrFail($this->role1->getKey()); 46 | $users = $role->users; 47 | 48 | expect($users)->not->toBeEmpty() 49 | ->and($users)->toBeInstanceOf(Collection::class) 50 | ->and($users->count())->toBe(1) 51 | ->and($users->first()->getKey())->toBe($this->user->getKey()); 52 | })->group('ManyToManyTest', 'EloquentRelationship', 'FeatureTest'); 53 | 54 | test('it can retrieve the related model in inverted way of many to many relationship using eager loading', function () { 55 | $role = Role::with('users')->findOrFail($this->role1->getKey()); 56 | $users = $role->users; 57 | 58 | expect($users)->not->toBeEmpty() 59 | ->and($users)->toBeInstanceOf(Collection::class) 60 | ->and($users->count())->toBe(1) 61 | ->and($users->first()->getKey())->toBe($this->user->getKey()); 62 | })->group('ManyToManyTest', 'EloquentRelationship', 'FeatureTest'); 63 | 64 | test('it can filter the many to many relationship by specifying a column value', function () { 65 | $user = User::findOrFail($this->user->getKey()); 66 | $role = $user->roles()->where('name', $this->role3->name)->first(); 67 | 68 | expect($role)->not->toBeNull() 69 | ->and($role->getKey())->toBe($this->role3->getKey()) 70 | ->and($role->name)->toBe($this->role3->name); 71 | })->group('ManyToManyTest', 'EloquentRelationship', 'FeatureTest'); 72 | -------------------------------------------------------------------------------- /tests/Feature/EloquentRelationship/OneToManyTest.php: -------------------------------------------------------------------------------- 1 | user = User::factory()->create(); 12 | $this->post1 = Post::factory()->create([ 13 | 'user_id' => $this->user->getKey(), 14 | ]); 15 | $this->post2 = Post::factory()->create([ 16 | 'user_id' => $this->user->getKey(), 17 | ]); 18 | }); 19 | 20 | afterEach(function () { 21 | Schema::dropAllTables(); 22 | }); 23 | 24 | test('it can retrieve the related model in one to many relationship', function () { 25 | $user = User::findOrFail($this->user->getKey()); 26 | $posts = $user->posts; 27 | 28 | expect($posts)->not->toBeEmpty() 29 | ->and($posts)->toBeInstanceOf(Collection::class) 30 | ->and($posts->count())->toBe(2) 31 | ->and($posts->first()->getKey())->toBe($this->post1->getKey()) 32 | ->and($posts->last()->getKey())->toBe($this->post2->getKey()) 33 | ->and($posts->first()->user->getKey())->toBe($this->user->getKey()) 34 | ->and($posts->last()->user->getKey())->toBe($this->user->getKey()); 35 | })->group('OneToManyTest', 'EloquentRelationship', 'FeatureTest'); 36 | 37 | test('it can retrieve the related model in one to many relationship using eager loading', function () { 38 | $user = User::with('posts')->findOrFail($this->user->getKey()); 39 | $posts = $user->posts; 40 | 41 | expect($posts)->not->toBeEmpty() 42 | ->and($posts)->toBeInstanceOf(Collection::class) 43 | ->and($posts->count())->toBe(2) 44 | ->and($posts->first()->getKey())->toBe($this->post1->getKey()) 45 | ->and($posts->last()->getKey())->toBe($this->post2->getKey()) 46 | ->and($posts->first()->user->getKey())->toBe($this->user->getKey()) 47 | ->and($posts->last()->user->getKey())->toBe($this->user->getKey()); 48 | })->group('OneToManyTest', 'EloquentRelationship', 'FeatureTest'); 49 | 50 | test('it can retrieve the related model in inverted way of one to many relationship', function () { 51 | $post = Post::findOrFail($this->post1->getKey()); 52 | $user = $post->user; 53 | 54 | expect($user)->not->toBeNull() 55 | ->and($user->getKey())->toBe($this->user->getKey()) 56 | ->and($user->name)->toBe($this->user->name) 57 | ->and($user->email)->toBe($this->user->email) 58 | ->and($user->email_verified_at->format('Y-m-d H:i:s'))->toBe($this->user->email_verified_at->format('Y-m-d H:i:s')); 59 | })->group('OneToManyTest', 'EloquentRelationship', 'FeatureTest'); 60 | 61 | test('it can retrieve the related model in inverted way of one to many relationship using eager loading', function () { 62 | $post = Post::with('user')->findOrFail($this->post1->getKey()); 63 | $user = $post->user; 64 | 65 | expect($user)->not->toBeNull() 66 | ->and($user->getKey())->toBe($this->user->getKey()) 67 | ->and($user->name)->toBe($this->user->name) 68 | ->and($user->email)->toBe($this->user->email) 69 | ->and($user->email_verified_at->format('Y-m-d H:i:s'))->toBe($this->user->email_verified_at->format('Y-m-d H:i:s')); 70 | })->group('OneToManyTest', 'EloquentRelationship', 'FeatureTest'); 71 | 72 | test('it can create a new Post record using eloquent relationship', function () { 73 | $user = User::findOrFail($this->user->getKey()); 74 | $post = $user->posts()->create([ 75 | 'title' => 'New Post Title', 76 | 'content' => 'New Post Content', 77 | ]); 78 | 79 | expect($post)->not->toBeNull() 80 | ->and($post->getKey())->not->toBeNull() 81 | ->and($post->user->getKey())->toBe($user->getKey()) 82 | ->and($post->title)->toBe('New Post Title') 83 | ->and($post->content)->toBe('New Post Content'); 84 | })->group('OneToManyTest', 'EloquentRelationship', 'FeatureTest'); 85 | 86 | test('it can filter the has many relationship by specifying column value', function () { 87 | $user = User::findOrFail($this->user->getKey()); 88 | $post = $user->posts()->where('title', $this->post1->title)->first(); 89 | 90 | expect($post)->not->toBeNull() 91 | ->and($post->getKey())->toBe($this->post1->getKey()) 92 | ->and($post->title)->toBe($this->post1->title) 93 | ->and($post->content)->toBe($this->post1->content); 94 | })->group('OneToManyTest', 'EloquentRelationship', 'FeatureTest'); 95 | -------------------------------------------------------------------------------- /tests/Feature/EloquentRelationship/OneToOneTest.php: -------------------------------------------------------------------------------- 1 | user = User::factory()->create(); 11 | $this->phone = Phone::factory()->create([ 12 | 'user_id' => $this->user->getKey(), 13 | ]); 14 | }); 15 | 16 | afterEach(function () { 17 | Schema::dropAllTables(); 18 | }); 19 | 20 | test('it can retrieve the related model in one to one relationship', function () { 21 | $user = User::findOrFail($this->user->getKey()); 22 | $phone = $user->phone; 23 | 24 | expect($phone)->not->toBeNull() 25 | ->and($phone->getKey())->toBe($this->phone->getKey()) 26 | ->and($phone->user->getKey())->toBe($this->user->getKey()) 27 | ->and($phone->phone_number)->toBe($this->phone->phone_number); 28 | })->group('OneToOneTest', 'EloquentRelationship', 'FeatureTest'); 29 | 30 | test('it can retrieve the related model in one to one relationship using eager loading', function () { 31 | $user = User::with('phone')->findOrFail($this->user->getKey()); 32 | $phone = $user->phone; 33 | 34 | expect($phone)->not->toBeNull() 35 | ->and($phone->getKey())->toBe($this->phone->getKey()) 36 | ->and($phone->user->getKey())->toBe($this->user->getKey()) 37 | ->and($phone->phone_number)->toBe($this->phone->phone_number); 38 | })->group('OneToOneTest', 'EloquentRelationship', 'FeatureTest'); 39 | 40 | test('it can retrieve the related model in inverted way of one to one relationship', function () { 41 | $phone = Phone::findOrFail($this->phone->getKey()); 42 | $user = $phone->user; 43 | 44 | expect($user)->not->toBeNull() 45 | ->and($user->getKey())->toBe($this->user->getKey()) 46 | ->and($user->name)->toBe($this->user->name) 47 | ->and($user->email)->toBe($this->user->email) 48 | ->and($user->email_verified_at->format('Y-m-d H:i:s'))->toBe($this->user->email_verified_at->format('Y-m-d H:i:s')); 49 | })->group('OneToOneTest', 'EloquentRelationship', 'FeatureTest'); 50 | 51 | test('it can retrieve the related model in inverted way of one to one relationship using eager loading', function () { 52 | $phone = Phone::with('user')->findOrFail($this->phone->getKey()); 53 | $user = $phone->user; 54 | 55 | expect($user)->not->toBeNull() 56 | ->and($user->getKey())->toBe($this->user->getKey()) 57 | ->and($user->name)->toBe($this->user->name) 58 | ->and($user->email)->toBe($this->user->email) 59 | ->and($user->email_verified_at->format('Y-m-d H:i:s'))->toBe($this->user->email_verified_at->format('Y-m-d H:i:s')); 60 | })->group('OneToOneTest', 'EloquentRelationship', 'FeatureTest'); 61 | -------------------------------------------------------------------------------- /tests/Feature/EloquentSoftDeleteTest.php: -------------------------------------------------------------------------------- 1 | role1 = Role::create(['name' => 'Role 1']); 10 | $this->role2 = Role::create(['name' => 'Role 2']); 11 | $this->role3 = Role::create(['name' => 'Role 3']); 12 | }); 13 | 14 | afterEach(function () { 15 | Schema::dropAllTables(); 16 | }); 17 | 18 | test('it can delete a single record', function () { 19 | $this->role2->delete(); 20 | 21 | expect(Role::count())->toBe(2) 22 | ->and(Role::withTrashed()->count())->toBe(3) 23 | ->and(Role::find($this->role2->getKey()))->toBeNull(); 24 | })->group('EloquentSoftDeleteTest', 'FeatureTest'); 25 | 26 | test('deleted record can be retrieved using soft deletes specific feature', function () { 27 | $this->role2->delete(); 28 | 29 | $role = Role::withTrashed()->find($this->role2->getKey()); 30 | 31 | expect($role)->not->toBeNull() 32 | ->and($role->getKey())->toBe($this->role2->getKey()) 33 | ->and($role->name)->toBe($this->role2->name); 34 | })->group('EloquentSoftDeleteTest', 'FeatureTest'); 35 | 36 | test('it can restore a soft deleted record', function () { 37 | $this->role2->delete(); 38 | 39 | expect(Role::count())->toBe(2) 40 | ->and(Role::withTrashed()->find($this->role2->getKey()))->not->toBeNull(); 41 | 42 | $role = Role::withTrashed()->find($this->role2->getKey()); 43 | $role->restore(); 44 | 45 | $role = Role::find($this->role2->getKey()); 46 | 47 | expect(Role::count())->toBe(3) 48 | ->and(Role::whereNotNull('deleted_at')->count())->toBe(0) 49 | ->and(Role::find($this->role2->getKey()))->not->toBeNull(); 50 | })->group('EloquentSoftDeleteTest', 'FeatureTest'); 51 | -------------------------------------------------------------------------------- /tests/Feature/EmbebdedReplicaTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('This test skipped by default because it need a running libsql server'); 16 | } 17 | clearDirectory(); 18 | sleep(2); 19 | DB::setDefaultConnection('otherdb2'); 20 | }); 21 | 22 | test('it can connect to a embedded replica', function () { 23 | DB::setDefaultConnection('otherdb2'); 24 | $mode = DB::connection('otherdb2')->getConnectionMode(); 25 | expect($mode)->toBe('remote_replica'); 26 | })->group('EmbeddedReplicaTest', 'FeatureTest'); 27 | 28 | test('it can get all rows from the projects table through the embedded replica', function () { 29 | DB::setDefaultConnection('otherdb2'); 30 | Schema::dropAllTables(); 31 | migrateTables('projects'); 32 | 33 | $this->project1 = Project::make()->setConnection('otherdb2')->factory()->create(); 34 | $this->project2 = Project::make()->setConnection('otherdb2')->factory()->create(); 35 | $this->project3 = Project::make()->setConnection('otherdb2')->factory()->create(); 36 | $projects = DB::connection('otherdb2')->table('projects')->get(); 37 | expect($projects->count())->toBe(3); 38 | clearDirectory(); 39 | })->group('EmbeddedReplicaTest', 'FeatureTest'); 40 | -------------------------------------------------------------------------------- /tests/Feature/LibsqlPDOStatementTest.php: -------------------------------------------------------------------------------- 1 | connection = DB::connection(); 10 | $this->pdo = $this->connection->getPdo(); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropAllTables(); 15 | }); 16 | 17 | test('it can fetch all row sets of a simple select query result in associative array format', function () { 18 | $expectation = [ 19 | [ 20 | 'type' => 'table', 21 | 'name' => 'migrations', 22 | 'tbl_name' => 'migrations', 23 | 'sql' => 'CREATE TABLE "migrations" ("id" integer primary key autoincrement not null, "migration" varchar not null, "batch" integer not null)', 24 | ], 25 | ]; 26 | 27 | $statement = $this->pdo->prepare('SELECT * FROM sqlite_schema WHERE type = ? AND name NOT LIKE ?'); 28 | $this->connection->bindValues($statement, $this->connection->prepareBindings(['table', 'sqlite_%'])); 29 | $statement->execute(); 30 | 31 | $statement->setFetchMode(\PDO::FETCH_ASSOC); 32 | $response = $statement->fetchAll(); 33 | 34 | expect(count($response))->toBe(1) 35 | ->and($response[0]['type'])->toBe($expectation[0]['type']) 36 | ->and($response[0]['name'])->toBe($expectation[0]['name']) 37 | ->and($response[0]['tbl_name'])->toBe($expectation[0]['tbl_name']) 38 | ->and($response[0]['sql'])->toBe($expectation[0]['sql']); 39 | })->group('LibsqlLPDOStatementTest', 'FeatureTest'); 40 | 41 | test('it can fetch each row set of a simple select query result in associative array format', function () { 42 | $expectation = [ 43 | 'type' => 'table', 44 | 'name' => 'migrations', 45 | 'tbl_name' => 'migrations', 46 | 'sql' => 'CREATE TABLE "migrations" ("id" integer primary key autoincrement not null, "migration" varchar not null, "batch" integer not null)', 47 | ]; 48 | 49 | $statement = $this->pdo->prepare('SELECT * FROM sqlite_schema WHERE type = ? AND name NOT LIKE ?'); 50 | $this->connection->bindValues($statement, $this->connection->prepareBindings(['table', 'sqlite_%'])); 51 | 52 | $statement->execute(); 53 | 54 | $statement->setFetchMode(\PDO::FETCH_ASSOC); 55 | $response = $statement->fetch(); 56 | 57 | expect($response['type'])->toBe($expectation['type']) 58 | ->and($response['name'])->toBe($expectation['name']) 59 | ->and($response['tbl_name'])->toBe($expectation['tbl_name']) 60 | ->and($response['sql'])->toBe($expectation['sql']) 61 | ->and($statement->fetch())->toBeFalse(); 62 | })->group('LibsqlLPDOStatementTest', 'FeatureTest'); 63 | 64 | test('it can count the rows of query result set', function () { 65 | 66 | $statement = $this->pdo->prepare('SELECT * FROM sqlite_schema WHERE type = ? AND name NOT LIKE ?'); 67 | $this->connection->bindValues($statement, $this->connection->prepareBindings(['table', 'sqlite_%'])); 68 | $statement->execute(); 69 | 70 | expect($statement->rowCount())->toBe(1); 71 | })->group('LibsqlLPDOStatementTest', 'FeatureTest'); 72 | 73 | test('it can perform query execution with binding values', function () { 74 | DB::statement('INSERT INTO "migrations" ("migration", "batch") VALUES (?, ?)', ['CreateUsersTable', 1]); 75 | 76 | $statement = $this->pdo->prepare('SELECT * FROM "migrations" WHERE "migration" = ? AND "batch" = ?'); 77 | $statement->execute(['CreateUsersTable', 1]); 78 | 79 | expect($statement->rowCount())->toBe(1); 80 | 81 | $result = $statement->fetch(); 82 | 83 | expect($result['migration'])->toBe('CreateUsersTable') 84 | ->and($result['batch'])->toBe(1); 85 | })->group('LibsqlLPDOStatementTest', 'FeatureTest'); 86 | 87 | test('it can perform update statement with binding values', function () { 88 | DB::statement('INSERT INTO "migrations" ("migration", "batch") VALUES (?, ?)', ['CreateUsersTable', 1]); 89 | 90 | $statement = $this->pdo->prepare('UPDATE "migrations" SET "migration" = ? WHERE "id" = ?'); 91 | $statement->execute(['CreateRolesTable', 1]); 92 | 93 | expect($statement->rowCount())->toBe(1); 94 | })->group('LibsqlLPDOStatementTest', 'FeatureTest'); 95 | -------------------------------------------------------------------------------- /tests/Feature/LibsqlPDOTest.php: -------------------------------------------------------------------------------- 1 | pdo = DB::connection()->getPdo(); 7 | 8 | $this->pdo->exec('CREATE TABLE "projects" ("id" INTEGER PRIMARY KEY, "name" TEXT);'); 9 | }); 10 | 11 | afterEach(function () { 12 | $this->pdo->exec('DROP TABLE IF EXISTS "projects";'); 13 | }); 14 | 15 | test('it can execute SQL command', function () { 16 | expect($this->pdo->exec('PRAGMA foreign_keys = ON;'))->toBe(0); 17 | })->group('LibsqlPDOTest', 'FeatureTest'); 18 | 19 | test('it can begin the database transaction, and rollback the changes.', function () { 20 | $this->pdo->beginTransaction(); 21 | 22 | $this->pdo->exec('INSERT INTO "projects" ("name") VALUES (\'Project 1\');'); 23 | $this->pdo->exec('INSERT INTO "projects" ("name") VALUES (\'Project 2\');'); 24 | 25 | expect($this->pdo->inTransaction())->toBeTrue() 26 | ->and($this->pdo->exec('SELECT * FROM "projects";'))->toBe(2); 27 | 28 | $this->pdo->rollBack(); 29 | 30 | expect($this->pdo->inTransaction())->toBeFalse() 31 | ->and($this->pdo->exec('SELECT * FROM "projects";'))->toBe(0); 32 | })->group('LibsqlPDOTest', 'FeatureTest'); 33 | 34 | test('it can begin the database transaction, and commit the changes.', function () { 35 | $this->pdo->beginTransaction(); 36 | 37 | $this->pdo->exec('INSERT INTO "projects" ("name") VALUES (\'Project 1\');'); 38 | $this->pdo->exec('INSERT INTO "projects" ("name") VALUES (\'Project 2\');'); 39 | 40 | expect($this->pdo->inTransaction())->toBeTrue() 41 | ->and($this->pdo->exec('SELECT * FROM "projects";'))->toBe(2); 42 | 43 | $this->pdo->commit(); 44 | 45 | expect($this->pdo->inTransaction())->toBeFalse() 46 | ->and($this->pdo->exec('SELECT * FROM "projects";'))->toBe(2); 47 | })->group('LibsqlPDOTest', 'FeatureTest'); 48 | -------------------------------------------------------------------------------- /tests/Feature/LibsqlSchemaBuilderTest.php: -------------------------------------------------------------------------------- 1 | toBe([]); 20 | })->group('LibsqlSchemaBuilderTest', 'FeatureTest'); 21 | 22 | test('it can retrieve all of the table information in the database', function () { 23 | DB::select('CREATE TABLE "migrations" ("id" integer primary key autoincrement not null, "migration" varchar not null, "batch" integer not null)'); 24 | 25 | $result = Schema::getTables()[0]; 26 | 27 | expect($result['name'])->toBe('migrations') 28 | ->and($result['schema'])->toBeNull() 29 | ->and($result['comment'])->toBeNull() 30 | ->and($result['collation'])->toBeNull() 31 | ->and($result['engine'])->toBeNull(); 32 | })->group('LibsqlSchemaBuilderTest', 'FeatureTest'); 33 | 34 | test('it can retrieve all of the column information in the table', function () { 35 | DB::select('CREATE TABLE "migrations" ("id" integer primary key autoincrement not null, "migration" varchar not null, "batch" integer not null)'); 36 | 37 | $result = collect(Schema::getColumns('migrations'))->keyBy('name'); 38 | 39 | expect($result->count())->toBe(3) 40 | ->and($result->has('id'))->toBeTrue() 41 | ->and($result->has('migration'))->toBeTrue() 42 | ->and($result->has('batch'))->toBeTrue() 43 | ->and($result->get('id'))->toBe([ 44 | 'name' => 'id', 45 | 'type_name' => 'integer', 46 | 'type' => 'integer', 47 | 'collation' => null, 48 | 'nullable' => false, 49 | 'default' => null, 50 | 'auto_increment' => true, 51 | 'comment' => null, 52 | 'generation' => null, 53 | 'pk' => 1, 54 | 'notnull' => 1, 55 | 'dflt_value' => null, 56 | 'cid' => 0, 57 | 'hidden' => 0, 58 | ]) 59 | ->and($result->get('migration'))->toBe([ 60 | 'name' => 'migration', 61 | 'type_name' => 'varchar', 62 | 'type' => 'varchar', 63 | 'collation' => null, 64 | 'nullable' => false, 65 | 'default' => null, 66 | 'auto_increment' => false, 67 | 'comment' => null, 68 | 'generation' => null, 69 | 'pk' => 0, 70 | 'notnull' => 1, 71 | 'dflt_value' => null, 72 | 'cid' => 1, 73 | 'hidden' => 0, 74 | ]) 75 | ->and($result->get('batch'))->toBe([ 76 | 'name' => 'batch', 77 | 'type_name' => 'integer', 78 | 'type' => 'integer', 79 | 'collation' => null, 80 | 'nullable' => false, 81 | 'default' => null, 82 | 'auto_increment' => false, 83 | 'comment' => null, 84 | 'generation' => null, 85 | 'pk' => 0, 86 | 'notnull' => 1, 87 | 'dflt_value' => null, 88 | 'cid' => 2, 89 | 'hidden' => 0, 90 | ]); 91 | })->group('LibsqlSchemaBuilderTest', 'FeatureTest'); 92 | 93 | test('it can create a new table', function () { 94 | Schema::dropIfExists('users'); 95 | Schema::create('users', function (Blueprint $table) { 96 | $table->id(); 97 | $table->string('name'); 98 | }); 99 | 100 | $result = Schema::getTables()[0]; 101 | 102 | expect($result['name'])->toBe('users') 103 | ->and($result['schema'])->toBeNull() 104 | ->and($result['comment'])->toBeNull() 105 | ->and($result['collation'])->toBeNull() 106 | ->and($result['engine'])->toBeNull(); 107 | 108 | $columns = collect(Schema::getColumns('users'))->keyBy('name')->keys()->all(); 109 | 110 | expect($columns)->toBe(['id', 'name']); 111 | })->group('LibsqlSchemaBuilderTest', 'FeatureTest'); 112 | 113 | test('it can alter an existing table.', function () { 114 | Schema::create('users', function (Blueprint $table) { 115 | $table->id(); 116 | $table->string('name'); 117 | }); 118 | 119 | Schema::table('users', function (Blueprint $table) { 120 | $table->string('email')->after('name'); 121 | }); 122 | 123 | expect(Schema::hasColumn('users', 'email'))->toBeTrue() 124 | ->and(Schema::hasColumns('users', ['id', 'name', 'email']))->toBeTrue() 125 | ->and(Schema::getColumnType('users', 'email', true))->toBe('varchar') 126 | ->and(Schema::getColumnListing('users'))->toBe(['id', 'name', 'email']); 127 | })->group('LibsqlSchemaBuilderTest', 'FeatureTest'); 128 | 129 | test('it can drop all views from the database', function () { 130 | $createSql = 'CREATE VIEW foo (id) AS SELECT 1'; 131 | 132 | DB::statement($createSql); 133 | 134 | $view = collect(Schema::getViews())->first(); 135 | 136 | expect($view['name'])->toBe('foo') 137 | ->and($view['schema'])->toBeNull() 138 | ->and($view['definition'])->toBe($createSql); 139 | 140 | Schema::dropAllViews(); 141 | 142 | expect(Schema::getViews())->toBe([]); 143 | })->group('LibsqlSchemaBuilderTest', 'FeatureTest'); 144 | -------------------------------------------------------------------------------- /tests/Feature/MulticonnectionTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('This test skipped by default because it need a running libsql server'); 10 | } 11 | 12 | DB::setDefaultConnection('otherdb'); 13 | Schema::dropAllTables(); 14 | migrateTables('projects'); 15 | 16 | $this->project1 = Project::make()->setConnection('otherdb')->factory()->create(); 17 | $this->project2 = Project::make()->setConnection('otherdb')->factory()->create(); 18 | $this->project3 = Project::make()->setConnection('otherdb')->factory()->create(); 19 | }); 20 | 21 | afterEach(function () { 22 | DB::setDefaultConnection('otherdb'); 23 | Schema::dropAllTables(); 24 | }); 25 | 26 | test('it can connect to a in-memory database', function () { 27 | $mode = DB::connection('libsql')->getConnectionMode(); 28 | expect($mode)->toBe('memory'); 29 | })->group('MultiConnectionsTest', 'FeatureTest'); 30 | 31 | test('it can connect to a remote database', function () { 32 | $mode = DB::connection('otherdb')->getConnectionMode(); 33 | expect($mode)->toBe('remote'); 34 | })->group('MultiConnectionsTest', 'FeatureTest'); 35 | 36 | test('each connection has its own libsql client instance', function () { 37 | $client1 = DB::connection('libsql')->getPdo(); // In Memory Connection 38 | $client2 = DB::connection('otherdb')->getPdo(); // Remote Connection 39 | 40 | expect($client1)->not->toBe($client2); 41 | })->group('MultiConnectionsTest', 'FeatureTest'); 42 | 43 | test('it can get all rows from the projects table through the remote connection', function () { 44 | $projects = DB::connection('otherdb')->table('projects')->get(); 45 | 46 | expect($projects)->toHaveCount(3) 47 | ->and($projects[0]->name)->toEqual($this->project1->name) 48 | ->and($projects[1]->name)->toEqual($this->project2->name) 49 | ->and($projects[2]->name)->toEqual($this->project3->name); 50 | })->group('MultiConnectionsTest', 'FeatureTest'); 51 | -------------------------------------------------------------------------------- /tests/Feature/QueryBuilder/DatabaseQueriesTest.php: -------------------------------------------------------------------------------- 1 | project1 = Project::factory()->create(); 11 | $this->project2 = Project::factory()->create(); 12 | $this->project3 = Project::factory()->create(); 13 | }); 14 | 15 | afterEach(function () { 16 | Schema::dropAllTables(); 17 | }); 18 | 19 | test('it can get all rows from the table', function () { 20 | $projects = DB::table('projects')->get(); 21 | 22 | expect($projects)->toHaveCount(3) 23 | ->and($projects[0]->name)->toEqual($this->project1->name) 24 | ->and($projects[1]->name)->toEqual($this->project2->name) 25 | ->and($projects[2]->name)->toEqual($this->project3->name); 26 | })->group('DatabaseQueriesTest', 'QueryBuilder', 'FeatureTest'); 27 | 28 | test('it can retrieve a single row from the table with first() method', function () { 29 | $project = DB::table('projects')->where('name', $this->project2->name)->first(); 30 | 31 | expect($project)->not->toBeNull() 32 | ->and($project)->toBeObject() 33 | ->and($project->id)->toEqual($this->project2->id) 34 | ->and($project->name)->toEqual($this->project2->name); 35 | })->group('DatabaseQueriesTest', 'QueryBuilder', 'FeatureTest'); 36 | 37 | test('it can retrieve a single row from the table with find() method', function () { 38 | $project = DB::table('projects')->find($this->project2->getKey()); 39 | 40 | expect($project)->not->toBeNull() 41 | ->and($project)->toBeObject() 42 | ->and($project->id)->toEqual($this->project2->id) 43 | ->and($project->name)->toEqual($this->project2->name); 44 | })->group('DatabaseQueriesTest', 'QueryBuilder', 'FeatureTest'); 45 | 46 | test('it will return null if there was no record with the given id to be found', function () { 47 | $project = DB::table('projects')->find(999); 48 | 49 | expect($project)->toBeNull(); 50 | })->group('DatabaseQueriesTest', 'QueryBuilder', 'FeatureTest'); 51 | 52 | test('it can retrieve a list of column values', function () { 53 | $expectation = [ 54 | $this->project1->name, 55 | $this->project2->name, 56 | $this->project3->name, 57 | ]; 58 | 59 | $projects = DB::table('projects')->get()->pluck('name')->toArray(); 60 | 61 | expect($projects)->toBeArray() 62 | ->and($projects)->toHaveCount(3) 63 | ->and($projects)->toEqual($expectation); 64 | })->group('DatabaseQueriesTest', 'QueryBuilder', 'FeatureTest'); 65 | 66 | test('it can stream the results lazily', function () { 67 | $expectations = [ 68 | $this->project1, 69 | $this->project2, 70 | $this->project3, 71 | ]; 72 | 73 | DB::table('projects') 74 | ->orderBy('id') 75 | ->lazy() 76 | ->each(function (object $project, int $index) use ($expectations) { 77 | expect($project)->toBeObject() 78 | ->and($project->id)->toEqual($expectations[$index]->id) 79 | ->and($project->name)->toEqual($expectations[$index]->name); 80 | }); 81 | })->group('DatabaseQueriesTest', 'QueryBuilder', 'FeatureTest'); 82 | 83 | test('it can count the records count', function () { 84 | expect(DB::table('projects')->count())->toEqual(3); 85 | })->group('DatabaseQueriesTest', 'QueryBuilder', 'FeatureTest'); 86 | 87 | test('it can return the maximum value of a column from the table', function () { 88 | expect(DB::table('projects')->max('id'))->toEqual(3); 89 | })->group('DatabaseQueriesTest', 'QueryBuilder', 'FeatureTest'); 90 | 91 | test('it can return the minimum value of a column from the table', function () { 92 | expect(DB::table('projects')->min('id'))->toEqual(1); 93 | })->group('DatabaseQueriesTest', 'QueryBuilder', 'FeatureTest'); 94 | 95 | test('it can return the average value of a column from the table', function () { 96 | expect(DB::table('projects')->avg('id'))->toEqual(2); 97 | })->group('DatabaseQueriesTest', 'QueryBuilder', 'FeatureTest'); 98 | 99 | test('it can determine if a record exists in the table', function () { 100 | $exists = DB::table('projects')->where('name', $this->project2->name)->exists(); 101 | $doesntExist = DB::table('projects')->where('name', 'unknown')->doesntExist(); 102 | 103 | expect($exists)->toBeTrue() 104 | ->and($doesntExist)->toBeTrue(); 105 | })->group('DatabaseQueriesTest', 'QueryBuilder', 'FeatureTest'); 106 | -------------------------------------------------------------------------------- /tests/Feature/QueryBuilder/DeleteStatementsTest.php: -------------------------------------------------------------------------------- 1 | user1 = User::factory()->create(); 11 | $this->user2 = User::factory()->create(); 12 | $this->user3 = User::factory()->create(); 13 | }); 14 | 15 | afterEach(function () { 16 | Schema::dropAllTables(); 17 | }); 18 | 19 | test('it can delete multiple records', function () { 20 | DB::table('users')->where('id', '>', 1)->delete(); 21 | 22 | expect(DB::table('users')->count())->toBe(1); 23 | })->group('DeleteStatementsTest', 'QueryBuilder', 'FeatureTest'); 24 | 25 | test('it can truncate the whole table content', function () { 26 | DB::table('users')->truncate(); 27 | 28 | expect(DB::table('users')->count())->toBe(0); 29 | })->group('DeleteStatementsTest', 'QueryBuilder', 'FeatureTest'); 30 | 31 | test('it can delete a single record', function () { 32 | DB::table('users')->where('id', $this->user2->getKey())->delete(); 33 | 34 | expect(DB::table('users')->count())->toBe(2); 35 | })->group('DeleteStatementsTest', 'QueryBuilder', 'FeatureTest'); 36 | 37 | test('it can delete all records', function () { 38 | DB::table('users')->delete(); 39 | 40 | expect(DB::table('users')->count())->toBe(0); 41 | })->group('DeleteStatementsTest', 'QueryBuilder', 'FeatureTest'); 42 | -------------------------------------------------------------------------------- /tests/Feature/QueryBuilder/InsertStatementsTest.php: -------------------------------------------------------------------------------- 1 | insert([ 17 | 'name' => 'John Doe', 18 | 'email' => 'john.doe@gmail.com', 19 | ]); 20 | 21 | $user = DB::table('users')->first(); 22 | 23 | expect($result)->toBeTrue() 24 | ->and(DB::table('users')->count())->toBe(1) 25 | ->and($user->name)->toBe('John Doe') 26 | ->and($user->email)->toBe('john.doe@gmail.com'); 27 | })->group('InsertStatementsTest', 'QueryBuilder', 'FeatureTest'); 28 | 29 | test('it can insert multiple records', function () { 30 | $result = DB::table('users')->insert([ 31 | [ 32 | 'name' => 'John Doe', 33 | 'email' => 'john.doe@gmail.com', 34 | ], 35 | [ 36 | 'name' => 'June Monroe', 37 | 'email' => 'june.monroe@gmail.com', 38 | ], 39 | ]); 40 | 41 | $users = DB::table('users')->get(); 42 | 43 | expect($result)->toBeTrue() 44 | ->and(DB::table('users')->count())->toBe(2) 45 | ->and($users->first()->name)->toBe('John Doe') 46 | ->and($users->first()->email)->toBe('john.doe@gmail.com') 47 | ->and($users->last()->name)->toBe('June Monroe') 48 | ->and($users->last()->email)->toBe('june.monroe@gmail.com'); 49 | })->group('InsertStatementsTest', 'QueryBuilder', 'FeatureTest'); 50 | 51 | test('it can get the auto increment id as the result of insert command', function () { 52 | User::factory()->create(); 53 | 54 | $expectation = User::factory()->make(); 55 | 56 | $result = DB::table('users')->insertGetId([ 57 | 'name' => $expectation->name, 58 | 'email' => $expectation->email, 59 | ]); 60 | 61 | $newUser = DB::table('users')->find($result); 62 | 63 | expect(DB::table('users')->count())->toBe(2) 64 | ->and($result)->toBe(2) 65 | ->and($newUser->name)->toBe($expectation->name) 66 | ->and($newUser->email)->toBe($expectation->email); 67 | })->group('InsertStatementsTest', 'QueryBuilder', 'FeatureTest'); 68 | -------------------------------------------------------------------------------- /tests/Feature/QueryBuilder/RawExpressionsTest.php: -------------------------------------------------------------------------------- 1 | user1 = User::factory()->create(); 12 | $this->user2 = User::factory()->create(); 13 | $this->user3 = User::factory()->create(); 14 | }); 15 | 16 | afterEach(function () { 17 | Schema::dropAllTables(); 18 | }); 19 | 20 | test('it can perform raw column selection', function () { 21 | $result = DB::table('users') 22 | ->select(DB::raw('count (*) as user_count')) 23 | ->get(); 24 | 25 | expect($result->first()->user_count)->toBe(3); 26 | })->group('RawExpressionsTest', 'QueryBuilder', 'FeatureTest'); 27 | 28 | test('it can perform selectRaw query', function () { 29 | $result = DB::table('users') 30 | ->selectRaw('id, id * ? as multiplied_id', [3]) 31 | ->orderBy('id') 32 | ->get(); 33 | 34 | expect($result->count())->toBe(3) 35 | ->and($result[0]->multiplied_id)->toBe((int) $this->user1->getKey() * 3) 36 | ->and($result[1]->multiplied_id)->toBe((int) $this->user2->getKey() * 3) 37 | ->and($result[2]->multiplied_id)->toBe((int) $this->user3->getKey() * 3); 38 | })->group('RawExpressionsTest', 'QueryBuilder', 'FeatureTest'); 39 | 40 | test('it can perform whereRaw query', function () { 41 | $newUser = User::factory()->create([ 42 | 'created_at' => Carbon::parse('1945-08-17 00:00:00'), 43 | ]); 44 | 45 | $selectedUser = DB::table('users') 46 | ->whereRaw("strftime('%Y-%m', created_at) = '1945-08'") 47 | ->first(); 48 | 49 | expect($selectedUser)->not->toBeNull() 50 | ->and($selectedUser->id)->toBe($newUser->id) 51 | ->and($selectedUser->name)->toBe($newUser->name); 52 | })->group('RawExpressionsTest', 'QueryBuilder', 'FeatureTest'); 53 | 54 | test('it can perform orderByRaw query', function () { 55 | $newUser = User::factory()->create([ 56 | 'created_at' => Carbon::parse('1945-08-17 00:00:00'), 57 | ]); 58 | 59 | $result = DB::table('users') 60 | ->orderByRaw('updated_at - created_at DESC') 61 | ->first(); 62 | 63 | expect($result)->not->toBeNull() 64 | ->and($result->id)->toBe($newUser->id) 65 | ->and($result->name)->toBe($newUser->name); 66 | })->group('RawExpressionsTest', 'QueryBuilder', 'FeatureTest'); 67 | -------------------------------------------------------------------------------- /tests/Feature/QueryBuilder/SelectStatementsTest.php: -------------------------------------------------------------------------------- 1 | user1 = User::factory()->create(); 11 | $this->user2 = User::factory()->create(); 12 | $this->user3 = User::factory()->create(); 13 | }); 14 | 15 | afterEach(function () { 16 | Schema::dropAllTables(); 17 | }); 18 | 19 | test('it can specify a select clause', function () { 20 | $users = DB::table('users')->select('name', 'email as user_email')->get(); 21 | 22 | expect($users)->toHaveCount(3) 23 | ->and($users[0]->name)->toEqual($this->user1->name) 24 | ->and($users[0]->user_email)->toEqual($this->user1->email) 25 | ->and($users[1]->name)->toEqual($this->user2->name) 26 | ->and($users[1]->user_email)->toEqual($this->user2->email) 27 | ->and($users[2]->name)->toEqual($this->user3->name) 28 | ->and($users[2]->user_email)->toEqual($this->user3->email); 29 | })->group('SelectStatementsTest', 'QueryBuilder', 'FeatureTest'); 30 | 31 | test('it can return distinct result', function () { 32 | $newUser = User::factory()->create([ 33 | 'name' => $this->user2->name, 34 | ]); 35 | 36 | $users = DB::table('users')->select('name')->distinct()->get(); 37 | 38 | expect($users)->toHaveCount(3) 39 | ->and(DB::table('users')->count())->toEqual(4) 40 | ->and($users[0]->name)->toEqual($this->user1->name) 41 | ->and($users[1]->name)->toEqual($this->user2->name) 42 | ->and($users[2]->name)->toEqual($this->user3->name); 43 | })->group('SelectStatementsTest', 'QueryBuilder', 'FeatureTest'); 44 | 45 | test('it can add another column selection', function () { 46 | $query = DB::table('users')->select('name'); 47 | 48 | $users = $query->addSelect('email')->get(); 49 | 50 | expect($users)->toHaveCount(3) 51 | ->and($users[0]->name)->toEqual($this->user1->name) 52 | ->and($users[0]->email)->toEqual($this->user1->email) 53 | ->and($users[1]->name)->toEqual($this->user2->name) 54 | ->and($users[1]->email)->toEqual($this->user2->email) 55 | ->and($users[2]->name)->toEqual($this->user3->name) 56 | ->and($users[2]->email)->toEqual($this->user3->email); 57 | })->group('SelectStatementsTest', 'QueryBuilder', 'FeatureTest'); 58 | -------------------------------------------------------------------------------- /tests/Feature/QueryBuilder/UpdateStatementsTest.php: -------------------------------------------------------------------------------- 1 | user = User::factory()->create(); 11 | }); 12 | 13 | afterEach(function () { 14 | Schema::dropAllTables(); 15 | }); 16 | 17 | test('it can update the user\'s email address', function () { 18 | DB::table('users') 19 | ->where('id', $this->user->getKey()) 20 | ->update([ 21 | 'email' => 'richan.fongdasen@gmail.com', 22 | ]); 23 | 24 | $updatedUser = DB::table('users')->find($this->user->getKey()); 25 | 26 | expect($updatedUser->email)->toBe('richan.fongdasen@gmail.com'); 27 | })->group('UpdateStatementsTest', 'QueryBuilder', 'FeatureTest'); 28 | 29 | test('it can insert a new record with updateOrInsert method', function () { 30 | DB::table('users') 31 | ->updateOrInsert( 32 | [ 33 | 'name' => 'John Doe', 34 | 'email' => 'john.doe@gmail.com', 35 | ], 36 | [ 37 | 'remember_token' => '1234567890', 38 | ] 39 | ); 40 | 41 | $user = DB::table('users') 42 | ->where('name', 'John Doe') 43 | ->where('email', 'john.doe@gmail.com') 44 | ->first(); 45 | 46 | expect($user->id)->toBe(2) 47 | ->and($user->remember_token)->toBe('1234567890'); 48 | })->group('UpdateStatementsTest', 'QueryBuilder', 'FeatureTest'); 49 | 50 | test('it can update an existing record with updateOrInsert method', function () { 51 | DB::table('users') 52 | ->updateOrInsert( 53 | [ 54 | 'name' => $this->user->name, 55 | 'email' => $this->user->email, 56 | ], 57 | [ 58 | 'remember_token' => '1234567890', 59 | ] 60 | ); 61 | 62 | $updatedUser = DB::table('users')->find($this->user->getKey()); 63 | 64 | expect(DB::hasModifiedRecords())->toBeTrue() 65 | ->and(DB::table('users')->count())->toBe(1) 66 | ->and($updatedUser->remember_token)->toBe('1234567890'); 67 | })->group('UpdateStatementsTest', 'QueryBuilder', 'FeatureTest'); 68 | 69 | test('it can increment and decrement a column value', function () { 70 | DB::table('users')->increment('id', 5); 71 | 72 | expect(DB::table('users')->first()->id)->toBe(6); 73 | 74 | DB::table('users')->decrement('id', 3); 75 | 76 | expect(DB::table('users')->first()->id)->toBe(3); 77 | })->group('UpdateStatementsTest', 'QueryBuilder', 'FeatureTest'); 78 | -------------------------------------------------------------------------------- /tests/Fixtures/Factories/CommentFactory.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class CommentFactory extends Factory 16 | { 17 | /** 18 | * The name of the factory's corresponding model. 19 | * 20 | * @var string 21 | */ 22 | protected $model = Comment::class; 23 | 24 | /** 25 | * Define the model's default state. 26 | * 27 | * @return array 28 | */ 29 | public function definition(): array 30 | { 31 | return [ 32 | 'user_id' => User::factory(), 33 | 'post_id' => Post::factory(), 34 | 'content' => $this->faker->paragraph(), // Generates random text content for comments 35 | ]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Fixtures/Factories/DeploymentFactory.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | public function definition(): array 20 | { 21 | return [ 22 | 'environment_id' => Environment::factory(), 23 | 'commit_hash' => sha1(Str::random(40)), 24 | ]; 25 | } 26 | 27 | public function modelName(): string 28 | { 29 | return Deployment::class; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Fixtures/Factories/EnvironmentFactory.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | public function definition(): array 19 | { 20 | return [ 21 | 'project_id' => Project::factory(), 22 | 'name' => fake()->text(rand(5, 10)), 23 | ]; 24 | } 25 | 26 | public function modelName(): string 27 | { 28 | return Environment::class; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Fixtures/Factories/PhoneFactory.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | public function definition(): array 19 | { 20 | return [ 21 | 'user_id' => User::factory(), 22 | 'phone_number' => fake()->phoneNumber(), 23 | ]; 24 | } 25 | 26 | public function modelName(): string 27 | { 28 | return Phone::class; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Fixtures/Factories/PostFactory.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | public function definition(): array 19 | { 20 | return [ 21 | 'user_id' => User::factory(), 22 | 'title' => fake()->text(rand(10, 30)), 23 | 'content' => fake()->paragraph(), 24 | ]; 25 | } 26 | 27 | public function modelName(): string 28 | { 29 | return Post::class; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Fixtures/Factories/ProjectFactory.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | public function definition(): array 18 | { 19 | return [ 20 | 'name' => fake()->text(rand(5, 10)), 21 | ]; 22 | } 23 | 24 | public function modelName(): string 25 | { 26 | return Project::class; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Fixtures/Factories/RoleFactory.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | public function definition(): array 18 | { 19 | return [ 20 | 'name' => fake()->text(rand(5, 10)), 21 | ]; 22 | } 23 | 24 | public function modelName(): string 25 | { 26 | return Role::class; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Fixtures/Factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | public function definition(): array 19 | { 20 | return [ 21 | 'name' => fake()->name(), 22 | 'email' => fake()->unique()->safeEmail(), 23 | 'email_verified_at' => now(), 24 | 'remember_token' => Str::random(10), 25 | ]; 26 | } 27 | 28 | public function modelName(): string 29 | { 30 | return User::class; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Fixtures/Migrations/create_comments_table.php: -------------------------------------------------------------------------------- 1 | id(); 17 | $table->foreignId('user_id')->constrained()->cascadeOnDelete(); 18 | $table->foreignId('post_id')->constrained()->cascadeOnDelete(); 19 | $table->text('content'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::dropIfExists('comments'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /tests/Fixtures/Migrations/create_deployments_table.php: -------------------------------------------------------------------------------- 1 | id(); 15 | $table->unsignedBigInteger('environment_id'); 16 | $table->string('commit_hash'); 17 | $table->timestamps(); 18 | 19 | $table->foreign('environment_id') 20 | ->references('id') 21 | ->on('environments') 22 | ->onDelete('cascade'); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('deployments'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /tests/Fixtures/Migrations/create_environments_table.php: -------------------------------------------------------------------------------- 1 | id(); 15 | $table->unsignedBigInteger('project_id'); 16 | $table->string('name'); 17 | $table->timestamps(); 18 | 19 | $table->foreign('project_id') 20 | ->references('id') 21 | ->on('projects') 22 | ->onDelete('cascade'); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('environments'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /tests/Fixtures/Migrations/create_phones_table.php: -------------------------------------------------------------------------------- 1 | id(); 15 | $table->unsignedBigInteger('user_id'); 16 | $table->string('phone_number'); 17 | $table->timestamps(); 18 | 19 | $table->foreign('user_id') 20 | ->references('id') 21 | ->on('users') 22 | ->onDelete('cascade'); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('phones'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /tests/Fixtures/Migrations/create_posts_table.php: -------------------------------------------------------------------------------- 1 | id(); 15 | $table->unsignedBigInteger('user_id'); 16 | $table->string('title'); 17 | $table->text('content'); 18 | $table->timestamps(); 19 | 20 | $table->foreign('user_id') 21 | ->references('id') 22 | ->on('users') 23 | ->onDelete('cascade'); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('posts'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /tests/Fixtures/Migrations/create_projects_table.php: -------------------------------------------------------------------------------- 1 | id(); 15 | $table->string('name'); 16 | $table->timestamps(); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | */ 23 | public function down(): void 24 | { 25 | Schema::dropIfExists('projects'); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /tests/Fixtures/Migrations/create_roles_table.php: -------------------------------------------------------------------------------- 1 | id(); 15 | $table->string('name'); 16 | $table->timestamps(); 17 | $table->softDeletes(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('roles'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /tests/Fixtures/Migrations/create_user_roles_table.php: -------------------------------------------------------------------------------- 1 | id(); 15 | $table->unsignedBigInteger('user_id'); 16 | $table->unsignedBigInteger('role_id'); 17 | 18 | $table->foreign('user_id') 19 | ->references('id') 20 | ->on('users') 21 | ->onDelete('cascade'); 22 | 23 | $table->foreign('role_id') 24 | ->references('id') 25 | ->on('roles') 26 | ->onDelete('cascade'); 27 | }); 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | */ 33 | public function down(): void 34 | { 35 | Schema::dropIfExists('user_roles'); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /tests/Fixtures/Migrations/create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 15 | $table->string('name'); 16 | $table->string('email')->unique(); 17 | $table->timestamp('email_verified_at')->nullable(); 18 | $table->rememberToken(); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | */ 26 | public function down(): void 27 | { 28 | Schema::dropIfExists('users'); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /tests/Fixtures/Models/Comment.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | protected $fillable = [ 21 | 'user_id', 22 | 'post_id', 23 | 'content', 24 | ]; 25 | 26 | /** 27 | * Get the attributes that should be cast. 28 | * 29 | * @return array 30 | */ 31 | protected function casts(): array 32 | { 33 | return [ 34 | 'id' => 'integer', 35 | 'user_id' => 'integer', 36 | 'post_id' => 'integer', 37 | ]; 38 | } 39 | 40 | /** 41 | * Get the user who created the comment. 42 | */ 43 | public function user(): BelongsTo 44 | { 45 | return $this->belongsTo(User::class); 46 | } 47 | 48 | /** 49 | * Get the post the comment belongs to. 50 | */ 51 | public function post(): BelongsTo 52 | { 53 | return $this->belongsTo(Post::class); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Fixtures/Models/Deployment.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | protected $fillable = [ 21 | 'commit_hash', 22 | ]; 23 | 24 | /** 25 | * Get the attributes that should be cast. 26 | * 27 | * @return array 28 | */ 29 | protected function casts(): array 30 | { 31 | return [ 32 | 'id' => 'integer', 33 | 'environment_id' => 'integer', 34 | ]; 35 | } 36 | 37 | public function environment(): BelongsTo 38 | { 39 | return $this->belongsTo(Environment::class); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Fixtures/Models/Environment.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | protected $fillable = [ 22 | 'name', 23 | ]; 24 | 25 | /** 26 | * Get the attributes that should be cast. 27 | * 28 | * @return array 29 | */ 30 | protected function casts(): array 31 | { 32 | return [ 33 | 'id' => 'integer', 34 | 'project_id' => 'integer', 35 | ]; 36 | } 37 | 38 | public function deployments(): HasMany 39 | { 40 | return $this->hasMany(Deployment::class); 41 | } 42 | 43 | public function project(): BelongsTo 44 | { 45 | return $this->belongsTo(Project::class); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Fixtures/Models/Phone.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | protected $fillable = [ 21 | 'user_id', 22 | 'phone_number', 23 | ]; 24 | 25 | /** 26 | * Get the attributes that should be cast. 27 | * 28 | * @return array 29 | */ 30 | protected function casts(): array 31 | { 32 | return [ 33 | 'id' => 'integer', 34 | 'user_id' => 'integer', 35 | ]; 36 | } 37 | 38 | public function user(): BelongsTo 39 | { 40 | return $this->belongsTo(User::class); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Fixtures/Models/Post.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | protected $fillable = [ 22 | 'user_id', 23 | 'title', 24 | 'content', 25 | ]; 26 | 27 | /** 28 | * Get the attributes that should be cast. 29 | * 30 | * @return array 31 | */ 32 | protected function casts(): array 33 | { 34 | return [ 35 | 'id' => 'integer', 36 | 'user_id' => 'integer', 37 | ]; 38 | } 39 | 40 | public function comments(): HasMany 41 | { 42 | return $this->hasMany(Comment::class); 43 | } 44 | 45 | public function user(): BelongsTo 46 | { 47 | return $this->belongsTo(User::class); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/Fixtures/Models/Project.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | protected $fillable = [ 22 | 'name', 23 | ]; 24 | 25 | /** 26 | * Get the attributes that should be cast. 27 | * 28 | * @return array 29 | */ 30 | protected function casts(): array 31 | { 32 | return [ 33 | 'id' => 'integer', 34 | ]; 35 | } 36 | 37 | public function environments(): HasMany 38 | { 39 | return $this->hasMany(Environment::class); 40 | } 41 | 42 | public function deployments(): HasManyThrough 43 | { 44 | return $this->hasManyThrough(Deployment::class, Environment::class); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Fixtures/Models/Role.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | protected $fillable = [ 23 | 'name', 24 | ]; 25 | 26 | /** 27 | * Get the attributes that should be cast. 28 | * 29 | * @return array 30 | */ 31 | protected function casts(): array 32 | { 33 | return [ 34 | 'id' => 'integer', 35 | ]; 36 | } 37 | 38 | public function users(): BelongsToMany 39 | { 40 | return $this->belongsToMany(User::class, 'user_roles'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Fixtures/Models/User.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | protected $fillable = [ 26 | 'name', 27 | 'email', 28 | ]; 29 | 30 | /** 31 | * The attributes that should be hidden for serialization. 32 | * 33 | * @var array 34 | */ 35 | protected $hidden = [ 36 | 'remember_token', 37 | ]; 38 | 39 | /** 40 | * Get the attributes that should be cast. 41 | * 42 | * @return array 43 | */ 44 | protected function casts(): array 45 | { 46 | return [ 47 | 'id' => 'integer', 48 | 'email_verified_at' => 'datetime', 49 | ]; 50 | } 51 | 52 | public function comments(): HasManyThrough 53 | { 54 | return $this->hasManyThrough(Comment::class, Post::class); 55 | } 56 | 57 | public function phone(): HasOne 58 | { 59 | return $this->hasOne(Phone::class); 60 | } 61 | 62 | public function posts(): HasMany 63 | { 64 | return $this->hasMany(Post::class); 65 | } 66 | 67 | public function roles(): BelongsToMany 68 | { 69 | return $this->belongsToMany(Role::class, 'user_roles'); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/Pest.php: -------------------------------------------------------------------------------- 1 | in(__DIR__); 10 | 11 | function migrateTables(...$tableNames): void 12 | { 13 | collect($tableNames) 14 | ->each(function (string $tableName) { 15 | $migration = include __DIR__ . '/Fixtures/Migrations/create_' . Str::snake(Str::plural($tableName)) . '_table.php'; 16 | $migration->up(); 17 | }); 18 | } 19 | 20 | function test_database_path(string $path): string 21 | { 22 | return __DIR__ . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . $path; 23 | } 24 | 25 | function clearDirectory(): void 26 | { 27 | $path = __DIR__ . DIRECTORY_SEPARATOR . 'database'; 28 | $files = File::allFiles($path); 29 | 30 | // Delete all files 31 | foreach ($files as $file) { 32 | File::delete($file); 33 | } 34 | } 35 | 36 | function shouldSkipTests() 37 | { 38 | return false; 39 | } 40 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | 'Libsql\\Laravel\\Tests\\Fixtures\\Factories\\' . class_basename($modelName) . 'Factory' 17 | ); 18 | } 19 | 20 | protected function getPackageProviders($app) 21 | { 22 | return [ 23 | LibsqlServiceProvider::class, 24 | ]; 25 | } 26 | 27 | public function getEnvironmentSetUp($app) 28 | { 29 | config()->set('database.connections', [ 30 | // In-Memory Connection 31 | 'libsql' => [ 32 | 'driver' => 'libsql', 33 | 'url' => '', 34 | 'password' => '', 35 | 'database' => ':memory:', 36 | 'prefix' => '', 37 | ], 38 | // Remote Connection 39 | 'otherdb' => [ 40 | 'driver' => 'libsql', 41 | 'database' => '', 42 | 'prefix' => '', 43 | 'url' => 'http://127.0.0.1:8081', 44 | // Replace the token with yours 45 | 'password' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpYXQiOjE3MzY2MzU1MTUsIm5iZiI6MTczNjYzNTUxNSwiZXhwIjoxNzM3MjQwMzE1LCJqdGkiOiJkYjEifQ.5sm4FN4PosAJ5h9wLay6q3ryAxbGRGuETU1A3F_Tr3WXpAEnr98tmAa92qcpZz_YZN0T_h4RqjGlEMgrSwIJAQ', 46 | ], 47 | // Embedded Replica 48 | 'otherdb2' => [ 49 | 'driver' => 'libsql', 50 | 'database' => test_database_path('otherdb2.db'), 51 | 'prefix' => '', 52 | 'url' => 'http://127.0.0.1:8081', 53 | // Replace the token with yours 54 | 'password' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpYXQiOjE3MzY2MzU1MTUsIm5iZiI6MTczNjYzNTUxNSwiZXhwIjoxNzM3MjQwMzE1LCJqdGkiOiJkYjEifQ.5sm4FN4PosAJ5h9wLay6q3ryAxbGRGuETU1A3F_Tr3WXpAEnr98tmAa92qcpZz_YZN0T_h4RqjGlEMgrSwIJAQ', 55 | ], 56 | ]); 57 | config()->set('database.default', 'libsql'); 58 | config()->set('queue.default', 'sync'); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Unit/Database/LibsqlConnectionTest.php: -------------------------------------------------------------------------------- 1 | enableQueryLog(); 7 | 8 | expect(DB::connection('libsql')->logging())->toBeTrue(); 9 | })->group('LibsqlConnectionTest', 'UnitTest'); 10 | 11 | test('it can disable query logging feature', function () { 12 | DB::connection('libsql')->disableQueryLog(); 13 | 14 | expect(DB::connection('libsql')->logging())->toBeFalse(); 15 | })->group('LibsqlConnectionTest', 'UnitTest'); 16 | 17 | test('it can get the query log', function () { 18 | DB::connection('libsql')->enableQueryLog(); 19 | 20 | $log = DB::connection('libsql')->getQueryLog(); 21 | 22 | expect($log)->toBeArray() 23 | ->and($log)->toHaveCount(0); 24 | })->group('LibsqlConnectionTest', 'UnitTest'); 25 | 26 | test('it can flush the query log', function () { 27 | DB::connection('libsql')->enableQueryLog(); 28 | 29 | DB::connection('libsql')->flushQueryLog(); 30 | 31 | $log = DB::connection('libsql')->getQueryLog(); 32 | 33 | expect($log)->toBeArray() 34 | ->and($log)->toHaveCount(0); 35 | })->group('LibsqlConnectionTest', 'UnitTest'); 36 | -------------------------------------------------------------------------------- /tests/Unit/Database/LibsqlPDOTest.php: -------------------------------------------------------------------------------- 1 | pdo = DB::connection()->getPdo(); 7 | }); 8 | 9 | test('it can manage the last insert id value', function () { 10 | $this->pdo->setLastInsertId(value: 123); 11 | 12 | expect($this->pdo->lastInsertId())->toBe('123'); 13 | })->group('LibsqlPDOTest', 'UnitTest'); 14 | -------------------------------------------------------------------------------- /tests/Unit/Database/LibsqlSchemaBuilderTest.php: -------------------------------------------------------------------------------- 1 | throws(FeatureNotSupportedException::class)->group('LibsqlSchemaBuilderTest', 'UnitTest'); 9 | 10 | test('it raises exception on dropping database.', function () { 11 | Schema::dropDatabaseIfExists('test'); 12 | })->throws(FeatureNotSupportedException::class)->group('LibsqlSchemaBuilderTest', 'UnitTest'); 13 | -------------------------------------------------------------------------------- /tests/database/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tursodatabase/libsql-laravel/df8b8d3a1d43d924945041e9dd49049713d9f0ca/tests/database/.gitkeep --------------------------------------------------------------------------------