mirror of
https://github.com/appwrite/appwrite
synced 2026-05-23 00:49:02 +00:00
Merge branch '1.8.x' of github.com:appwrite/appwrite into refactor-functions-schedule
This commit is contained in:
commit
3ca9468b11
19 changed files with 2540 additions and 42 deletions
|
|
@ -7096,6 +7096,22 @@
|
|||
"type": "boolean",
|
||||
"description": "Is collection enabled? When set to 'disabled', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.",
|
||||
"x-example": false
|
||||
},
|
||||
"attributes": {
|
||||
"type": "array",
|
||||
"description": "Array of attribute definitions to create. Each attribute should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.",
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"type": "array",
|
||||
"description": "Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of attribute keys), orders (array of ASC\/DESC, optional), and lengths (array of integers, optional).",
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -36519,6 +36535,22 @@
|
|||
"type": "boolean",
|
||||
"description": "Is table enabled? When set to 'disabled', users cannot access the table but Server SDKs with and API key can still read and write to the table. No data is lost when this is toggled.",
|
||||
"x-example": false
|
||||
},
|
||||
"columns": {
|
||||
"type": "array",
|
||||
"description": "Array of column definitions to create. Each column should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.",
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"type": "array",
|
||||
"description": "Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of column keys), orders (array of ASC\/DESC, optional), and lengths (array of integers, optional).",
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
|
|||
|
|
@ -6558,6 +6558,22 @@
|
|||
"type": "boolean",
|
||||
"description": "Is collection enabled? When set to 'disabled', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.",
|
||||
"x-example": false
|
||||
},
|
||||
"attributes": {
|
||||
"type": "array",
|
||||
"description": "Array of attribute definitions to create. Each attribute should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.",
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"type": "array",
|
||||
"description": "Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of attribute keys), orders (array of ASC\/DESC, optional), and lengths (array of integers, optional).",
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -26348,6 +26364,22 @@
|
|||
"type": "boolean",
|
||||
"description": "Is table enabled? When set to 'disabled', users cannot access the table but Server SDKs with and API key can still read and write to the table. No data is lost when this is toggled.",
|
||||
"x-example": false
|
||||
},
|
||||
"columns": {
|
||||
"type": "array",
|
||||
"description": "Array of column definitions to create. Each column should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.",
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"type": "array",
|
||||
"description": "Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of column keys), orders (array of ASC\/DESC, optional), and lengths (array of integers, optional).",
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
|
|||
|
|
@ -7096,6 +7096,22 @@
|
|||
"type": "boolean",
|
||||
"description": "Is collection enabled? When set to 'disabled', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.",
|
||||
"x-example": false
|
||||
},
|
||||
"attributes": {
|
||||
"type": "array",
|
||||
"description": "Array of attribute definitions to create. Each attribute should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.",
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"type": "array",
|
||||
"description": "Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of attribute keys), orders (array of ASC\/DESC, optional), and lengths (array of integers, optional).",
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -36519,6 +36535,22 @@
|
|||
"type": "boolean",
|
||||
"description": "Is table enabled? When set to 'disabled', users cannot access the table but Server SDKs with and API key can still read and write to the table. No data is lost when this is toggled.",
|
||||
"x-example": false
|
||||
},
|
||||
"columns": {
|
||||
"type": "array",
|
||||
"description": "Array of column definitions to create. Each column should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.",
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"type": "array",
|
||||
"description": "Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of column keys), orders (array of ASC\/DESC, optional), and lengths (array of integers, optional).",
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
|
|||
|
|
@ -6558,6 +6558,22 @@
|
|||
"type": "boolean",
|
||||
"description": "Is collection enabled? When set to 'disabled', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.",
|
||||
"x-example": false
|
||||
},
|
||||
"attributes": {
|
||||
"type": "array",
|
||||
"description": "Array of attribute definitions to create. Each attribute should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.",
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"type": "array",
|
||||
"description": "Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of attribute keys), orders (array of ASC\/DESC, optional), and lengths (array of integers, optional).",
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -26348,6 +26364,22 @@
|
|||
"type": "boolean",
|
||||
"description": "Is table enabled? When set to 'disabled', users cannot access the table but Server SDKs with and API key can still read and write to the table. No data is lost when this is toggled.",
|
||||
"x-example": false
|
||||
},
|
||||
"columns": {
|
||||
"type": "array",
|
||||
"description": "Array of column definitions to create. Each column should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.",
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"type": "array",
|
||||
"description": "Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of column keys), orders (array of ASC\/DESC, optional), and lengths (array of integers, optional).",
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
|
|||
|
|
@ -7209,6 +7209,24 @@
|
|||
"description": "Is collection enabled? When set to 'disabled', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.",
|
||||
"default": true,
|
||||
"x-example": false
|
||||
},
|
||||
"attributes": {
|
||||
"type": "array",
|
||||
"description": "Array of attribute definitions to create. Each attribute should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.",
|
||||
"default": [],
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"type": "array",
|
||||
"description": "Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of attribute keys), orders (array of ASC\/DESC, optional), and lengths (array of integers, optional).",
|
||||
"default": [],
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -36555,6 +36573,24 @@
|
|||
"description": "Is table enabled? When set to 'disabled', users cannot access the table but Server SDKs with and API key can still read and write to the table. No data is lost when this is toggled.",
|
||||
"default": true,
|
||||
"x-example": false
|
||||
},
|
||||
"columns": {
|
||||
"type": "array",
|
||||
"description": "Array of column definitions to create. Each column should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.",
|
||||
"default": [],
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"type": "array",
|
||||
"description": "Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of column keys), orders (array of ASC\/DESC, optional), and lengths (array of integers, optional).",
|
||||
"default": [],
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
|
|||
|
|
@ -6661,6 +6661,24 @@
|
|||
"description": "Is collection enabled? When set to 'disabled', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.",
|
||||
"default": true,
|
||||
"x-example": false
|
||||
},
|
||||
"attributes": {
|
||||
"type": "array",
|
||||
"description": "Array of attribute definitions to create. Each attribute should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.",
|
||||
"default": [],
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"type": "array",
|
||||
"description": "Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of attribute keys), orders (array of ASC\/DESC, optional), and lengths (array of integers, optional).",
|
||||
"default": [],
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -26445,6 +26463,24 @@
|
|||
"description": "Is table enabled? When set to 'disabled', users cannot access the table but Server SDKs with and API key can still read and write to the table. No data is lost when this is toggled.",
|
||||
"default": true,
|
||||
"x-example": false
|
||||
},
|
||||
"columns": {
|
||||
"type": "array",
|
||||
"description": "Array of column definitions to create. Each column should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.",
|
||||
"default": [],
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"type": "array",
|
||||
"description": "Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of column keys), orders (array of ASC\/DESC, optional), and lengths (array of integers, optional).",
|
||||
"default": [],
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
|
|||
|
|
@ -7209,6 +7209,24 @@
|
|||
"description": "Is collection enabled? When set to 'disabled', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.",
|
||||
"default": true,
|
||||
"x-example": false
|
||||
},
|
||||
"attributes": {
|
||||
"type": "array",
|
||||
"description": "Array of attribute definitions to create. Each attribute should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.",
|
||||
"default": [],
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"type": "array",
|
||||
"description": "Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of attribute keys), orders (array of ASC\/DESC, optional), and lengths (array of integers, optional).",
|
||||
"default": [],
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -36555,6 +36573,24 @@
|
|||
"description": "Is table enabled? When set to 'disabled', users cannot access the table but Server SDKs with and API key can still read and write to the table. No data is lost when this is toggled.",
|
||||
"default": true,
|
||||
"x-example": false
|
||||
},
|
||||
"columns": {
|
||||
"type": "array",
|
||||
"description": "Array of column definitions to create. Each column should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.",
|
||||
"default": [],
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"type": "array",
|
||||
"description": "Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of column keys), orders (array of ASC\/DESC, optional), and lengths (array of integers, optional).",
|
||||
"default": [],
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
|
|||
|
|
@ -6661,6 +6661,24 @@
|
|||
"description": "Is collection enabled? When set to 'disabled', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.",
|
||||
"default": true,
|
||||
"x-example": false
|
||||
},
|
||||
"attributes": {
|
||||
"type": "array",
|
||||
"description": "Array of attribute definitions to create. Each attribute should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.",
|
||||
"default": [],
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"type": "array",
|
||||
"description": "Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of attribute keys), orders (array of ASC\/DESC, optional), and lengths (array of integers, optional).",
|
||||
"default": [],
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -26445,6 +26463,24 @@
|
|||
"description": "Is table enabled? When set to 'disabled', users cannot access the table but Server SDKs with and API key can still read and write to the table. No data is lost when this is toggled.",
|
||||
"default": true,
|
||||
"x-example": false
|
||||
},
|
||||
"columns": {
|
||||
"type": "array",
|
||||
"description": "Array of column definitions to create. Each column should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.",
|
||||
"default": [],
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"type": "array",
|
||||
"description": "Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of column keys), orders (array of ASC\/DESC, optional), and lengths (array of integers, optional).",
|
||||
"default": [],
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
"utopia-php/detector": "0.2.*",
|
||||
"utopia-php/domains": "0.9.*",
|
||||
"utopia-php/emails": "0.6.*",
|
||||
"utopia-php/dns": "1.1.*",
|
||||
"utopia-php/dns": "1.4.*",
|
||||
"utopia-php/dsn": "0.2.1",
|
||||
"utopia-php/framework": "0.33.*",
|
||||
"utopia-php/fetch": "0.4.*",
|
||||
|
|
|
|||
14
composer.lock
generated
14
composer.lock
generated
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "0cad126c9b41c0d496462ba03ff36d7b",
|
||||
"content-hash": "7c9cb03eb5267f1e7a3ffc037ae22b6a",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
|
|
@ -3999,16 +3999,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/dns",
|
||||
"version": "1.1.4",
|
||||
"version": "1.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/dns.git",
|
||||
"reference": "eea6b9299a1420ae6c574f16eb1e9da8689ac56b"
|
||||
"reference": "dce3453364a4524b7250db8d8eb74820b814409e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/dns/zipball/eea6b9299a1420ae6c574f16eb1e9da8689ac56b",
|
||||
"reference": "eea6b9299a1420ae6c574f16eb1e9da8689ac56b",
|
||||
"url": "https://api.github.com/repos/utopia-php/dns/zipball/dce3453364a4524b7250db8d8eb74820b814409e",
|
||||
"reference": "dce3453364a4524b7250db8d8eb74820b814409e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -4050,9 +4050,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/dns/issues",
|
||||
"source": "https://github.com/utopia-php/dns/tree/1.1.4"
|
||||
"source": "https://github.com/utopia-php/dns/tree/1.4.0"
|
||||
},
|
||||
"time": "2025-11-26T13:38:10+00:00"
|
||||
"time": "2025-12-05T10:09:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/domains",
|
||||
|
|
|
|||
|
|
@ -105,4 +105,64 @@ abstract class Action extends UtopiaAction
|
|||
? Exception::COLLECTION_LIMIT_EXCEEDED
|
||||
: Exception::TABLE_LIMIT_EXCEEDED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate format unsupported exception.
|
||||
*/
|
||||
protected function getFormatUnsupportedException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::ATTRIBUTE_FORMAT_UNSUPPORTED
|
||||
: Exception::COLUMN_FORMAT_UNSUPPORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the correct default unsupported message.
|
||||
*/
|
||||
protected function getDefaultUnsupportedException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED
|
||||
: Exception::COLUMN_DEFAULT_UNSUPPORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate parent level not found exception.
|
||||
*/
|
||||
protected function getParentNotFoundException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::COLLECTION_NOT_FOUND
|
||||
: Exception::TABLE_NOT_FOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the correct invalid structure message.
|
||||
*/
|
||||
protected function getStructureException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::DOCUMENT_INVALID_STRUCTURE
|
||||
: Exception::ROW_INVALID_STRUCTURE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exception for unknown attribute/column in index.
|
||||
*/
|
||||
protected function getParentUnknownException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::ATTRIBUTE_UNKNOWN
|
||||
: Exception::COLUMN_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exception for invalid attribute/column type in index.
|
||||
*/
|
||||
protected function getParentInvalidTypeException(): string
|
||||
{
|
||||
return $this->isCollectionsAPI()
|
||||
? Exception::ATTRIBUTE_TYPE_INVALID
|
||||
: Exception::COLUMN_TYPE_INVALID;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ use Appwrite\SDK\ContentType;
|
|||
use Appwrite\SDK\Deprecated;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\Attributes as AttributesValidator;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Indexes as IndexesValidator;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
|
@ -20,10 +22,13 @@ use Utopia\Database\Exception\NotFound as NotFoundException;
|
|||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Index as IndexValidator;
|
||||
use Utopia\Database\Validator\Permissions;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\JSON;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
|
|
@ -75,13 +80,15 @@ class Create extends Action
|
|||
->param('permissions', null, new Nullable(new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE)), 'An array of permissions strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('documentSecurity', false, new Boolean(true), 'Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('enabled', true, new Boolean(), 'Is collection enabled? When set to \'disabled\', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.', true)
|
||||
->param('attributes', [], new ArrayList(new JSON(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attribute definitions to create. Each attribute should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.', true)
|
||||
->param('indexes', [], new ArrayList(new JSON(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of attribute keys), orders (array of ASC/DESC, optional), and lengths (array of integers, optional).', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, array $attributes, array $indexes, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
{
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
|
|
@ -113,20 +120,122 @@ class Create extends Action
|
|||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collectionKey = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence();
|
||||
$databaseKey = 'database_' . $database->getSequence();
|
||||
|
||||
$attributesValidator = new AttributesValidator(
|
||||
APP_LIMIT_ARRAY_PARAMS_SIZE,
|
||||
$dbForProject->getAdapter()->getSupportForSpatialAttributes()
|
||||
);
|
||||
|
||||
if (!$attributesValidator->isValid($attributes)) {
|
||||
$dbForProject->deleteDocument($databaseKey, $collection->getId());
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $attributesValidator->getDescription());
|
||||
}
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
if (($attribute['type'] ?? '') === Database::VAR_RELATIONSHIP) {
|
||||
$dbForProject->deleteDocument($databaseKey, $collection->getId());
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Relationship attributes cannot be created inline. Use the create relationship endpoint instead.');
|
||||
}
|
||||
}
|
||||
|
||||
$collectionAttributes = [];
|
||||
$attributeDocuments = [];
|
||||
try {
|
||||
foreach ($attributes as $attributeDef) {
|
||||
$attrDoc = $this->buildAttributeDocument($database, $collection, $attributeDef);
|
||||
$collectionAttributes[] = $attrDoc['collection'];
|
||||
$attributeDocuments[] = $attrDoc['document'];
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$dbForProject->deleteDocument($databaseKey, $collection->getId());
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Validate indexes
|
||||
$indexesValidator = new IndexesValidator($dbForProject->getLimitForIndexes());
|
||||
if (!$indexesValidator->isValid($indexes)) {
|
||||
$dbForProject->deleteDocument($databaseKey, $collection->getId());
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $indexesValidator->getDescription());
|
||||
}
|
||||
|
||||
$collectionIndexes = [];
|
||||
$indexDocuments = [];
|
||||
try {
|
||||
foreach ($indexes as $indexDef) {
|
||||
$idxDoc = $this->buildIndexDocument($database, $collection, $indexDef, $collectionAttributes);
|
||||
$collectionIndexes[] = $idxDoc['collection'];
|
||||
$indexDocuments[] = $idxDoc['document'];
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$dbForProject->deleteDocument($databaseKey, $collection->getId());
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Validate indexes with DB adapter capabilities
|
||||
$indexValidator = new IndexValidator(
|
||||
$collectionAttributes,
|
||||
[],
|
||||
$dbForProject->getAdapter()->getMaxIndexLength(),
|
||||
$dbForProject->getAdapter()->getInternalIndexesKeys(),
|
||||
$dbForProject->getAdapter()->getSupportForIndexArray(),
|
||||
$dbForProject->getAdapter()->getSupportForSpatialIndexNull(),
|
||||
$dbForProject->getAdapter()->getSupportForSpatialIndexOrder(),
|
||||
$dbForProject->getAdapter()->getSupportForVectors(),
|
||||
$dbForProject->getAdapter()->getSupportForAttributes(),
|
||||
$dbForProject->getAdapter()->getSupportForMultipleFulltextIndexes(),
|
||||
$dbForProject->getAdapter()->getSupportForIdenticalIndexes()
|
||||
);
|
||||
|
||||
foreach ($collectionIndexes as $indexDoc) {
|
||||
if (!$indexValidator->isValid($indexDoc)) {
|
||||
$dbForProject->deleteDocument($databaseKey, $collection->getId());
|
||||
throw new Exception($this->getInvalidIndexException(), $indexValidator->getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$dbForProject->createCollection(
|
||||
id: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
id: $collectionKey,
|
||||
attributes: $collectionAttributes,
|
||||
indexes: $collectionIndexes,
|
||||
permissions: $permissions,
|
||||
documentSecurity: $documentSecurity
|
||||
);
|
||||
} catch (DuplicateException) {
|
||||
$dbForProject->deleteDocument($databaseKey, $collection->getId());
|
||||
throw new Exception($this->getDuplicateException());
|
||||
} catch (IndexException) {
|
||||
throw new Exception($this->getInvalidIndexException());
|
||||
} catch (IndexException $e) {
|
||||
$dbForProject->deleteDocument($databaseKey, $collection->getId());
|
||||
throw new Exception($this->getInvalidIndexException(), $e->getMessage());
|
||||
} catch (LimitException) {
|
||||
$dbForProject->deleteDocument($databaseKey, $collection->getId());
|
||||
throw new Exception($this->getLimitException());
|
||||
} catch (\Throwable $e) {
|
||||
$dbForProject->deleteDocument($databaseKey, $collection->getId());
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Create documents in attributes and indexes collections
|
||||
try {
|
||||
if (!empty($attributeDocuments)) {
|
||||
$dbForProject->createDocuments('attributes', $attributeDocuments);
|
||||
}
|
||||
if (!empty($indexDocuments)) {
|
||||
$dbForProject->createDocuments('indexes', $indexDocuments);
|
||||
}
|
||||
} catch (DuplicateException) {
|
||||
$this->cleanup($dbForProject, $databaseKey, $collectionKey, $collection->getId());
|
||||
throw new Exception($this->getDuplicateException());
|
||||
} catch (\Throwable $e) {
|
||||
$this->cleanup($dbForProject, $databaseKey, $collectionKey, $collection->getId());
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('database_' . $database->getSequence(), $collection->getId());
|
||||
$dbForProject->purgeCachedCollection('database_' . $database->getSequence() . '_collection_' . $collection->getSequence());
|
||||
|
||||
$queueForEvents
|
||||
->setContext('database', $database)
|
||||
->setParam('databaseId', $databaseId)
|
||||
|
|
@ -136,4 +245,167 @@ class Create extends Action
|
|||
->setStatusCode(SwooleResponse::STATUS_CODE_CREATED)
|
||||
->dynamic($collection, $this->getResponseModel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Build attribute Document objects from a definition array
|
||||
*
|
||||
* @return array{collection: Document, document: Document}
|
||||
*/
|
||||
protected function buildAttributeDocument(
|
||||
Document $database,
|
||||
Document $collection,
|
||||
array $attribute,
|
||||
): array {
|
||||
$key = $attribute['key'];
|
||||
$type = $attribute['type'];
|
||||
$size = $attribute['size'] ?? 0;
|
||||
$required = $attribute['required'] ?? false;
|
||||
$signed = $attribute['signed'] ?? true;
|
||||
$array = $attribute['array'] ?? false;
|
||||
$format = $attribute['format'] ?? '';
|
||||
$formatOptions = [];
|
||||
$filters = $attribute['filters'] ?? [];
|
||||
$default = $attribute['default'] ?? null;
|
||||
|
||||
if ($format === APP_DATABASE_ATTRIBUTE_ENUM && isset($attribute['elements'])) {
|
||||
$formatOptions = ['elements' => $attribute['elements']];
|
||||
}
|
||||
|
||||
if (isset($attribute['min']) || isset($attribute['max'])) {
|
||||
$format = $type === Database::VAR_INTEGER
|
||||
? APP_DATABASE_ATTRIBUTE_INT_RANGE
|
||||
: APP_DATABASE_ATTRIBUTE_FLOAT_RANGE;
|
||||
|
||||
$formatOptions = [
|
||||
'min' => $attribute['min'] ?? ($type === Database::VAR_INTEGER ? \PHP_INT_MIN : -\PHP_FLOAT_MAX),
|
||||
'max' => $attribute['max'] ?? ($type === Database::VAR_INTEGER ? \PHP_INT_MAX : \PHP_FLOAT_MAX),
|
||||
];
|
||||
}
|
||||
|
||||
$collectionDoc = new Document([
|
||||
'$id' => $key,
|
||||
'key' => $key,
|
||||
'type' => $type,
|
||||
'size' => $size,
|
||||
'required' => $required,
|
||||
'signed' => $signed,
|
||||
'default' => $default,
|
||||
'array' => $array,
|
||||
'format' => $format,
|
||||
'formatOptions' => $formatOptions,
|
||||
'filters' => $filters,
|
||||
]);
|
||||
|
||||
$document = new Document([
|
||||
'$id' => ID::custom($database->getSequence() . '_' . $collection->getSequence() . '_' . $key),
|
||||
'key' => $key,
|
||||
'databaseInternalId' => $database->getSequence(),
|
||||
'databaseId' => $database->getId(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'collectionId' => $collection->getId(),
|
||||
'type' => $type,
|
||||
'status' => 'available',
|
||||
'size' => $size,
|
||||
'required' => $required,
|
||||
'signed' => $signed,
|
||||
'default' => $default,
|
||||
'array' => $array,
|
||||
'format' => $format,
|
||||
'formatOptions' => $formatOptions,
|
||||
'filters' => $filters,
|
||||
]);
|
||||
|
||||
return [
|
||||
'collection' => $collectionDoc,
|
||||
'document' => $document,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build index Document objects from a definition array
|
||||
*
|
||||
* @return array{collection: Document, document: Document}
|
||||
*/
|
||||
protected function buildIndexDocument(Document $database, Document $collection, array $indexDef, array $attributeDocuments): array
|
||||
{
|
||||
$key = $indexDef['key'];
|
||||
$type = $indexDef['type'];
|
||||
$indexAttributes = $indexDef['attributes'];
|
||||
$orders = $indexDef['orders'] ?? [];
|
||||
$lengths = $indexDef['lengths'] ?? [];
|
||||
|
||||
$attrKeys = array_map(fn ($a) => $a->getAttribute('key'), $attributeDocuments);
|
||||
|
||||
// Build lengths and orders based on attribute properties
|
||||
foreach ($indexAttributes as $i => $attr) {
|
||||
$attrIndex = array_search($attr, $attrKeys);
|
||||
if ($attrIndex !== false) {
|
||||
$attrDoc = $attributeDocuments[$attrIndex];
|
||||
$attrArray = $attrDoc->getAttribute('array', false);
|
||||
|
||||
if (empty($lengths[$i])) {
|
||||
$lengths[$i] = null;
|
||||
}
|
||||
|
||||
if ($attrArray === true) {
|
||||
$lengths[$i] = Database::MAX_ARRAY_INDEX_LENGTH;
|
||||
$orders[$i] = null;
|
||||
}
|
||||
} else {
|
||||
if (empty($lengths[$i])) {
|
||||
$lengths[$i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$collectionDoc = new Document([
|
||||
'$id' => $key,
|
||||
'key' => $key,
|
||||
'type' => $type,
|
||||
'attributes' => $indexAttributes,
|
||||
'lengths' => $lengths,
|
||||
'orders' => $orders,
|
||||
]);
|
||||
|
||||
$document = new Document([
|
||||
'$id' => ID::custom($database->getSequence() . '_' . $collection->getSequence() . '_' . $key),
|
||||
'key' => $key,
|
||||
'status' => 'available',
|
||||
'databaseInternalId' => $database->getSequence(),
|
||||
'databaseId' => $database->getId(),
|
||||
'collectionInternalId' => $collection->getSequence(),
|
||||
'collectionId' => $collection->getId(),
|
||||
'type' => $type,
|
||||
'attributes' => $indexAttributes,
|
||||
'lengths' => $lengths,
|
||||
'orders' => $orders,
|
||||
]);
|
||||
|
||||
return [
|
||||
'collection' => $collectionDoc,
|
||||
'document' => $document,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup on failure: delete the collection document and the underlying DB collection
|
||||
*/
|
||||
protected function cleanup(
|
||||
Database $dbForProject,
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
string $collectionDocumentId
|
||||
): void {
|
||||
try {
|
||||
$dbForProject->deleteCollection($collectionId);
|
||||
} catch (\Throwable) {
|
||||
// Ignore cleanup errors for collection deletion
|
||||
}
|
||||
|
||||
try {
|
||||
$dbForProject->deleteDocument($databaseId, $collectionDocumentId);
|
||||
} catch (\Throwable) {
|
||||
// Ignore cleanup errors for document deletion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,8 +108,10 @@ class Create extends Action
|
|||
throw new Exception($this->getLimitException(), 'Index limit exceeded');
|
||||
}
|
||||
|
||||
// Convert Document array to array of attribute metadata
|
||||
$oldAttributes = \array_map(fn ($a) => $a->getArrayCopy(), $collection->getAttribute('attributes'));
|
||||
$oldAttributes = \array_map(
|
||||
fn ($a) => $a->getArrayCopy(),
|
||||
$collection->getAttribute('attributes')
|
||||
);
|
||||
|
||||
$oldAttributes[] = [
|
||||
'key' => '$id',
|
||||
|
|
@ -120,7 +122,6 @@ class Create extends Action
|
|||
'default' => null,
|
||||
'size' => Database::LENGTH_KEY
|
||||
];
|
||||
|
||||
$oldAttributes[] = [
|
||||
'key' => '$createdAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
|
|
@ -131,7 +132,6 @@ class Create extends Action
|
|||
'default' => null,
|
||||
'size' => 0
|
||||
];
|
||||
|
||||
$oldAttributes[] = [
|
||||
'key' => '$updatedAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
|
|
@ -145,7 +145,6 @@ class Create extends Action
|
|||
|
||||
$contextType = $this->getParentContext();
|
||||
foreach ($attributes as $i => $attribute) {
|
||||
// find attribute metadata in collection document
|
||||
$attributeIndex = \array_search($attribute, array_column($oldAttributes, 'key'));
|
||||
|
||||
if ($attributeIndex === false) {
|
||||
|
|
@ -160,7 +159,6 @@ class Create extends Action
|
|||
throw new Exception($this->getParentInvalidTypeException(), "Cannot create an index for a relationship $contextType: " . $oldAttributes[$attributeIndex]['key']);
|
||||
}
|
||||
|
||||
// Ensure attribute is available
|
||||
if ($attributeStatus !== 'available') {
|
||||
$contextType = ucfirst($contextType);
|
||||
throw new Exception($this->getParentNotAvailableException(), "$contextType not available: " . $oldAttributes[$attributeIndex]['key']);
|
||||
|
|
@ -171,8 +169,8 @@ class Create extends Action
|
|||
}
|
||||
|
||||
if ($attributeArray === true) {
|
||||
$lengths[$i] = Database::MAX_ARRAY_INDEX_LENGTH;
|
||||
$orders[$i] = null;
|
||||
// Because of a bug in MySQL, we cannot create indexes on array attributes for now, otherwise queries break.
|
||||
throw new Exception(Exception::INDEX_INVALID, 'Creating indexes on array attributes is not currently supported.');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ use Appwrite\Utopia\Response as UtopiaResponse;
|
|||
use Utopia\Database\Validator\Permissions;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\JSON;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
|
|
@ -60,6 +62,8 @@ class Create extends CollectionCreate
|
|||
->param('permissions', null, new Nullable(new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE)), 'An array of permissions strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('rowSecurity', false, new Boolean(true), 'Enables configuring permissions for individual rows. A user needs one of row or table level permissions to access a row. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('enabled', true, new Boolean(), 'Is table enabled? When set to \'disabled\', users cannot access the table but Server SDKs with and API key can still read and write to the table. No data is lost when this is toggled.', true)
|
||||
->param('columns', [], new ArrayList(new JSON(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of column definitions to create. Each column should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.', true)
|
||||
->param('indexes', [], new ArrayList(new JSON(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of column keys), orders (array of ASC/DESC, optional), and lengths (array of integers, optional).', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
|
|
|
|||
358
src/Appwrite/Utopia/Database/Validator/Attributes.php
Normal file
358
src/Appwrite/Utopia/Database/Validator/Attributes.php
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator;
|
||||
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Email;
|
||||
use Utopia\Validator\IP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
|
||||
class Attributes extends Validator
|
||||
{
|
||||
protected int $maxAttributes;
|
||||
protected string $message = 'Invalid attributes';
|
||||
|
||||
/**
|
||||
* @var array<string> Supported attribute types
|
||||
*/
|
||||
protected array $supportedTypes = [
|
||||
Database::VAR_STRING,
|
||||
Database::VAR_INTEGER,
|
||||
Database::VAR_FLOAT,
|
||||
Database::VAR_BOOLEAN,
|
||||
Database::VAR_DATETIME,
|
||||
Database::VAR_POINT,
|
||||
Database::VAR_LINESTRING,
|
||||
Database::VAR_POLYGON,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string> Supported formats for string attributes
|
||||
*/
|
||||
protected array $supportedFormats = [
|
||||
'',
|
||||
APP_DATABASE_ATTRIBUTE_EMAIL,
|
||||
APP_DATABASE_ATTRIBUTE_ENUM,
|
||||
APP_DATABASE_ATTRIBUTE_IP,
|
||||
APP_DATABASE_ATTRIBUTE_URL,
|
||||
];
|
||||
|
||||
/**
|
||||
* @param int $maxAttributes Maximum number of attributes allowed
|
||||
* @param bool $supportForSpatialAttributes Whether DB supports spatial attributes
|
||||
*/
|
||||
public function __construct(
|
||||
int $maxAttributes = APP_LIMIT_ARRAY_PARAMS_SIZE,
|
||||
protected bool $supportForSpatialAttributes = true,
|
||||
) {
|
||||
$this->maxAttributes = $maxAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Description
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (!\is_array($value)) {
|
||||
$this->message = 'Attributes must be an array';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\count($value) > $this->maxAttributes) {
|
||||
$this->message = 'Maximum of ' . $this->maxAttributes . ' attributes allowed';
|
||||
return false;
|
||||
}
|
||||
|
||||
$keyValidator = new Key();
|
||||
$keys = [];
|
||||
|
||||
foreach ($value as $index => $attribute) {
|
||||
if (!\is_array($attribute)) {
|
||||
$this->message = "Attribute at index $index must be an object";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if (!isset($attribute['key'])) {
|
||||
$this->message = "Attribute at index $index is missing required field 'key'";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($attribute['type'])) {
|
||||
$this->message = "Attribute at index $index is missing required field 'type'";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate key
|
||||
if (!$keyValidator->isValid($attribute['key'])) {
|
||||
$this->message = "Invalid key for attribute at index $index: " . $keyValidator->getDescription();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for duplicate keys
|
||||
if (in_array($attribute['key'], $keys)) {
|
||||
$this->message = "Duplicate attribute key: " . $attribute['key'];
|
||||
return false;
|
||||
}
|
||||
$keys[] = $attribute['key'];
|
||||
|
||||
// Check for reserved keys
|
||||
$reservedKeys = ['$id', '$createdAt', '$updatedAt', '$permissions', '$collection'];
|
||||
if (\in_array($attribute['key'], $reservedKeys)) {
|
||||
$this->message = "Attribute key '" . $attribute['key'] . "' is reserved and cannot be used";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate type
|
||||
if (!\in_array($attribute['type'], $this->supportedTypes)) {
|
||||
$this->message = "Invalid type for attribute '" . $attribute['key'] . "': " . $attribute['type'];
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate spatial type support
|
||||
if (\in_array($attribute['type'], Database::SPATIAL_TYPES) && !$this->supportForSpatialAttributes) {
|
||||
$this->message = "Spatial attributes are not supported by the current database";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate size for string types
|
||||
if ($attribute['type'] === Database::VAR_STRING) {
|
||||
if (!isset($attribute['size']) || !is_int($attribute['size']) || $attribute['size'] < 1 || $attribute['size'] > APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH) {
|
||||
$this->message = "Invalid or missing size for string attribute '" . $attribute['key'] . "'. Size must be between 1 and " . APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate format if provided
|
||||
if (isset($attribute['format']) && $attribute['format'] !== '') {
|
||||
// Format is only allowed for string type
|
||||
if ($attribute['type'] !== Database::VAR_STRING) {
|
||||
$this->message = "Format is only allowed for string type for attribute '" . $attribute['key'] . "'";
|
||||
return false;
|
||||
}
|
||||
if (!in_array($attribute['format'], $this->supportedFormats)) {
|
||||
$this->message = "Invalid format for attribute '" . $attribute['key'] . "': " . $attribute['format'];
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate required field if provided
|
||||
if (isset($attribute['required']) && !is_bool($attribute['required'])) {
|
||||
$this->message = "Invalid 'required' value for attribute '" . $attribute['key'] . "': must be a boolean";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate array field if provided
|
||||
if (isset($attribute['array']) && !is_bool($attribute['array'])) {
|
||||
$this->message = "Invalid 'array' value for attribute '" . $attribute['key'] . "': must be a boolean";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate signed field if provided
|
||||
if (isset($attribute['signed']) && !is_bool($attribute['signed'])) {
|
||||
$this->message = "Invalid 'signed' value for attribute '" . $attribute['key'] . "': must be a boolean";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate signed only for integer/float types
|
||||
if (isset($attribute['signed']) && !in_array($attribute['type'], [Database::VAR_INTEGER, Database::VAR_FLOAT])) {
|
||||
$this->message = "Attribute '" . $attribute['key'] . "': 'signed' can only be used with integer or float types";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate required and default conflict
|
||||
if (isset($attribute['required']) && $attribute['required'] === true && isset($attribute['default']) && $attribute['default'] !== null) {
|
||||
$this->message = "Attribute '" . $attribute['key'] . "' cannot have a default value when required is true";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate array and default conflict
|
||||
if (isset($attribute['array']) && $attribute['array'] === true && isset($attribute['default']) && $attribute['default'] !== null) {
|
||||
$this->message = "Attribute '" . $attribute['key'] . "' cannot have a default value when array is true";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate min/max range for integer/float
|
||||
if (isset($attribute['min']) || isset($attribute['max'])) {
|
||||
if (!in_array($attribute['type'], [Database::VAR_INTEGER, Database::VAR_FLOAT])) {
|
||||
$this->message = "Attribute '" . $attribute['key'] . "': min/max can only be used with integer or float types";
|
||||
return false;
|
||||
}
|
||||
|
||||
// If both are set, validate ordering
|
||||
if (isset($attribute['min']) && isset($attribute['max']) && $attribute['min'] > $attribute['max']) {
|
||||
$this->message = "Attribute '" . $attribute['key'] . "': minimum value must be less than or equal to maximum value";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate default value matches attribute type
|
||||
if (isset($attribute['default'])) {
|
||||
switch ($attribute['type']) {
|
||||
case Database::VAR_STRING:
|
||||
if (!is_string($attribute['default'])) {
|
||||
$this->message = "Default value for string attribute '" . $attribute['key'] . "' must be a string";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate string size
|
||||
$size = $attribute['size'] ?? 0;
|
||||
if ($size > 0) {
|
||||
$textValidator = new Text($size, 0);
|
||||
if (!$textValidator->isValid($attribute['default'])) {
|
||||
$this->message = "Default value for attribute '" . $attribute['key'] . "' exceeds maximum size of $size characters";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate format-specific defaults
|
||||
$format = $attribute['format'] ?? '';
|
||||
if ($format === APP_DATABASE_ATTRIBUTE_EMAIL) {
|
||||
$emailValidator = new Email();
|
||||
if (!$emailValidator->isValid($attribute['default'])) {
|
||||
$this->message = "Default value for email attribute '" . $attribute['key'] . "' must be a valid email address";
|
||||
return false;
|
||||
}
|
||||
} elseif ($format === APP_DATABASE_ATTRIBUTE_IP) {
|
||||
$ipValidator = new IP();
|
||||
if (!$ipValidator->isValid($attribute['default'])) {
|
||||
$this->message = "Default value for IP attribute '" . $attribute['key'] . "' must be a valid IP address";
|
||||
return false;
|
||||
}
|
||||
} elseif ($format === APP_DATABASE_ATTRIBUTE_URL) {
|
||||
$urlValidator = new URL();
|
||||
if (!$urlValidator->isValid($attribute['default'])) {
|
||||
$this->message = "Default value for URL attribute '" . $attribute['key'] . "' must be a valid URL";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Database::VAR_INTEGER:
|
||||
if (!is_int($attribute['default'])) {
|
||||
$this->message = "Default value for integer attribute '" . $attribute['key'] . "' must be an integer";
|
||||
return false;
|
||||
}
|
||||
// Validate within range if min/max specified
|
||||
if (isset($attribute['min']) || isset($attribute['max'])) {
|
||||
$min = $attribute['min'] ?? \PHP_INT_MIN;
|
||||
$max = $attribute['max'] ?? \PHP_INT_MAX;
|
||||
$rangeValidator = new Range($min, $max, Database::VAR_INTEGER);
|
||||
if (!$rangeValidator->isValid($attribute['default'])) {
|
||||
$this->message = "Default value for integer attribute '" . $attribute['key'] . "' must be between $min and $max";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Database::VAR_FLOAT:
|
||||
if (!is_float($attribute['default']) && !is_int($attribute['default'])) {
|
||||
$this->message = "Default value for float attribute '" . $attribute['key'] . "' must be a number";
|
||||
return false;
|
||||
}
|
||||
// Validate within range if min/max specified
|
||||
if (isset($attribute['min']) || isset($attribute['max'])) {
|
||||
$min = $attribute['min'] ?? -\PHP_FLOAT_MAX;
|
||||
$max = $attribute['max'] ?? \PHP_FLOAT_MAX;
|
||||
$rangeValidator = new Range($min, $max, Database::VAR_FLOAT);
|
||||
if (!$rangeValidator->isValid((float)$attribute['default'])) {
|
||||
$this->message = "Default value for float attribute '" . $attribute['key'] . "' must be between $min and $max";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Database::VAR_BOOLEAN:
|
||||
if (!is_bool($attribute['default'])) {
|
||||
$this->message = "Default value for boolean attribute '" . $attribute['key'] . "' must be a boolean";
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Database::VAR_DATETIME:
|
||||
if (!is_string($attribute['default'])) {
|
||||
$this->message = "Default value for datetime attribute '" . $attribute['key'] . "' must be a string in ISO 8601 format";
|
||||
return false;
|
||||
}
|
||||
// Basic datetime format validation
|
||||
$datetimeValidator = new DatetimeValidator();
|
||||
if (!$datetimeValidator->isValid($attribute['default'])) {
|
||||
$this->message = "Default value for datetime attribute '" . $attribute['key'] . "' must be in valid ISO 8601 format";
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate enum elements if format is enum
|
||||
if (isset($attribute['format']) && $attribute['format'] === APP_DATABASE_ATTRIBUTE_ENUM) {
|
||||
if (!isset($attribute['elements']) || !is_array($attribute['elements']) || empty($attribute['elements'])) {
|
||||
$this->message = "Attribute '" . $attribute['key'] . "' with enum format must have 'elements' array";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate each enum element
|
||||
foreach ($attribute['elements'] as $elementIndex => $element) {
|
||||
if (!is_string($element) || empty($element)) {
|
||||
$this->message = "Enum element at index $elementIndex for attribute '" . $attribute['key'] . "' must be a non-empty string";
|
||||
return false;
|
||||
}
|
||||
if (strlen($element) > Database::LENGTH_KEY) {
|
||||
$this->message = "Enum element at index $elementIndex for attribute '" . $attribute['key'] . "' exceeds maximum length of " . Database::LENGTH_KEY . " characters";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate default exists in elements
|
||||
if (isset($attribute['default']) && $attribute['default'] !== null) {
|
||||
if (!in_array($attribute['default'], $attribute['elements'], true)) {
|
||||
$this->message = "Default value for enum attribute '" . $attribute['key'] . "' must be one of the provided elements";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_ARRAY;
|
||||
}
|
||||
}
|
||||
200
src/Appwrite/Utopia/Database/Validator/Indexes.php
Normal file
200
src/Appwrite/Utopia/Database/Validator/Indexes.php
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator;
|
||||
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Validator;
|
||||
|
||||
class Indexes extends Validator
|
||||
{
|
||||
protected string $message = 'Invalid indexes';
|
||||
|
||||
/**
|
||||
* @var array<string> Supported index types
|
||||
*/
|
||||
protected array $supportedTypes = [
|
||||
Database::INDEX_KEY,
|
||||
Database::INDEX_FULLTEXT,
|
||||
Database::INDEX_UNIQUE,
|
||||
Database::INDEX_SPATIAL,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string> Supported orders
|
||||
*/
|
||||
protected array $supportedOrders = [
|
||||
Database::ORDER_ASC,
|
||||
Database::ORDER_DESC,
|
||||
];
|
||||
|
||||
/**
|
||||
* @param int $maxIndexes Maximum number of indexes allowed
|
||||
*/
|
||||
public function __construct(
|
||||
protected int $maxIndexes = APP_LIMIT_ARRAY_PARAMS_SIZE,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (!is_array($value)) {
|
||||
$this->message = 'Indexes must be an array';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count($value) > $this->maxIndexes) {
|
||||
$this->message = 'Maximum of ' . $this->maxIndexes . ' indexes allowed';
|
||||
return false;
|
||||
}
|
||||
|
||||
$keyValidator = new Key();
|
||||
$keys = [];
|
||||
|
||||
foreach ($value as $i => $index) {
|
||||
if (!is_array($index)) {
|
||||
$this->message = "Index at position $i must be an object";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if (!isset($index['key']) || !is_string($index['key'])) {
|
||||
$this->message = "Index at position $i is missing required field 'key'";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($index['type']) || !is_string($index['type'])) {
|
||||
$this->message = "Index at position $i is missing required field 'type'";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($index['attributes']) || !is_array($index['attributes'])) {
|
||||
$this->message = "Index at position $i is missing required field 'attributes' (must be an array)";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate key format
|
||||
if (!$keyValidator->isValid($index['key'])) {
|
||||
$this->message = "Invalid key for index at position $i: " . $keyValidator->getDescription();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for duplicate keys
|
||||
if (in_array($index['key'], $keys)) {
|
||||
$this->message = "Duplicate index key: " . $index['key'];
|
||||
return false;
|
||||
}
|
||||
$keys[] = $index['key'];
|
||||
|
||||
// Validate type
|
||||
if (!in_array($index['type'], $this->supportedTypes)) {
|
||||
$this->message = "Invalid type for index '" . $index['key'] . "': " . $index['type'];
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate attributes array
|
||||
if (empty($index['attributes'])) {
|
||||
$this->message = "Index '" . $index['key'] . "' must have at least one attribute";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count($index['attributes']) > APP_LIMIT_ARRAY_PARAMS_SIZE) {
|
||||
$this->message = "Index '" . $index['key'] . "' cannot have more than " . APP_LIMIT_ARRAY_PARAMS_SIZE . " attributes";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate each attribute in the index
|
||||
$systemAttrs = ['$id', '$createdAt', '$updatedAt'];
|
||||
foreach ($index['attributes'] as $attrIndex => $attr) {
|
||||
if (!is_string($attr)) {
|
||||
$this->message = "Invalid attribute at position $attrIndex in index '" . $index['key'] . "': must be a string";
|
||||
return false;
|
||||
}
|
||||
if (!$keyValidator->isValid($attr) && !in_array($attr, $systemAttrs)) {
|
||||
$this->message = "Invalid attribute name '$attr' in index '" . $index['key'] . "'";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$attrCount = count($index['attributes']);
|
||||
|
||||
// Validate orders if provided
|
||||
if (isset($index['orders'])) {
|
||||
if (!is_array($index['orders'])) {
|
||||
$this->message = "Index '" . $index['key'] . "' orders must be an array";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count($index['orders']) !== $attrCount) {
|
||||
$this->message = "Index '" . $index['key'] . "': orders array length (" . count($index['orders']) . ") must match attributes array length ($attrCount)";
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($index['orders'] as $order) {
|
||||
if ($order !== null && $order !== '' && !in_array($order, $this->supportedOrders)) {
|
||||
$this->message = "Invalid order '$order' in index '" . $index['key'] . "'. Must be 'ASC' or 'DESC'";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate lengths if provided
|
||||
if (isset($index['lengths'])) {
|
||||
if (!is_array($index['lengths'])) {
|
||||
$this->message = "Index '" . $index['key'] . "' lengths must be an array";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count($index['lengths']) !== $attrCount) {
|
||||
$this->message = "Index '" . $index['key'] . "': lengths array length (" . count($index['lengths']) . ") must match attributes array length ($attrCount)";
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($index['lengths'] as $length) {
|
||||
if ($length !== null && (!is_int($length) || $length < 0)) {
|
||||
$this->message = "Invalid length in index '" . $index['key'] . "': must be a non-negative integer or null";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_ARRAY;
|
||||
}
|
||||
}
|
||||
|
|
@ -1352,7 +1352,7 @@ trait DatabasesBase
|
|||
]);
|
||||
|
||||
$this->assertEquals(400, $fulltextArray['headers']['status-code']);
|
||||
$this->assertEquals('"Fulltext" index is forbidden on array attributes', $fulltextArray['body']['message']);
|
||||
$this->assertEquals('Creating indexes on array attributes is not currently supported.', $fulltextArray['body']['message']);
|
||||
|
||||
$actorsArray = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
|
@ -1364,7 +1364,8 @@ trait DatabasesBase
|
|||
'attributes' => ['actors'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $actorsArray['headers']['status-code']);
|
||||
$this->assertEquals(400, $actorsArray['headers']['status-code']);
|
||||
$this->assertEquals('Creating indexes on array attributes is not currently supported.', $actorsArray['body']['message']);
|
||||
|
||||
$twoLevelsArray = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
|
@ -1377,7 +1378,8 @@ trait DatabasesBase
|
|||
'orders' => ['DESC', 'DESC'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $twoLevelsArray['headers']['status-code']);
|
||||
$this->assertEquals(400, $twoLevelsArray['headers']['status-code']);
|
||||
$this->assertEquals('Creating indexes on array attributes is not currently supported.', $twoLevelsArray['body']['message']);
|
||||
|
||||
$unknown = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
|
@ -1403,7 +1405,8 @@ trait DatabasesBase
|
|||
'orders' => ['DESC'], // Check order is removed in API
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $index1['headers']['status-code']);
|
||||
$this->assertEquals(400, $index1['headers']['status-code']);
|
||||
$this->assertEquals('Creating indexes on array attributes is not currently supported.', $index1['body']['message']);
|
||||
|
||||
$index2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
|
@ -1415,7 +1418,8 @@ trait DatabasesBase
|
|||
'attributes' => ['integers'], // array attribute
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $index2['headers']['status-code']);
|
||||
$this->assertEquals(400, $index2['headers']['status-code']);
|
||||
$this->assertEquals('Creating indexes on array attributes is not currently supported.', $index2['body']['message']);
|
||||
|
||||
/**
|
||||
* Create Indexes by worker
|
||||
|
|
@ -1429,7 +1433,7 @@ trait DatabasesBase
|
|||
]), []);
|
||||
|
||||
$this->assertIsArray($movies['body']['indexes']);
|
||||
$this->assertCount(8, $movies['body']['indexes']);
|
||||
$this->assertCount(4, $movies['body']['indexes']);
|
||||
$this->assertEquals($titleIndex['body']['key'], $movies['body']['indexes'][0]['key']);
|
||||
$this->assertEquals($releaseYearIndex['body']['key'], $movies['body']['indexes'][1]['key']);
|
||||
$this->assertEquals($releaseWithDate1['body']['key'], $movies['body']['indexes'][2]['key']);
|
||||
|
|
@ -1483,8 +1487,7 @@ trait DatabasesBase
|
|||
$this->assertEquals('lengthTestIndex', $index['body']['key']);
|
||||
$this->assertEquals([128, 200], $index['body']['lengths']);
|
||||
|
||||
// Test case for lengths array overriding
|
||||
// set a length for an array attribute, it should get overriden with Database::MAX_ARRAY_INDEX_LENGTH
|
||||
// Test case for array attribute index (should be blocked)
|
||||
$create = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/indexes", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
|
|
@ -1495,14 +1498,8 @@ trait DatabasesBase
|
|||
'attributes' => ['actors'],
|
||||
'lengths' => [120]
|
||||
]);
|
||||
$this->assertEquals(202, $create['headers']['status-code']);
|
||||
|
||||
$index = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$collectionId}/indexes/lengthOverrideTestIndex", [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->assertEquals([Database::MAX_ARRAY_INDEX_LENGTH], $index['body']['lengths']);
|
||||
$this->assertEquals(400, $create['headers']['status-code']);
|
||||
$this->assertEquals('Creating indexes on array attributes is not currently supported.', $create['body']['message']);
|
||||
|
||||
// Test case for count of lengths greater than attributes (should throw 400)
|
||||
$create = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/indexes", [
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1334,7 +1334,7 @@ trait DatabasesBase
|
|||
]);
|
||||
|
||||
$this->assertEquals(400, $fulltextArray['headers']['status-code']);
|
||||
$this->assertEquals('"Fulltext" index is forbidden on array attributes', $fulltextArray['body']['message']);
|
||||
$this->assertEquals('Creating indexes on array attributes is not currently supported.', $fulltextArray['body']['message']);
|
||||
|
||||
$actorsArray = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $data['moviesId'] . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
|
@ -1346,7 +1346,8 @@ trait DatabasesBase
|
|||
'columns' => ['actors'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $actorsArray['headers']['status-code']);
|
||||
$this->assertEquals(400, $actorsArray['headers']['status-code']);
|
||||
$this->assertEquals('Creating indexes on array attributes is not currently supported.', $actorsArray['body']['message']);
|
||||
|
||||
$twoLevelsArray = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $data['moviesId'] . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
|
@ -1359,7 +1360,8 @@ trait DatabasesBase
|
|||
'orders' => ['DESC', 'DESC'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $twoLevelsArray['headers']['status-code']);
|
||||
$this->assertEquals(400, $twoLevelsArray['headers']['status-code']);
|
||||
$this->assertEquals('Creating indexes on array attributes is not currently supported.', $twoLevelsArray['body']['message']);
|
||||
|
||||
$unknown = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $data['moviesId'] . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
|
@ -1385,7 +1387,8 @@ trait DatabasesBase
|
|||
'orders' => ['DESC'], // Check order is removed in API
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $index1['headers']['status-code']);
|
||||
$this->assertEquals(400, $index1['headers']['status-code']);
|
||||
$this->assertEquals('Creating indexes on array attributes is not currently supported.', $index1['body']['message']);
|
||||
|
||||
$index2 = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $data['moviesId'] . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
|
@ -1397,7 +1400,8 @@ trait DatabasesBase
|
|||
'columns' => ['integers'], // array column
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $index2['headers']['status-code']);
|
||||
$this->assertEquals(400, $index2['headers']['status-code']);
|
||||
$this->assertEquals('Creating indexes on array attributes is not currently supported.', $index2['body']['message']);
|
||||
|
||||
/**
|
||||
* Create Indexes by worker
|
||||
|
|
@ -1411,7 +1415,7 @@ trait DatabasesBase
|
|||
]), []);
|
||||
|
||||
$this->assertIsArray($movies['body']['indexes']);
|
||||
$this->assertCount(8, $movies['body']['indexes']);
|
||||
$this->assertCount(4, $movies['body']['indexes']);
|
||||
$this->assertEquals($titleIndex['body']['key'], $movies['body']['indexes'][0]['key']);
|
||||
$this->assertEquals($releaseYearIndex['body']['key'], $movies['body']['indexes'][1]['key']);
|
||||
$this->assertEquals($releaseWithDate1['body']['key'], $movies['body']['indexes'][2]['key']);
|
||||
|
|
|
|||
Loading…
Reference in a new issue