├── .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 |
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 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
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 | 
101 |
102 |
103 |
104 |
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
--------------------------------------------------------------------------------