Refactor database handling to streamline DSN retrieval for documents and tables databases

This commit is contained in:
ArnabChatterjee20k
2025-10-30 12:27:52 +05:30
parent fae79e5c3f
commit 4e79259c4e
9 changed files with 113 additions and 157 deletions
+11
View File
@@ -78,6 +78,17 @@ return [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('documentsDatabase'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 256,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('logo'),
'type' => Database::VAR_STRING,
+61 -31
View File
@@ -51,6 +51,65 @@ use Utopia\Validator\Text;
use Utopia\Validator\URL;
use Utopia\Validator\WhiteList;
function getDatabaseDSN(string $databasetype, $region): string
{
$databases = [];
$databaseKeys = [];
/**
* @var string|null $databaseOverride
*/
$databaseOverride = '';
$dbScheme = '';
switch ($databasetype) {
case 'documentsDatabase':
$databases = Config::getParam('pools-documentsdb', []);
$databaseKeys = System::getEnv('_APP_DATABASE_DOCUMENTSDB_KEYS', '');
$databaseOverride = System::getEnv('_APP_DATABASE_DOCUMENTSDB_OVERRIDE');
$dbScheme = System::getEnv('_APP_DB_HOST_DOCUMENTSDB', 'mongodb');
break;
default:
// legacy/tablesdb
$databases = Config::getParam('pools-database', []);
$databaseKeys = System::getEnv('_APP_DATABASE_KEYS', '');
$databaseOverride = System::getEnv('_APP_DATABASE_OVERRIDE');
$dbScheme = System::getEnv('_APP_DB_HOST', 'mysql');
break;
}
if ($region !== 'default') {
$keys = explode(',', $databaseKeys);
$databases = array_filter($keys, function ($value) use ($region) {
return str_contains($value, $region);
});
}
$index = \array_search($databaseOverride, $databases);
if ($index !== false) {
$dsn = $databases[$index];
} else {
$dsn = $databases[array_rand($databases)];
}
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn, $sharedTables)) {
$schema = 'appwrite';
$database = 'appwrite';
$namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', '');
$dsn = $schema . '://' . $dsn . '?database=' . $database;
if (!empty($namespace)) {
$dsn .= '&namespace=' . $namespace;
}
}
try {
// for validation
new DSN($dsn);
} catch (\InvalidArgumentException) {
$dsn = $dbScheme.'://' . $dsn;
}
return $dsn;
}
App::init()
->groups(['projects'])
->inject('project')
@@ -138,37 +197,7 @@ App::post('/v1/projects')
throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project.");
}
$databases = Config::getParam('pools-database', []);
if ($region !== 'default') {
$databaseKeys = System::getEnv('_APP_DATABASE_KEYS', '');
$keys = explode(',', $databaseKeys);
$databases = array_filter($keys, function ($value) use ($region) {
return str_contains($value, $region);
});
}
$databaseOverride = System::getEnv('_APP_DATABASE_OVERRIDE');
$index = \array_search($databaseOverride, $databases);
if ($index !== false) {
$dsn = $databases[$index];
} else {
$dsn = $databases[array_rand($databases)];
}
// TODO: Temporary until all projects are using shared tables.
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn, $sharedTables)) {
$schema = 'appwrite';
$database = 'appwrite';
$namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', '');
$dsn = $schema . '://' . $dsn . '?database=' . $database;
if (!empty($namespace)) {
$dsn .= '&namespace=' . $namespace;
}
}
$dsn = getDatabaseDSN('databases', $region);
try {
$project = $dbForPlatform->createDocument('projects', new Document([
@@ -203,6 +232,7 @@ App::post('/v1/projects')
'accessedAt' => DateTime::now(),
'search' => implode(' ', [$projectId, $name]),
'database' => $dsn,
'documentsDatabase' => getDatabaseDSN('documentsDatabase', $region)
]));
} catch (Duplicate) {
throw new Exception(Exception::PROJECT_ALREADY_EXISTS);
@@ -22,3 +22,6 @@ const INDEX = 'index';
const DOCUMENTS = 'document';
const ATTRIBUTES = 'attribute';
const COLLECTIONS = 'collection';
const TABLESDB = 'tablesdb';
const DOCUMENTSDB = 'documentsdb';
@@ -19,9 +19,7 @@ use Utopia\Database\Exception\Index as IndexException;
use Utopia\Database\Exception\Limit as LimitException;
use Utopia\Database\Exception\Structure as StructureException;
use Utopia\Database\Helpers\ID;
use Utopia\DSN\DSN;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\System\System;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
@@ -34,58 +32,10 @@ class Create extends Action
protected function getDatabaseDSN(Document $project): string
{
$region = $project->getAttribute('region');
$databases = [];
$databaseKeys = [];
/**
* @var string|null $databaseOverride
*/
$databaseOverride = '';
$dbScheme = '';
switch ($this->getDatabaseType()) {
case 'documentsdb':
$databases = Config::getParam('pools-documentsdb', []);
$databaseKeys = System::getEnv('_APP_DATABASE_DOCUMENTSDB_KEYS', '');
$databaseOverride = System::getEnv('_APP_DATABASE_DOCUMENTSDB_OVERRIDE');
$dbScheme = System::getEnv('_APP_DB_HOST_DOCUMENTSDB', 'mongodb');
break;
default:
// legacy/tablesdb case where projects having the location of the database
return $project->getAttribute('database');
}
if ($region !== 'default') {
$keys = explode(',', $databaseKeys);
$databases = array_filter($keys, function ($value) use ($region) {
return str_contains($value, $region);
});
}
$index = \array_search($databaseOverride, $databases);
if ($index !== false) {
$dsn = $databases[$index];
} else {
$dsn = $databases[array_rand($databases)];
}
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn, $sharedTables)) {
$schema = 'appwrite';
$database = 'appwrite';
$namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', '');
$dsn = $schema . '://' . $dsn . '?database=' . $database;
if (!empty($namespace)) {
$dsn .= '&namespace=' . $namespace;
}
}
try {
// for validation
new DSN($dsn);
} catch (\InvalidArgumentException) {
$dsn = $dbScheme.'://' . $dsn;
}
return $dsn;
return match ($this->getDatabaseType()) {
DOCUMENTSDB => $project->getAttribute('documentsDatabase'),
default => $project->getAttribute('database'),
};
}
public function __construct()
@@ -10,11 +10,25 @@ abstract class Action extends UtopiaAction
* The current API context (either 'table' or 'collection').
*/
private ?string $context = COLLECTIONS;
private ?string $databaseType = DOCUMENTSDB;
protected function getDatabaseType(): string
{
return $this->databaseType;
}
public function setHttpPath(string $path): UtopiaAction
{
if (\str_contains($path, '/tablesdb')) {
$this->context = TABLES;
switch (true) {
case str_contains($path, '/tablesdb'):
$this->context = TABLES;
$this->databaseType = TABLESDB;
break;
case str_contains($path, '/documentsdb'):
$this->context = COLLECTIONS;
$this->databaseType = DOCUMENTSDB;
break;
}
return parent::setHttpPath($path);
}
@@ -66,6 +66,7 @@ class Update extends Action
->param('transactionId', '', new UID(), 'Transaction ID.')
->param('commit', false, new Boolean(), 'Commit transaction?', true)
->param('rollback', false, new Boolean(), 'Rollback transaction?', true)
->inject('project')
->inject('response')
->inject('dbForProject')
->inject('getDatabasesDB')
@@ -102,7 +103,7 @@ class Update extends Action
* @throws \Utopia\Database\Exception
* @throws StructureException
*/
public function action(string $transactionId, bool $commit, bool $rollback, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Document $user, TransactionState $transactionState, Delete $queueForDeletes, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks): void
public function action(string $transactionId, bool $commit, bool $rollback, Document $project, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Document $user, TransactionState $transactionState, Delete $queueForDeletes, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks): void
{
if (!$commit && !$rollback) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Either commit or rollback must be true');
@@ -137,22 +138,7 @@ class Update extends Action
$databaseOperations = [];
try {
// transactions scoped to db wise
// taking a sample record to connect and start transactions
$operation = Authorization::skip(fn () => $dbForProject->findOne('transactionLogs', [
Query::equal('transactionInternalId', [$transaction->getSequence()])
]));
if ($operation->isEmpty()) {
// for transaction logs processing
$dbForDatabases = $dbForProject;
} else {
$databaseInternalId = $operation['databaseInternalId'];
$databaseDoc = Authorization::skip(fn () => $dbForProject->findOne('databases', [
Query::equal('$sequence', [$databaseInternalId])
]));
$dbForDatabases = $getDatabasesDB($databaseDoc);
}
$dbForDatabases = $getDatabasesDB(new Document(['database' => $this->getDatabaseDSN($project)]));
$dbForDatabases->withTransaction(function () use ($dbForProject, $transactionState, $queueForDeletes, $transactionId, &$transaction, &$operations, &$totalOperations, &$databaseOperations, $dbForDatabases) {
Authorization::skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
'status' => 'committing',
@@ -420,6 +406,15 @@ class Update extends Action
->dynamic($transaction, UtopiaResponse::MODEL_TRANSACTION);
}
private function getDatabaseDSN(Document $project)
{
var_dump($this->getDatabaseType());
return match ($this->getDatabaseType()) {
DOCUMENTSDB => $project->getAttribute('documentsDatabase'),
default => $project->getAttribute('database'),
};
}
/**
* Handle create operation
*
@@ -50,6 +50,7 @@ class Update extends TransactionsUpdate
->param('transactionId', '', new UID(), 'Transaction ID.')
->param('commit', false, new Boolean(), 'Commit transaction?', true)
->param('rollback', false, new Boolean(), 'Rollback transaction?', true)
->inject('project')
->inject('response')
->inject('dbForProject')
->inject('getDatabasesDB')
@@ -50,6 +50,7 @@ class Update extends TransactionsUpdate
->param('transactionId', '', new UID(), 'Transaction ID.')
->param('commit', false, new Boolean(), 'Commit transaction?', true)
->param('rollback', false, new Boolean(), 'Rollback transaction?', true)
->inject('project')
->inject('response')
->inject('dbForProject')
->inject('getDatabasesDB')
+4 -53
View File
@@ -17,7 +17,6 @@ use Utopia\Database\Exception\Restricted;
use Utopia\Database\Exception\Structure;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\DSN\DSN;
use Utopia\Locale\Locale;
use Utopia\Migration\Destination;
use Utopia\Migration\Destinations\Appwrite as DestinationAppwrite;
@@ -449,58 +448,10 @@ class Migrations extends Action
protected function getDatabaseDSN(string $databaseType): string
{
$databases = [];
$databaseKeys = [];
/**
* @var string|null $databaseOverride
*/
$databaseOverride = '';
$dbScheme = '';
$region = $this->project->getAttribute('region');
switch ($databaseType) {
case 'documentsdb':
$databases = Config::getParam('pools-documentsdb', []);
$databaseKeys = System::getEnv('_APP_DATABASE_DOCUMENTSDB_KEYS', '');
$databaseOverride = System::getEnv('_APP_DATABASE_DOCUMENTSDB_OVERRIDE');
$dbScheme = System::getEnv('_APP_DB_HOST_DOCUMENTSDB', 'mongodb');
break;
default:
// legacy/tablesdb case where projects having the location of the database
return $this->project->getAttribute('database');
}
if ($region !== 'default') {
$keys = explode(',', $databaseKeys);
$databases = array_filter($keys, function ($value) use ($region) {
return str_contains($value, $region);
});
}
$index = \array_search($databaseOverride, $databases);
if ($index !== false) {
$dsn = $databases[$index];
} else {
$dsn = $databases[array_rand($databases)];
}
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn, $sharedTables)) {
$schema = 'appwrite';
$database = 'appwrite';
$namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', '');
$dsn = $schema . '://' . $dsn . '?database=' . $database;
if (!empty($namespace)) {
$dsn .= '&namespace=' . $namespace;
}
}
try {
// for validation
new DSN($dsn);
} catch (\InvalidArgumentException) {
$dsn = $dbScheme.'://' . $dsn;
}
return $dsn;
return match ($databaseType) {
'documentsdb' => $this->project->getAttribute('documentsDatabase'),
default => $this->project->getAttribute('database'),
};
}
/**
* Handle actions to be performed when a CSV export migration is successfully completed