Initialize Nest databases library.
This commit is contained in:
commit
0f67e50cbe
63 changed files with 5446 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# IDEA
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
# Composer
|
||||||
|
vendor/
|
52
composer.json
Normal file
52
composer.json
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"name": "nest/database",
|
||||||
|
"description": "Nest databases service.",
|
||||||
|
"type": "library",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Madeorsk",
|
||||||
|
"email": "madeorsk@protonmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Nest\\Database\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "vcs",
|
||||||
|
"url": "https://code.zeptotech.net/Nest/Core"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "vcs",
|
||||||
|
"url": "https://code.zeptotech.net/Nest/Events"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "vcs",
|
||||||
|
"url": "https://code.zeptotech.net/Nest/Cli"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "vcs",
|
||||||
|
"url": "https://code.zeptotech.net/Nest/Model"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "vcs",
|
||||||
|
"url": "https://code.zeptotech.net/Nest/Configuration"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": "^8.3",
|
||||||
|
"ext-pdo": "*",
|
||||||
|
"nest/core": "dev-main",
|
||||||
|
"nest/events": "dev-main",
|
||||||
|
"nest/cli": "dev-main",
|
||||||
|
"nest/model": "dev-main",
|
||||||
|
"nesbot/carbon": "^3.0",
|
||||||
|
"symfony/uid": "^7.1"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"nest/configuration": "dev-main"
|
||||||
|
}
|
||||||
|
}
|
893
composer.lock
generated
Normal file
893
composer.lock
generated
Normal file
|
@ -0,0 +1,893 @@
|
||||||
|
{
|
||||||
|
"_readme": [
|
||||||
|
"This file locks the dependencies of your project to a known state",
|
||||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
|
"This file is @generated automatically"
|
||||||
|
],
|
||||||
|
"content-hash": "1870dee719d71a9f4147598dd1dc7fcc",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "carbonphp/carbon-doctrine-types",
|
||||||
|
"version": "3.2.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/CarbonPHP/carbon-doctrine-types.git",
|
||||||
|
"reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d",
|
||||||
|
"reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"doctrine/dbal": "<4.0.0 || >=5.0.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"doctrine/dbal": "^4.0.0",
|
||||||
|
"nesbot/carbon": "^2.71.0 || ^3.0.0",
|
||||||
|
"phpunit/phpunit": "^10.3"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Carbon\\Doctrine\\": "src/Carbon/Doctrine/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "KyleKatarn",
|
||||||
|
"email": "kylekatarnls@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Types to use Carbon in Doctrine",
|
||||||
|
"keywords": [
|
||||||
|
"carbon",
|
||||||
|
"date",
|
||||||
|
"datetime",
|
||||||
|
"doctrine",
|
||||||
|
"time"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues",
|
||||||
|
"source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/kylekatarnls",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://opencollective.com/Carbon",
|
||||||
|
"type": "open_collective"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/nesbot/carbon",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-02-09T16:56:22+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nesbot/carbon",
|
||||||
|
"version": "3.8.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/briannesbitt/Carbon.git",
|
||||||
|
"reference": "e1268cdbc486d97ce23fef2c666dc3c6b6de9947"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/e1268cdbc486d97ce23fef2c666dc3c6b6de9947",
|
||||||
|
"reference": "e1268cdbc486d97ce23fef2c666dc3c6b6de9947",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"carbonphp/carbon-doctrine-types": "<100.0",
|
||||||
|
"ext-json": "*",
|
||||||
|
"php": "^8.1",
|
||||||
|
"psr/clock": "^1.0",
|
||||||
|
"symfony/clock": "^6.3 || ^7.0",
|
||||||
|
"symfony/polyfill-mbstring": "^1.0",
|
||||||
|
"symfony/translation": "^4.4.18 || ^5.2.1|| ^6.0 || ^7.0"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"psr/clock-implementation": "1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"doctrine/dbal": "^3.6.3 || ^4.0",
|
||||||
|
"doctrine/orm": "^2.15.2 || ^3.0",
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.57.2",
|
||||||
|
"kylekatarnls/multi-tester": "^2.5.3",
|
||||||
|
"ondrejmirtes/better-reflection": "^6.25.0.4",
|
||||||
|
"phpmd/phpmd": "^2.15.0",
|
||||||
|
"phpstan/extension-installer": "^1.3.1",
|
||||||
|
"phpstan/phpstan": "^1.11.2",
|
||||||
|
"phpunit/phpunit": "^10.5.20",
|
||||||
|
"squizlabs/php_codesniffer": "^3.9.0"
|
||||||
|
},
|
||||||
|
"bin": [
|
||||||
|
"bin/carbon"
|
||||||
|
],
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "3.x-dev",
|
||||||
|
"dev-2.x": "2.x-dev"
|
||||||
|
},
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Carbon\\Laravel\\ServiceProvider"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"phpstan": {
|
||||||
|
"includes": [
|
||||||
|
"extension.neon"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Carbon\\": "src/Carbon/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Brian Nesbitt",
|
||||||
|
"email": "brian@nesbot.com",
|
||||||
|
"homepage": "https://markido.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "kylekatarnls",
|
||||||
|
"homepage": "https://github.com/kylekatarnls"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "An API extension for DateTime that supports 281 different languages.",
|
||||||
|
"homepage": "https://carbon.nesbot.com",
|
||||||
|
"keywords": [
|
||||||
|
"date",
|
||||||
|
"datetime",
|
||||||
|
"time"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"docs": "https://carbon.nesbot.com/docs",
|
||||||
|
"issues": "https://github.com/briannesbitt/Carbon/issues",
|
||||||
|
"source": "https://github.com/briannesbitt/Carbon"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/sponsors/kylekatarnls",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://opencollective.com/Carbon#sponsor",
|
||||||
|
"type": "opencollective"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-11-07T17:46:48+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nest/cli",
|
||||||
|
"version": "dev-main",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://code.zeptotech.net/Nest/Cli",
|
||||||
|
"reference": "1163a270b415b4e64fbb60c5f7fb1bf94ca4870e"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"nest/core": "dev-main",
|
||||||
|
"php": "^8.3"
|
||||||
|
},
|
||||||
|
"default-branch": true,
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Nest\\Cli\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Madeorsk",
|
||||||
|
"email": "madeorsk@protonmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Nest CLI service and engine.",
|
||||||
|
"time": "2024-11-08T14:56:14+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nest/core",
|
||||||
|
"version": "dev-main",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://code.zeptotech.net/Nest/Core",
|
||||||
|
"reference": "22296b8d7c8b528089189a8d9500395424cb6468"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.3"
|
||||||
|
},
|
||||||
|
"default-branch": true,
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Nest\\": "src/"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src/Utils/Array.php",
|
||||||
|
"src/Utils/Paths.php",
|
||||||
|
"src/Utils/Reflection.php",
|
||||||
|
"src/Utils/String.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Madeorsk",
|
||||||
|
"email": "madeorsk@protonmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Nest framework core.",
|
||||||
|
"time": "2024-11-08T11:12:38+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nest/events",
|
||||||
|
"version": "dev-main",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://code.zeptotech.net/Nest/Events",
|
||||||
|
"reference": "fcb3e114a7f9bdbcfd90f9283dd2c2588da09590"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"nest/core": "dev-main",
|
||||||
|
"php": "^8.3"
|
||||||
|
},
|
||||||
|
"default-branch": true,
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Nest\\Events\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Madeorsk",
|
||||||
|
"email": "madeorsk@protonmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Nest events implementation.",
|
||||||
|
"time": "2024-11-08T14:59:15+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/clock",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/clock.git",
|
||||||
|
"reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d",
|
||||||
|
"reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.0 || ^8.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Clock\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "https://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for reading the clock.",
|
||||||
|
"homepage": "https://github.com/php-fig/clock",
|
||||||
|
"keywords": [
|
||||||
|
"clock",
|
||||||
|
"now",
|
||||||
|
"psr",
|
||||||
|
"psr-20",
|
||||||
|
"time"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/php-fig/clock/issues",
|
||||||
|
"source": "https://github.com/php-fig/clock/tree/1.0.0"
|
||||||
|
},
|
||||||
|
"time": "2022-11-25T14:36:26+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/clock",
|
||||||
|
"version": "v7.1.6",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/clock.git",
|
||||||
|
"reference": "97bebc53548684c17ed696bc8af016880f0f098d"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/clock/zipball/97bebc53548684c17ed696bc8af016880f0f098d",
|
||||||
|
"reference": "97bebc53548684c17ed696bc8af016880f0f098d",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.2",
|
||||||
|
"psr/clock": "^1.0",
|
||||||
|
"symfony/polyfill-php83": "^1.28"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"psr/clock-implementation": "1.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"Resources/now.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\Clock\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Nicolas Grekas",
|
||||||
|
"email": "p@tchwork.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Decouples applications from the system clock",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"clock",
|
||||||
|
"psr20",
|
||||||
|
"time"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/clock/tree/v7.1.6"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-09-25T14:20:29+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-mbstring",
|
||||||
|
"version": "v1.31.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||||
|
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
|
||||||
|
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"ext-mbstring": "*"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-mbstring": "For best performance"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"thanks": {
|
||||||
|
"name": "symfony/polyfill",
|
||||||
|
"url": "https://github.com/symfony/polyfill"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Nicolas Grekas",
|
||||||
|
"email": "p@tchwork.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony polyfill for the Mbstring extension",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"mbstring",
|
||||||
|
"polyfill",
|
||||||
|
"portable",
|
||||||
|
"shim"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-09-09T11:45:10+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-php83",
|
||||||
|
"version": "v1.31.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-php83.git",
|
||||||
|
"reference": "2fb86d65e2d424369ad2905e83b236a8805ba491"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491",
|
||||||
|
"reference": "2fb86d65e2d424369ad2905e83b236a8805ba491",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"thanks": {
|
||||||
|
"name": "symfony/polyfill",
|
||||||
|
"url": "https://github.com/symfony/polyfill"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Php83\\": ""
|
||||||
|
},
|
||||||
|
"classmap": [
|
||||||
|
"Resources/stubs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Nicolas Grekas",
|
||||||
|
"email": "p@tchwork.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"polyfill",
|
||||||
|
"portable",
|
||||||
|
"shim"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-09-09T11:45:10+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-uuid",
|
||||||
|
"version": "v1.31.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-uuid.git",
|
||||||
|
"reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2",
|
||||||
|
"reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"ext-uuid": "*"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-uuid": "For best performance"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"thanks": {
|
||||||
|
"name": "symfony/polyfill",
|
||||||
|
"url": "https://github.com/symfony/polyfill"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Uuid\\": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Grégoire Pineau",
|
||||||
|
"email": "lyrixx@lyrixx.info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony polyfill for uuid functions",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"polyfill",
|
||||||
|
"portable",
|
||||||
|
"uuid"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-09-09T11:45:10+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/translation",
|
||||||
|
"version": "v7.1.6",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/translation.git",
|
||||||
|
"reference": "b9f72ab14efdb6b772f85041fa12f820dee8d55f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/translation/zipball/b9f72ab14efdb6b772f85041fa12f820dee8d55f",
|
||||||
|
"reference": "b9f72ab14efdb6b772f85041fa12f820dee8d55f",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.2",
|
||||||
|
"symfony/polyfill-mbstring": "~1.0",
|
||||||
|
"symfony/translation-contracts": "^2.5|^3.0"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/config": "<6.4",
|
||||||
|
"symfony/console": "<6.4",
|
||||||
|
"symfony/dependency-injection": "<6.4",
|
||||||
|
"symfony/http-client-contracts": "<2.5",
|
||||||
|
"symfony/http-kernel": "<6.4",
|
||||||
|
"symfony/service-contracts": "<2.5",
|
||||||
|
"symfony/twig-bundle": "<6.4",
|
||||||
|
"symfony/yaml": "<6.4"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"symfony/translation-implementation": "2.3|3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"nikic/php-parser": "^4.18|^5.0",
|
||||||
|
"psr/log": "^1|^2|^3",
|
||||||
|
"symfony/config": "^6.4|^7.0",
|
||||||
|
"symfony/console": "^6.4|^7.0",
|
||||||
|
"symfony/dependency-injection": "^6.4|^7.0",
|
||||||
|
"symfony/finder": "^6.4|^7.0",
|
||||||
|
"symfony/http-client-contracts": "^2.5|^3.0",
|
||||||
|
"symfony/http-kernel": "^6.4|^7.0",
|
||||||
|
"symfony/intl": "^6.4|^7.0",
|
||||||
|
"symfony/polyfill-intl-icu": "^1.21",
|
||||||
|
"symfony/routing": "^6.4|^7.0",
|
||||||
|
"symfony/service-contracts": "^2.5|^3",
|
||||||
|
"symfony/yaml": "^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"Resources/functions.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\Translation\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Provides tools to internationalize your application",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/translation/tree/v7.1.6"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-09-28T12:35:13+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/translation-contracts",
|
||||||
|
"version": "v3.5.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/translation-contracts.git",
|
||||||
|
"reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a",
|
||||||
|
"reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "3.5-dev"
|
||||||
|
},
|
||||||
|
"thanks": {
|
||||||
|
"name": "symfony/contracts",
|
||||||
|
"url": "https://github.com/symfony/contracts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Contracts\\Translation\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Test/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Nicolas Grekas",
|
||||||
|
"email": "p@tchwork.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Generic abstractions related to translation",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"abstractions",
|
||||||
|
"contracts",
|
||||||
|
"decoupling",
|
||||||
|
"interfaces",
|
||||||
|
"interoperability",
|
||||||
|
"standards"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/translation-contracts/tree/v3.5.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-04-18T09:32:20+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/uid",
|
||||||
|
"version": "v7.1.6",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/uid.git",
|
||||||
|
"reference": "65befb3bb2d503bbffbd08c815aa38b472999917"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/uid/zipball/65befb3bb2d503bbffbd08c815aa38b472999917",
|
||||||
|
"reference": "65befb3bb2d503bbffbd08c815aa38b472999917",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.2",
|
||||||
|
"symfony/polyfill-uuid": "^1.15"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/console": "^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\Uid\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Grégoire Pineau",
|
||||||
|
"email": "lyrixx@lyrixx.info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Nicolas Grekas",
|
||||||
|
"email": "p@tchwork.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Provides an object-oriented API to generate and represent UIDs",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"UID",
|
||||||
|
"ulid",
|
||||||
|
"uuid"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/uid/tree/v7.1.6"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-09-25T14:20:29+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": {
|
||||||
|
"nest/cli": 20,
|
||||||
|
"nest/core": 20,
|
||||||
|
"nest/events": 20
|
||||||
|
},
|
||||||
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": {
|
||||||
|
"php": "^8.3",
|
||||||
|
"ext-pdo": "*"
|
||||||
|
},
|
||||||
|
"platform-dev": {},
|
||||||
|
"plugin-api-version": "2.6.0"
|
||||||
|
}
|
43
src/Cli/Migrations/MigrateCommand.php
Normal file
43
src/Cli/Migrations/MigrateCommand.php
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Cli\Commands\Migrations;
|
||||||
|
|
||||||
|
use Nest\Application;
|
||||||
|
use Nest\Cli\Commands\CommandHandler;
|
||||||
|
use Nest\Cli\Out;
|
||||||
|
use Nest\Database\Migrations\Migration;
|
||||||
|
use Nest\Database\Exceptions\Migrations\MigrationNotFoundException;
|
||||||
|
use Nest\Database\Exceptions\Query\MissingConditionValueException;
|
||||||
|
use Nest\Database\Exceptions\UnknownDatabaseException;
|
||||||
|
use Nest\Model\Exceptions\MissingRequiredFieldException;
|
||||||
|
use Nest\Types\Exceptions\IncompatibleTypeException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class MigrateCommand extends CommandHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws Throwable
|
||||||
|
* @throws MigrationNotFoundException
|
||||||
|
* @throws IncompatibleTypeException
|
||||||
|
* @throws UnknownDatabaseException
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
* @throws MissingRequiredFieldException
|
||||||
|
*/
|
||||||
|
public function __invoke(): void
|
||||||
|
{
|
||||||
|
Application::get()->migrations()->onMigrationStart = function (Migration $migration) {
|
||||||
|
Out::print("Executing migration V{$migration->getVersion()} ".Out::BOLD_ATTRIBUTE."{$migration->getName()}".Out::RESET_ATTRIBUTES);
|
||||||
|
echo " ".Out::ITALIC_ATTRIBUTE.$migration->getDescription().Out::RESET_ATTRIBUTES."\n\n";
|
||||||
|
};
|
||||||
|
Application::get()->migrations()->onMigrationEnd = function (Migration $migration) {
|
||||||
|
echo "\n";
|
||||||
|
Out::print("V{$migration->getVersion()}_{$migration->getName()} finished.");
|
||||||
|
echo "\n";
|
||||||
|
};
|
||||||
|
|
||||||
|
// Execute all remaining migrations.
|
||||||
|
Application::get()->migrations()->load()->migrate();
|
||||||
|
|
||||||
|
Out::success("Migrations executed successfully.");
|
||||||
|
}
|
||||||
|
}
|
43
src/Cli/Migrations/MigrateOneCommand.php
Normal file
43
src/Cli/Migrations/MigrateOneCommand.php
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Cli\Commands\Migrations;
|
||||||
|
|
||||||
|
use Nest\Application;
|
||||||
|
use Nest\Cli\Commands\CommandHandler;
|
||||||
|
use Nest\Cli\Out;
|
||||||
|
use Nest\Database\Migrations\Migration;
|
||||||
|
use Nest\Database\Exceptions\Migrations\MigrationNotFoundException;
|
||||||
|
use Nest\Database\Exceptions\Query\MissingConditionValueException;
|
||||||
|
use Nest\Database\Exceptions\UnknownDatabaseException;
|
||||||
|
use Nest\Model\Exceptions\MissingRequiredFieldException;
|
||||||
|
use Nest\Types\Exceptions\IncompatibleTypeException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class MigrateOneCommand extends CommandHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws Throwable
|
||||||
|
* @throws MigrationNotFoundException
|
||||||
|
* @throws IncompatibleTypeException
|
||||||
|
* @throws UnknownDatabaseException
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
* @throws MissingRequiredFieldException
|
||||||
|
*/
|
||||||
|
public function __invoke(string $migrationId): void
|
||||||
|
{
|
||||||
|
Application::get()->migrations()->onMigrationStart = function (Migration $migration) {
|
||||||
|
Out::print("Executing migration V{$migration->getVersion()} ".Out::BOLD_ATTRIBUTE."{$migration->getName()}".Out::RESET_ATTRIBUTES);
|
||||||
|
echo " ".Out::ITALIC_ATTRIBUTE.$migration->getDescription().Out::RESET_ATTRIBUTES."\n\n";
|
||||||
|
};
|
||||||
|
Application::get()->migrations()->onMigrationEnd = function (Migration $migration) {
|
||||||
|
echo "\n";
|
||||||
|
Out::print("V{$migration->getVersion()}_{$migration->getName()} finished.");
|
||||||
|
echo "\n";
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to execute given migration.
|
||||||
|
Application::get()->migrations()->load()->migrateOne($migrationId);
|
||||||
|
|
||||||
|
Out::success("Success.");
|
||||||
|
}
|
||||||
|
}
|
47
src/Cli/Migrations/MigrationsCommands.php
Normal file
47
src/Cli/Migrations/MigrationsCommands.php
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Cli\Commands\Migrations;
|
||||||
|
|
||||||
|
use Nest\Cli\Cli;
|
||||||
|
use Nest\Cli\Commands\CommandDefinition;
|
||||||
|
use Nest\Cli\Commands\ParameterDefinition;
|
||||||
|
use Nest\Cli\Exceptions\Command\InvalidCommandHandlerException;
|
||||||
|
use Nest\Cli\Exceptions\IncompatibleCliHandlerSubcommands;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrations commands definition manager.
|
||||||
|
*/
|
||||||
|
class MigrationsCommands
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Cli $cli The CLI in which to define migrations commands.
|
||||||
|
* @return void
|
||||||
|
* @throws IncompatibleCliHandlerSubcommands
|
||||||
|
* @throws InvalidCommandHandlerException
|
||||||
|
*/
|
||||||
|
public static function define(Cli $cli): void
|
||||||
|
{
|
||||||
|
$cli->command("migrations")
|
||||||
|
->description("Migrations manager.")
|
||||||
|
->subcommands([
|
||||||
|
"migrate" => fn (CommandDefinition $subcommand) => $subcommand
|
||||||
|
->description("Execute all remaining migrations.")
|
||||||
|
->handler(MigrateCommand::class)
|
||||||
|
,
|
||||||
|
"migrateOne" => fn (CommandDefinition $subcommand) => $subcommand
|
||||||
|
->description("Perform one migration.")
|
||||||
|
->parameter("migrationId", fn (ParameterDefinition $parameter) => $parameter->description("Migration ID of migration to execute."))
|
||||||
|
->handler(MigrateOneCommand::class)
|
||||||
|
,
|
||||||
|
"rollback" => fn (CommandDefinition $subcommand) => $subcommand
|
||||||
|
->description("Rollback latest migration.")
|
||||||
|
->handler(RollbackCommand::class)
|
||||||
|
,
|
||||||
|
"new" => fn (CommandDefinition $subcommand) => $subcommand
|
||||||
|
->description("Generate a migration with the given name.")
|
||||||
|
->parameter("name", fn (ParameterDefinition $parameter) => $parameter->description("Name of the new migration."))
|
||||||
|
->handler(NewCommand::class)
|
||||||
|
,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
18
src/Cli/Migrations/NewCommand.php
Normal file
18
src/Cli/Migrations/NewCommand.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Cli\Commands\Migrations;
|
||||||
|
|
||||||
|
use Nest\Application;
|
||||||
|
use Nest\Cli\Commands\CommandHandler;
|
||||||
|
use Nest\Cli\Out;
|
||||||
|
|
||||||
|
class NewCommand extends CommandHandler
|
||||||
|
{
|
||||||
|
public function __invoke(string $name): void
|
||||||
|
{
|
||||||
|
// Create a new migration.
|
||||||
|
Application::get()->migrations()->newMigration($name);
|
||||||
|
|
||||||
|
Out::success("Migration successfully created.");
|
||||||
|
}
|
||||||
|
}
|
33
src/Cli/Migrations/RollbackCommand.php
Normal file
33
src/Cli/Migrations/RollbackCommand.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Cli\Commands\Migrations;
|
||||||
|
|
||||||
|
use Nest\Application;
|
||||||
|
use Nest\Cli\Commands\CommandHandler;
|
||||||
|
use Nest\Cli\Out;
|
||||||
|
use Nest\Database\Migrations\Migration;
|
||||||
|
use Nest\Database\Exceptions\Migrations\MigrationNotFoundException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class RollbackCommand extends CommandHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws MigrationNotFoundException
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
public function __invoke(): void
|
||||||
|
{
|
||||||
|
Application::get()->migrations()->onRollbackStart = function (Migration $migration) {
|
||||||
|
Out::print("Starting to roll back V{$migration->getVersion()} ".Out::BOLD_ATTRIBUTE."{$migration->getName()}".Out::RESET_ATTRIBUTES);
|
||||||
|
echo " ".Out::ITALIC_ATTRIBUTE.$migration->getDescription().Out::RESET_ATTRIBUTES."\n\n";
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rollback latest migration.
|
||||||
|
$migration = Application::get()->migrations()->load()->rollbackLatest();
|
||||||
|
|
||||||
|
if (!empty($migration))
|
||||||
|
Out::success("Rolled back V{$migration->getVersion()} {$migration->getName()}");
|
||||||
|
else
|
||||||
|
Out::warning("Nothing to rollback.");
|
||||||
|
}
|
||||||
|
}
|
51
src/Configuration/DatabaseFactory.php
Normal file
51
src/Configuration/DatabaseFactory.php
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Configuration;
|
||||||
|
|
||||||
|
use Nest\Database\Database;
|
||||||
|
use Nest\Database\Exceptions\Configuration\MissingRequiredConfigurationValueException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a database object from a configuration array.
|
||||||
|
*/
|
||||||
|
abstract class DatabaseFactory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $databaseIdentifier Database identifier.
|
||||||
|
* @param array $configuration Database configuration array.
|
||||||
|
*/
|
||||||
|
public function __construct(protected string $databaseIdentifier, protected array $configuration)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a required configuration value.
|
||||||
|
* @param string $configKey Configuration key.
|
||||||
|
* @return mixed Configuration value.
|
||||||
|
* @throws MissingRequiredConfigurationValueException
|
||||||
|
*/
|
||||||
|
public function getRequiredConfig(string $configKey): mixed
|
||||||
|
{
|
||||||
|
if (!isset($this->configuration[$configKey]))
|
||||||
|
throw new MissingRequiredConfigurationValueException($this->databaseIdentifier, static::class, $configKey);
|
||||||
|
|
||||||
|
return $this->configuration[$configKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an optional configuration value.
|
||||||
|
* @param string $configKey Configuration key.
|
||||||
|
* @param mixed|null $default Default configuration value.
|
||||||
|
* @return mixed Configuration value.
|
||||||
|
*/
|
||||||
|
public function getOptionalConfig(string $configKey, mixed $default = null): mixed
|
||||||
|
{
|
||||||
|
return $this->configuration[$configKey] ?? $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make the database object from its configuration array.
|
||||||
|
* @return Database The created database.
|
||||||
|
* @throws MissingRequiredConfigurationValueException
|
||||||
|
*/
|
||||||
|
public abstract function make(): Database;
|
||||||
|
}
|
40
src/Configuration/DatabasesArrayConfiguration.php
Normal file
40
src/Configuration/DatabasesArrayConfiguration.php
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Configuration;
|
||||||
|
|
||||||
|
use Nest\Application;
|
||||||
|
use Nest\Configuration\Exceptions\ConfigurationValueNotFoundException;
|
||||||
|
use Nest\Database\Exceptions\Configuration\UndefinedDatabaseTypeException;
|
||||||
|
|
||||||
|
class DatabasesArrayConfiguration extends DatabasesConfiguration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Application $application The application.
|
||||||
|
* @param string $configurationKey Configuration key where to find the databases configuration array.
|
||||||
|
*/
|
||||||
|
public function __construct(protected Application $application, protected string $configurationKey)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
* @throws ConfigurationValueNotFoundException
|
||||||
|
* @throws UndefinedDatabaseTypeException
|
||||||
|
*/
|
||||||
|
#[\Override] public function getFactories(): array
|
||||||
|
{
|
||||||
|
// Initialize loaded factories.
|
||||||
|
$factories = [];
|
||||||
|
|
||||||
|
foreach ($this->application->configuration()->getValue($this->configurationKey) as $identifier => $configuration)
|
||||||
|
{
|
||||||
|
if (empty($configuration["type"]))
|
||||||
|
// Undefined database type, throwing an exception.
|
||||||
|
throw new UndefinedDatabaseTypeException($identifier);
|
||||||
|
|
||||||
|
// Create the factory with the given configuration.
|
||||||
|
$factories[$identifier] = new $configuration["type"]($identifier, $configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $factories; // Return loaded factories.
|
||||||
|
}
|
||||||
|
}
|
14
src/Configuration/DatabasesConfiguration.php
Normal file
14
src/Configuration/DatabasesConfiguration.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Configuration;
|
||||||
|
|
||||||
|
use Nest\Services\ServiceConfiguration;
|
||||||
|
|
||||||
|
abstract class DatabasesConfiguration extends ServiceConfiguration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get database factories.
|
||||||
|
* @return array<string, DatabaseFactory> Database factories, indexed by databases identifiers.
|
||||||
|
*/
|
||||||
|
public abstract function getFactories(): array;
|
||||||
|
}
|
144
src/Database.php
Normal file
144
src/Database.php
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database;
|
||||||
|
|
||||||
|
use Nest\Database\Query\QueryBuilder;
|
||||||
|
use Nest\Database\Transactions\Transaction;
|
||||||
|
use Nest\Exceptions\Database\NotCurrentTransactionException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class of a database.
|
||||||
|
*/
|
||||||
|
abstract class Database
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The current transaction, in one is started.
|
||||||
|
* @var Transaction|null
|
||||||
|
*/
|
||||||
|
private ?Transaction $transaction = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the database.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function connect(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a query.
|
||||||
|
* @param string $statement The query statement.
|
||||||
|
* @param array $bindings The query bindings.
|
||||||
|
* @return object[] Query result as an array of objects.
|
||||||
|
*/
|
||||||
|
public abstract function execute(string $statement, array $bindings = []): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a queries adapter for the current database.
|
||||||
|
* @return DatabaseAdapter A database queries adapter.
|
||||||
|
*/
|
||||||
|
public abstract function getQueriesAdapter(): DatabaseAdapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new query on the database.
|
||||||
|
* @param ?string $table The table on which to build the new query.
|
||||||
|
* @return QueryBuilder A query builder.
|
||||||
|
*/
|
||||||
|
public function query(?string $table = null): QueryBuilder
|
||||||
|
{
|
||||||
|
return new QueryBuilder($this, $table);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new query on the given table of the database.
|
||||||
|
* @param string $table The table on which to build the new query.
|
||||||
|
* @return QueryBuilder A query builder.
|
||||||
|
*/
|
||||||
|
public function table(string $table): QueryBuilder
|
||||||
|
{
|
||||||
|
return $this->query($table);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current transaction, if there is one.
|
||||||
|
* @return Transaction|null
|
||||||
|
*/
|
||||||
|
public function getCurrentTransaction(): ?Transaction
|
||||||
|
{
|
||||||
|
return $this->transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the state of a transaction changed (inactive -> active or active -> inactive).
|
||||||
|
* @param Transaction $transaction
|
||||||
|
* @return void
|
||||||
|
* @throws NotCurrentTransactionException
|
||||||
|
*/
|
||||||
|
public function onTransactionStateChanged(Transaction $transaction): void
|
||||||
|
{
|
||||||
|
if ($transaction->isActive())
|
||||||
|
{ // Set the newly active transaction as the current one.
|
||||||
|
|
||||||
|
// Its parent should be the current transaction, if it exists.
|
||||||
|
if (!empty($transaction->getParent()) && $transaction->getParent()->getUuid() != $this->transaction->getUuid())
|
||||||
|
throw new NotCurrentTransactionException($this->transaction, $transaction->getParent());
|
||||||
|
|
||||||
|
$this->transaction = $transaction;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // The transaction has been terminated, it should be the current one.
|
||||||
|
if (!empty($this->transaction) && $this->transaction->getUuid() != $transaction->getUuid())
|
||||||
|
throw new NotCurrentTransactionException($this->transaction, $transaction);
|
||||||
|
|
||||||
|
$this->transaction = $transaction->getParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a new transaction (in the current one, if there is one).
|
||||||
|
* @return Transaction The new transaction.
|
||||||
|
* @throws NotCurrentTransactionException
|
||||||
|
*/
|
||||||
|
public function startTransaction(): Transaction
|
||||||
|
{
|
||||||
|
// Create the transaction as a child of the existing one, if there is one.
|
||||||
|
$newTransaction = new Transaction($this, $this->transaction);
|
||||||
|
if (!empty($this->transaction))
|
||||||
|
// Set the new transaction as child of the current one.
|
||||||
|
$this->transaction->setChild($newTransaction);
|
||||||
|
|
||||||
|
$newTransaction->start(); // Start the new transaction.
|
||||||
|
|
||||||
|
return $newTransaction; // Return the new transaction.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a new transaction and execute the function in parameter.
|
||||||
|
* The transaction will be committed / rolled back automatically at the end of the function.
|
||||||
|
* A rollback happen if any exception is thrown in the function.
|
||||||
|
* @param callable(Transaction): mixed $function The function to execute in a transaction.
|
||||||
|
* @return mixed Function result.
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
public function transaction(callable $function): mixed
|
||||||
|
{
|
||||||
|
// Start a new transaction.
|
||||||
|
$transaction = $this->startTransaction();
|
||||||
|
|
||||||
|
try
|
||||||
|
{ // Trying to run the function in the started transaction.
|
||||||
|
$result = $function($transaction);
|
||||||
|
|
||||||
|
// The function finished successfully, commit the transaction.
|
||||||
|
$transaction->commit();
|
||||||
|
|
||||||
|
// Return result of the function.
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
catch (Throwable $throwable)
|
||||||
|
{ // An exception has been thrown, rolling back the transaction and throw it again.
|
||||||
|
$transaction->rollback();
|
||||||
|
throw $throwable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
199
src/DatabaseAdapter.php
Normal file
199
src/DatabaseAdapter.php
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database;
|
||||||
|
|
||||||
|
use Nest\Database\Migrations\Diff\TableColumn;
|
||||||
|
use Nest\Database\Migrations\Diff\TableForeignKey;
|
||||||
|
use Nest\Database\Migrations\Diff\TableIndex;
|
||||||
|
use Nest\Database\Query\Join\JoinBuilder;
|
||||||
|
use Nest\Database\Query\Raw;
|
||||||
|
use Nest\Database\Query\Where\ConditionBuilder;
|
||||||
|
use Nest\Exceptions\Database\Query\MissingConditionValueException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A database queries adapter.
|
||||||
|
*/
|
||||||
|
abstract class DatabaseAdapter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Database $database The database on which to execute the queries.
|
||||||
|
*/
|
||||||
|
public function __construct(protected Database $database)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a new transaction.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function newTransaction(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rollback the current transaction.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function rollbackTransaction(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commit the current transaction.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function commitTransaction(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a table creation.
|
||||||
|
* @param string $tableName Name of the table to create.
|
||||||
|
* @param bool $ifNotExists True to try to add if table does not exist, will just pass if it exists.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function createTable(string $tableName, bool $ifNotExists = false): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a table deletion.
|
||||||
|
* @param string $tableName Name of the table to drop.
|
||||||
|
* @param bool $ifExists True to try to drop if table exists, will just pass if it does not exist.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function dropTable(string $tableName, bool $ifExists = false): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename a table.
|
||||||
|
* @param string $tableName Table to rename.
|
||||||
|
* @param string $newTableName New table name.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function renameTable(string $tableName, string $newTableName): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a column to a table.
|
||||||
|
* @param TableColumn $tableColumn The column to add to a table.
|
||||||
|
* @param bool $ifNotExists True to try to add if column does not exist, will just pass if it exists.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function addTableColumn(TableColumn $tableColumn, bool $ifNotExists = false): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set index of a table column.
|
||||||
|
* @param TableColumn $tableColumn The table column with index data.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function setTableColumnIndex(TableColumn $tableColumn): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename a table column.
|
||||||
|
* @param TableColumn $tableColumn The table column to rename.
|
||||||
|
* @param string $newName New name of the column.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function renameTableColumn(TableColumn $tableColumn, string $newName): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modify a table colum.
|
||||||
|
* @param TableColumn $tableColumn The column to modify.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function alterTableColumn(TableColumn $tableColumn): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop a table column.
|
||||||
|
* @param string $tableName The table on which to drop a column.
|
||||||
|
* @param string $columnName The column name to drop.
|
||||||
|
* @param bool $ifExists True to try to drop if column exists, will just pass if it does not exist.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function dropTableColumn(string $tableName, string $columnName, bool $ifExists = false): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop a table constraint.
|
||||||
|
* @param string $tableName The table on which to drop a constraint.
|
||||||
|
* @param string $constraintName The constraint name to drop.
|
||||||
|
* @param bool $ifExists True to try to drop if constraint exists, will just pass if it does not exist.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function dropConstraint(string $tableName, string $constraintName, bool $ifExists = false): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a foreign key constraint.
|
||||||
|
* @param TableForeignKey $foreignKey The table foreign key to create.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function createForeignKey(TableForeignKey $foreignKey): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a table index.
|
||||||
|
* @param TableIndex $index The table index to create.
|
||||||
|
* @param bool $ifNotExists True to try to add if index does not exist, will just pass if it exists.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function createIndex(TableIndex $index, bool $ifNotExists = false): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename an existing index.
|
||||||
|
* @param string $indexName The table index to rename.
|
||||||
|
* @param string $newName The new name of the index.
|
||||||
|
* @param bool $ifExists True to try to rename if index exists, will just pass if it does not exist.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function renameIndex(string $indexName, string $newName, bool $ifExists = false): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop a table index.
|
||||||
|
* @param TableIndex $index The table index to drop.
|
||||||
|
* @param bool $ifExists True to try to drop if index exists, will just pass if it does not exist.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function dropIndex(TableIndex $index, bool $ifExists = false): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a SELECT query.
|
||||||
|
* @param string $tableName The table from which to select.
|
||||||
|
* @param (Raw|string)[] $selected Selected columns from the table.
|
||||||
|
* @param JoinBuilder[] $joins Joins of the query.
|
||||||
|
* @param ConditionBuilder[] $wheres Conditions applied to rows for selection.
|
||||||
|
* @param int|null $limit Limit of retrieved results. NULL by default = no limit.
|
||||||
|
* @return Raw SQL and its bindings.
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
*/
|
||||||
|
public abstract function buildSelect(string $tableName, array $selected, array $joins, array $wheres, ?int $limit = null): Raw;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select rows from a table.
|
||||||
|
* @param string $tableName The table from which to select.
|
||||||
|
* @param (Raw|string)[] $selected Selected columns from the table.
|
||||||
|
* @param JoinBuilder[] $joins Joins of the query.
|
||||||
|
* @param ConditionBuilder[] $wheres Conditions applied to rows for selection.
|
||||||
|
* @param int|null $limit Limit of retrieved results. NULL by default = no limit.
|
||||||
|
* @return object[] Selected rows objects.
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
*/
|
||||||
|
public abstract function select(string $tableName, array $selected, array $joins, array $wheres, ?int $limit = null): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert new rows in a table.
|
||||||
|
* @param string $tableName The table where to insert.
|
||||||
|
* @param string[] $columns Columns of the table that are provided in rows values.
|
||||||
|
* @param array<string, Raw|string|int|float|null> $rows Rows values.
|
||||||
|
* @param bool $returning True to return inserted objects.
|
||||||
|
* @return object[]|bool Inserted rows objects if returning is true, true otherwise.
|
||||||
|
*/
|
||||||
|
public abstract function insert(string $tableName, array $columns, array $rows, bool $returning = false): array|bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update rows of a table.
|
||||||
|
* @param string $tableName The table to update.
|
||||||
|
* @param array<string, Raw|string|int|float|null> $set Set values in the rows.
|
||||||
|
* @param ConditionBuilder[] $wheres Conditions applied to rows for update.
|
||||||
|
* @param bool $returning True to return updated objects.
|
||||||
|
* @return object[]|bool Updated rows objects if returning is true, true otherwise.
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
*/
|
||||||
|
public abstract function update(string $tableName, array $set, array $wheres, bool $returning = false): array|bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete rows from a table.
|
||||||
|
* @param string $tableName The table in which to delete rows.
|
||||||
|
* @param array $wheres Conditions applied to rows for deletion.
|
||||||
|
* @return void
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
*/
|
||||||
|
public abstract function delete(string $tableName, array $wheres): void;
|
||||||
|
}
|
57
src/Databases.php
Normal file
57
src/Databases.php
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database;
|
||||||
|
|
||||||
|
use Nest\Application;
|
||||||
|
use Nest\Database\Configuration\DatabasesConfiguration;
|
||||||
|
use Nest\Exceptions\Database\Configuration\MissingRequiredConfigurationValueException;
|
||||||
|
use Nest\Exceptions\Database\UnknownDatabaseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Databases manager.
|
||||||
|
*/
|
||||||
|
class Databases
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Loaded databases.
|
||||||
|
* @var array<string, Database>
|
||||||
|
*/
|
||||||
|
private array $databases;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Application $application Application of the databases.
|
||||||
|
* @param DatabasesConfiguration $configuration Configuration of the databases.
|
||||||
|
* @throws MissingRequiredConfigurationValueException
|
||||||
|
*/
|
||||||
|
public function __construct(protected Application $application, protected DatabasesConfiguration $configuration)
|
||||||
|
{
|
||||||
|
$this->load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load databases from their factories.
|
||||||
|
* @return void
|
||||||
|
* @throws MissingRequiredConfigurationValueException
|
||||||
|
*/
|
||||||
|
public function load(): void
|
||||||
|
{
|
||||||
|
foreach ($this->configuration->getFactories() as $identifier => $databaseFactory)
|
||||||
|
{ // Load all databases from their factories.
|
||||||
|
$this->databases[$identifier] = $databaseFactory->make();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $identifier Database identifier.
|
||||||
|
* @return Database Database corresponding to the given identifier.
|
||||||
|
* @throws UnknownDatabaseException
|
||||||
|
*/
|
||||||
|
public function db(string $identifier): Database
|
||||||
|
{
|
||||||
|
if (empty($this->databases[$identifier]))
|
||||||
|
// Cannot find a database with this identifier, throwing an exception.
|
||||||
|
throw new UnknownDatabaseException($identifier);
|
||||||
|
|
||||||
|
return $this->databases[$identifier]; // Return loaded database.
|
||||||
|
}
|
||||||
|
}
|
39
src/DatabasesService.php
Normal file
39
src/DatabasesService.php
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database;
|
||||||
|
|
||||||
|
use Nest\Database\Configuration\DatabasesConfiguration;
|
||||||
|
use Nest\Exceptions\Database\Configuration\MissingRequiredConfigurationValueException;
|
||||||
|
use Nest\Exceptions\Services\Configuration\UndefinedServiceConfigurationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Databases Nest service.
|
||||||
|
*/
|
||||||
|
trait DatabasesService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The databases manager.
|
||||||
|
* @var Databases
|
||||||
|
*/
|
||||||
|
private Databases $databases;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
* @throws MissingRequiredConfigurationValueException
|
||||||
|
* @throws UndefinedServiceConfigurationException
|
||||||
|
*/
|
||||||
|
protected function __nest__DatabasesService(): void
|
||||||
|
{
|
||||||
|
// Initialize databases manager.
|
||||||
|
$this->databases = new Databases($this, $this->getServiceConfiguration(DatabasesConfiguration::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Databases service.
|
||||||
|
* @return Databases The databases manager.
|
||||||
|
*/
|
||||||
|
public function databases(): Databases
|
||||||
|
{
|
||||||
|
return $this->databases;
|
||||||
|
}
|
||||||
|
}
|
13
src/Events/PdoDatabaseAfterConnectionEvent.php
Normal file
13
src/Events/PdoDatabaseAfterConnectionEvent.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Events;
|
||||||
|
|
||||||
|
use Nest\Database\PdoDatabase;
|
||||||
|
use Nest\Events\Event;
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
class PdoDatabaseAfterConnectionEvent extends Event
|
||||||
|
{
|
||||||
|
public function __construct(public PdoDatabase $database, public PDO $pdo)
|
||||||
|
{}
|
||||||
|
}
|
12
src/Events/PdoDatabaseBeforeConnectionEvent.php
Normal file
12
src/Events/PdoDatabaseBeforeConnectionEvent.php
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Events;
|
||||||
|
|
||||||
|
use Nest\Database\PdoDatabase;
|
||||||
|
use Nest\Events\Event;
|
||||||
|
|
||||||
|
class PdoDatabaseBeforeConnectionEvent extends Event
|
||||||
|
{
|
||||||
|
public function __construct(public PdoDatabase $database)
|
||||||
|
{}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Exceptions\Configuration;
|
||||||
|
|
||||||
|
use Nest\Database\Exceptions\DatabaseException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when database loading fails because of a missing required configuration.
|
||||||
|
*/
|
||||||
|
class MissingRequiredConfigurationValueException extends DatabaseException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $databaseIdentifier Database identifier.
|
||||||
|
* @param string $factoryClass Database factory class.
|
||||||
|
* @param string $configKey Missing configuration key.
|
||||||
|
* @param int $code [optional] The Exception code.
|
||||||
|
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||||
|
*/
|
||||||
|
public function __construct(public string $databaseIdentifier, public string $factoryClass, public string $configKey, int $code = 0, ?Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct("Cannot load database $this->databaseIdentifier: cannot find required configuration $this->configKey for factory $this->factoryClass.", $code, $previous);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Exceptions\Configuration;
|
||||||
|
|
||||||
|
use Nest\Database\Exceptions\DatabaseException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when a database type is missing in one of the configurations.
|
||||||
|
*/
|
||||||
|
class UndefinedDatabaseTypeException extends DatabaseException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $identifier Database identifier with missing type.
|
||||||
|
* @param int $code [optional] The Exception code.
|
||||||
|
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||||
|
*/
|
||||||
|
public function __construct(public string $identifier, int $code = 0, ?Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct("", $code, $previous);
|
||||||
|
}
|
||||||
|
}
|
9
src/Exceptions/DatabaseException.php
Normal file
9
src/Exceptions/DatabaseException.php
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Exceptions;
|
||||||
|
|
||||||
|
use Nest\Exceptions\Exception;
|
||||||
|
|
||||||
|
class DatabaseException extends Exception
|
||||||
|
{
|
||||||
|
}
|
14
src/Exceptions/InvalidTransactionStateException.php
Normal file
14
src/Exceptions/InvalidTransactionStateException.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when the current transaction does not allow to commit or rollback the transaction.
|
||||||
|
*/
|
||||||
|
class InvalidTransactionStateException extends DatabaseException
|
||||||
|
{
|
||||||
|
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct($message, $code, $previous);
|
||||||
|
}
|
||||||
|
}
|
23
src/Exceptions/Migrations/CannotRollbackException.php
Normal file
23
src/Exceptions/Migrations/CannotRollbackException.php
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Exceptions\Migrations;
|
||||||
|
|
||||||
|
use Nest\Database\Migrations\Migration;
|
||||||
|
use Nest\Database\Exceptions\DatabaseException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when a migration cannot be rolled back.
|
||||||
|
*/
|
||||||
|
class CannotRollbackException extends DatabaseException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Migration $migration The migration which cannot be rolled back.
|
||||||
|
* @param int $code [optional] The Exception code.
|
||||||
|
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||||
|
*/
|
||||||
|
public function __construct(public Migration $migration, int $code = 0, ?Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct("Cannot rollback migration {$this->migration->getName()}.", $code, $previous);
|
||||||
|
}
|
||||||
|
}
|
22
src/Exceptions/Migrations/MigrationNotFoundException.php
Normal file
22
src/Exceptions/Migrations/MigrationNotFoundException.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Exceptions\Migrations;
|
||||||
|
|
||||||
|
use Nest\Database\Exceptions\DatabaseException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when a given migration was not found.
|
||||||
|
*/
|
||||||
|
class MigrationNotFoundException extends DatabaseException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $id Migration ID that wasn't found.
|
||||||
|
* @param int $code [optional] The Exception code.
|
||||||
|
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||||
|
*/
|
||||||
|
public function __construct(public string $id, int $code = 0, ?Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct("Migration $this->id not found.", $code, $previous);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Exceptions\Migrations;
|
||||||
|
|
||||||
|
use Nest\Database\Exceptions\DatabaseException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when a new table column has an undefined type.
|
||||||
|
*/
|
||||||
|
class UndefinedNewColumnTypeException extends DatabaseException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $tableName Table name.
|
||||||
|
* @param string $columnName Column name.
|
||||||
|
* @param int $code [optional] The Exception code.
|
||||||
|
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||||
|
*/
|
||||||
|
public function __construct(public string $tableName, public string $columnName, int $code = 0, ?Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct("Undefined column type for \"$this->tableName\".\"$this->columnName\".", $code, $previous);
|
||||||
|
}
|
||||||
|
}
|
26
src/Exceptions/NotCurrentTransactionException.php
Normal file
26
src/Exceptions/NotCurrentTransactionException.php
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Exceptions;
|
||||||
|
|
||||||
|
use Nest\Database\Transactions\Transaction;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when the altered transaction is not the current one.
|
||||||
|
*/
|
||||||
|
class NotCurrentTransactionException extends DatabaseException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Transaction $currentTransaction The current transaction.
|
||||||
|
* @param Transaction $alteredTransaction The altered transaction.
|
||||||
|
* @param int $code [optional] The Exception code.
|
||||||
|
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||||
|
*/
|
||||||
|
public function __construct(public Transaction $currentTransaction, public Transaction $alteredTransaction, int $code = 0, ?Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct(
|
||||||
|
"Only the current transaction (".$this->currentTransaction->getUuid().") can be altered. Cannot alter ".$this->alteredTransaction->getUuid().".",
|
||||||
|
$code, $previous
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
21
src/Exceptions/Query/MissingConditionValueException.php
Normal file
21
src/Exceptions/Query/MissingConditionValueException.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Exceptions\Query;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when no condition value has been given to the condition builder.
|
||||||
|
*/
|
||||||
|
class MissingConditionValueException extends QueryBuilderException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $incompleteSql Incomplete SQL condition.
|
||||||
|
* @param int $code [optional] The Exception code.
|
||||||
|
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||||
|
*/
|
||||||
|
public function __construct(public string $incompleteSql, int $code = 0, ?Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct("Cannot complete SQL condition: $this->incompleteSql", $code, $previous);
|
||||||
|
}
|
||||||
|
}
|
22
src/Exceptions/Query/NoPrimaryFieldException.php
Normal file
22
src/Exceptions/Query/NoPrimaryFieldException.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Exceptions\Query;
|
||||||
|
|
||||||
|
use Nest\Model\Entity;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when an entity has no primary fields to use in WHERE clause.
|
||||||
|
*/
|
||||||
|
class NoPrimaryFieldException extends QueryBuilderException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Entity $entity The entity without primary fields.
|
||||||
|
* @param int $code [optional] The Exception code.
|
||||||
|
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||||
|
*/
|
||||||
|
public function __construct(public Entity $entity, int $code = 0, ?Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct("No defined primary fields for ".get_class($this->entity).", cannot apply WHERE clause on it.", $code, $previous);
|
||||||
|
}
|
||||||
|
}
|
9
src/Exceptions/Query/QueryBuilderException.php
Normal file
9
src/Exceptions/Query/QueryBuilderException.php
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Exceptions\Query;
|
||||||
|
|
||||||
|
use Nest\Database\Exceptions\DatabaseException;
|
||||||
|
|
||||||
|
class QueryBuilderException extends DatabaseException
|
||||||
|
{
|
||||||
|
}
|
21
src/Exceptions/UnknownDatabaseException.php
Normal file
21
src/Exceptions/UnknownDatabaseException.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Exceptions;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when a requested database is unknown.
|
||||||
|
*/
|
||||||
|
class UnknownDatabaseException extends DatabaseException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $identifier Database identifier.
|
||||||
|
* @param int $code [optional] The Exception code.
|
||||||
|
* @param null|Throwable $previous [optional] The previous throwable used for the exception chaining.
|
||||||
|
*/
|
||||||
|
public function __construct(public string $identifier, int $code = 0, ?Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct("Unknown database $this->identifier.", $code, $previous);
|
||||||
|
}
|
||||||
|
}
|
47
src/Migrations/Configuration/MigrationsConfiguration.php
Normal file
47
src/Migrations/Configuration/MigrationsConfiguration.php
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Migrations\Configuration;
|
||||||
|
|
||||||
|
use Nest\Services\ServiceConfiguration;
|
||||||
|
use function Nest\Utils\path_join;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrations configuration class.
|
||||||
|
*/
|
||||||
|
abstract class MigrationsConfiguration extends ServiceConfiguration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return string Database migrations PHP namespace.
|
||||||
|
*/
|
||||||
|
public abstract function getMigrationsNamespace(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Database migrations path.
|
||||||
|
*/
|
||||||
|
public abstract function getMigrationsPath(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Database preparations path.
|
||||||
|
*/
|
||||||
|
public function getPreparationsPath(): string
|
||||||
|
{
|
||||||
|
return path_join($this->getMigrationsPath(), "Prepare");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get database to use for migrations table.
|
||||||
|
* @return string Database identifier.
|
||||||
|
*/
|
||||||
|
public function getMigrationsDatabase(): string
|
||||||
|
{
|
||||||
|
return "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Done migrations table name.
|
||||||
|
*/
|
||||||
|
public function getMigrationsTable(): string
|
||||||
|
{
|
||||||
|
return "_migrations";
|
||||||
|
}
|
||||||
|
}
|
106
src/Migrations/Configuration/MigrationsConfigurator.php
Normal file
106
src/Migrations/Configuration/MigrationsConfigurator.php
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Migrations\Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrations configurator.
|
||||||
|
*/
|
||||||
|
class MigrationsConfigurator extends MigrationsConfiguration
|
||||||
|
{
|
||||||
|
private string $migrationsNamespace;
|
||||||
|
private string $migrationsPath;
|
||||||
|
private string $preparationsPath;
|
||||||
|
private string $migrationsDatabase;
|
||||||
|
private string $migrationsTable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $migrationsNamespace Database migrations PHP namespace.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setMigrationsNamespace(string $migrationsNamespace): static
|
||||||
|
{
|
||||||
|
$this->migrationsNamespace = $migrationsNamespace;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function getMigrationsNamespace(): string
|
||||||
|
{
|
||||||
|
return $this->migrationsNamespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $migrationsPath Database migrations path.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setMigrationsPath(string $migrationsPath): static
|
||||||
|
{
|
||||||
|
$this->migrationsPath = $migrationsPath;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function getMigrationsPath(): string
|
||||||
|
{
|
||||||
|
return $this->migrationsPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $preparationsPath Database preparations path.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setPreparationsPath(string $preparationsPath): static
|
||||||
|
{
|
||||||
|
$this->preparationsPath = $preparationsPath;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function getPreparationsPath(): string
|
||||||
|
{
|
||||||
|
return $this->preparationsPath ?? parent::getPreparationsPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set database to use for migrations table.
|
||||||
|
* @param string $databaseIdentifier Database identifier.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setMigrationsDatabase(string $databaseIdentifier): static
|
||||||
|
{
|
||||||
|
$this->migrationsDatabase = $databaseIdentifier;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getMigrationsDatabase(): string
|
||||||
|
{
|
||||||
|
return $this->migrationsDatabase ?? parent::getMigrationsDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $migrationsTable Done migrations table name.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setMigrationsTable(string $migrationsTable): static
|
||||||
|
{
|
||||||
|
$this->migrationsTable = $migrationsTable;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function getMigrationsTable(): string
|
||||||
|
{
|
||||||
|
return $this->migrationsTable ?? parent::getMigrationsTable();
|
||||||
|
}
|
||||||
|
}
|
137
src/Migrations/Diff/Table.php
Normal file
137
src/Migrations/Diff/Table.php
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Migrations\Diff;
|
||||||
|
|
||||||
|
use Nest\Database\Database;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table modifications manager.
|
||||||
|
*/
|
||||||
|
class Table
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Database $database The database on which to perform operations on the given table.
|
||||||
|
* @param string $tableName Name of the table to modify.
|
||||||
|
*/
|
||||||
|
public function __construct(protected Database $database, protected string $tableName)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The database on which to perform operations on the table.
|
||||||
|
* @return Database
|
||||||
|
*/
|
||||||
|
public function getDatabase(): Database
|
||||||
|
{
|
||||||
|
return $this->database;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get table name.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getTableName(): string
|
||||||
|
{
|
||||||
|
return $this->tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the table.
|
||||||
|
* @param bool $ifNotExists True to try to add if table does not exist, will just pass if it exists.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function create(bool $ifNotExists = false): static
|
||||||
|
{
|
||||||
|
$this->getDatabase()->getQueriesAdapter()->createTable($this->getTableName(), $ifNotExists);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the table, if it does not exists.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function createIfNotExists(): static
|
||||||
|
{
|
||||||
|
return $this->create(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop the table.
|
||||||
|
* @param bool $ifExists True to try to drop if table exists, will just pass if it does not exist.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function drop(bool $ifExists = false): static
|
||||||
|
{
|
||||||
|
$this->getDatabase()->getQueriesAdapter()->dropTable($this->getTableName(), $ifExists);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop the table, if it exists.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function dropIfExists(): static
|
||||||
|
{
|
||||||
|
return $this->drop(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename the table.
|
||||||
|
* @param string $newName New name of the table.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function rename(string $newName): void
|
||||||
|
{
|
||||||
|
$this->getDatabase()->getQueriesAdapter()->renameTable($this->getTableName(), $newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or create a column for the current table.
|
||||||
|
* @param string $columnName Column name.
|
||||||
|
* @return TableColumn The table column.
|
||||||
|
*/
|
||||||
|
public function column(string $columnName): TableColumn
|
||||||
|
{
|
||||||
|
return new TableColumn($this, $columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a foreign key creator for the current table.
|
||||||
|
* @return TableForeignKey The table foreign key creator.
|
||||||
|
*/
|
||||||
|
public function foreignKey(): TableForeignKey
|
||||||
|
{
|
||||||
|
return new TableForeignKey($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or create an index for the current table.
|
||||||
|
* @param string $indexName Index name.
|
||||||
|
* @return TableIndex The table index.
|
||||||
|
*/
|
||||||
|
public function index(string $indexName): TableIndex
|
||||||
|
{
|
||||||
|
return new TableIndex($this, $indexName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop a constraint with the given name.
|
||||||
|
* @param string $constraintName The constraint to drop.
|
||||||
|
* @param bool $ifExists True to try to drop if constraint exists, will just pass if it does not exist.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function dropConstraint(string $constraintName, bool $ifExists = false): void
|
||||||
|
{
|
||||||
|
$this->getDatabase()->getQueriesAdapter()->dropConstraint($this->getTableName(), $constraintName, $ifExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop a constraint with the given name if it exists.
|
||||||
|
* @param string $constraintName The constraint to drop.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function dropConstraintIfExists(string $constraintName): void
|
||||||
|
{
|
||||||
|
$this->dropConstraint($constraintName, true);
|
||||||
|
}
|
||||||
|
}
|
188
src/Migrations/Diff/TableColumn.php
Normal file
188
src/Migrations/Diff/TableColumn.php
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Migrations\Diff;
|
||||||
|
|
||||||
|
use BackedEnum;
|
||||||
|
use Nest\Database\Query\Raw;
|
||||||
|
use Nest\Database\Exceptions\Migrations\UndefinedNewColumnTypeException;
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table column class.
|
||||||
|
*/
|
||||||
|
class TableColumn
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* SQL type of the column.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column index definition.
|
||||||
|
* @var TableColumnIndex
|
||||||
|
*/
|
||||||
|
public TableColumnIndex $index;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column is a primary key.
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public bool $primary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default column value.
|
||||||
|
* @var Raw|string|int|float|null
|
||||||
|
*/
|
||||||
|
public Raw|string|int|float|null $default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is column nullable or not.
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public bool $nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new table column.
|
||||||
|
* @param Table $table Table of the column.
|
||||||
|
* @param string $name Column name.
|
||||||
|
*/
|
||||||
|
public function __construct(public readonly Table $table, public readonly string $name)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the SQL type of the column.
|
||||||
|
* @param string|Stringable|BackedEnum $type SQL type of the column.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function type(string|Stringable|BackedEnum $type): static
|
||||||
|
{
|
||||||
|
$this->type = (string) ($type instanceof BackedEnum ? $type->value : $type);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set default column value.
|
||||||
|
* @param Raw|string|int|float|null $value Default column value.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function default(Raw|string|int|float|null $value): static
|
||||||
|
{
|
||||||
|
$this->default = $value;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set default column value to NOW.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function defaultNow(): static
|
||||||
|
{
|
||||||
|
return $this->default(new Raw("NOW()"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set nullable for the current column.
|
||||||
|
* @param bool $nullable True if the column is nullable, false otherwise.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function nullable(bool $nullable = true): static
|
||||||
|
{
|
||||||
|
$this->nullable = $nullable;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current column is a primary key.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function primary(): static
|
||||||
|
{
|
||||||
|
// Set the current column as a primary key.
|
||||||
|
$this->primary = true;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Index the current column.
|
||||||
|
* @param bool $unique True to ensure a unique value for each row, false otherwise.
|
||||||
|
* @param string|null $method Custom index method (raw SQL!).
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function index(bool $unique = false, ?string $method = null): static
|
||||||
|
{
|
||||||
|
// Initialize the column index.
|
||||||
|
$this->index = new TableColumnIndex();
|
||||||
|
$this->index->unique = $unique;
|
||||||
|
if (!empty($method)) $this->index->method = $method;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique index the current column.
|
||||||
|
* @param string|null $method Custom index method (raw SQL!).
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function unique(?string $method = null): static
|
||||||
|
{
|
||||||
|
// Initialize the column index.
|
||||||
|
return $this->index(true, $method);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the current column to the table.
|
||||||
|
* @param bool $ifNotExists True to try to add if column does not exist, will just pass if it exists.
|
||||||
|
* @return void
|
||||||
|
* @throws UndefinedNewColumnTypeException
|
||||||
|
*/
|
||||||
|
public function add(bool $ifNotExists = false): void
|
||||||
|
{
|
||||||
|
if (empty($this->type))
|
||||||
|
// Throw an exception if no type is provided.
|
||||||
|
throw new UndefinedNewColumnTypeException($this->table->getTableName(), $this->name);
|
||||||
|
|
||||||
|
// Add table column.
|
||||||
|
$this->table->getDatabase()->getQueriesAdapter()->addTableColumn($this, $ifNotExists);
|
||||||
|
|
||||||
|
// Set column index, if it's defined.
|
||||||
|
if (!empty($this->index))
|
||||||
|
$this->table->getDatabase()->getQueriesAdapter()->setTableColumnIndex($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename the column.
|
||||||
|
* @param string $newName New name of the column.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function rename(string $newName): void
|
||||||
|
{
|
||||||
|
// Rename table column.
|
||||||
|
$this->table->getDatabase()->getQueriesAdapter()->renameTableColumn($this, $newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alter the current table column.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function alter(): void
|
||||||
|
{
|
||||||
|
// Alter table column.
|
||||||
|
$this->table->getDatabase()->getQueriesAdapter()->alterTableColumn($this);
|
||||||
|
|
||||||
|
// Set column index, if it's defined.
|
||||||
|
if (!empty($this->index))
|
||||||
|
$this->table->getDatabase()->getQueriesAdapter()->setTableColumnIndex($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop the current column from the table.
|
||||||
|
* @param bool $ifExists True to try to drop if column exists, will just pass if it does not exist.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function drop(bool $ifExists = false): void
|
||||||
|
{
|
||||||
|
// Drop table column.
|
||||||
|
$this->table->getDatabase()->getQueriesAdapter()->dropTableColumn($this->table->getTableName(), $this->name, $ifExists);
|
||||||
|
}
|
||||||
|
}
|
21
src/Migrations/Diff/TableColumnIndex.php
Normal file
21
src/Migrations/Diff/TableColumnIndex.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Migrations\Diff;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table column index definition.
|
||||||
|
*/
|
||||||
|
class TableColumnIndex
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Unique key index.
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public bool $unique = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom index method (raw SQL!).
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $method;
|
||||||
|
}
|
102
src/Migrations/Diff/TableForeignKey.php
Normal file
102
src/Migrations/Diff/TableForeignKey.php
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Migrations\Diff;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table foreign key configurator.
|
||||||
|
*/
|
||||||
|
class TableForeignKey
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Foreign key columns.
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
public array $columns = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Referenced table.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $referencedTable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Referenced columns.
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
public array $referencedColumns;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action on delete.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $onDelete;
|
||||||
|
/**
|
||||||
|
* Action on update.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $onUpdate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new table foreign key.
|
||||||
|
* @param Table $table Table of the foreign key.
|
||||||
|
*/
|
||||||
|
public function __construct(public readonly Table $table)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add foreign key columns.
|
||||||
|
* @param string ...$columns Foreign key columns.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function columns(string ...$columns): static
|
||||||
|
{
|
||||||
|
array_push($this->columns, ...$columns);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set referenced table and columns..
|
||||||
|
* @param string $referencedTable Referenced table name.
|
||||||
|
* @param string[] $referencedColumns Referenced columns in table.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function references(string $referencedTable, string ...$referencedColumns): static
|
||||||
|
{
|
||||||
|
$this->referencedTable = $referencedTable;
|
||||||
|
$this->referencedColumns = $referencedColumns;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set action on delete.
|
||||||
|
* @param string $referentialAction Raw action.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function onDelete(string $referentialAction): static
|
||||||
|
{
|
||||||
|
$this->onDelete = $referentialAction;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set action on update.
|
||||||
|
* @param string $referentialAction Raw action.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function onUpdate(string $referentialAction): static
|
||||||
|
{
|
||||||
|
$this->onUpdate = $referentialAction;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the defined foreign key.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function create(): void
|
||||||
|
{
|
||||||
|
$this->table->getDatabase()->getQueriesAdapter()->createForeignKey($this);
|
||||||
|
}
|
||||||
|
}
|
167
src/Migrations/Diff/TableIndex.php
Normal file
167
src/Migrations/Diff/TableIndex.php
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Migrations\Diff;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table index class.
|
||||||
|
*/
|
||||||
|
class TableIndex
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Index columns.
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
public array $columns;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw expression to index.
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
public ?string $rawExpression = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether indexed values must be uniques or not.
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public bool $unique = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Index method to use.
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
public ?string $method = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ASC or DESC.
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
public ?string $order = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NULLS FIRST or NULLS LAST.
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
public ?string $nulls = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new table index.
|
||||||
|
* @param Table $table Table of the index.
|
||||||
|
* @param string $name Index name.
|
||||||
|
*/
|
||||||
|
public function __construct(public readonly Table $table, public readonly string $name)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add columns in the index.
|
||||||
|
* @param string ...$columns Columns to add in the index.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function columns(string ...$columns): static
|
||||||
|
{
|
||||||
|
array_push($this->columns, ...$columns);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a raw expression to index.
|
||||||
|
* @param string $rawExpression Raw expression to index.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function expression(string $rawExpression): static
|
||||||
|
{
|
||||||
|
$this->rawExpression = $rawExpression;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set that indexed values must be uniques.
|
||||||
|
* @param bool $unique True to set unique, false to set non-unique.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function unique(bool $unique = true): static
|
||||||
|
{
|
||||||
|
$this->unique = $unique;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the method to used to index.
|
||||||
|
* @param string|null $method Index method to use.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function using(?string $method): static
|
||||||
|
{
|
||||||
|
$this->method = $method;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ascending sort order.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function asc(): static
|
||||||
|
{
|
||||||
|
$this->order = "ASC";
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Descending sort order.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function desc(): static
|
||||||
|
{
|
||||||
|
$this->order = "DESC";
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nulls sort before non-nulls.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function nullsFirst(): static
|
||||||
|
{
|
||||||
|
$this->nulls = "NULLS FIRST";
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Nulls sort after non-nulls.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function nullsLast(): static
|
||||||
|
{
|
||||||
|
$this->nulls = "NULLS LAST";
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create index.
|
||||||
|
* @param bool $ifNotExists True to try to add if index does not exist, will just pass if it exists.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function create(bool $ifNotExists = false): void
|
||||||
|
{
|
||||||
|
$this->table->getDatabase()->getQueriesAdapter()->createIndex($this, $ifNotExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename index.
|
||||||
|
* @param string $newName The new name of the index.
|
||||||
|
* @param bool $ifExists True to try to rename if index exists, will just pass if it does not exist.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function renameTo(string $newName, bool $ifExists = false): void
|
||||||
|
{
|
||||||
|
$this->table->getDatabase()->getQueriesAdapter()->renameIndex($this->name, $newName, $ifExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop index.
|
||||||
|
* @param bool $ifExists True to try to drop if index exists, will just pass if it does not exist.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function drop(bool $ifExists = false): void
|
||||||
|
{
|
||||||
|
$this->table->getDatabase()->getQueriesAdapter()->dropIndex($this, $ifExists);
|
||||||
|
}
|
||||||
|
}
|
67
src/Migrations/Migration.php
Normal file
67
src/Migrations/Migration.php
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Migrations;
|
||||||
|
|
||||||
|
use Nest\Database\Database;
|
||||||
|
use Nest\Database\Migrations\Diff\Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A database migration.
|
||||||
|
*/
|
||||||
|
abstract class Migration
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new database migration.
|
||||||
|
* @param Database $database The database of the migration.
|
||||||
|
* @param string $version Version identifier.
|
||||||
|
* @param string $name Database migration name.
|
||||||
|
*/
|
||||||
|
public function __construct(protected readonly Database $database, private readonly string $version, private readonly string $name)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string The version identifier.
|
||||||
|
*/
|
||||||
|
public function getVersion(): string
|
||||||
|
{
|
||||||
|
return $this->version;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @return string The database migration name.
|
||||||
|
*/
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes operations of the current database migration.
|
||||||
|
* @return string Description of database migration.
|
||||||
|
*/
|
||||||
|
public abstract function getDescription(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do the migration.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function up(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undo the migration.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public abstract function down(): void;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new table diff manager.
|
||||||
|
* @param string $tableName Table name to alter.
|
||||||
|
* @return Table Table diff manager.
|
||||||
|
*/
|
||||||
|
protected function table(string $tableName): Table
|
||||||
|
{
|
||||||
|
return new Table($this->database, $tableName);
|
||||||
|
}
|
||||||
|
}
|
572
src/Migrations/Migrations.php
Normal file
572
src/Migrations/Migrations.php
Normal file
|
@ -0,0 +1,572 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Migrations;
|
||||||
|
|
||||||
|
use Nest\Application;
|
||||||
|
use Nest\Database\Database;
|
||||||
|
use Nest\Database\Migrations\Configuration\MigrationsConfiguration;
|
||||||
|
use DateTime;
|
||||||
|
use Nest\Database\Migrations\Diff\Table;
|
||||||
|
use Nest\Database\PostgreSql\Columns\Timestamp;
|
||||||
|
use Nest\Database\PostgreSql\Columns\Type;
|
||||||
|
use Nest\Database\Exceptions\Migrations\MigrationNotFoundException;
|
||||||
|
use Nest\Database\Exceptions\Migrations\UndefinedNewColumnTypeException;
|
||||||
|
use Nest\Database\Exceptions\Query\MissingConditionValueException;
|
||||||
|
use Nest\Database\Exceptions\UnknownDatabaseException;
|
||||||
|
use Nest\Exceptions\InvalidTypeException;
|
||||||
|
use Nest\Model\Exceptions\MissingRequiredFieldException;
|
||||||
|
use Nest\Model\Exceptions\UndefinedRelationException;
|
||||||
|
use Nest\Model\Exceptions\UnhandledPropertyTypeException;
|
||||||
|
use Nest\Model\Exceptions\IncompatibleTypeException;
|
||||||
|
use Nest\Model\EntityBlueprint;
|
||||||
|
use Throwable;
|
||||||
|
use function Nest\Utils\path_join;
|
||||||
|
|
||||||
|
class Migrations
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The database on which to execute the migrations.
|
||||||
|
* @var Database
|
||||||
|
*/
|
||||||
|
protected Database $database;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The migrations array.
|
||||||
|
* Indexed by their version identifier.
|
||||||
|
* @var array<string, array>
|
||||||
|
*/
|
||||||
|
private array $migrations = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The preparations scripts array.
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
private array $preparations = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration start event.
|
||||||
|
* @var callable(Migration): void|null
|
||||||
|
*/
|
||||||
|
public $onMigrationStart = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration end event.
|
||||||
|
* @var callable(Migration): void|null
|
||||||
|
*/
|
||||||
|
public $onMigrationEnd = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rollback start event.
|
||||||
|
* @var callable(Migration): void|null
|
||||||
|
*/
|
||||||
|
public $onRollbackStart = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rollback end event.
|
||||||
|
* @var callable(Migration): void|null
|
||||||
|
*/
|
||||||
|
public $onRollbackEnd = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Application $application The application.
|
||||||
|
* @param MigrationsConfiguration $configuration The migrations configuration.
|
||||||
|
*/
|
||||||
|
public function __construct(protected Application $application, protected MigrationsConfiguration $configuration)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial load of the migrations system.
|
||||||
|
* @return $this
|
||||||
|
* @throws IncompatibleTypeException
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
* @throws UnknownDatabaseException
|
||||||
|
* @throws UndefinedNewColumnTypeException
|
||||||
|
*/
|
||||||
|
public function load(): static
|
||||||
|
{
|
||||||
|
// Get migrations database.
|
||||||
|
$this->database = $this->application->databases()->db($this->configuration->getMigrationsDatabase());
|
||||||
|
|
||||||
|
// Initial load of database migrations and preparations.
|
||||||
|
$this->prepareMigrationsTable();
|
||||||
|
$this->loadMigrations();
|
||||||
|
$this->loadPreparations();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to register a new migration file.
|
||||||
|
* @param string $migrationFile The migration file name.
|
||||||
|
* @param string $subdirectory Subdirectory where the migration file has been found.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function registerMigration(string $migrationFile, string $subdirectory): void
|
||||||
|
{
|
||||||
|
if (preg_match("/^V([0-9]+)_([^.]+)\\.php$/", $migrationFile, $matches))
|
||||||
|
{ // The filename matches migration name format.
|
||||||
|
$version = $matches[1];
|
||||||
|
$name = $matches[2];
|
||||||
|
|
||||||
|
$this->migrations[$version] = [
|
||||||
|
"version" => $version,
|
||||||
|
"name" => $name,
|
||||||
|
"migrated" => false, // Already run migrations are found by using findMigrated after migration registration.
|
||||||
|
"entity" => null, // Migration entity in database, when it's migrated.
|
||||||
|
"package" => $subdirectory,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively load migrations in the given path with subdirectory.
|
||||||
|
* @param string $migrationsPath The path where to find migrations.
|
||||||
|
* @param string $subdirectory The subdirectory / subpackage of registered migration files.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function _loadMigrations(string $migrationsPath, string $subdirectory = ""): void
|
||||||
|
{
|
||||||
|
// Get the list of files in the migrations directory.
|
||||||
|
$migrationFiles = scandir($migrationsPath);
|
||||||
|
|
||||||
|
foreach ($migrationFiles as $migrationFile)
|
||||||
|
{ // Try to register each migration file.
|
||||||
|
$migrationFileFullPath = path_join($migrationsPath, $migrationFile);
|
||||||
|
if (is_dir($migrationFileFullPath) && $migrationFile != "." && $migrationFile != "..")
|
||||||
|
{ // If it is a directory, we try to read it recursively.
|
||||||
|
$this->_loadMigrations($migrationFileFullPath, !empty($subdirectory) ? path_join($subdirectory, $migrationFile) : $migrationFile);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // It is a file, we try to register it as a migration file.
|
||||||
|
$this->registerMigration($migrationFile, $subdirectory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all existing migrations.
|
||||||
|
* @return void
|
||||||
|
* @throws IncompatibleTypeException
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
*/
|
||||||
|
protected function loadMigrations(): void
|
||||||
|
{
|
||||||
|
// Reset migrations list.
|
||||||
|
$this->migrations = [];
|
||||||
|
|
||||||
|
// Load migrations in the configured migrations path.
|
||||||
|
$this->_loadMigrations($this->configuration->getMigrationsPath());
|
||||||
|
|
||||||
|
// Sort migrations by lexicographic order of version identifiers.
|
||||||
|
ksort($this->migrations);
|
||||||
|
|
||||||
|
// Find done migrations.
|
||||||
|
$this->findMigrated();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all preparations scripts.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function loadPreparations(): void
|
||||||
|
{
|
||||||
|
// Reset preparations scripts list.
|
||||||
|
$this->preparations = [];
|
||||||
|
|
||||||
|
if (file_exists($this->configuration->getPreparationsPath()))
|
||||||
|
{ // If there is a preparation folder.
|
||||||
|
|
||||||
|
// Get all preparations scripts files.
|
||||||
|
$this->preparations = scandir($this->configuration->getPreparationsPath());
|
||||||
|
|
||||||
|
// Filter non-SQL files.
|
||||||
|
$this->preparations = array_filter($this->preparations, fn ($preparationScript) => (
|
||||||
|
// Only keep files ending with ".sql".
|
||||||
|
is_file($preparationScript) && str_ends_with($preparationScript, ".sql")
|
||||||
|
));
|
||||||
|
|
||||||
|
// Sort in lexicographic order.
|
||||||
|
sort($this->preparations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare migrations table: create it if it doesn't exists.
|
||||||
|
* @return void
|
||||||
|
* @throws UndefinedNewColumnTypeException
|
||||||
|
*/
|
||||||
|
protected function prepareMigrationsTable(): void
|
||||||
|
{
|
||||||
|
$table = new Table($this->database, $this->configuration->getMigrationsTable());
|
||||||
|
$table->createIfNotExists();
|
||||||
|
$table->column("id")->type(Type::Varchar)->primary()->add(true);
|
||||||
|
$table->column("name")->type(Type::Varchar)->index()->add(true);
|
||||||
|
$table->column("created_at")->type(new Timestamp(true))->defaultNow()->add(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a migration model for the current migrations.
|
||||||
|
* @return \Nest\Database\Migrations\Model\Migration
|
||||||
|
*/
|
||||||
|
protected function getMigrationModel(): \Nest\Database\Migrations\Model\Migration
|
||||||
|
{
|
||||||
|
// Create an anonymous class for the current migrations.
|
||||||
|
$migrationModel = new class extends \Nest\Database\Migrations\Model\Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Migrations table name.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public static string $migrationsTableName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function definition(EntityBlueprint $blueprint): EntityBlueprint
|
||||||
|
{
|
||||||
|
if (!empty(static::$migrationsTableName))
|
||||||
|
// Set migrations table name.
|
||||||
|
$blueprint->setTable(static::$migrationsTableName);
|
||||||
|
|
||||||
|
return parent::definition($blueprint);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set migrations table name.
|
||||||
|
// $migrationModel cannot be used as is: the table name has been defined after the first definition.
|
||||||
|
$migrationModel::$migrationsTableName = $this->configuration->getMigrationsTable();
|
||||||
|
|
||||||
|
return $migrationModel->new(); // Return migrations model base instance.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find done migrations.
|
||||||
|
* @return void
|
||||||
|
* @throws IncompatibleTypeException
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
* @throws UnknownDatabaseException
|
||||||
|
* @throws InvalidTypeException
|
||||||
|
* @throws UndefinedRelationException
|
||||||
|
* @throws UnhandledPropertyTypeException
|
||||||
|
*/
|
||||||
|
protected function findMigrated(): void
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get migrated migrations.
|
||||||
|
* @var \Nest\Database\Migrations\Model\Migration[] $migrations
|
||||||
|
*/
|
||||||
|
$migrations = $this->getMigrationModel()->query()->get();
|
||||||
|
|
||||||
|
foreach ($migrations as $migration)
|
||||||
|
{ // Set each migration as migrated.
|
||||||
|
$this->migrations[$migration->id]["migrated"] = true;
|
||||||
|
$this->migrations[$migration->id]["entity"] = $migration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the given migration as migrated.
|
||||||
|
* @param string $migrationId The migration version identifier.
|
||||||
|
* @return void
|
||||||
|
* @throws IncompatibleTypeException
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
* @throws UnknownDatabaseException
|
||||||
|
* @throws MissingRequiredFieldException
|
||||||
|
*/
|
||||||
|
public function markMigrated(string $migrationId): void
|
||||||
|
{
|
||||||
|
// Mark migration as migrated.
|
||||||
|
$this->migrations[$migrationId]["migrated"] = true;
|
||||||
|
|
||||||
|
// Create a migration and save it as migrated.
|
||||||
|
$this->migrations[$migrationId]["entity"] = $this->getMigrationModel()->new();
|
||||||
|
$this->migrations[$migrationId]["entity"]->id = $migrationId;
|
||||||
|
$this->migrations[$migrationId]["entity"]->name = $this->migrations[$migrationId]["name"];
|
||||||
|
$this->migrations[$migrationId]["entity"]->save();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Mark the given migration as NOT migrated.
|
||||||
|
* @param string $migrationId The migration version identifier.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function markNotMigrated(string $migrationId): void
|
||||||
|
{
|
||||||
|
$this->migrations[$migrationId]["entity"]->delete();
|
||||||
|
$this->migrations[$migrationId]["migrated"] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a new version for a new database migration.
|
||||||
|
* @return string The version of a new migration.
|
||||||
|
*/
|
||||||
|
protected function getNewVersion(): string
|
||||||
|
{
|
||||||
|
// Generate a new version from current date and time.
|
||||||
|
return (new DateTime())->format("YmdHis");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a new migration with the given name.
|
||||||
|
* @param string $migrationName The migration name.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function newMigration(string $migrationName): void
|
||||||
|
{
|
||||||
|
// Database migration filename.
|
||||||
|
$filename = path_join($this->configuration->getMigrationsPath(), "V".($version = $this->getNewVersion()))."_{$migrationName}.php";
|
||||||
|
// Generate new migration content.
|
||||||
|
file_put_contents($filename, <<<EOD
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace {$this->configuration->getMigrationsNamespace()};
|
||||||
|
|
||||||
|
use Nest\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
final class V{$version}_{$migrationName} extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function getDescription(): string
|
||||||
|
{ return ""; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
throw new \Nest\Exceptions\Database\Migrations\CannotRollbackException(\$this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOD
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get migrations versions list.
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getMigrations(): array
|
||||||
|
{
|
||||||
|
return array_keys($this->migrations);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new migration instance from its version identifier.
|
||||||
|
* @param array $migrationData Migration data associative array.
|
||||||
|
* @return Migration The migration instance.
|
||||||
|
*/
|
||||||
|
protected function newMigrationInstance(array $migrationData): Migration
|
||||||
|
{
|
||||||
|
// Get migration full class name.
|
||||||
|
$fullClassName = "\\".$this->configuration->getMigrationsNamespace()
|
||||||
|
// Add subpackage if there is one.
|
||||||
|
.(!empty($migrationData["package"]) ? "\\".$migrationData["package"] : "")
|
||||||
|
// Add migration class name.
|
||||||
|
."\\V{$migrationData["version"]}_{$migrationData["name"]}";
|
||||||
|
|
||||||
|
// Instantiate migration.
|
||||||
|
return new $fullClassName($this->database, $migrationData["version"], $migrationData["name"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a migration.
|
||||||
|
* @param string $migrationId The migration version identifier.
|
||||||
|
* @return Migration The executed migration instance.
|
||||||
|
* @throws IncompatibleTypeException
|
||||||
|
* @throws MigrationNotFoundException
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
* @throws MissingRequiredFieldException
|
||||||
|
* @throws UnknownDatabaseException
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
public function migrateOne(string $migrationId): Migration
|
||||||
|
{
|
||||||
|
if (empty($this->migrations[$migrationId]))
|
||||||
|
// Migration not found.
|
||||||
|
throw new MigrationNotFoundException($migrationId);
|
||||||
|
|
||||||
|
// Migrate in a transaction.
|
||||||
|
return $this->database->transaction(function () use ($migrationId) {
|
||||||
|
// Get the migration instance.
|
||||||
|
$migration = $this->newMigrationInstance($this->migrations[$migrationId]);
|
||||||
|
|
||||||
|
if (!empty($this->onMigrationStart))
|
||||||
|
// Fire migration start event, if it is defined.
|
||||||
|
($this->onMigrationStart)($migration);
|
||||||
|
|
||||||
|
// Execute migration.
|
||||||
|
$migration->up();
|
||||||
|
|
||||||
|
// Mark the migration as migrated.
|
||||||
|
$this->markMigrated($migrationId);
|
||||||
|
|
||||||
|
if (!empty($this->onMigrationEnd))
|
||||||
|
// Fire migration end event, if it is defined.
|
||||||
|
($this->onMigrationEnd)($migration);
|
||||||
|
|
||||||
|
// Return executed migration.
|
||||||
|
return $migration;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute all unapplied migrations.
|
||||||
|
* @return Migration[] The applied migrations list.
|
||||||
|
* @throws IncompatibleTypeException
|
||||||
|
* @throws MigrationNotFoundException
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
* @throws MissingRequiredFieldException
|
||||||
|
* @throws UnknownDatabaseException
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
public function migrate(): array
|
||||||
|
{
|
||||||
|
// Migrate in a transaction.
|
||||||
|
return $this->database->transaction(function () {
|
||||||
|
// Initialize the executed migrations.
|
||||||
|
$migrated = [];
|
||||||
|
|
||||||
|
// Clear preparations before executing migrations.
|
||||||
|
$this->clearPreparations();
|
||||||
|
|
||||||
|
foreach ($this->migrations as $version => $migration)
|
||||||
|
{ // For each migration, if it is not executed, we execute it.
|
||||||
|
if (!$migration["migrated"])
|
||||||
|
{ // If the migration isn't done, execute it.
|
||||||
|
$migrated[$version] = $this->migrateOne($version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do database preparations after migrations.
|
||||||
|
$this->prepare();
|
||||||
|
|
||||||
|
return $migrated; // Return executed migrations list.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rollback a migration.
|
||||||
|
* @param string $migrationId The migration version identifier.
|
||||||
|
* @return Migration The rolled back migration.
|
||||||
|
* @throws MigrationNotFoundException
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
public function rollbackOne(string $migrationId): Migration
|
||||||
|
{
|
||||||
|
if (empty($this->migrations[$migrationId]))
|
||||||
|
// Migration not found.
|
||||||
|
throw new MigrationNotFoundException($migrationId);
|
||||||
|
|
||||||
|
// Rollback in a transaction.
|
||||||
|
return $this->database->transaction(function () use ($migrationId) {
|
||||||
|
// Clear preparations before rolling back.
|
||||||
|
$this->clearPreparations();
|
||||||
|
|
||||||
|
// Get the migration instance.
|
||||||
|
$migration = $this->newMigrationInstance($this->migrations[$migrationId]);
|
||||||
|
|
||||||
|
if (!empty($this->onRollbackStart))
|
||||||
|
// Fire rollback start event, if it is defined.
|
||||||
|
($this->onRollbackStart)($migration);
|
||||||
|
|
||||||
|
// Rollback migration.
|
||||||
|
$migration->down();
|
||||||
|
|
||||||
|
// Mark the migration as not migrated.
|
||||||
|
$this->markNotMigrated($migrationId);
|
||||||
|
|
||||||
|
// Do database preparations after rollback.
|
||||||
|
$this->prepare();
|
||||||
|
|
||||||
|
if (!empty($this->onRollbackEnd))
|
||||||
|
// Fire rollback start event, if it is defined.
|
||||||
|
($this->onRollbackEnd)($migration);
|
||||||
|
|
||||||
|
return $migration; // Return rolled back migration.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rollback the latest migrated migration
|
||||||
|
* @return Migration|null The rolled back migration, if there is one.
|
||||||
|
* @throws MigrationNotFoundException
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
public function rollbackLatest(): ?Migration
|
||||||
|
{
|
||||||
|
// Get latest migration ID.
|
||||||
|
$latestMigration = null;
|
||||||
|
foreach (array_reverse($this->migrations, true) as $migrationId => $migration)
|
||||||
|
{ // Exploring migrations in reverse order (the most recent first).
|
||||||
|
if ($migration["migrated"])
|
||||||
|
{ // The first migrated migration is taken as the latest one.
|
||||||
|
$latestMigration = $migrationId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($latestMigration))
|
||||||
|
// Rollback the latest migrated migration.
|
||||||
|
return $this->rollbackOne($latestMigration);
|
||||||
|
else
|
||||||
|
// No latest migration, do nothing and return NULL.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The preparations clear scripts filename regex.
|
||||||
|
*/
|
||||||
|
const string PREPARATIONS_CLEAR_SCRIPT_REGEX = "/^0+_(.*)$/";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear database preparations.
|
||||||
|
* Database preparations are cleared before migrations and should drop all functions and views used by tables that can
|
||||||
|
* be changed by migrations.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function clearPreparations(): void
|
||||||
|
{
|
||||||
|
foreach ($this->preparations as $preparationScript)
|
||||||
|
{ // Execute all preparations clear scripts.
|
||||||
|
if (preg_match(static::PREPARATIONS_CLEAR_SCRIPT_REGEX, $preparationScript))
|
||||||
|
// Only execute files with 00_ prefix.
|
||||||
|
$this->execPreparation($preparationScript);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do database preparations.
|
||||||
|
* Database preparations are executed after migrations and can create functions and views based on tables.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function prepare(): void
|
||||||
|
{
|
||||||
|
foreach ($this->preparations as $preparationScript)
|
||||||
|
{ // Execute all preparations that are not clear scripts.
|
||||||
|
if (!preg_match(static::PREPARATIONS_CLEAR_SCRIPT_REGEX, $preparationScript))
|
||||||
|
// Only execute files that aren't preparations clear scripts.
|
||||||
|
$this->execPreparation($preparationScript);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a given preparation script.
|
||||||
|
* @param string $preparationScript The preparation script to execute.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function execPreparation(string $preparationScript): void
|
||||||
|
{
|
||||||
|
// Read preparation script and execute it.
|
||||||
|
$this->database->execute(file_get_contents($preparationScript));
|
||||||
|
}
|
||||||
|
}
|
42
src/Migrations/MigrationsService.php
Normal file
42
src/Migrations/MigrationsService.php
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Migrations;
|
||||||
|
|
||||||
|
use Nest\Database\Migrations\Configuration\MigrationsConfiguration;
|
||||||
|
use Nest\Database\Exceptions\Query\MissingConditionValueException;
|
||||||
|
use Nest\Database\Exceptions\UnknownDatabaseException;
|
||||||
|
use Nest\Exceptions\Services\Configuration\UndefinedServiceConfigurationException;
|
||||||
|
use Nest\Types\Exceptions\IncompatibleTypeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nest database migrations service.
|
||||||
|
*/
|
||||||
|
trait MigrationsService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The database migrations instance.
|
||||||
|
* @var Migrations
|
||||||
|
*/
|
||||||
|
private Migrations $migrations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws UndefinedServiceConfigurationException
|
||||||
|
* @throws UnknownDatabaseException
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
* @throws IncompatibleTypeException
|
||||||
|
*/
|
||||||
|
protected function __nest__MigrationsService(): void
|
||||||
|
{
|
||||||
|
// Initialize database migrations instance.
|
||||||
|
$this->migrations = new Migrations($this, $this->getServiceConfiguration(MigrationsConfiguration::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database migrations service.
|
||||||
|
* @return Migrations The database migrations manager.
|
||||||
|
*/
|
||||||
|
public function migrations(): Migrations
|
||||||
|
{
|
||||||
|
return $this->migrations;
|
||||||
|
}
|
||||||
|
}
|
44
src/Migrations/Model/Migration.php
Normal file
44
src/Migrations/Model/Migration.php
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Migrations\Model;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Nest\Model\Entity;
|
||||||
|
use Nest\Model\EntityBlueprint;
|
||||||
|
use Nest\Types\Definitions\StringType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class of an executed migration.
|
||||||
|
*/
|
||||||
|
class Migration extends Entity
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Migration version ID.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration name.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration execution date.
|
||||||
|
* @var Carbon
|
||||||
|
*/
|
||||||
|
public Carbon $created_at;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function definition(EntityBlueprint $blueprint): EntityBlueprint
|
||||||
|
{
|
||||||
|
$blueprint->field("id", StringType::class)->primary();
|
||||||
|
$blueprint->field("name", StringType::class)->index();
|
||||||
|
$blueprint->createdAt();
|
||||||
|
|
||||||
|
return $blueprint;
|
||||||
|
}
|
||||||
|
}
|
111
src/PdoDatabase.php
Normal file
111
src/PdoDatabase.php
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database;
|
||||||
|
|
||||||
|
use Nest\Database\Events\PdoDatabaseAfterConnectionEvent;
|
||||||
|
use Nest\Database\Events\PdoDatabaseBeforeConnectionEvent;
|
||||||
|
use Nest\Events\HasEvents;
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class of a PDO database.
|
||||||
|
*/
|
||||||
|
abstract class PdoDatabase extends Database
|
||||||
|
{
|
||||||
|
use HasEvents;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The PDO connection to database.
|
||||||
|
* @var PDO
|
||||||
|
*/
|
||||||
|
protected PDO $pdo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string The PDO DSN to connect to the database.
|
||||||
|
*/
|
||||||
|
protected abstract function getDsn(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string The username to connect to the database.
|
||||||
|
*/
|
||||||
|
protected abstract function getUsername(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string The password to connect to the database.
|
||||||
|
*/
|
||||||
|
protected abstract function getPassword(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array PDO options.
|
||||||
|
* @see PDO::__construct
|
||||||
|
*/
|
||||||
|
protected function getPdoOptions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
// Persistent connection by default.
|
||||||
|
PDO::ATTR_PERSISTENT => true,
|
||||||
|
PDO::ERRMODE_EXCEPTION => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function connect(): void
|
||||||
|
{
|
||||||
|
$this->getEventsManager()->fire(new PdoDatabaseBeforeConnectionEvent($this));
|
||||||
|
|
||||||
|
// Connect to the database using PDO.
|
||||||
|
$this->pdo = new PDO($this->getDsn(), $this->getUsername(), $this->getPassword(), $this->getPdoOptions());
|
||||||
|
|
||||||
|
$this->getEventsManager()->fire(new PdoDatabaseAfterConnectionEvent($this, $this->pdo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function execute(string $statement, array $bindings = []): array
|
||||||
|
{
|
||||||
|
// Prepare the statement.
|
||||||
|
$statement = $this->getConnection()->prepare($statement);
|
||||||
|
foreach ($bindings as $param => $value)
|
||||||
|
{ // Assign bindings to the statement.
|
||||||
|
$statement->bindValue(is_int($param) ? $param + 1 : $param, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exception is thrown in case of failure.
|
||||||
|
$statement->execute();
|
||||||
|
|
||||||
|
// Fetching results.
|
||||||
|
$result = [];
|
||||||
|
while ($obj = $statement->fetchObject())
|
||||||
|
{ // Fetch next object while there is one.
|
||||||
|
$result[] = $obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return results.
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the connection to the database is active.
|
||||||
|
* @return bool True if the connection to the database is active, false otherwise.
|
||||||
|
*/
|
||||||
|
public function isConnected(): bool
|
||||||
|
{
|
||||||
|
return !empty($this->pdo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current connection to the database.
|
||||||
|
* @return PDO
|
||||||
|
*/
|
||||||
|
public function getConnection(): PDO
|
||||||
|
{
|
||||||
|
if (!$this->isConnected())
|
||||||
|
// If we are not connected, trying to connect to the database.
|
||||||
|
$this->connect();
|
||||||
|
|
||||||
|
return $this->pdo;
|
||||||
|
}
|
||||||
|
}
|
22
src/PostgreSql/Columns/Bit.php
Normal file
22
src/PostgreSql/Columns/Bit.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\PostgreSql\Columns;
|
||||||
|
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostgreSQL fixed-length bit string column type.
|
||||||
|
*/
|
||||||
|
class Bit implements Stringable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param int $size Size of the fixed-length bit string column type.
|
||||||
|
*/
|
||||||
|
public function __construct(protected int $size)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return "bit($this->size)";
|
||||||
|
}
|
||||||
|
}
|
22
src/PostgreSql/Columns/Char.php
Normal file
22
src/PostgreSql/Columns/Char.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\PostgreSql\Columns;
|
||||||
|
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostgreSQL fixed-length character string column type.
|
||||||
|
*/
|
||||||
|
class Char implements Stringable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param int $size Size of the fixed-length character string column type.
|
||||||
|
*/
|
||||||
|
public function __construct(protected int $size)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return "char($this->size)";
|
||||||
|
}
|
||||||
|
}
|
24
src/PostgreSql/Columns/Numeric.php
Normal file
24
src/PostgreSql/Columns/Numeric.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\PostgreSql\Columns;
|
||||||
|
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostgreSQL numeric column type.
|
||||||
|
*/
|
||||||
|
class Numeric implements Stringable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param int $precision Precision of the numeric column type.
|
||||||
|
* @param int $scale Scale of the numeric column type.
|
||||||
|
* @see https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-NUMERIC-DECIMAL
|
||||||
|
*/
|
||||||
|
public function __construct(protected int $precision, protected int $scale = 0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return "numeric($this->precision, $this->scale)";
|
||||||
|
}
|
||||||
|
}
|
22
src/PostgreSql/Columns/Time.php
Normal file
22
src/PostgreSql/Columns/Time.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\PostgreSql\Columns;
|
||||||
|
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostgreSQL time column type.
|
||||||
|
*/
|
||||||
|
class Time implements Stringable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param bool $timezone Add timezone to the value.
|
||||||
|
*/
|
||||||
|
public function __construct(protected bool $timezone = false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return "time" . ($this->timezone ? " with time zone" : "");
|
||||||
|
}
|
||||||
|
}
|
22
src/PostgreSql/Columns/Timestamp.php
Normal file
22
src/PostgreSql/Columns/Timestamp.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\PostgreSql\Columns;
|
||||||
|
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostgreSQL date + time column type.
|
||||||
|
*/
|
||||||
|
class Timestamp implements Stringable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param bool $timezone Add timezone to the value.
|
||||||
|
*/
|
||||||
|
public function __construct(protected bool $timezone = false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return "timestamp" . ($this->timezone ? " with time zone" : "");
|
||||||
|
}
|
||||||
|
}
|
62
src/PostgreSql/Columns/Type.php
Normal file
62
src/PostgreSql/Columns/Type.php
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\PostgreSql\Columns;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostgreSQL simple column types.
|
||||||
|
* See also parametric types:
|
||||||
|
* @see Bit
|
||||||
|
* @see Varbit
|
||||||
|
* @see Char
|
||||||
|
* @see Varchar
|
||||||
|
* @see Numeric
|
||||||
|
*/
|
||||||
|
enum Type: string
|
||||||
|
{
|
||||||
|
case Boolean = "boolean";
|
||||||
|
|
||||||
|
case SmallInt = "smallint";
|
||||||
|
case SmallSerial = "smallserial";
|
||||||
|
case Int = "int";
|
||||||
|
case Serial = "serial";
|
||||||
|
case BigInt = "bigint";
|
||||||
|
case BigSerial = "bigserial";
|
||||||
|
|
||||||
|
case Float = "float4";
|
||||||
|
case Double = "float8";
|
||||||
|
case Numeric = "numeric";
|
||||||
|
case Money = "money";
|
||||||
|
|
||||||
|
case Bit = "bit";
|
||||||
|
case Varbit = "varbit";
|
||||||
|
case ByteArray = "bytea";
|
||||||
|
|
||||||
|
case Char = "char";
|
||||||
|
case Varchar = "varchar";
|
||||||
|
case Text = "text";
|
||||||
|
case TextSearchQuery = "tsquery";
|
||||||
|
case TextSearchVector = "tsvector";
|
||||||
|
|
||||||
|
case Uuid = "uuid";
|
||||||
|
|
||||||
|
case Json = "json";
|
||||||
|
case JsonBinary = "jsonb";
|
||||||
|
case Xml = "xml";
|
||||||
|
|
||||||
|
case Date = "date";
|
||||||
|
case Time = "time";
|
||||||
|
case Timestamp = "timestamp";
|
||||||
|
case Interval = "interval";
|
||||||
|
|
||||||
|
case Cidr = "cidr";
|
||||||
|
case MacAddr = "macaddr";
|
||||||
|
case MacAddr8 = "macaddr8";
|
||||||
|
|
||||||
|
case Point = "point";
|
||||||
|
case Line = "line";
|
||||||
|
case Segment = "lseg";
|
||||||
|
case Path = "path";
|
||||||
|
case Polygon = "polygon";
|
||||||
|
case Box = "box";
|
||||||
|
case Circle = "circle";
|
||||||
|
}
|
22
src/PostgreSql/Columns/Varbit.php
Normal file
22
src/PostgreSql/Columns/Varbit.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\PostgreSql\Columns;
|
||||||
|
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostgreSQL variable-length bit string column type.
|
||||||
|
*/
|
||||||
|
class Varbit implements Stringable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param int $size Size of the variable-length bit string column type.
|
||||||
|
*/
|
||||||
|
public function __construct(protected int $size)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return "varbit($this->size)";
|
||||||
|
}
|
||||||
|
}
|
22
src/PostgreSql/Columns/Varchar.php
Normal file
22
src/PostgreSql/Columns/Varchar.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\PostgreSql\Columns;
|
||||||
|
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostgreSQL variable-length character string column type.
|
||||||
|
*/
|
||||||
|
class Varchar implements Stringable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param int $size Size of the variable-length character string column type.
|
||||||
|
*/
|
||||||
|
public function __construct(protected int $size)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return "varchar($this->size)";
|
||||||
|
}
|
||||||
|
}
|
23
src/PostgreSql/PostgreSql.php
Normal file
23
src/PostgreSql/PostgreSql.php
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\PostgreSql;
|
||||||
|
|
||||||
|
use Nest\Database\Configuration\DatabaseFactory;
|
||||||
|
use Nest\Database\Database;
|
||||||
|
|
||||||
|
class PostgreSql extends DatabaseFactory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function make(): Database
|
||||||
|
{
|
||||||
|
return new PostgreSqlDatabase(
|
||||||
|
$this->getRequiredConfig("host"),
|
||||||
|
$this->getRequiredConfig("database"),
|
||||||
|
$this->getRequiredConfig("username"),
|
||||||
|
$this->getRequiredConfig("password"),
|
||||||
|
$this->getOptionalConfig("port", 5432),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
501
src/PostgreSql/PostgreSqlAdapter.php
Normal file
501
src/PostgreSql/PostgreSqlAdapter.php
Normal file
|
@ -0,0 +1,501 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\PostgreSql;
|
||||||
|
|
||||||
|
use Nest\Database\DatabaseAdapter;
|
||||||
|
use Nest\Database\Migrations\Diff\TableColumn;
|
||||||
|
use Nest\Database\Migrations\Diff\TableForeignKey;
|
||||||
|
use Nest\Database\Migrations\Diff\TableIndex;
|
||||||
|
use Nest\Database\Query\Join\JoinBuilder;
|
||||||
|
use Nest\Database\Query\Raw;
|
||||||
|
use Nest\Database\Query\Where\ConditionBuilder;
|
||||||
|
use Nest\Database\Exceptions\Query\MissingConditionValueException;
|
||||||
|
use Override;
|
||||||
|
use function Nest\Utils\format_object_name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostgreSQL database queries adapter.
|
||||||
|
*/
|
||||||
|
class PostgreSqlAdapter extends DatabaseAdapter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function newTransaction(): void
|
||||||
|
{
|
||||||
|
$this->database->execute("BEGIN;");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function rollbackTransaction(): void
|
||||||
|
{
|
||||||
|
$this->database->execute("ROLLBACK;");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function commitTransaction(): void
|
||||||
|
{
|
||||||
|
$this->database->execute("COMMIT;");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[Override] public function createTable(string $tableName, bool $ifNotExists = false): void
|
||||||
|
{
|
||||||
|
$this->database->execute("CREATE TABLE ".($ifNotExists ? "IF NOT EXISTS " : "").format_object_name($tableName)."();");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function dropTable(string $tableName, bool $ifExists = false): void
|
||||||
|
{
|
||||||
|
$this->database->execute("DROP TABLE ".($ifExists ? "IF EXISTS " : "").format_object_name($tableName).";");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function renameTable(string $tableName, string $newTableName): void
|
||||||
|
{
|
||||||
|
$this->database->execute("ALTER TABLE ".format_object_name($tableName)." RENAME TO ".format_object_name($newTableName).";");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function addTableColumn(TableColumn $tableColumn, bool $ifNotExists = false): void
|
||||||
|
{
|
||||||
|
// SQL base query.
|
||||||
|
$sql = "ADD COLUMN".($ifNotExists ? " IF NOT EXISTS" : "")." ".format_object_name($tableColumn->name)." $tableColumn->type";
|
||||||
|
|
||||||
|
// Set nullable or not.
|
||||||
|
if (!empty($tableColumn->nullable))
|
||||||
|
$sql .= " NULL";
|
||||||
|
else
|
||||||
|
$sql .= " NOT NULL";
|
||||||
|
|
||||||
|
// Set default value if there is one.
|
||||||
|
if (isset($tableColumn->default))
|
||||||
|
$sql .= " DEFAULT ".((string) $tableColumn->default);
|
||||||
|
|
||||||
|
if (!empty($tableColumn->primary))
|
||||||
|
$sql .= " PRIMARY KEY";
|
||||||
|
|
||||||
|
// Execute add table column.
|
||||||
|
$this->database->execute("ALTER TABLE ".format_object_name($tableColumn->table->getTableName())." {$sql};");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function setTableColumnIndex(TableColumn $tableColumn): void
|
||||||
|
{
|
||||||
|
// Create index name.
|
||||||
|
$indexName = $tableColumn->table->getTableName()."_".$tableColumn->name."_index";
|
||||||
|
|
||||||
|
// Drop existing index, if it exists.
|
||||||
|
$this->database->execute("DROP INDEX IF EXISTS {$indexName};");
|
||||||
|
|
||||||
|
// SQL base query.
|
||||||
|
$sql = "CREATE ".($tableColumn->index->unique ? "UNIQUE " : "")."INDEX $indexName ON ".format_object_name($tableColumn->table->getTableName());
|
||||||
|
|
||||||
|
// Set index method if specified.
|
||||||
|
if (!empty($tableColumn->index->method))
|
||||||
|
$sql .= " USING ({$tableColumn->index->method})";
|
||||||
|
|
||||||
|
// Set column name in new index.
|
||||||
|
$sql .= " ({$tableColumn->name})";
|
||||||
|
|
||||||
|
// Execute add column index.
|
||||||
|
$this->database->execute("$sql;");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function renameTableColumn(TableColumn $tableColumn, string $newName): void
|
||||||
|
{
|
||||||
|
$this->database->execute("ALTER TABLE ".format_object_name($tableColumn->table->getTableName()).
|
||||||
|
" RENAME COLUMN ".format_object_name($tableColumn->name).
|
||||||
|
" TO ".format_object_name($newName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function alterTableColumn(TableColumn $tableColumn): void
|
||||||
|
{
|
||||||
|
// SQL base query.
|
||||||
|
$sql = "ALTER COLUMN ".format_object_name($tableColumn->name);
|
||||||
|
|
||||||
|
if (!empty($tableColumn->type))
|
||||||
|
$sql .= " TYPE $tableColumn->type";
|
||||||
|
|
||||||
|
// Set nullable or not.
|
||||||
|
if (isset($tableColumn->nullable))
|
||||||
|
{
|
||||||
|
if ($tableColumn->nullable)
|
||||||
|
$sql .= " DROP NOT NULL";
|
||||||
|
else
|
||||||
|
$sql .= " SET NOT NULL";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default value if there is one.
|
||||||
|
if (isset($tableColumn->default))
|
||||||
|
$sql .= " SET DEFAULT ".((string) $tableColumn->default);
|
||||||
|
|
||||||
|
// Execute add table column.
|
||||||
|
$this->database->execute("ALTER TABLE ".format_object_name($tableColumn->table->getTableName())." {$sql};");
|
||||||
|
|
||||||
|
if (!empty($tableColumn->primary))
|
||||||
|
// Set the column as a primary key.
|
||||||
|
$this->database->execute("ALTER TABLE ".format_object_name($tableColumn->table->getTableName())." ADD PRIMARY KEY (".format_object_name($tableColumn->name).");");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function dropTableColumn(string $tableName, string $columnName, bool $ifExists = false): void
|
||||||
|
{
|
||||||
|
// SQL base query.
|
||||||
|
$sql = "DROP COLUMN ".($ifExists ? "IF EXISTS " : "").format_object_name($columnName);
|
||||||
|
|
||||||
|
// Execute drop table column.
|
||||||
|
$this->database->execute("ALTER TABLE ".format_object_name($tableName)." {$sql};");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function dropConstraint(string $tableName, string $constraintName, bool $ifExists = false): void
|
||||||
|
{
|
||||||
|
// SQL base query.
|
||||||
|
$sql = "DROP CONSTRAINT ".($ifExists ? "IF EXISTS " : "").format_object_name($constraintName);
|
||||||
|
|
||||||
|
// Execute drop table constraint.
|
||||||
|
$this->database->execute("ALTER TABLE ".format_object_name($tableName)." {$sql};");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function createForeignKey(TableForeignKey $foreignKey): void
|
||||||
|
{
|
||||||
|
// Format columns list.
|
||||||
|
$columns = implode(", ", array_map(fn ($column) => format_object_name($column), $foreignKey->columns));
|
||||||
|
|
||||||
|
// Format referenced columns list.
|
||||||
|
$referencedColumns = implode(", ", array_map(fn ($column) => format_object_name($column), $foreignKey->referencedColumns));
|
||||||
|
|
||||||
|
// Base SQL.
|
||||||
|
$sql = "ADD FOREIGN KEY ($columns) REFERENCES ".format_object_name($foreignKey->referencedTable)."($referencedColumns)";
|
||||||
|
|
||||||
|
// Set ON DELETE and ON UPDATE actions.
|
||||||
|
if (!empty($foreignKey->onDelete))
|
||||||
|
$sql .= " ON DELETE {$foreignKey->onDelete}";
|
||||||
|
if (!empty($foreignKey->onUpdate))
|
||||||
|
$sql .= " ON UPDATE {$foreignKey->onUpdate}";
|
||||||
|
|
||||||
|
// Execute add foreign key constraint.
|
||||||
|
$this->database->execute("ALTER TABLE ".format_object_name($foreignKey->table->getTableName())." {$sql};");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function createIndex(TableIndex $index, bool $ifNotExists = false): void
|
||||||
|
{
|
||||||
|
// Base SQL.
|
||||||
|
$sql = "CREATE (UNIQUE) INDEX".($ifNotExists ? " IF NOT EXISTS" : "")." $index->name ON ".format_object_name($index->table->getTableName());
|
||||||
|
|
||||||
|
if (!empty($index->method))
|
||||||
|
$sql .= " USING $index->method";
|
||||||
|
|
||||||
|
$sql .= "(";
|
||||||
|
|
||||||
|
if (!empty($index->rawExpression))
|
||||||
|
$sql .= "($index->rawExpression)";
|
||||||
|
else
|
||||||
|
{ // Format indexed columns.
|
||||||
|
// Format columns list.
|
||||||
|
$columns = implode(", ", array_map(fn ($column) => format_object_name($column), $index->columns));
|
||||||
|
$sql .= "($columns)";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set index order, if defined.
|
||||||
|
if (!empty($index->order))
|
||||||
|
$sql .= " $index->order";
|
||||||
|
if (!empty($index->nulls))
|
||||||
|
$sql .= " $index->nulls";
|
||||||
|
|
||||||
|
$sql .= ")";
|
||||||
|
|
||||||
|
// Execute index creation.
|
||||||
|
$this->database->execute("$sql;");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function renameIndex(string $indexName, string $newName, bool $ifExists = false): void
|
||||||
|
{
|
||||||
|
// Execute index rename.
|
||||||
|
$this->database->execute("ALTER INDEX".($ifExists ? "IF EXISTS " : "")." ".format_object_name($indexName).
|
||||||
|
" RENAME TO ".format_object_name($newName).";");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function dropIndex(TableIndex $index, bool $ifExists = false): void
|
||||||
|
{
|
||||||
|
// Execute index drop.
|
||||||
|
$this->database->execute("DROP INDEX".($ifExists ? "IF EXISTS " : "")." ".format_object_name($index->name).";");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build JOIN clauses from join builders.
|
||||||
|
* @param JoinBuilder[] $joins Joins to build.
|
||||||
|
* @return array{string, array} The built SQL with its bindings, if there are some. Bindings can be empty or non-existent.
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
*/
|
||||||
|
protected function buildJoins(array $joins): array
|
||||||
|
{
|
||||||
|
// Return empty join clauses if there are no conditions.
|
||||||
|
if (empty($joins)) return ["", []];
|
||||||
|
|
||||||
|
$sql = "";
|
||||||
|
$bindings = [];
|
||||||
|
|
||||||
|
// Build all join clauses.
|
||||||
|
foreach ($joins as $join)
|
||||||
|
{ // For each join clause, build its conditions and append the built bindings.
|
||||||
|
[$onSql, $onBindings] = $this->buildWhere($join->getConditions(), "ON");
|
||||||
|
$sql .= "$join->type JOIN \"$join->table\" $onSql ";
|
||||||
|
array_push($bindings, ...$onBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return all joins clauses SQL and bindings.
|
||||||
|
return [$sql, $bindings];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build WHERE clause from conditions builders.
|
||||||
|
* @param ConditionBuilder[] $wheres Conditions to build.
|
||||||
|
* @param string $keyword The keyword to use. "WHERE" by default, but can be replaced by "ON" for JOIN conditions.
|
||||||
|
* @return array{string, array} The built SQL with its bindings, if there are some. Bindings can be empty or non-existent.
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
*/
|
||||||
|
protected function buildWhere(array $wheres, string $keyword = "WHERE"): array
|
||||||
|
{
|
||||||
|
// Return empty where clause if there are no conditions.
|
||||||
|
if (empty($wheres)) return ["", []];
|
||||||
|
|
||||||
|
// Build all conditions.
|
||||||
|
$conditions = array_map(fn (ConditionBuilder $condition) => $condition->toSql(), $wheres);
|
||||||
|
// Join all conditions with AND.
|
||||||
|
$sql = implode("AND", array_column($conditions, 0));
|
||||||
|
// Join all values bindings.
|
||||||
|
$bindings = array_merge(...array_column($conditions, 1));
|
||||||
|
|
||||||
|
// Return built WHERE clause.
|
||||||
|
return ["$keyword $sql", $bindings];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function buildSelect(string $tableName, array $selected, array $joins, array $wheres, ?int $limit = null): Raw
|
||||||
|
{
|
||||||
|
$bindings = []; // Initialize empty bindings.
|
||||||
|
|
||||||
|
// SQL base query: select the table columns.
|
||||||
|
$sql = "SELECT ".implode(", ", array_map(function (Raw|string|array $select) use (&$bindings) {
|
||||||
|
if ($select instanceof Raw)
|
||||||
|
{ // Raw selection.
|
||||||
|
// Add its bindings to the list.
|
||||||
|
array_push($bindings, ...$select->bindings);
|
||||||
|
// Return raw SQL.
|
||||||
|
return (string) $select;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// Format selection and return its SQL.
|
||||||
|
return format_object_name((string) $select);
|
||||||
|
}, $selected));
|
||||||
|
|
||||||
|
// Append FROM clause.
|
||||||
|
$sql .= " FROM ".format_object_name($tableName);
|
||||||
|
|
||||||
|
// Append JOIN clauses.
|
||||||
|
[$joinsSql, $joinsBindings] = $this->buildJoins($joins);
|
||||||
|
if (!empty($joinsSql))
|
||||||
|
{
|
||||||
|
$sql .= " $joinsSql";
|
||||||
|
array_push($bindings, ...$joinsBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append WHERE clause.
|
||||||
|
[$whereSql, $whereBindings] = $this->buildWhere($wheres);
|
||||||
|
if (!empty($whereSql))
|
||||||
|
{
|
||||||
|
$sql .= " $whereSql";
|
||||||
|
array_push($bindings, ...$whereBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append LIMIT clause.
|
||||||
|
if (!is_null($limit))
|
||||||
|
$sql .= " LIMIT $limit";
|
||||||
|
|
||||||
|
// Return raw SQL with its bindings.
|
||||||
|
return new Raw($sql, $bindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function select(string $tableName, array $selected, array $joins, array $wheres, ?int $limit = null): array
|
||||||
|
{
|
||||||
|
// Build SELECT query.
|
||||||
|
$raw = $this->buildSelect($tableName, $selected, $joins, $wheres, $limit);
|
||||||
|
|
||||||
|
// Execute built query and return result.
|
||||||
|
return $this->database->execute($raw->sql, $raw->bindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function insert(string $tableName, array $columns, array $rows, bool $returning = false): array|bool
|
||||||
|
{
|
||||||
|
// SQL base query: insert into the table columns.
|
||||||
|
$sql = "INSERT INTO ".format_object_name($tableName).
|
||||||
|
" (".implode(", ", array_map(fn (string $column) => format_object_name($column), $columns)).")";
|
||||||
|
|
||||||
|
// Add values (with bindings).
|
||||||
|
$sql .= " VALUES ";
|
||||||
|
$rowsValues = [];
|
||||||
|
$bindings = [];
|
||||||
|
foreach ($rows as $row)
|
||||||
|
{ // Create a new VALUES tuple for each row.
|
||||||
|
// Initialize row parts of the current row.
|
||||||
|
$rowParts = [];
|
||||||
|
$rowBindings = [];
|
||||||
|
|
||||||
|
foreach ($row as $value)
|
||||||
|
{ // For each row value, add a row part corresponding to its type.
|
||||||
|
if ($value instanceof Raw)
|
||||||
|
// Add a new raw row part.
|
||||||
|
$rowParts[] = (string) $value;
|
||||||
|
else
|
||||||
|
{ // Add a new row part, with its binding.
|
||||||
|
$rowParts[] = "?";
|
||||||
|
$rowBindings[] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append current row tuple to values, with its bindings.
|
||||||
|
$rowsValues[] = "(".implode(", ", $rowParts).")";
|
||||||
|
array_push($bindings, ...$rowBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build values SQL and append it to the query.
|
||||||
|
$sql .= implode(", ", $rowsValues);
|
||||||
|
|
||||||
|
if ($returning)
|
||||||
|
// Inserted rows shall be returned, indicating it in the query.
|
||||||
|
$sql .= "RETURNING *";
|
||||||
|
|
||||||
|
// SQL query end.
|
||||||
|
$sql .= ";";
|
||||||
|
|
||||||
|
// Execute INSERT query.
|
||||||
|
$result = $this->database->execute($sql, $bindings);
|
||||||
|
|
||||||
|
if ($returning)
|
||||||
|
// Returning inserted rows.
|
||||||
|
return $result;
|
||||||
|
else
|
||||||
|
// INSERT executed successfully.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function update(string $tableName, array $set, array $wheres, bool $returning = false): array|bool
|
||||||
|
{
|
||||||
|
// SQL base query: update the given table.
|
||||||
|
$sql = "UPDATE ".format_object_name($tableName);
|
||||||
|
$bindings = []; // Initialize empty bindings.
|
||||||
|
|
||||||
|
// Initialize SET clause.
|
||||||
|
$sql .= " SET ";
|
||||||
|
$setParts = [];
|
||||||
|
foreach ($set as $columnName => $value)
|
||||||
|
{ // For each set value, add a set part corresponding to its type.
|
||||||
|
$currentSet = format_object_name($columnName)." = ";
|
||||||
|
if ($value instanceof Raw)
|
||||||
|
// Add a new raw set part.
|
||||||
|
$setParts[] = $currentSet.((string) $value);
|
||||||
|
else
|
||||||
|
{ // Add a new set part, with its binding.
|
||||||
|
$setParts[] = $currentSet."?";
|
||||||
|
$bindings[] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append SET columns.
|
||||||
|
$sql .= implode(", ", $setParts);
|
||||||
|
|
||||||
|
// Append WHERE clause.
|
||||||
|
[$whereSql, $whereBindings] = $this->buildWhere($wheres);
|
||||||
|
if (!empty($whereSql))
|
||||||
|
{
|
||||||
|
$sql .= " $whereSql";
|
||||||
|
array_push($bindings, ...$whereBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($returning)
|
||||||
|
// Inserted rows shall be returned, indicating it in the query.
|
||||||
|
$sql .= "RETURNING *";
|
||||||
|
|
||||||
|
// SQL query end.
|
||||||
|
$sql .= ";";
|
||||||
|
|
||||||
|
// Execute UPDATE query.
|
||||||
|
$result = $this->database->execute($sql, $bindings);
|
||||||
|
|
||||||
|
if ($returning)
|
||||||
|
// Returning updated rows.
|
||||||
|
return $result;
|
||||||
|
else
|
||||||
|
// UPDATE executed successfully.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function delete(string $tableName, array $wheres): void
|
||||||
|
{
|
||||||
|
// SQL base query: delete from the given table.
|
||||||
|
$sql = "DELETE FROM ".format_object_name($tableName);
|
||||||
|
|
||||||
|
// Append WHERE clause.
|
||||||
|
[$whereSql, $bindings] = $this->buildWhere($wheres);
|
||||||
|
if (!empty($whereSql))
|
||||||
|
$sql .= " $whereSql";
|
||||||
|
|
||||||
|
// SQL query end.
|
||||||
|
$sql .= ";";
|
||||||
|
|
||||||
|
// Execute DELETE query.
|
||||||
|
$this->database->execute($sql, $bindings);
|
||||||
|
}
|
||||||
|
}
|
51
src/PostgreSql/PostgreSqlDatabase.php
Normal file
51
src/PostgreSql/PostgreSqlDatabase.php
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\PostgreSql;
|
||||||
|
|
||||||
|
use Nest\Database\DatabaseAdapter;
|
||||||
|
use Nest\Database\PdoDatabase;
|
||||||
|
|
||||||
|
class PostgreSqlDatabase extends PdoDatabase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $host Database host or unix socket directory path.
|
||||||
|
* @param string $database Database name.
|
||||||
|
* @param string $username Username to use to connect to the database.
|
||||||
|
* @param string $password Password to use to connect to the database.
|
||||||
|
* @param int $port Database port.
|
||||||
|
*/
|
||||||
|
public function __construct(protected string $host, protected string $database, protected string $username, protected string $password, protected int $port = 5432)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] public function getQueriesAdapter(): DatabaseAdapter
|
||||||
|
{
|
||||||
|
return new PostgreSqlAdapter($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] protected function getDsn(): string
|
||||||
|
{
|
||||||
|
return "pgsql:host={$this->host};port={$this->port};dbname={$this->database}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] protected function getUsername(): string
|
||||||
|
{
|
||||||
|
return $this->username;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
#[\Override] protected function getPassword(): string
|
||||||
|
{
|
||||||
|
return $this->password;
|
||||||
|
}
|
||||||
|
}
|
43
src/Query/DeleteQuery.php
Normal file
43
src/Query/DeleteQuery.php
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Query;
|
||||||
|
|
||||||
|
use Nest\Database\Database;
|
||||||
|
use Nest\Database\Query\Where\HasWhere;
|
||||||
|
use Nest\Database\Exceptions\Query\MissingConditionValueException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder of a DELETE query.
|
||||||
|
*/
|
||||||
|
class DeleteQuery
|
||||||
|
{
|
||||||
|
use HasWhere;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new DELETE query.
|
||||||
|
* @param Database $database The database on which to execute the query.
|
||||||
|
* @param string $table Base table of the DELETE query.
|
||||||
|
*/
|
||||||
|
public function __construct(protected Database $database, protected string $table)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the database on which to execute the query.
|
||||||
|
* @return Database
|
||||||
|
*/
|
||||||
|
public function getDatabase(): Database
|
||||||
|
{
|
||||||
|
return $this->database;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute DELETE query.
|
||||||
|
* @return void
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
*/
|
||||||
|
public function execute(): void
|
||||||
|
{
|
||||||
|
// Execute built query and return result.
|
||||||
|
$this->database->getQueriesAdapter()->delete($this->table, $this->wheres);
|
||||||
|
}
|
||||||
|
}
|
75
src/Query/InsertQuery.php
Normal file
75
src/Query/InsertQuery.php
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Query;
|
||||||
|
|
||||||
|
use Nest\Database\Database;
|
||||||
|
use function Nest\Utils\array_unique_quick;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder of an INSERT query.
|
||||||
|
*/
|
||||||
|
class InsertQuery
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Values to insert.
|
||||||
|
* @var array<array<string, Raw|string|int|float|null>>
|
||||||
|
*/
|
||||||
|
protected array $values = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new INSERT query.
|
||||||
|
* @param Database $database The database on which to execute the query.
|
||||||
|
* @param string $table Base table of the INSERT query.
|
||||||
|
*/
|
||||||
|
public function __construct(protected Database $database, protected string $table)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset values to insert.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function resetValues(): static
|
||||||
|
{
|
||||||
|
$this->values = [];
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add values to insert.
|
||||||
|
* @param array<string, Raw|string|int|float|null> ...$row Each parameter is an associative array that represents one row data.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function values(array ...$row): static
|
||||||
|
{
|
||||||
|
// Append all rows to values array.
|
||||||
|
array_push($this->values, ...$row);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute insert query.
|
||||||
|
* @param bool $returning True to return inserted objects.
|
||||||
|
* @return object[]|bool Inserted objects if returning is true, true otherwise.
|
||||||
|
*/
|
||||||
|
public function execute(bool $returning = false): array|bool
|
||||||
|
{
|
||||||
|
// Find all inserted columns.
|
||||||
|
$columns = array_unique_quick(array_merge(...array_map(fn (array $row) => array_keys($row), $this->values)));
|
||||||
|
|
||||||
|
// Prepare rows to insert (fill rows with DEFAULT values, if some of them are missing something).
|
||||||
|
$rows = array_map(function (array $rowValues) use ($columns) {
|
||||||
|
// Initialize current row.
|
||||||
|
$row = [];
|
||||||
|
|
||||||
|
foreach ($columns as $column)
|
||||||
|
{ // Get value for each column: from row values if it is defined, use DEFAULT otherwise.
|
||||||
|
$row[$column] = $rowValues[$column] ?? new Raw("DEFAULT");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $row; // Return full row ready to insert.
|
||||||
|
}, $this->values);
|
||||||
|
|
||||||
|
// Perform insert.
|
||||||
|
return $this->database->getQueriesAdapter()->insert($this->table, $columns, $rows, $returning);
|
||||||
|
}
|
||||||
|
}
|
78
src/Query/Join/HasJoin.php
Normal file
78
src/Query/Join/HasJoin.php
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Query\Join;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add join capacity to a query builder.
|
||||||
|
*/
|
||||||
|
trait HasJoin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Join clauses.
|
||||||
|
* @var JoinBuilder[]
|
||||||
|
*/
|
||||||
|
protected array $joins = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset JOIN clauses.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function resetJoins(): static
|
||||||
|
{
|
||||||
|
$this->joins = [];
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new join clause.
|
||||||
|
* @param string $type Type of the join clause.
|
||||||
|
* @param string $table Joined table.
|
||||||
|
* @return JoinBuilder New join clause builder.
|
||||||
|
*/
|
||||||
|
public function join(string $type, string $table): JoinBuilder
|
||||||
|
{
|
||||||
|
$join = new JoinBuilder($this, $type, $table);
|
||||||
|
$this->joins[] = $join;
|
||||||
|
return $join;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new inner join clause.
|
||||||
|
* @param string $table Joined table.
|
||||||
|
* @return JoinBuilder New join clause builder.
|
||||||
|
*/
|
||||||
|
public function innerJoin(string $table): JoinBuilder
|
||||||
|
{
|
||||||
|
return $this->join(JoinBuilder::INNER, $table);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new outer join clause.
|
||||||
|
* @param string $table Joined table.
|
||||||
|
* @return JoinBuilder New join clause builder.
|
||||||
|
*/
|
||||||
|
public function outerJoin(string $table): JoinBuilder
|
||||||
|
{
|
||||||
|
return $this->join(JoinBuilder::OUTER, $table);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new left join clause.
|
||||||
|
* @param string $table Joined table.
|
||||||
|
* @return JoinBuilder New join clause builder.
|
||||||
|
*/
|
||||||
|
public function leftJoin(string $table): JoinBuilder
|
||||||
|
{
|
||||||
|
return $this->join(JoinBuilder::LEFT, $table);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new right join clause.
|
||||||
|
* @param string $table Joined table.
|
||||||
|
* @return JoinBuilder New join clause builder.
|
||||||
|
*/
|
||||||
|
public function rightJoin(string $table): JoinBuilder
|
||||||
|
{
|
||||||
|
return $this->join(JoinBuilder::RIGHT, $table);
|
||||||
|
}
|
||||||
|
}
|
75
src/Query/Join/JoinBuilder.php
Normal file
75
src/Query/Join/JoinBuilder.php
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Query\Join;
|
||||||
|
|
||||||
|
use Nest\Database\Query\Where\ConditionBuilder;
|
||||||
|
|
||||||
|
class JoinBuilder
|
||||||
|
{
|
||||||
|
const string INNER = "INNER";
|
||||||
|
const string OUTER = "OUTER";
|
||||||
|
const string LEFT = "LEFT";
|
||||||
|
const string RIGHT = "RIGHT";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $query The calling query.
|
||||||
|
* @param string $type Join type (INNER, OUTER, LEFT, RIGHT, ...).
|
||||||
|
* @param string $table The table to join.
|
||||||
|
*/
|
||||||
|
public function __construct(protected mixed $query, public readonly string $type, public readonly string $table)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ON conditions of the JOIN clause.
|
||||||
|
* @var ConditionBuilder[]
|
||||||
|
*/
|
||||||
|
protected array $on = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset ON conditions.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function resetOn(): static
|
||||||
|
{
|
||||||
|
$this->on = [];
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a ON condition.
|
||||||
|
* @param string|callable $column The column of the condition, or a condition builder callable.
|
||||||
|
* @param string $operator The operator of the condition (or the value column if no value is passed).
|
||||||
|
* @param string|null $valueColumn The value column of the condition.
|
||||||
|
* @return mixed The query.
|
||||||
|
*/
|
||||||
|
public function on(string|callable $column, string $operator, string $valueColumn = null): mixed
|
||||||
|
{
|
||||||
|
if (is_callable($column))
|
||||||
|
{ // Callable condition builder.
|
||||||
|
$this->on[] = $column(new ConditionBuilder());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // Simple condition, registering it.
|
||||||
|
if (!empty($operator) && empty($valueColumn))
|
||||||
|
{ // If there are 2 parameters, considering the second one as the value, with default operator.
|
||||||
|
$valueColumn = $operator;
|
||||||
|
// Default operator: "=".
|
||||||
|
$operator = "=";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the simple condition.
|
||||||
|
$this->on[] = (new ConditionBuilder())->column($column)->operator($operator)->column($valueColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get join conditions.
|
||||||
|
* @return ConditionBuilder[] ON conditions of the JOIN clause.
|
||||||
|
*/
|
||||||
|
public function getConditions(): array
|
||||||
|
{
|
||||||
|
return $this->on;
|
||||||
|
}
|
||||||
|
}
|
92
src/Query/QueryBuilder.php
Normal file
92
src/Query/QueryBuilder.php
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Query;
|
||||||
|
|
||||||
|
use Nest\Database\Database;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database query builder.
|
||||||
|
*/
|
||||||
|
class QueryBuilder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Base table of the query.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected string $table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a new query builder.
|
||||||
|
* @param Database $database The database on which to execute the query.
|
||||||
|
* @param string|null $table Base table of the query.
|
||||||
|
*/
|
||||||
|
public function __construct(protected Database $database, ?string $table = null)
|
||||||
|
{
|
||||||
|
if (!empty($table)) $this->setTable($table);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the database on which to execute the query.
|
||||||
|
* @return Database
|
||||||
|
*/
|
||||||
|
public function getDatabase(): Database
|
||||||
|
{
|
||||||
|
return $this->database;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the table to use in the query.
|
||||||
|
* @param string $table The table to use in the query.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setTable(string $table): static
|
||||||
|
{
|
||||||
|
$this->table = $table;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the table to use in the query.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getTable(): string
|
||||||
|
{
|
||||||
|
return $this->table;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new SELECT query.
|
||||||
|
* @return SelectQuery A select query.
|
||||||
|
*/
|
||||||
|
public function newSelect(): SelectQuery
|
||||||
|
{
|
||||||
|
return new SelectQuery($this->getDatabase(), $this->getTable());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new INSERT query.
|
||||||
|
* @return InsertQuery An INSERT query.
|
||||||
|
*/
|
||||||
|
public function newInsert(): InsertQuery
|
||||||
|
{
|
||||||
|
return new InsertQuery($this->getDatabase(), $this->getTable());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new UPDATE query.
|
||||||
|
* @return UpdateQuery An UPDATE query.
|
||||||
|
*/
|
||||||
|
public function newUpdate(): UpdateQuery
|
||||||
|
{
|
||||||
|
return new UpdateQuery($this->getDatabase(), $this->getTable());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new DELETE query.
|
||||||
|
* @return DeleteQuery A DELETE query.
|
||||||
|
*/
|
||||||
|
public function newDelete(): DeleteQuery
|
||||||
|
{
|
||||||
|
return new DeleteQuery($this->getDatabase(), $this->getTable());
|
||||||
|
}
|
||||||
|
}
|
23
src/Query/Raw.php
Normal file
23
src/Query/Raw.php
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw SQL content.
|
||||||
|
*/
|
||||||
|
readonly class Raw
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create new raw SQL content.
|
||||||
|
* @param string $sql Raw SQL content.
|
||||||
|
* @param array $bindings Raw SQL bindings.
|
||||||
|
*/
|
||||||
|
public function __construct(public string $sql, public array $bindings = [])
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->sql;
|
||||||
|
}
|
||||||
|
}
|
135
src/Query/SelectQuery.php
Normal file
135
src/Query/SelectQuery.php
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Query;
|
||||||
|
|
||||||
|
use Nest\Database\Database;
|
||||||
|
use Nest\Database\Query\Join\HasJoin;
|
||||||
|
use Nest\Database\Query\Where\HasWhere;
|
||||||
|
use Nest\Database\Exceptions\Query\MissingConditionValueException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder of a SELECT query.
|
||||||
|
*/
|
||||||
|
class SelectQuery
|
||||||
|
{
|
||||||
|
use HasJoin;
|
||||||
|
use HasWhere;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selected columns.
|
||||||
|
* @var (Raw|string)[]
|
||||||
|
*/
|
||||||
|
protected array $selected = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limit of results to get.
|
||||||
|
* NULL = no limit.
|
||||||
|
* @var int|null
|
||||||
|
*/
|
||||||
|
protected ?int $limit = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new SELECT query.
|
||||||
|
* @param Database $database The database on which to execute the query.
|
||||||
|
* @param string $table Base table of the SELECT query.
|
||||||
|
*/
|
||||||
|
public function __construct(protected Database $database, protected string $table)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the database on which to execute the query.
|
||||||
|
* @return Database
|
||||||
|
*/
|
||||||
|
public function getDatabase(): Database
|
||||||
|
{
|
||||||
|
return $this->database;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get retrieved table name.
|
||||||
|
* @return string Selected table name.
|
||||||
|
*/
|
||||||
|
public function getTableName(): string
|
||||||
|
{
|
||||||
|
return $this->table;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset selected columns.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function resetSelect(): static
|
||||||
|
{
|
||||||
|
$this->selected = [];
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add selected columns.
|
||||||
|
* @param string|Raw ...$selectedColumns Selected columns.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function select(string|Raw ...$selectedColumns): static
|
||||||
|
{
|
||||||
|
// Append SELECTed columns.
|
||||||
|
array_push($this->selected, ...$selectedColumns);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add raw selected SQL.
|
||||||
|
* @param string ...$rawSelect Raw selected SQL.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function selectRaw(string ...$rawSelect): static
|
||||||
|
{
|
||||||
|
// Append raw SQL SELECT.
|
||||||
|
array_push($this->selected, ...array_map(fn (string $rawSql) => new Raw($rawSql), $rawSelect));
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the new limit of results.
|
||||||
|
* @param int|null $limit Limit of retrieved results. NULL to remove the limit.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function limit(?int $limit): static
|
||||||
|
{
|
||||||
|
$this->limit = $limit;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build SELECT query.
|
||||||
|
* @return Raw SQL and its bindings.
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
*/
|
||||||
|
public function build(): Raw
|
||||||
|
{
|
||||||
|
// Build SELECT query and return its result.
|
||||||
|
return $this->database->getQueriesAdapter()->buildSelect($this->table,
|
||||||
|
// Select all columns by default.
|
||||||
|
empty($this->selected) ? [new Raw("*")] : $this->selected,
|
||||||
|
$this->joins,
|
||||||
|
$this->wheres,
|
||||||
|
$this->limit,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute SELECT query.
|
||||||
|
* @return object[] SELECT query result.
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
*/
|
||||||
|
public function execute(): array
|
||||||
|
{
|
||||||
|
// Execute built query and return result.
|
||||||
|
return $this->database->getQueriesAdapter()->select($this->table,
|
||||||
|
// Select all columns by default.
|
||||||
|
empty($this->selected) ? [new Raw("*")] : $this->selected,
|
||||||
|
$this->joins,
|
||||||
|
$this->wheres,
|
||||||
|
$this->limit,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
80
src/Query/UpdateQuery.php
Normal file
80
src/Query/UpdateQuery.php
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Query;
|
||||||
|
|
||||||
|
use Nest\Database\Database;
|
||||||
|
use Nest\Database\Query\Where\HasWhere;
|
||||||
|
use Nest\Database\Exceptions\Query\MissingConditionValueException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder of an UPDATE query.
|
||||||
|
*/
|
||||||
|
class UpdateQuery
|
||||||
|
{
|
||||||
|
use HasWhere;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set values.
|
||||||
|
* @var array<string, Raw|string|int|float|null>
|
||||||
|
*/
|
||||||
|
protected array $set = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new UPDATE query.
|
||||||
|
* @param Database $database The database on which to execute the query.
|
||||||
|
* @param string $table Base table of the UPDATE query.
|
||||||
|
*/
|
||||||
|
public function __construct(protected Database $database, protected string $table)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the database on which to execute the query.
|
||||||
|
* @return Database
|
||||||
|
*/
|
||||||
|
public function getDatabase(): Database
|
||||||
|
{
|
||||||
|
return $this->database;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset set values.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function resetSet(): static
|
||||||
|
{
|
||||||
|
$this->set = [];
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add set columns values.
|
||||||
|
* @param string|array<string, Raw|string|int|float|null> $column The name of the column in column mode, or an associative array of all set columns with their corresponding value.
|
||||||
|
* @param Raw|string|int|float|null $value The value to set, when setting in column mode (with a column as first parameter).
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function set(string|array $column, Raw|string|int|float|null $value = null): static
|
||||||
|
{
|
||||||
|
if (is_array($column))
|
||||||
|
{ // The first parameter is an array, adding set values in array mode.
|
||||||
|
$this->set = array_merge($this->set, $column);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // The first parameter is a string, adding set value in column mode.
|
||||||
|
$this->set[$column] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute update query.
|
||||||
|
* @param bool $returning True to return inserted objects.
|
||||||
|
* @return object[]|bool Updated objects if returning is true, true otherwise.
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
*/
|
||||||
|
public function execute(bool $returning = false): array|bool
|
||||||
|
{
|
||||||
|
// Execute built query and return result.
|
||||||
|
return $this->database->getQueriesAdapter()->update($this->table, $this->set, $this->wheres, $returning);
|
||||||
|
}
|
||||||
|
}
|
215
src/Query/Where/ConditionBuilder.php
Normal file
215
src/Query/Where/ConditionBuilder.php
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Query\Where;
|
||||||
|
|
||||||
|
use Nest\Database\Exceptions\Query\MissingConditionValueException;
|
||||||
|
use function Nest\Utils\format_object_name;
|
||||||
|
|
||||||
|
class ConditionBuilder
|
||||||
|
{
|
||||||
|
protected string $column;
|
||||||
|
protected string $operator;
|
||||||
|
protected mixed $value;
|
||||||
|
protected string $valueColumn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* And conditions to append.
|
||||||
|
* @var ConditionBuilder[]
|
||||||
|
*/
|
||||||
|
protected array $ands = [];
|
||||||
|
/**
|
||||||
|
* Or conditions to append.
|
||||||
|
* @var ConditionBuilder[]
|
||||||
|
*/
|
||||||
|
protected array $ors = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set column of the condition.
|
||||||
|
* When called a second time when there's no value, set the value column.
|
||||||
|
* @param string $column Column (or value column) to use.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function column(string $column): static
|
||||||
|
{
|
||||||
|
if (empty($this->column) || !empty($this->value))
|
||||||
|
$this->column = $column;
|
||||||
|
else
|
||||||
|
$this->valueColumn = $column;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set operator of the condition.
|
||||||
|
* @param string $operator Operator to use.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function operator(string $operator): static
|
||||||
|
{
|
||||||
|
$this->operator = $operator;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set value of the condition.
|
||||||
|
* @param mixed $value Value to use.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function value(mixed $value): static
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set "=" operator.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function equals(): static
|
||||||
|
{
|
||||||
|
return $this->operator("=");
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Set "LIKE" operator.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function like(): static
|
||||||
|
{
|
||||||
|
return $this->operator("LIKE");
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Set "EXISTS" operator.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function exists(): static
|
||||||
|
{
|
||||||
|
return $this->operator("EXISTS");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append and conditions.
|
||||||
|
* @param callable ...$conditionBuilder And conditions builders.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function and(callable ...$conditionBuilder): static
|
||||||
|
{
|
||||||
|
foreach ($conditionBuilder as $conditionBuilderCallable)
|
||||||
|
{
|
||||||
|
$this->ands[] = $conditionBuilderCallable(new ConditionBuilder());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append or conditions.
|
||||||
|
* @param callable ...$conditionBuilder Or conditions builders.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function or(callable ...$conditionBuilder): static
|
||||||
|
{
|
||||||
|
foreach ($conditionBuilder as $conditionBuilderCallable)
|
||||||
|
{
|
||||||
|
$this->ors[] = $conditionBuilderCallable(new ConditionBuilder());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build SQL AND subcondition.
|
||||||
|
* @param ConditionBuilder $condition The subcondition builder.
|
||||||
|
* @return array{string, string[]} The built SQL with its bindings, if there are some. Bindings can be empty or non-existent.
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
*/
|
||||||
|
private function buildAndSql(ConditionBuilder $condition): array
|
||||||
|
{
|
||||||
|
[$sql, $bindings] = $condition->toSql();
|
||||||
|
return ["AND $sql", $bindings];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Build SQL OR subcondition.
|
||||||
|
* @param ConditionBuilder $condition The subcondition builder.
|
||||||
|
* @return array{string, string[]} The built SQL with its bindings, if there are some. Bindings can be empty or non-existent.
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
*/
|
||||||
|
private function buildOrSql(ConditionBuilder $condition): array
|
||||||
|
{
|
||||||
|
[$sql, $bindings] = $condition->toSql();
|
||||||
|
return ["OR $sql", $bindings];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build SQL of the condition.
|
||||||
|
* @return array{string, string[]} The built SQL with its bindings, if there are some. Bindings can be empty or non-existent.
|
||||||
|
* @throws MissingConditionValueException
|
||||||
|
*/
|
||||||
|
public function toSql(): array
|
||||||
|
{
|
||||||
|
// Build subconditions.
|
||||||
|
$subconditions = [
|
||||||
|
...array_map(
|
||||||
|
fn (ConditionBuilder $condition) => $this->buildAndSql($condition),
|
||||||
|
$this->ands,
|
||||||
|
), ...array_map(
|
||||||
|
fn (ConditionBuilder $condition) => $this->buildOrSql($condition),
|
||||||
|
$this->ors,
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
// SQL to append after current condition.
|
||||||
|
$appendSql = "";
|
||||||
|
// Values bindings to append after current condition.
|
||||||
|
$appendBindings = [];
|
||||||
|
foreach ($subconditions as [$sql, $bindings])
|
||||||
|
{ // Append current subcondition to SQL.
|
||||||
|
$appendSql .= $sql;
|
||||||
|
// Append current subcondition bindings.
|
||||||
|
array_push($appendBindings, ...$bindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($this->column))
|
||||||
|
{ // If a column is defined, build the condition on it.
|
||||||
|
if (($this->operator ?? "=") === "EXISTS" || (empty($this->operator) && empty($this->value) && empty($this->valueColumn)))
|
||||||
|
// "EXISTS" operator, or no operator nor value are defined.
|
||||||
|
return ["(EXISTS ".format_object_name($this->column)." $appendSql)", $appendBindings];
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!empty($this->value))
|
||||||
|
{ // Condition value is a bound value.
|
||||||
|
if (is_array($this->value))
|
||||||
|
{
|
||||||
|
$valueSql = "(".implode(",", array_fill(0, count($this->value), "?")).")";
|
||||||
|
$bindings = $this->value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$valueSql = "?";
|
||||||
|
$bindings = [$this->value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif (!empty($this->valueColumn))
|
||||||
|
{ // Condition value is an SQL column.
|
||||||
|
$valueSql = format_object_name($this->valueColumn);
|
||||||
|
$bindings = [];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// A condition value is missing.
|
||||||
|
throw new MissingConditionValueException("(".format_object_name($this->column)." $this->operator [...MISSING_CONDITION_VALUE...])");
|
||||||
|
|
||||||
|
// Default operator is "=".
|
||||||
|
$operator = $this->operator ?? "=";
|
||||||
|
|
||||||
|
return ["(".format_object_name($this->column)." $operator $valueSql $appendSql)", [...$bindings, ...$appendBindings]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // No column defined, just return subconditions, if there are some.
|
||||||
|
if (str_starts_with($appendSql, "OR")) $appendSql = trim(substr($appendSql, 2));
|
||||||
|
if (str_starts_with($appendSql, "AND")) $appendSql = trim(substr($appendSql, 3));
|
||||||
|
return ["($appendSql)", $appendBindings];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
154
src/Query/Where/HasWhere.php
Normal file
154
src/Query/Where/HasWhere.php
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Query\Where;
|
||||||
|
|
||||||
|
use Nest\Database\Exceptions\Query\NoPrimaryFieldException;
|
||||||
|
use Nest\Model\Entities;
|
||||||
|
use Nest\Model\Entity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add where capacity to a query builder.
|
||||||
|
*/
|
||||||
|
trait HasWhere
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Conditions in where clause.
|
||||||
|
* @var ConditionBuilder[]
|
||||||
|
*/
|
||||||
|
protected array $wheres = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset WHERE conditions.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function resetWheres(): static
|
||||||
|
{
|
||||||
|
$this->wheres = [];
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a where condition.
|
||||||
|
* @param string|callable $column The column of the condition, or a condition builder callable.
|
||||||
|
* @param mixed|null $operator The operator of the condition (or the value if no value is passed).
|
||||||
|
* @param mixed|null $value The value of the condition.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function where(string|callable $column, mixed $operator = null, mixed $value = null): static
|
||||||
|
{
|
||||||
|
if (is_callable($column))
|
||||||
|
{ // Callable condition builder.
|
||||||
|
$this->wheres[] = $column(new ConditionBuilder());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // Simple condition, registering it.
|
||||||
|
if (!empty($operator) && empty($value))
|
||||||
|
{ // If there are 2 parameters, considering the second one as the value, with default operator.
|
||||||
|
$value = $operator;
|
||||||
|
$operator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($value))
|
||||||
|
{ // A value is defined.
|
||||||
|
// Default operator: "=".
|
||||||
|
if (empty($operator)) $operator = "=";
|
||||||
|
|
||||||
|
// Create the simple condition.
|
||||||
|
$this->wheres[] = (new ConditionBuilder())->column($column)->operator($operator)->value($value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // No value is given, considering a simple existence check.
|
||||||
|
$this->whereExists($column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a where IN condition.
|
||||||
|
* @param string $column The column of the condition.
|
||||||
|
* @param array $values The values of the condition.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function whereIn(string $column, array $values): static
|
||||||
|
{
|
||||||
|
return $this->where($column, "IN", $values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a where condition on a column as a value.
|
||||||
|
* @param string $column The column of the condition.
|
||||||
|
* @param string $operator The operator of the condition (or the value column if no value is passed).
|
||||||
|
* @param string|null $valueColumn The value column of the condition.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function whereColumn(string $column, string $operator, ?string $valueColumn = null): static
|
||||||
|
{
|
||||||
|
if (!empty($operator) && empty($valueColumn))
|
||||||
|
{ // If there are 2 parameters, considering the second one as the value, with default operator.
|
||||||
|
$valueColumn = $operator;
|
||||||
|
$operator = "=";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the simple condition.
|
||||||
|
$this->wheres[] = (new ConditionBuilder())->column($column)->operator($operator)->column($valueColumn);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a where exists condition.
|
||||||
|
* @param string $column The column to check for existence.
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function whereExists(string $column): static
|
||||||
|
{
|
||||||
|
// Create an existence check.
|
||||||
|
$this->wheres[] = (new ConditionBuilder())->column($column)->exists();
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add condition to find given entities.
|
||||||
|
* @param Entity|Entities $entities Entity or entities to find.
|
||||||
|
* @return $this
|
||||||
|
* @throws NoPrimaryFieldException
|
||||||
|
*/
|
||||||
|
public function whereKeyOf(Entity|Entities $entities): static
|
||||||
|
{
|
||||||
|
// Normalize parameter to a simple entities array.
|
||||||
|
if ($entities instanceof Entities)
|
||||||
|
$entitiesArray = $entities->get();
|
||||||
|
else
|
||||||
|
$entitiesArray = [$entities];
|
||||||
|
|
||||||
|
// Initialize entities conditions.
|
||||||
|
$entitiesConditions = [];
|
||||||
|
foreach ($entitiesArray as $entity)
|
||||||
|
{ // Add condition for each entity to find.
|
||||||
|
// Get current entity primary fields and normalize it to an array.
|
||||||
|
$primaryFields = $entity->getPrimaryFields();
|
||||||
|
if (!is_array($primaryFields)) $primaryFields = [$primaryFields];
|
||||||
|
|
||||||
|
if (empty($primaryFields))
|
||||||
|
// If there are no primary fields, thrown an exception to ensure not to do something on EVERY rows.
|
||||||
|
throw new NoPrimaryFieldException($entity);
|
||||||
|
|
||||||
|
// Create conditions for current entity.
|
||||||
|
$entityConditions = [];
|
||||||
|
foreach ($primaryFields as $primaryField)
|
||||||
|
// Create a condition builder for each field.
|
||||||
|
$entityConditions[] = fn (ConditionBuilder $condition) =>
|
||||||
|
$condition->column($primaryField)->equals()->value($entity->$primaryField);
|
||||||
|
|
||||||
|
// Add the full entity condition to the entities conditions array.
|
||||||
|
$entitiesConditions[] = fn (ConditionBuilder $condition) => $condition->and(...$entityConditions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add condition to match every entities.
|
||||||
|
return $this->where(fn (ConditionBuilder $condition) => (
|
||||||
|
$condition->or(...$entitiesConditions)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
139
src/Transactions/Transaction.php
Normal file
139
src/Transactions/Transaction.php
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Nest\Database\Transactions;
|
||||||
|
|
||||||
|
use Nest\Database\Database;
|
||||||
|
use Nest\Exceptions\Database\NotCurrentTransactionException;
|
||||||
|
use Symfony\Component\Uid\Uuid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instance of a database transaction.
|
||||||
|
*/
|
||||||
|
class Transaction
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* UUID of the transaction.
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private string $uuid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the transaction is active (started and not committed nor rolled back).
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private bool $active = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtransaction of the current transaction.
|
||||||
|
* @var Transaction|null
|
||||||
|
*/
|
||||||
|
protected ?Transaction $childTransaction = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new transaction for the given database.
|
||||||
|
* @param Database $database The database.
|
||||||
|
* @param Transaction|null $parentTransaction The parent transaction, if there is one.
|
||||||
|
*/
|
||||||
|
public function __construct(protected Database $database, protected ?Transaction $parentTransaction = null)
|
||||||
|
{
|
||||||
|
$this->uuid = Uuid::v4()->toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get UUID of the transaction.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getUuid(): string
|
||||||
|
{
|
||||||
|
return $this->uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the parent transaction.
|
||||||
|
* @return Transaction|null
|
||||||
|
*/
|
||||||
|
public function getParent(): ?Transaction
|
||||||
|
{
|
||||||
|
return $this->parentTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current child transaction of this transaction.
|
||||||
|
* @param Transaction|null $transaction The child transaction. NULL if there is no child transaction.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setChild(?Transaction $transaction): void
|
||||||
|
{
|
||||||
|
$this->childTransaction = $transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the transaction.
|
||||||
|
* @return void
|
||||||
|
* @throws NotCurrentTransactionException
|
||||||
|
*/
|
||||||
|
public function start(): void
|
||||||
|
{
|
||||||
|
// Transaction activation state changed.
|
||||||
|
$this->active = true;
|
||||||
|
$this->database->onTransactionStateChanged($this);
|
||||||
|
|
||||||
|
// Start the transaction.
|
||||||
|
$this->database->getQueriesAdapter()->newTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commit the transaction.
|
||||||
|
* @return void
|
||||||
|
* @throws NotCurrentTransactionException
|
||||||
|
*/
|
||||||
|
public function commit(): void
|
||||||
|
{
|
||||||
|
if (!$this->isActive())
|
||||||
|
// Do not commit a non-active transaction.
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!empty($this->childTransaction))
|
||||||
|
// First commit the child transaction, if there is one.
|
||||||
|
$this->childTransaction->commit();
|
||||||
|
|
||||||
|
// Transaction activation state changed.
|
||||||
|
$this->active = false;
|
||||||
|
$this->database->onTransactionStateChanged($this);
|
||||||
|
|
||||||
|
// Commit the current transaction.
|
||||||
|
$this->database->getQueriesAdapter()->commitTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rollback the transaction.
|
||||||
|
* @return void
|
||||||
|
* @throws NotCurrentTransactionException
|
||||||
|
*/
|
||||||
|
public function rollback(): void
|
||||||
|
{
|
||||||
|
if (!$this->isActive())
|
||||||
|
// Do not rollback a non-active transaction.
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!empty($this->childTransaction))
|
||||||
|
// First rollback the child transaction, if there is one.
|
||||||
|
$this->childTransaction->rollback();
|
||||||
|
|
||||||
|
// Transaction activation state changed.
|
||||||
|
$this->active = false;
|
||||||
|
$this->database->onTransactionStateChanged($this);
|
||||||
|
|
||||||
|
// Rollback the current transaction.
|
||||||
|
$this->database->getQueriesAdapter()->rollbackTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the transaction is still active (not committed nor rolled back).
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isActive(): bool
|
||||||
|
{
|
||||||
|
return $this->active;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue