1
0
Fork 0
mirror of synced 2024-06-27 02:31:04 +12:00

feat: inital commit for multiple db pools

This commit is contained in:
Christy Jacob 2022-06-14 16:57:57 +02:00
parent f1afc45ae2
commit 039d9f0ead
10 changed files with 479 additions and 122 deletions

2
.env
View file

@ -23,6 +23,8 @@ _APP_DB_SCHEMA=appwrite
_APP_DB_USER=user
_APP_DB_PASS=password
_APP_DB_ROOT_PASS=rootsecretpassword
_APP_PROJECT_DB=db_fra1_02=mysql://user:password@mariadb:3306/appwrite
_APP_CONSOLE_DB=db_fra1_01=mysql://user:password@mariadb:3306/appwrite
_APP_STORAGE_DEVICE=Local
_APP_STORAGE_S3_ACCESS_KEY=
_APP_STORAGE_S3_SECRET=

View file

@ -390,6 +390,17 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => 'database',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'logo',
'type' => Database::VAR_STRING,

View file

@ -104,6 +104,7 @@ App::post('/v1/projects')
'domains' => null,
'auths' => $auths,
'search' => implode(' ', [$projectId, $name]),
'database'
]));
/** @var array $collections */
$collections = Config::getParam('collections', []);

View file

@ -66,7 +66,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
do {
try {
$attempts++;
$db = $register->get('dbPool')->get();
$pool = $register->get('poolForConsole')->get();
$redis = $register->get('redisPool')->get();
break; // leave the do-while if successful
} catch (\Exception $e) {

View file

@ -23,6 +23,8 @@ use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Extend\Exception;
use Appwrite\Auth\Auth;
use Appwrite\Database\DatabasePool;
use Appwrite\DSN\DSN;
use Appwrite\Event\Audit;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
@ -442,29 +444,98 @@ $register->set('logger', function () {
$adapter = new $classname($providerConfig);
return new Logger($adapter);
});
$register->set('dbPool', function () {
$register->set('poolForConsole', function () {
$dbs = App::getEnv('_APP_CONSOLE_DB', '');
$dbs = explode(',', $dbs);
$pools = new DatabasePool();
foreach ($dbs as $db) {
$db = explode('=', $db);
$name = $db[0];
$dsn = new DSN($db[1]);
// var_dump($dsn->getHost(), $dsn->getPort(), $dsn->getDatabase(), $dsn->getUser(), $dsn->getPassword());
$pool = new PDOPool(
(new PDOConfig())
->withHost($dsn->getHost())
->withPort($dsn->getPort())
->withDbName($dsn->getDatabase())
->withCharset('utf8mb4')
->withUsername($dsn->getUser())
->withPassword($dsn->getPassword())
->withOptions([
PDO::ATTR_ERRMODE => App::isDevelopment() ? PDO::ERRMODE_WARNING : PDO::ERRMODE_SILENT, // If in production mode, warnings are not displayed
]),
64
);
$pools->add($name, $pool);
}
return $pools;
});
$register->set('poolForProject', function () {
// Register DB connection
$dbHost = App::getEnv('_APP_DB_HOST', '');
$dbPort = App::getEnv('_APP_DB_PORT', '');
$dbUser = App::getEnv('_APP_DB_USER', '');
$dbPass = App::getEnv('_APP_DB_PASS', '');
$dbScheme = App::getEnv('_APP_DB_SCHEMA', '');
// $dbHost = App::getEnv('_APP_DB_HOST', '');
// $dbPort = App::getEnv('_APP_DB_PORT', '');
// $dbUser = App::getEnv('_APP_DB_USER', '');
// $dbPass = App::getEnv('_APP_DB_PASS', '');
// $dbScheme = App::getEnv('_APP_DB_SCHEMA', '');
$pool = new PDOPool(
(new PDOConfig())
->withHost($dbHost)
->withPort($dbPort)
->withDbName($dbScheme)
->withCharset('utf8mb4')
->withUsername($dbUser)
->withPassword($dbPass)
->withOptions([
PDO::ATTR_ERRMODE => App::isDevelopment() ? PDO::ERRMODE_WARNING : PDO::ERRMODE_SILENT, // If in production mode, warnings are not displayed
]),
64
);
// var_dump($dbHost, $dbPort, $dbScheme, $dbUser, $dbPass);
// $pool = new PDOPool(
// (new PDOConfig())
// ->withHost($dbHost)
// ->withPort($dbPort)
// ->withDbName($dbScheme)
// ->withCharset('utf8mb4')
// ->withUsername($dbUser)
// ->withPassword($dbPass)
// ->withOptions([
// PDO::ATTR_ERRMODE => App::isDevelopment() ? PDO::ERRMODE_WARNING : PDO::ERRMODE_SILENT, // If in production mode, warnings are not displayed
// ]),
// 64
// );
return $pool;
// return $pool;
// $dbForConsole = App::getEnv('_APP_CONSOLE_DB', '');
// $dbForConsole = explode('=', $dbForConsole);
// $name = $dbForConsole[0];
// $dsn = new DSN($dbForConsole[1]);
$dbs = App::getEnv('_APP_PROJECT_DB', '');
$dbs = explode(',', $dbs);
$pools = new DatabasePool();
foreach ($dbs as $db) {
$db = explode('=', $db);
$name = $db[0];
$dsn = new DSN($db[1]);
// var_dump($dsn->getHost(), $dsn->getPort(), $dsn->getDatabase(), $dsn->getUser(), $dsn->getPassword());
$pool = new PDOPool(
(new PDOConfig())
->withHost($dsn->getHost())
->withPort($dsn->getPort())
->withDbName($dsn->getDatabase())
->withCharset('utf8mb4')
->withUsername($dsn->getUser())
->withPassword($dsn->getPassword())
->withOptions([
PDO::ATTR_ERRMODE => App::isDevelopment() ? PDO::ERRMODE_WARNING : PDO::ERRMODE_SILENT, // If in production mode, warnings are not displayed
]),
64
);
$pools->add($name, $pool);
}
return $pools;
});
$register->set('redisPool', function () {
$redisHost = App::getEnv('_APP_REDIS_HOST', '');
@ -867,6 +938,10 @@ App::setResource('console', function () {
App::setResource('dbForProject', function ($db, $cache, $project) {
$cache = new Cache(new RedisCache($cache));
// Get name of database from the projects collection in the console DB
// $dbName = $project->getAttribute('database','');
$database = new Database(new MariaDB($db), $cache);
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$database->setNamespace("_{$project->getId()}");

192
composer.lock generated
View file

@ -689,16 +689,16 @@
},
{
"name": "guzzlehttp/psr7",
"version": "2.2.1",
"version": "2.2.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "c94a94f120803a18554c1805ef2e539f8285f9a2"
"reference": "a119247127ff95789a2d95c347cd74721fbedaa4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/c94a94f120803a18554c1805ef2e539f8285f9a2",
"reference": "c94a94f120803a18554c1805ef2e539f8285f9a2",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/a119247127ff95789a2d95c347cd74721fbedaa4",
"reference": "a119247127ff95789a2d95c347cd74721fbedaa4",
"shasum": ""
},
"require": {
@ -784,7 +784,7 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.2.1"
"source": "https://github.com/guzzle/psr7/tree/2.2.2"
},
"funding": [
{
@ -800,7 +800,7 @@
"type": "tidelift"
}
],
"time": "2022-03-20T21:55:58+00:00"
"time": "2022-06-08T19:55:23+00:00"
},
{
"name": "influxdb/influxdb-php",
@ -1704,88 +1704,6 @@
],
"time": "2022-02-25T11:15:52+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.26.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-ctype": "*"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.26-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.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": "2022-05-24T11:49:31+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.26.0",
@ -2905,21 +2823,21 @@
},
{
"name": "webmozart/assert",
"version": "1.10.0",
"version": "1.11.0",
"source": {
"type": "git",
"url": "https://github.com/webmozarts/assert.git",
"reference": "6964c76c7804814a842473e0c8fd15bab0f18e25"
"reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25",
"reference": "6964c76c7804814a842473e0c8fd15bab0f18e25",
"url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991",
"reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"symfony/polyfill-ctype": "^1.8"
"ext-ctype": "*",
"php": "^7.2 || ^8.0"
},
"conflict": {
"phpstan/phpstan": "<0.12.20",
@ -2957,9 +2875,9 @@
],
"support": {
"issues": "https://github.com/webmozarts/assert/issues",
"source": "https://github.com/webmozarts/assert/tree/1.10.0"
"source": "https://github.com/webmozarts/assert/tree/1.11.0"
},
"time": "2021-03-09T10:59:23+00:00"
"time": "2022-06-03T18:03:27+00:00"
}
],
"packages-dev": [
@ -5086,6 +5004,88 @@
],
"time": "2022-04-18T20:38:04+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.26.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-ctype": "*"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.26-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.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": "2022-05-24T11:49:31+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.26.0",

View file

@ -143,6 +143,8 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_PROJECT_DB
- _APP_CONSOLE_DB
- _APP_SMTP_HOST
- _APP_SMTP_PORT
- _APP_SMTP_SECURE
@ -221,6 +223,7 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB
- _APP_USAGE_STATS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
@ -251,6 +254,7 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
@ -311,6 +315,7 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB
- *x-env-storage
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
@ -344,6 +349,7 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
@ -375,6 +381,7 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
@ -409,6 +416,7 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
@ -439,6 +447,7 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB
- _APP_FUNCTIONS_TIMEOUT
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
@ -551,6 +560,7 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB
- _APP_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_ABUSE
@ -576,11 +586,8 @@ services:
environment:
- _APP_ENV
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
- _APP_USAGE_AGGREGATION_INTERVAL

134
src/Appwrite/DSN/DSN.php Normal file
View file

@ -0,0 +1,134 @@
<?php
namespace Appwrite\DSN;
class DSN {
/**
* @var string
*/
protected string $scheme;
/**
* @var ?string
*/
protected ?string $user;
/**
* @var ?string
*/
protected ?string $password;
/**
* @var string
*/
protected string $host;
/**
* @var ?string
*/
protected ?string $port;
/**
* @var ?string
*/
protected ?string $database;
/**
* @var ?string
*/
protected ?string $query;
/**
* Construct
*
* Construct a new DSN object
*
* @param string $dsn
*/
public function __construct(string $dsn)
{
$parts = parse_url($dsn);
if (!$parts) {
throw new \InvalidArgumentException("Unable to parse DSN: $dsn");
}
$this->scheme = $parts['scheme'] ?? null;
$this->user = $parts['user'] ?? null;
$this->password = $parts['pass'] ?? null;
$this->host = $parts['host'] ?? null;
$this->port = $parts['port'] ?? null;
$this->database = $parts['path'] ?? null;
$this->query = $parts['query'] ?? null;
}
/**
* Return the scheme.
*
* @return string
*/
public function getScheme(): string
{
return $this->scheme;
}
/**
* Return the user.
*
* @return ?string
*/
public function getUser(): ?string
{
return $this->user;
}
/**
* Return the password.
*
* @return ?string
*/
public function getPassword(): ?string
{
return $this->password;
}
/**
* Return the host
*
* @return string
*/
public function getHost(): string
{
return $this->host;
}
/**
* Return the port
*
* @return ?string
*/
public function getPort(): ?string
{
return $this->port;
}
/**
* Return the database
*
* @return ?string
*/
public function getDatabase(): ?string
{
return ltrim($this->database, '/');
}
/**
* Return the query string
*
* @return ?string
*/
public function getQuery(): ?string
{
return $this->query;
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace Appwrite\Database;
use PDO;
use Appwrite\Extend\Exception;
use Swoole\Database\PDOPool;
use Swoole\Database\PDOProxy;
class DatabasePool {
protected array $pools = [];
public function add(string $name, PDOPool $dbPool): void
{
$this->pools[$name] = $dbPool;
}
public function get(string $name = 'console'): ?PDOProxy
{
$pool = $this->pools[$name] ?? null;
if ($pool === null) {
throw new Exception("Database Pool with name : $name not found. Please check the value of _APP_PROJECT_DB in .env", 500);
}
return $pool->get();
}
public function put(PDOProxy $db, string $name = 'console'): void
{
$pool = $this->pools[$name] ?? null;
if ($pool === null) {
throw new Exception("Database Pool with name : $name not found. Cannot put", 500);
}
$pool->put($db);
}
}

View file

@ -0,0 +1,90 @@
<?php
namespace Appwrite\Tests;
use Appwrite\DSN\DSN;
use PHPUnit\Framework\TestCase;
class DSNTest extends TestCase
{
public function setUp(): void
{
}
public function tearDown(): void
{
}
public function testSuccess(): void
{
$dsn = new DSN("mariadb://user:password@localhost:3306/database?charset=utf8&timezone=UTC");
$this->assertEquals("mariadb", $dsn->getScheme());
$this->assertEquals("user", $dsn->getUser());
$this->assertEquals("password", $dsn->getPassword());
$this->assertEquals("localhost", $dsn->getHost());
$this->assertEquals("3306", $dsn->getPort());
$this->assertEquals("database", $dsn->getDatabase());
$this->assertEquals("charset=utf8&timezone=UTC", $dsn->getQuery());
$dsn = new DSN("mariadb://user@localhost:3306/database?charset=utf8&timezone=UTC");
$this->assertEquals("mariadb", $dsn->getScheme());
$this->assertEquals("user", $dsn->getUser());
$this->assertNull($dsn->getPassword());
$this->assertEquals("localhost", $dsn->getHost());
$this->assertEquals("3306", $dsn->getPort());
$this->assertEquals("database", $dsn->getDatabase());
$this->assertEquals("charset=utf8&timezone=UTC", $dsn->getQuery());
$dsn = new DSN("mariadb://user@localhost/database?charset=utf8&timezone=UTC");
$this->assertEquals("mariadb", $dsn->getScheme());
$this->assertEquals("user", $dsn->getUser());
$this->assertNull($dsn->getPassword());
$this->assertEquals("localhost", $dsn->getHost());
$this->assertNull($dsn->getPort());
$this->assertEquals("database", $dsn->getDatabase());
$this->assertEquals("charset=utf8&timezone=UTC", $dsn->getQuery());
$dsn = new DSN("mariadb://user@localhost?charset=utf8&timezone=UTC");
$this->assertEquals("mariadb", $dsn->getScheme());
$this->assertEquals("user", $dsn->getUser());
$this->assertNull($dsn->getPassword());
$this->assertEquals("localhost", $dsn->getHost());
$this->assertNull($dsn->getPort());
$this->assertEmpty($dsn->getDatabase());
$this->assertEquals("charset=utf8&timezone=UTC", $dsn->getQuery());
$dsn = new DSN("mariadb://user@localhost");
$this->assertEquals("mariadb", $dsn->getScheme());
$this->assertEquals("user", $dsn->getUser());
$this->assertNull($dsn->getPassword());
$this->assertEquals("localhost", $dsn->getHost());
$this->assertNull($dsn->getPort());
$this->assertEmpty($dsn->getDatabase());
$this->assertNull($dsn->getQuery());
$dsn = new DSN("mariadb://user:@localhost");
$this->assertEquals("mariadb", $dsn->getScheme());
$this->assertEquals("user", $dsn->getUser());
$this->assertEmpty($dsn->getPassword());
$this->assertEquals("localhost", $dsn->getHost());
$this->assertNull($dsn->getPort());
$this->assertEmpty($dsn->getDatabase());
$this->assertNull($dsn->getQuery());
$dsn = new DSN("mariadb://localhost");
$this->assertEquals("mariadb", $dsn->getScheme());
$this->assertNull($dsn->getUser());
$this->assertNull($dsn->getPassword());
$this->assertEquals("localhost", $dsn->getHost());
$this->assertNull($dsn->getPort());
$this->assertEmpty($dsn->getDatabase());
$this->assertNull($dsn->getQuery());
}
public function testFail(): void
{
$this->expectException(\InvalidArgumentException::class);
$dsn = new DSN("mariadb://");
}
}