From 090210d1907d71ae630e06cb8e15c5408cb91920 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Sat, 24 Jan 2026 23:31:35 +1300 Subject: [PATCH] DAT-1076: Add Capability enum and supports() method to replace getSupportFor* methods - Create Capability enum with all existing capability types (54 capabilities) - Create NotSupported exception class for cleaner error handling - Add abstract supports() method to Adapter base class - Add requireSupport() helper method that throws NotSupported when capability is missing - Add getAdapterName() abstract method for error messages - Implement supports() method in SQL, MariaDB, MySQL, SQLite, Postgres, Mongo, and Pool adapters - Add supports() wrapper method to Database class This provides a unified API for checking adapter capabilities: if (!$this->supports(Capability::FullTextIndex)) { throw new NotSupported(Capability::FullTextIndex->value, $this->getAdapterName()); } Or using the helper method: $this->requireSupport(Capability::FullTextIndex); The existing getSupportFor* methods are preserved for backward compatibility. Co-Authored-By: Claude Opus 4.5 --- src/Database/Adapter.php | 29 ++++++++++++ src/Database/Adapter/MariaDB.php | 5 +++ src/Database/Adapter/Mongo.php | 59 +++++++++++++++++++++++++ src/Database/Adapter/MySQL.php | 5 +++ src/Database/Adapter/Pool.php | 11 +++++ src/Database/Adapter/Postgres.php | 5 +++ src/Database/Adapter/SQL.php | 59 +++++++++++++++++++++++++ src/Database/Adapter/SQLite.php | 5 +++ src/Database/Capability.php | 58 ++++++++++++++++++++++++ src/Database/Database.php | 11 +++++ src/Database/Exception/NotSupported.php | 13 ++++++ 11 files changed, 260 insertions(+) create mode 100644 src/Database/Capability.php create mode 100644 src/Database/Exception/NotSupported.php diff --git a/src/Database/Adapter.php b/src/Database/Adapter.php index b189c6b7a..853647d28 100644 --- a/src/Database/Adapter.php +++ b/src/Database/Adapter.php @@ -8,6 +8,7 @@ use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Exception\Limit as LimitException; +use Utopia\Database\Exception\NotSupported as NotSupportedException; use Utopia\Database\Exception\Relationship as RelationshipException; use Utopia\Database\Exception\Restricted as RestrictedException; use Utopia\Database\Exception\Timeout as TimeoutException; @@ -1519,4 +1520,32 @@ public function getSupportForRegex(): bool { return $this->getSupportForPCRERegex() || $this->getSupportForPOSIXRegex(); } + + /** + * Get the adapter name for error messages + * + * @return string + */ + abstract public function getAdapterName(): string; + + /** + * Check if the adapter supports a specific capability + * + * @param Capability $capability + * @return bool + */ + abstract public function supports(Capability $capability): bool; + + /** + * Require that a capability is supported, throwing an exception if not + * + * @param Capability $capability + * @throws NotSupportedException + */ + public function requireSupport(Capability $capability): void + { + if (!$this->supports($capability)) { + throw new NotSupportedException($capability->value, $this->getAdapterName()); + } + } } diff --git a/src/Database/Adapter/MariaDB.php b/src/Database/Adapter/MariaDB.php index 71927fded..77a6452ca 100644 --- a/src/Database/Adapter/MariaDB.php +++ b/src/Database/Adapter/MariaDB.php @@ -2283,4 +2283,9 @@ public function getSupportForPOSIXRegex(): bool { return false; } + + public function getAdapterName(): string + { + return 'MariaDB'; + } } diff --git a/src/Database/Adapter/Mongo.php b/src/Database/Adapter/Mongo.php index de156c4c2..d0243acfe 100644 --- a/src/Database/Adapter/Mongo.php +++ b/src/Database/Adapter/Mongo.php @@ -7,6 +7,7 @@ use MongoDB\BSON\UTCDateTime; use stdClass; use Utopia\Database\Adapter; +use Utopia\Database\Capability; use Utopia\Database\Change; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -3467,4 +3468,62 @@ public function getSupportForTrigramIndex(): bool { return false; } + + public function getAdapterName(): string + { + return 'MongoDB'; + } + + public function supports(Capability $capability): bool + { + return match ($capability) { + Capability::Schemas => $this->getSupportForSchemas(), + Capability::Attributes => $this->getSupportForAttributes(), + Capability::SchemaAttributes => $this->getSupportForSchemaAttributes(), + Capability::Index => $this->getSupportForIndex(), + Capability::IndexArray => $this->getSupportForIndexArray(), + Capability::CastIndexArray => $this->getSupportForCastIndexArray(), + Capability::UniqueIndex => $this->getSupportForUniqueIndex(), + Capability::FulltextIndex => $this->getSupportForFulltextIndex(), + Capability::FulltextWildcardIndex => $this->getSupportForFulltextWildcardIndex(), + Capability::MultipleFulltextIndexes => $this->getSupportForMultipleFulltextIndexes(), + Capability::TrigramIndex => $this->getSupportForTrigramIndex(), + Capability::IdenticalIndexes => $this->getSupportForIdenticalIndexes(), + Capability::Casting => $this->getSupportForCasting(), + Capability::NumericCasting => false, + Capability::InternalCasting => $this->getSupportForInternalCasting(), + Capability::UTCCasting => $this->getSupportForUTCCasting(), + Capability::QueryContains => $this->getSupportForQueryContains(), + Capability::JSONOverlaps => false, + Capability::Timeouts => $this->getSupportForTimeouts(), + Capability::Relationships => $this->getSupportForRelationships(), + Capability::UpdateLock => $this->getSupportForUpdateLock(), + Capability::BatchOperations => $this->getSupportForBatchOperations(), + Capability::AttributeResizing => $this->getSupportForAttributeResizing(), + Capability::GetConnectionId => $this->getSupportForGetConnectionId(), + Capability::Upserts => $this->getSupportForUpserts(), + Capability::Vectors => $this->getSupportForVectors(), + Capability::CacheSkipOnFailure => $this->getSupportForCacheSkipOnFailure(), + Capability::Reconnection => $this->getSupportForReconnection(), + Capability::Hostname => $this->getSupportForHostname(), + Capability::BatchCreateAttributes => $this->getSupportForBatchCreateAttributes(), + Capability::SpatialAttributes => $this->getSupportForSpatialAttributes(), + Capability::SpatialIndexNull => $this->getSupportForSpatialIndexNull(), + Capability::SpatialIndexOrder => $this->getSupportForSpatialIndexOrder(), + Capability::SpatialAxisOrder => $this->getSupportForSpatialAxisOrder(), + Capability::OptionalSpatialAttributeWithExistingRows => $this->getSupportForOptionalSpatialAttributeWithExistingRows(), + Capability::BoundaryInclusiveContains => $this->getSupportForBoundaryInclusiveContains(), + Capability::DistanceBetweenMultiDimensionGeometryInMeters => $this->getSupportForDistanceBetweenMultiDimensionGeometryInMeters(), + Capability::Object => $this->getSupportForObject(), + Capability::ObjectIndexes => $this->getSupportForObjectIndexes(), + Capability::Operators => $this->getSupportForOperators(), + Capability::OrderRandom => $this->getSupportForOrderRandom(), + Capability::AlterLocks => $this->getSupportForAlterLocks(), + Capability::NonUtfCharacters => $this->getSupportNonUtfCharacters(), + Capability::IntegerBooleans => $this->getSupportForIntegerBooleans(), + Capability::PCRERegex => $this->getSupportForPCRERegex(), + Capability::POSIXRegex => $this->getSupportForPOSIXRegex(), + Capability::Regex => $this->getSupportForRegex(), + }; + } } diff --git a/src/Database/Adapter/MySQL.php b/src/Database/Adapter/MySQL.php index a382e890e..c2556880e 100644 --- a/src/Database/Adapter/MySQL.php +++ b/src/Database/Adapter/MySQL.php @@ -319,4 +319,9 @@ protected function getOperatorSQL(string $column, \Utopia\Database\Operator $ope // For all other operators, use parent implementation return parent::getOperatorSQL($column, $operator, $bindIndex); } + + public function getAdapterName(): string + { + return 'MySQL'; + } } diff --git a/src/Database/Adapter/Pool.php b/src/Database/Adapter/Pool.php index 263c8a183..d94408728 100644 --- a/src/Database/Adapter/Pool.php +++ b/src/Database/Adapter/Pool.php @@ -3,6 +3,7 @@ namespace Utopia\Database\Adapter; use Utopia\Database\Adapter; +use Utopia\Database\Capability; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception as DatabaseException; @@ -642,4 +643,14 @@ public function getSupportNonUtfCharacters(): bool { return $this->delegate(__FUNCTION__, \func_get_args()); } + + public function getAdapterName(): string + { + return $this->delegate(__FUNCTION__, \func_get_args()); + } + + public function supports(Capability $capability): bool + { + return $this->delegate(__FUNCTION__, \func_get_args()); + } } diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index a611f1038..ae8485d78 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -2842,4 +2842,9 @@ protected function getSQLTable(string $name): string return "{$this->quote($this->getDatabase())}.{$this->quote($table)}"; } + + public function getAdapterName(): string + { + return 'PostgreSQL'; + } } diff --git a/src/Database/Adapter/SQL.php b/src/Database/Adapter/SQL.php index 4a7c34520..2a2247c8b 100644 --- a/src/Database/Adapter/SQL.php +++ b/src/Database/Adapter/SQL.php @@ -6,6 +6,7 @@ use PDOException; use Swoole\Database\PDOStatementProxy; use Utopia\Database\Adapter; +use Utopia\Database\Capability; use Utopia\Database\Change; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -3574,4 +3575,62 @@ public function getLockType(): string return ''; } + + public function getAdapterName(): string + { + return 'SQL'; + } + + public function supports(Capability $capability): bool + { + return match ($capability) { + Capability::Schemas => $this->getSupportForSchemas(), + Capability::Attributes => $this->getSupportForAttributes(), + Capability::SchemaAttributes => $this->getSupportForSchemaAttributes(), + Capability::Index => $this->getSupportForIndex(), + Capability::IndexArray => $this->getSupportForIndexArray(), + Capability::CastIndexArray => $this->getSupportForCastIndexArray(), + Capability::UniqueIndex => $this->getSupportForUniqueIndex(), + Capability::FulltextIndex => $this->getSupportForFulltextIndex(), + Capability::FulltextWildcardIndex => $this->getSupportForFulltextWildcardIndex(), + Capability::MultipleFulltextIndexes => $this->getSupportForMultipleFulltextIndexes(), + Capability::TrigramIndex => $this->getSupportForTrigramIndex(), + Capability::IdenticalIndexes => $this->getSupportForIdenticalIndexes(), + Capability::Casting => $this->getSupportForCasting(), + Capability::NumericCasting => $this->getSupportForNumericCasting(), + Capability::InternalCasting => $this->getSupportForInternalCasting(), + Capability::UTCCasting => $this->getSupportForUTCCasting(), + Capability::QueryContains => $this->getSupportForQueryContains(), + Capability::JSONOverlaps => $this->getSupportForJSONOverlaps(), + Capability::Timeouts => $this->getSupportForTimeouts(), + Capability::Relationships => $this->getSupportForRelationships(), + Capability::UpdateLock => $this->getSupportForUpdateLock(), + Capability::BatchOperations => $this->getSupportForBatchOperations(), + Capability::AttributeResizing => $this->getSupportForAttributeResizing(), + Capability::GetConnectionId => $this->getSupportForGetConnectionId(), + Capability::Upserts => $this->getSupportForUpserts(), + Capability::Vectors => $this->getSupportForVectors(), + Capability::CacheSkipOnFailure => $this->getSupportForCacheSkipOnFailure(), + Capability::Reconnection => $this->getSupportForReconnection(), + Capability::Hostname => $this->getSupportForHostname(), + Capability::BatchCreateAttributes => $this->getSupportForBatchCreateAttributes(), + Capability::SpatialAttributes => $this->getSupportForSpatialAttributes(), + Capability::SpatialIndexNull => $this->getSupportForSpatialIndexNull(), + Capability::SpatialIndexOrder => $this->getSupportForSpatialIndexOrder(), + Capability::SpatialAxisOrder => $this->getSupportForSpatialAxisOrder(), + Capability::OptionalSpatialAttributeWithExistingRows => $this->getSupportForOptionalSpatialAttributeWithExistingRows(), + Capability::BoundaryInclusiveContains => $this->getSupportForBoundaryInclusiveContains(), + Capability::DistanceBetweenMultiDimensionGeometryInMeters => $this->getSupportForDistanceBetweenMultiDimensionGeometryInMeters(), + Capability::Object => $this->getSupportForObject(), + Capability::ObjectIndexes => $this->getSupportForObjectIndexes(), + Capability::Operators => $this->getSupportForOperators(), + Capability::OrderRandom => $this->getSupportForOrderRandom(), + Capability::AlterLocks => $this->getSupportForAlterLocks(), + Capability::NonUtfCharacters => $this->getSupportNonUtfCharacters(), + Capability::IntegerBooleans => $this->getSupportForIntegerBooleans(), + Capability::PCRERegex => $this->getSupportForPCRERegex(), + Capability::POSIXRegex => $this->getSupportForPOSIXRegex(), + Capability::Regex => $this->getSupportForRegex(), + }; + } } diff --git a/src/Database/Adapter/SQLite.php b/src/Database/Adapter/SQLite.php index 425a12d8a..1ef6c9791 100644 --- a/src/Database/Adapter/SQLite.php +++ b/src/Database/Adapter/SQLite.php @@ -1913,4 +1913,9 @@ public function getSupportForPOSIXRegex(): bool { return false; } + + public function getAdapterName(): string + { + return 'SQLite'; + } } diff --git a/src/Database/Capability.php b/src/Database/Capability.php new file mode 100644 index 000000000..ebc029d61 --- /dev/null +++ b/src/Database/Capability.php @@ -0,0 +1,58 @@ +adapter; } + /** + * Check if the adapter supports a specific capability + * + * @param Capability $capability + * @return bool + */ + public function supports(Capability $capability): bool + { + return $this->adapter->supports($capability); + } + /** * Run a callback inside a transaction. * diff --git a/src/Database/Exception/NotSupported.php b/src/Database/Exception/NotSupported.php new file mode 100644 index 000000000..1bcc6e1a4 --- /dev/null +++ b/src/Database/Exception/NotSupported.php @@ -0,0 +1,13 @@ +