Merge branch '1.8.x' into feat-update-preview-url-in-vcs-controller

This commit is contained in:
Khushboo Verma 2025-09-04 11:11:37 +05:30 committed by GitHub
commit 14b278a9a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 6217 additions and 85 deletions

View file

@ -842,6 +842,12 @@ return [
'code' => 400,
],
Exception::ATTRIBUTE_TYPE_NOT_SUPPORTED => [
'name' => Exception::ATTRIBUTE_TYPE_NOT_SUPPORTED,
'description' => 'Attribute type is not supported.',
'code' => 400,
],
/** Exists for both Attributes & Columns */
Exception::RELATIONSHIP_VALUE_INVALID => [
'name' => Exception::RELATIONSHIP_VALUE_INVALID,
@ -900,6 +906,11 @@ return [
'description' => "Existing data is too large for new size, truncate your existing data then try again.",
'code' => 400,
],
Exception::COLUMN_TYPE_NOT_SUPPORTED => [
'name' => Exception::COLUMN_TYPE_NOT_SUPPORTED,
'description' => 'Column type is not supported.',
'code' => 400,
],
/** Indexes */
Exception::INDEX_NOT_FOUND => [

View file

@ -562,15 +562,18 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
cpus: $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
logging: $resource->getAttribute('logging', true),
requestTimeout: 30
requestTimeout: 30,
responseFormat: Executor::RESPONSE_FORMAT_ARRAY_HEADERS
);
$headerOverrides = [];
// Branded 404 override
$isResponseBranded = false;
if ($executionResponse['statusCode'] === 404 && $deployment->getAttribute('adapter', '') === 'static') {
$layout = new View(__DIR__ . '/../views/general/404.phtml');
$executionResponse['body'] = $layout->render();
$executionResponse['headers']['content-length'] = \strlen($executionResponse['body']);
$headerOverrides['content-length'] = \strlen($executionResponse['body']);
$isResponseBranded = true;
}
@ -580,15 +583,16 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$transformation = new Transformation();
$transformation->addAdapter(new Preview());
$transformation->setInput($executionResponse['body']);
$transformation->setTraits($executionResponse['headers']);
$simpleHeaders = [];
foreach ($executionResponse['headers'] as $key => $value) {
$simpleHeaders[$key] = \is_array($value) ? \implode(', ', $value) : $value;
}
$transformation->setTraits($simpleHeaders);
if ($isPreview && $transformation->transform()) {
$executionResponse['body'] = $transformation->getOutput();
foreach ($executionResponse['headers'] as $key => $value) {
if (\strtolower($key) === 'content-length') {
$executionResponse['headers'][$key] = \strlen($executionResponse['body']);
}
}
$headerOverrides['content-length'] = \strlen($executionResponse['body']);
}
}
}
@ -602,25 +606,33 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
->setParam('code', $executionResponse['statusCode']);
$executionResponse['body'] = $layout->render();
foreach ($executionResponse['headers'] as $key => $value) {
if (\strtolower($key) === 'content-length') {
$executionResponse['headers'][$key] = \strlen($executionResponse['body']);
} elseif (\strtolower($key) === 'content-type') {
$executionResponse['headers'][$key] = 'text/html';
}
}
$headerOverrides['content-length'] = \strlen($executionResponse['body']);
$headerOverrides['content-type'] = 'text/html';
}
if ($deployment->getAttribute('resourceType') === 'functions') {
$executionResponse['headers']['x-appwrite-execution-id'] = $execution->getId();
$headerOverrides['x-appwrite-execution-id'] = $execution->getId();
} elseif ($deployment->getAttribute('resourceType') === 'sites') {
$executionResponse['headers']['x-appwrite-log-id'] = $execution->getId();
$headerOverrides['x-appwrite-log-id'] = $execution->getId();
}
foreach ($headerOverrides as $key => $value) {
if (\array_key_exists($key, $executionResponse['headers'])) {
if (\is_array($executionResponse['headers'][$key])) {
$executionResponse['headers'][$key][] = $value;
} else {
$executionResponse['headers'][$key] = [$executionResponse['headers'][$key], $value];
}
} else {
$executionResponse['headers'][$key] = $value;
}
}
$headersFiltered = [];
foreach ($executionResponse['headers'] as $key => $value) {
if (\in_array(\strtolower($key), FUNCTION_ALLOWLIST_HEADERS_RESPONSE)) {
$headersFiltered[] = ['name' => $key, 'value' => $value];
$headersFiltered[] = ['name' => $key, 'value' => \is_array($value) ? \implode(', ', $value) : $value];
}
}
@ -687,7 +699,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$headers = [];
foreach (($executionResponse['headers'] ?? []) as $key => $value) {
$headers[] = ['name' => $key, 'value' => $value];
$headers[] = ['name' => $key, 'value' => \is_array($value) ? \implode(', ', $value) : $value];
}
$execution->setAttribute('responseBody', $executionResponse['body'] ?? '');
@ -696,16 +708,17 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$body = $execution['responseBody'] ?? '';
$contentType = 'text/plain';
foreach ($execution['responseHeaders'] as $header) {
if (\strtolower($header['name']) === 'content-type') {
$contentType = $header['value'];
}
if (\strtolower($header['name']) === 'transfer-encoding') {
foreach ($executionResponse['headers'] as $name => $values) {
if (\strtolower($name) === 'content-type') {
$contentType = \is_array($values) ? $values[0] : $values;
continue;
}
$response->addHeader(\strtolower($header['name']), $header['value']);
if (\strtolower($name) === 'transfer-encoding') {
continue;
}
$response->setHeader($name, $values);
}
$response

View file

@ -46,6 +46,9 @@ const APP_DATABASE_ATTRIBUTE_DATETIME = 'datetime';
const APP_DATABASE_ATTRIBUTE_URL = 'url';
const APP_DATABASE_ATTRIBUTE_INT_RANGE = 'intRange';
const APP_DATABASE_ATTRIBUTE_FLOAT_RANGE = 'floatRange';
const APP_DATABASE_ATTRIBUTE_POINT = 'point';
const APP_DATABASE_ATTRIBUTE_LINE = 'line';
const APP_DATABASE_ATTRIBUTE_POLYGON = 'polygon';
const APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH = 1_073_741_824; // 2^32 bits / 4 bits per char
const APP_DATABASE_TIMEOUT_MILLISECONDS_API = 15 * 1000; // 15 seconds
const APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER = 300 * 1000; // 5 minutes

169
composer.lock generated
View file

@ -2599,16 +2599,16 @@
},
{
"name": "symfony/http-client",
"version": "v7.3.2",
"version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
"reference": "1c064a0c67749923483216b081066642751cc2c7"
"reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-client/zipball/1c064a0c67749923483216b081066642751cc2c7",
"reference": "1c064a0c67749923483216b081066642751cc2c7",
"url": "https://api.github.com/repos/symfony/http-client/zipball/333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019",
"reference": "333b9bd7639cbdaecd25a3a48a9d2dcfaa86e019",
"shasum": ""
},
"require": {
@ -2616,6 +2616,7 @@
"psr/log": "^1|^2|^3",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/http-client-contracts": "~3.4.4|^3.5.2",
"symfony/polyfill-php83": "^1.29",
"symfony/service-contracts": "^2.5|^3"
},
"conflict": {
@ -2674,7 +2675,7 @@
"http"
],
"support": {
"source": "https://github.com/symfony/http-client/tree/v7.3.2"
"source": "https://github.com/symfony/http-client/tree/v7.3.3"
},
"funding": [
{
@ -2694,7 +2695,7 @@
"type": "tidelift"
}
],
"time": "2025-07-15T11:36:08+00:00"
"time": "2025-08-27T07:45:05+00:00"
},
{
"name": "symfony/http-client-contracts",
@ -2939,6 +2940,86 @@
],
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-php83",
"version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php83.git",
"reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5",
"reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php83\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-07-08T02:45:35+00:00"
},
{
"name": "symfony/service-contracts",
"version": "v3.6.0",
@ -3557,16 +3638,16 @@
},
{
"name": "utopia-php/database",
"version": "1.2.3",
"version": "1.3.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "8a536fead840d9da6ee819fe6b80e0f047997f69"
"reference": "06ffa2b1c977f5451200a1ee82a500be1390a789"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/8a536fead840d9da6ee819fe6b80e0f047997f69",
"reference": "8a536fead840d9da6ee819fe6b80e0f047997f69",
"url": "https://api.github.com/repos/utopia-php/database/zipball/06ffa2b1c977f5451200a1ee82a500be1390a789",
"reference": "06ffa2b1c977f5451200a1ee82a500be1390a789",
"shasum": ""
},
"require": {
@ -3607,9 +3688,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/1.2.3"
"source": "https://github.com/utopia-php/database/tree/1.3.0"
},
"time": "2025-08-27T11:47:04+00:00"
"time": "2025-09-02T16:20:02+00:00"
},
{
"name": "utopia-php/detector",
@ -4109,16 +4190,16 @@
},
{
"name": "utopia-php/migration",
"version": "1.0.0",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
"reference": "0e4499d9dd2c90c2be188cc5fb7a32d9a892b569"
"reference": "38171023efd3abe650d2abc5ac65f5df52311da6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/0e4499d9dd2c90c2be188cc5fb7a32d9a892b569",
"reference": "0e4499d9dd2c90c2be188cc5fb7a32d9a892b569",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/38171023efd3abe650d2abc5ac65f5df52311da6",
"reference": "38171023efd3abe650d2abc5ac65f5df52311da6",
"shasum": ""
},
"require": {
@ -4159,9 +4240,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
"source": "https://github.com/utopia-php/migration/tree/1.0.0"
"source": "https://github.com/utopia-php/migration/tree/1.0.1"
},
"time": "2025-08-13T09:15:53+00:00"
"time": "2025-08-28T13:41:25+00:00"
},
{
"name": "utopia-php/orchestration",
@ -7410,16 +7491,16 @@
},
{
"name": "symfony/console",
"version": "v7.3.2",
"version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "5f360ebc65c55265a74d23d7fe27f957870158a1"
"reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/5f360ebc65c55265a74d23d7fe27f957870158a1",
"reference": "5f360ebc65c55265a74d23d7fe27f957870158a1",
"url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
"reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
"shasum": ""
},
"require": {
@ -7484,7 +7565,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.3.2"
"source": "https://github.com/symfony/console/tree/v7.3.3"
},
"funding": [
{
@ -7504,7 +7585,7 @@
"type": "tidelift"
}
],
"time": "2025-07-30T17:13:41+00:00"
"time": "2025-08-25T06:35:40+00:00"
},
{
"name": "symfony/filesystem",
@ -7646,16 +7727,16 @@
},
{
"name": "symfony/options-resolver",
"version": "v7.3.2",
"version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
"reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37"
"reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/119bcf13e67dbd188e5dbc74228b1686f66acd37",
"reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/0ff2f5c3df08a395232bbc3c2eb7e84912df911d",
"reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d",
"shasum": ""
},
"require": {
@ -7693,7 +7774,7 @@
"options"
],
"support": {
"source": "https://github.com/symfony/options-resolver/tree/v7.3.2"
"source": "https://github.com/symfony/options-resolver/tree/v7.3.3"
},
"funding": [
{
@ -7713,7 +7794,7 @@
"type": "tidelift"
}
],
"time": "2025-07-15T11:36:08+00:00"
"time": "2025-08-05T10:16:07+00:00"
},
{
"name": "symfony/polyfill-ctype",
@ -8047,16 +8128,16 @@
},
{
"name": "symfony/process",
"version": "v7.3.0",
"version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af"
"reference": "32241012d521e2e8a9d713adb0812bb773b907f1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af",
"reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af",
"url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1",
"reference": "32241012d521e2e8a9d713adb0812bb773b907f1",
"shasum": ""
},
"require": {
@ -8088,7 +8169,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v7.3.0"
"source": "https://github.com/symfony/process/tree/v7.3.3"
},
"funding": [
{
@ -8099,25 +8180,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-04-17T09:11:12+00:00"
"time": "2025-08-18T09:42:54+00:00"
},
{
"name": "symfony/string",
"version": "v7.3.2",
"version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "42f505aff654e62ac7ac2ce21033818297ca89ca"
"reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca",
"reference": "42f505aff654e62ac7ac2ce21033818297ca89ca",
"url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
"reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
"shasum": ""
},
"require": {
@ -8175,7 +8260,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v7.3.2"
"source": "https://github.com/symfony/string/tree/v7.3.3"
},
"funding": [
{
@ -8195,7 +8280,7 @@
"type": "tidelift"
}
],
"time": "2025-07-10T08:47:49+00:00"
"time": "2025-08-25T06:35:40+00:00"
},
{
"name": "textalk/websocket",

View file

@ -966,7 +966,7 @@ services:
hostname: exc1
<<: *x-logging
stop_signal: SIGINT
image: openruntimes/executor:0.8.1
image: openruntimes/executor:0.11.0
restart: unless-stopped
networks:
- appwrite

View file

@ -0,0 +1 @@
Create a geometric line attribute.

View file

@ -0,0 +1 @@
Create a geometric 2d point attribute.

View file

@ -0,0 +1 @@
Create a geometric polygon attribute.

View file

@ -0,0 +1 @@
Update a line attribute. Changing the `default` value will not update already existing documents.

View file

@ -0,0 +1 @@
Update a point attribute. Changing the `default` value will not update already existing documents.

View file

@ -0,0 +1 @@
Update a polygon attribute. Changing the `default` value will not update already existing documents.

View file

@ -0,0 +1 @@
Create a geometric line attribute.

View file

@ -0,0 +1 @@
Create a geometric point attribute.

View file

@ -0,0 +1 @@
Create a geometric polygon attribute.

View file

@ -0,0 +1 @@
Update a line column. Changing the `default` value will not update already existing documents.

View file

@ -0,0 +1 @@
Update a point column. Changing the `default` value will not update already existing documents.

View file

@ -0,0 +1 @@
Update a polygon column. Changing the `default` value will not update already existing documents.

View file

@ -235,6 +235,8 @@ class Exception extends \Exception
public const ATTRIBUTE_TYPE_INVALID = 'attribute_type_invalid';
public const ATTRIBUTE_INVALID_RESIZE = 'attribute_invalid_resize';
public const ATTRIBUTE_TYPE_NOT_SUPPORTED = 'ATTRIBUTE_TYPE_NOT_SUPPORTED';
/** Columns */
public const COLUMN_NOT_FOUND = 'column_not_found';
public const COLUMN_UNKNOWN = 'column_unknown';
@ -247,6 +249,8 @@ class Exception extends \Exception
public const COLUMN_TYPE_INVALID = 'column_type_invalid';
public const COLUMN_INVALID_RESIZE = 'column_invalid_resize';
public const COLUMN_TYPE_NOT_SUPPORTED = 'COLUMN_TYPE_NOT_SUPPORTED';
/** Relationship */
public const RELATIONSHIP_VALUE_INVALID = 'relationship_value_invalid';

View file

@ -58,6 +58,7 @@ class Mapper
'json' => Types::json(),
'none' => Types::json(),
'any' => Types::json(),
'spatial' => Types::json(),
];
foreach ($defaults as $type => $default) {
@ -456,6 +457,9 @@ class Mapper
'boolean' => static::model("{$prefix}Boolean"),
'datetime' => static::model("{$prefix}Datetime"),
'relationship' => static::model("{$prefix}Relationship"),
'point' => static::model("{$prefix}Point"),
'linestring' => static::model("{$prefix}Line"),
'polygon' => static::model("{$prefix}Polygon"),
default => throw new Exception('Unknown ' . strtolower($prefix) . ' implementation'),
};
}

View file

@ -209,6 +209,14 @@ abstract class Action extends UtopiaAction
: Exception::COLUMN_NOT_AVAILABLE;
}
/**
* Get the exception for spatial type attribute not supported by the database adapter
*/
protected function getSpatialTypeNotSupportedException(): string
{
return $this->isCollectionsAPI() ? Exception::ATTRIBUTE_TYPE_NOT_SUPPORTED : Exception::COLUMN_TYPE_NOT_SUPPORTED;
}
/**
* Get the correct collections context for Events queue.
*/
@ -245,6 +253,18 @@ abstract class Action extends UtopiaAction
? UtopiaResponse::MODEL_ATTRIBUTE_RELATIONSHIP
: UtopiaResponse::MODEL_COLUMN_RELATIONSHIP,
Database::VAR_POINT => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_POINT
: UtopiaResponse::MODEL_COLUMN_POINT,
Database::VAR_LINESTRING => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_LINE
: UtopiaResponse::MODEL_COLUMN_LINE,
Database::VAR_POLYGON => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_POLYGON
: UtopiaResponse::MODEL_COLUMN_POLYGON,
Database::VAR_STRING => match ($format) {
APP_DATABASE_ATTRIBUTE_EMAIL => $isCollections
? UtopiaResponse::MODEL_ATTRIBUTE_EMAIL
@ -286,6 +306,10 @@ abstract class Action extends UtopiaAction
$default = $attribute->getAttribute('default');
$options = $attribute->getAttribute('options', []);
if (in_array($type, Database::SPATIAL_TYPES) && !$dbForProject->getAdapter()->getSupportForSpatialAttributes()) {
throw new Exception($this->getSpatialTypeNotSupportedException());
}
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($db->isEmpty()) {
@ -434,7 +458,7 @@ abstract class Action extends UtopiaAction
return $attribute;
}
protected function updateAttribute(string $databaseId, string $collectionId, string $key, Database $dbForProject, Event $queueForEvents, string $type, int $size = null, string $filter = null, string|bool|int|float $default = null, bool $required = null, int|float|null $min = null, int|float|null $max = null, array $elements = null, array $options = [], string $newKey = null): Document
protected function updateAttribute(string $databaseId, string $collectionId, string $key, Database $dbForProject, Event $queueForEvents, string $type, int $size = null, string $filter = null, string|bool|int|float|array $default = null, bool $required = null, int|float|null $min = null, int|float|null $max = null, array $elements = null, array $options = [], string $newKey = null): Document
{
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));

View file

@ -0,0 +1,90 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Line;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Spatial;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
class Create extends Action
{
public static function getName(): string
{
return 'createLineAttribute';
}
protected function getResponseModel(): string|array
{
return UtopiaResponse::MODEL_ATTRIBUTE_LINE;
}
public function __construct()
{
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/line')
->desc('Create line attribute')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
->label('scope', 'collections.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'attribute.create')
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/create-line-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_ACCEPTED,
model: $this->getResponseModel(),
)
],
deprecated: new Deprecated(
since: '1.8.0',
replaceWith: 'tablesDB.createLineColumn',
),
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_LINESTRING)), 'Default value for attribute when not provided, as JSON string. Cannot be set when attribute is required.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
->callback($this->action(...));
}
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
{
$default = \is_string($default) ? \json_decode($default, true) : $default;
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
'type' => Database::VAR_LINESTRING,
'required' => $required,
'default' => $default
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,95 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Line;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Spatial;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
class Update extends Action
{
public static function getName(): string
{
return 'updateLineAttribute';
}
protected function getResponseModel(): string|array
{
return UtopiaResponse::MODEL_ATTRIBUTE_LINE;
}
public function __construct()
{
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/line/:key')
->desc('Update line attribute')
->groups(['api', 'database', 'schema'])
->label('scope', 'collections.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update')
->label('audits.event', 'attribute.update')
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/update-line-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel(),
)
],
contentType: ContentType::JSON,
deprecated: new Deprecated(
since: '1.8.0',
replaceWith: 'tablesDB.updateLineColumn',
),
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#createCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_LINESTRING)), 'Default value for attribute when not provided, as JSON string. Cannot be set when attribute is required.', true)
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback($this->action(...));
}
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
{
$default = \is_string($default) ? \json_decode($default, true) : $default;
$attribute = $this->updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
type: Database::VAR_LINESTRING,
default: $default,
required: $required,
newKey: $newKey
);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,90 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Point;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Spatial;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
class Create extends Action
{
public static function getName(): string
{
return 'createPointAttribute';
}
protected function getResponseModel(): string|array
{
return UtopiaResponse::MODEL_ATTRIBUTE_POINT;
}
public function __construct()
{
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/point')
->desc('Create point attribute')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
->label('scope', 'collections.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'attribute.create')
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/create-point-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_ACCEPTED,
model: $this->getResponseModel(),
)
],
deprecated: new Deprecated(
since: '1.8.0',
replaceWith: 'tablesDB.createPointColumn',
),
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_POINT)), 'Default value for attribute when not provided, as JSON string. Cannot be set when attribute is required.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
->callback($this->action(...));
}
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
{
$default = \is_string($default) ? \json_decode($default, true) : $default;
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
'type' => Database::VAR_POINT,
'required' => $required,
'default' => $default,
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,95 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Point;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Spatial;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
class Update extends Action
{
public static function getName(): string
{
return 'updatePointAttribute';
}
protected function getResponseModel(): string|array
{
return UtopiaResponse::MODEL_ATTRIBUTE_POINT;
}
public function __construct()
{
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/point/:key')
->desc('Update point attribute')
->groups(['api', 'database', 'schema'])
->label('scope', 'collections.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update')
->label('audits.event', 'attribute.update')
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/update-point-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel(),
)
],
contentType: ContentType::JSON,
deprecated: new Deprecated(
since: '1.8.0',
replaceWith: 'tablesDB.updatePointColumn',
),
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#createCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_POINT)), 'Default value for attribute when not provided, as JSON string. Cannot be set when attribute is required.', true)
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback($this->action(...));
}
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
{
$default = \is_string($default) ? \json_decode($default, true) : $default;
$attribute = $this->updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
type: Database::VAR_POINT,
default: $default,
required: $required,
newKey: $newKey
);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,90 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Polygon;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Spatial;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
class Create extends Action
{
public static function getName(): string
{
return 'createPolygonAttribute';
}
protected function getResponseModel(): string|array
{
return UtopiaResponse::MODEL_ATTRIBUTE_POLYGON;
}
public function __construct()
{
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/polygon')
->desc('Create polygon attribute')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
->label('scope', 'collections.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'attribute.create')
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/create-polygon-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_ACCEPTED,
model: $this->getResponseModel(),
)
],
deprecated: new Deprecated(
since: '1.8.0',
replaceWith: 'tablesDB.createPolygonColumn',
),
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_POLYGON)), 'Default value for attribute when not provided, as JSON string. Cannot be set when attribute is required.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
->callback($this->action(...));
}
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents): void
{
$default = \is_string($default) ? \json_decode($default, true) : $default;
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
'key' => $key,
'type' => Database::VAR_POLYGON,
'required' => $required,
'default' => $default,
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -0,0 +1,95 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Polygon;
use Appwrite\Event\Event;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Deprecated;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Spatial;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
class Update extends Action
{
public static function getName(): string
{
return 'updatePolygonAttribute';
}
protected function getResponseModel(): string|array
{
return UtopiaResponse::MODEL_ATTRIBUTE_POLYGON;
}
public function __construct()
{
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/databases/:databaseId/collections/:collectionId/attributes/polygon/:key')
->desc('Update polygon attribute')
->groups(['api', 'database', 'schema'])
->label('scope', 'collections.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update')
->label('audits.event', 'attribute.update')
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/databases/update-polygon-attribute.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel(),
)
],
contentType: ContentType::JSON,
deprecated: new Deprecated(
since: '1.8.0',
replaceWith: 'tablesDB.updatePolygonColumn',
),
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#createCollection).')
->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_POLYGON)), 'Default value for attribute when not provided, as JSON string. Cannot be set when attribute is required.', true)
->param('newKey', null, new Key(), 'New attribute key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback($this->action(...));
}
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, ?string $newKey, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
{
$default = \is_string($default) ? \json_decode($default, true) : $default;
$attribute = $this->updateAttribute(
databaseId: $databaseId,
collectionId: $collectionId,
key: $key,
dbForProject: $dbForProject,
queueForEvents: $queueForEvents,
type: Database::VAR_POLYGON,
default: $default,
required: $required,
newKey: $newKey
);
$response
->setStatusCode(SwooleResponse::STATUS_CODE_OK)
->dynamic($attribute, $this->getResponseModel());
}
}

View file

@ -2,6 +2,7 @@
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Bulk;
use Appwrite\Event\Event;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Action;
@ -74,11 +75,15 @@ class Upsert extends Action
->inject('response')
->inject('dbForProject')
->inject('queueForStatsUsage')
->inject('queueForEvents')
->inject('queueForRealtime')
->inject('queueForFunctions')
->inject('queueForWebhooks')
->inject('plan')
->callback($this->action(...));
}
public function action(string $databaseId, string $collectionId, array $documents, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan): void
public function action(string $databaseId, string $collectionId, array $documents, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void
{
$database = $dbForProject->getDocument('databases', $databaseId);
if ($database->isEmpty()) {
@ -141,5 +146,16 @@ class Upsert extends Action
'total' => $modified,
$this->getSdkGroup() => $upserted
]), $this->getResponseModel());
$this->triggerBulk(
'databases.[databaseId].collections.[collectionId].documents.[documentId].upsert',
$database,
$collection,
$upserted,
$queueForEvents,
$queueForRealtime,
$queueForFunctions,
$queueForWebhooks
);
}
}

View file

@ -71,7 +71,7 @@ class Create extends Action
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', null, new Key(), 'Index Key.')
->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE]), 'Index type.')
->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL]), 'Index type.')
->param('attributes', null, new ArrayList(new Key(true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attributes to index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' attributes are allowed, each 32 characters long.')
->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true)
->param('lengths', [], new ArrayList(new Nullable(new Integer()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Length of index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE, optional: true)
@ -190,11 +190,21 @@ class Create extends Action
'orders' => $orders,
]);
$maxIndexLength = $dbForProject->getAdapter()->getMaxIndexLength();
$internalIndexesKeys = $dbForProject->getAdapter()->getInternalIndexesKeys();
$supportForIndexArray = $dbForProject->getAdapter()->getSupportForIndexArray();
$supportForSpatialAttributes = $dbForProject->getAdapter()->getSupportForSpatialAttributes();
$supportForSpatialIndexNull = $dbForProject->getAdapter()->getSupportForSpatialIndexNull();
$supportForSpatialIndexOrder = $dbForProject->getAdapter()->getSupportForSpatialIndexOrder();
$validator = new IndexValidator(
$collection->getAttribute('attributes'),
$dbForProject->getAdapter()->getMaxIndexLength(),
$dbForProject->getAdapter()->getInternalIndexesKeys(),
$dbForProject->getAdapter()->getSupportForIndexArray()
$maxIndexLength,
$internalIndexesKeys,
$supportForIndexArray,
$supportForSpatialAttributes,
$supportForSpatialIndexNull,
$supportForSpatialIndexOrder
);
if (!$validator->isValid($index)) {

View file

@ -0,0 +1,66 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Line;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Line\Create as LineCreate;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Spatial;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
class Create extends LineCreate
{
public static function getName(): string
{
return 'createLineColumn';
}
protected function getResponseModel(): string|array
{
return UtopiaResponse::MODEL_COLUMN_LINE;
}
public function __construct()
{
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/line')
->desc('Create line column')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/tablesdb/create-line-column.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_ACCEPTED,
model: $this->getResponseModel(),
)
]
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).')
->param('key', '', new Key(), 'Column Key.')
->param('required', null, new Boolean(), 'Is column required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_LINESTRING)), 'Default value for column when not provided, as JSON string. Cannot be set when column is required.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
->callback($this->action(...));
}
}

View file

@ -0,0 +1,68 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Line;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Line\Update as LineUpdate;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Spatial;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
class Update extends LineUpdate
{
public static function getName(): string
{
return 'updateLineColumn';
}
protected function getResponseModel(): string|array
{
return UtopiaResponse::MODEL_COLUMN_LINE;
}
public function __construct()
{
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/line/:key')
->desc('Update line column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/tablesdb/update-line-column.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel(),
)
],
contentType: ContentType::JSON
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).')
->param('key', '', new Key(), 'Column Key.')
->param('required', null, new Boolean(), 'Is column required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_LINESTRING)), 'Default value for column when not provided, as JSON string. Cannot be set when column is required.', true)
->param('newKey', null, new Key(), 'New Column Key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback($this->action(...));
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Point;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Point\Create as PointCreate;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Spatial;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
class Create extends PointCreate
{
public static function getName(): string
{
return 'createPointColumn';
}
protected function getResponseModel(): string|array
{
return UtopiaResponse::MODEL_COLUMN_POINT;
}
public function __construct()
{
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/point')
->desc('Create point column')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/tablesdb/create-point-column.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_ACCEPTED,
model: $this->getResponseModel(),
)
]
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).')
->param('key', '', new Key(), 'Column Key.')
->param('required', null, new Boolean(), 'Is column required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_POINT)), 'Default value for column when not provided, as JSON string. Cannot be set when column is required.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
->callback($this->action(...));
}
}

View file

@ -0,0 +1,68 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Point;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Point\Update as PointUpdate;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Spatial;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
class Update extends PointUpdate
{
public static function getName(): string
{
return 'updatePointColumn';
}
protected function getResponseModel(): string|array
{
return UtopiaResponse::MODEL_COLUMN_POINT;
}
public function __construct()
{
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/point/:key')
->desc('Update point column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/tablesdb/update-point-column.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel(),
)
],
contentType: ContentType::JSON
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).')
->param('key', '', new Key(), 'Column Key.')
->param('required', null, new Boolean(), 'Is column required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_POINT)), 'Default value for column when not provided, as JSON string. Cannot be set when column is required.', true)
->param('newKey', null, new Key(), 'New Column Key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback($this->action(...));
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Polygon;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Polygon\Create as PolygonCreate;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Spatial;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
class Create extends PolygonCreate
{
public static function getName(): string
{
return 'createPolygonColumn';
}
protected function getResponseModel(): string|array
{
return UtopiaResponse::MODEL_COLUMN_POLYGON;
}
public function __construct()
{
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/polygon')
->desc('Create polygon column')
->groups(['api', 'database', 'schema'])
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'column.create')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/tablesdb/create-polygon-column.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_ACCEPTED,
model: $this->getResponseModel(),
)
]
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).')
->param('key', '', new Key(), 'Column Key.')
->param('required', null, new Boolean(), 'Is column required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_POLYGON)), 'Default value for column when not provided, as JSON string. Cannot be set when column is required.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForDatabase')
->inject('queueForEvents')
->callback($this->action(...));
}
}

View file

@ -0,0 +1,68 @@
<?php
namespace Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Polygon;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Polygon\Update as PolygonUpdate;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Spatial;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Swoole\Response as SwooleResponse;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
class Update extends PolygonUpdate
{
public static function getName(): string
{
return 'updatePolygonColumn';
}
protected function getResponseModel(): string|array
{
return UtopiaResponse::MODEL_COLUMN_POLYGON;
}
public function __construct()
{
$this
->setHttpMethod(self::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/tablesdb/:databaseId/tables/:tableId/columns/polygon/:key')
->desc('Update polygon column')
->groups(['api', 'database', 'schema'])
->label('scope', ['tables.write', 'collections.write'])
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
->label('audits.event', 'column.update')
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
->label('sdk', new Method(
namespace: $this->getSdkNamespace(),
group: $this->getSdkGroup(),
name: self::getName(),
description: '/docs/references/tablesdb/update-polygon-column.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: SwooleResponse::STATUS_CODE_OK,
model: $this->getResponseModel(),
)
],
contentType: ContentType::JSON
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('tableId', '', new UID(), 'Table ID. You can create a new table using the TablesDB service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).')
->param('key', '', new Key(), 'Column Key.')
->param('required', null, new Boolean(), 'Is column required?')
->param('default', null, new Nullable(new Spatial(Database::VAR_POLYGON)), 'Default value for column when not provided, as JSON string. Cannot be set when column is required.', true)
->param('newKey', null, new Key(), 'New Column Key.', true)
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->callback($this->action(...));
}
}

View file

@ -58,7 +58,7 @@ class Create extends IndexCreate
->param('databaseId', '', new UID(), 'Database ID.')
->param('tableId', '', new UID(), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/tablesdb#tablesDBCreate).')
->param('key', null, new Key(), 'Index Key.')
->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE]), 'Index type.')
->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL]), 'Index type.')
->param('columns', null, new ArrayList(new Key(true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of columns to index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' columns are allowed, each 32 characters long.')
->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true)
->param('lengths', [], new ArrayList(new Nullable(new Integer()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Length of index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE, optional: true)

View file

@ -61,6 +61,10 @@ class Upsert extends DocumentsUpsert
->inject('response')
->inject('dbForProject')
->inject('queueForStatsUsage')
->inject('queueForEvents')
->inject('queueForRealtime')
->inject('queueForFunctions')
->inject('queueForWebhooks')
->inject('plan')
->callback($this->action(...));
}

View file

@ -18,6 +18,12 @@ use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\In
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Integer\Update as UpdateIntegerAttribute;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\IP\Create as CreateIPAttribute;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\IP\Update as UpdateIPAttribute;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Line\Create as CreateLineAttribute;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Line\Update as UpdateLineAttribute;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Point\Create as CreatePointAttribute;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Point\Update as UpdatePointAttribute;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Polygon\Create as CreatePolygonAttribute;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Polygon\Update as UpdatePolygonAttribute;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Relationship\Create as CreateRelationshipAttribute;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Relationship\Update as UpdateRelationshipAttribute;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\String\Create as CreateStringAttribute;
@ -132,6 +138,18 @@ class Collections extends Base
$service->addAction(CreateIPAttribute::getName(), new CreateIPAttribute());
$service->addAction(UpdateIPAttribute::getName(), new UpdateIPAttribute());
// Attribute: Line
$service->addAction(CreateLineAttribute::getName(), new CreateLineAttribute());
$service->addAction(UpdateLineAttribute::getName(), new UpdateLineAttribute());
// Attribute: Point
$service->addAction(CreatePointAttribute::getName(), new CreatePointAttribute());
$service->addAction(UpdatePointAttribute::getName(), new UpdatePointAttribute());
// Attribute: Polygon
$service->addAction(CreatePolygonAttribute::getName(), new CreatePolygonAttribute());
$service->addAction(UpdatePolygonAttribute::getName(), new UpdatePolygonAttribute());
// Attribute: Relationship
$service->addAction(CreateRelationshipAttribute::getName(), new CreateRelationshipAttribute());
$service->addAction(UpdateRelationshipAttribute::getName(), new UpdateRelationshipAttribute());

View file

@ -21,6 +21,12 @@ use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Integer\Cre
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Integer\Update as UpdateInteger;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\IP\Create as CreateIP;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\IP\Update as UpdateIP;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Line\Create as CreateLine;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Line\Update as UpdateLine;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Point\Create as CreatePoint;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Point\Update as UpdatePoint;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Polygon\Create as CreatePolygon;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Polygon\Update as UpdatePolygon;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Relationship\Create as CreateRelationship;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\Relationship\Update as UpdateRelationship;
use Appwrite\Platform\Modules\Databases\Http\TablesDB\Tables\Columns\String\Create as CreateString;
@ -134,6 +140,18 @@ class Tables extends Base
$service->addAction(CreateIP::getName(), new CreateIP());
$service->addAction(UpdateIP::getName(), new UpdateIP());
// Column: Line
$service->addAction(CreateLine::getName(), new CreateLine());
$service->addAction(UpdateLine::getName(), new UpdateLine());
// Column: Point
$service->addAction(CreatePoint::getName(), new CreatePoint());
$service->addAction(UpdatePoint::getName(), new UpdatePoint());
// Column: Polygon
$service->addAction(CreatePolygon::getName(), new CreatePolygon());
$service->addAction(UpdatePolygon::getName(), new UpdatePolygon());
// Column: Relationship
$service->addAction(CreateRelationship::getName(), new CreateRelationship());
$service->addAction(UpdateRelationship::getName(), new UpdateRelationship());

View file

@ -0,0 +1,42 @@
<?php
namespace Appwrite\Utopia\Database\Validator;
use Utopia\Database\Validator\Spatial as SpatialValidator;
use Utopia\Validator\JSON;
class Spatial extends JSON
{
private string $spatialAttributeType;
public function getDescription(): string
{
return 'Value must be a valid spatial type JSON string';
}
/**
* @param string $spatialAttributeType
*/
public function __construct(string $spatialAttributeType)
{
$this->spatialAttributeType = $spatialAttributeType;
}
/**
* Is valid.
*
* Returns true if valid or false if not.
*
* @param $value
*
* @return bool
*/
public function isValid($value): bool
{
if (!parent::isValid($value)) {
return false;
}
$value = \json_decode($value, true);
$validator = new SpatialValidator($this->spatialAttributeType);
return $validator->isValid($value);
}
}

View file

@ -23,7 +23,10 @@ use Appwrite\Utopia\Response\Model\AttributeEnum;
use Appwrite\Utopia\Response\Model\AttributeFloat;
use Appwrite\Utopia\Response\Model\AttributeInteger;
use Appwrite\Utopia\Response\Model\AttributeIP;
use Appwrite\Utopia\Response\Model\AttributeLine;
use Appwrite\Utopia\Response\Model\AttributeList;
use Appwrite\Utopia\Response\Model\AttributePoint;
use Appwrite\Utopia\Response\Model\AttributePolygon;
use Appwrite\Utopia\Response\Model\AttributeRelationship;
use Appwrite\Utopia\Response\Model\AttributeString;
use Appwrite\Utopia\Response\Model\AttributeURL;
@ -41,7 +44,10 @@ use Appwrite\Utopia\Response\Model\ColumnFloat;
use Appwrite\Utopia\Response\Model\ColumnIndex;
use Appwrite\Utopia\Response\Model\ColumnInteger;
use Appwrite\Utopia\Response\Model\ColumnIP;
use Appwrite\Utopia\Response\Model\ColumnLine;
use Appwrite\Utopia\Response\Model\ColumnList;
use Appwrite\Utopia\Response\Model\ColumnPoint;
use Appwrite\Utopia\Response\Model\ColumnPolygon;
use Appwrite\Utopia\Response\Model\ColumnRelationship;
use Appwrite\Utopia\Response\Model\ColumnString;
use Appwrite\Utopia\Response\Model\ColumnURL;
@ -203,6 +209,9 @@ class Response extends SwooleResponse
public const MODEL_ATTRIBUTE_URL = 'attributeUrl';
public const MODEL_ATTRIBUTE_DATETIME = 'attributeDatetime';
public const MODEL_ATTRIBUTE_RELATIONSHIP = 'attributeRelationship';
public const MODEL_ATTRIBUTE_POINT = 'attributePoint';
public const MODEL_ATTRIBUTE_LINE = 'attributeLine';
public const MODEL_ATTRIBUTE_POLYGON = 'attributePolygon';
// Database Columns
public const MODEL_COLUMN = 'column';
@ -217,6 +226,9 @@ class Response extends SwooleResponse
public const MODEL_COLUMN_URL = 'columnUrl';
public const MODEL_COLUMN_DATETIME = 'columnDatetime';
public const MODEL_COLUMN_RELATIONSHIP = 'columnRelationship';
public const MODEL_COLUMN_POINT = 'columnPoint';
public const MODEL_COLUMN_LINE = 'columnLine';
public const MODEL_COLUMN_POLYGON = 'columnPolygon';
// Users
public const MODEL_ACCOUNT = 'account';
@ -399,6 +411,8 @@ class Response extends SwooleResponse
*/
protected static bool $showSensitive = false;
protected SwooleHTTPResponse $swoole;
/**
* Response constructor.
*
@ -406,6 +420,8 @@ class Response extends SwooleResponse
*/
public function __construct(SwooleHTTPResponse $response)
{
$this->swoole = $response;
$this
// General
->setModel(new None())
@ -482,6 +498,9 @@ class Response extends SwooleResponse
->setModel(new AttributeURL())
->setModel(new AttributeDatetime())
->setModel(new AttributeRelationship())
->setModel(new AttributePoint())
->setModel(new AttributeLine())
->setModel(new AttributePolygon())
// Table API Models
->setModel(new Table())
->setModel(new Column())
@ -496,6 +515,9 @@ class Response extends SwooleResponse
->setModel(new ColumnURL())
->setModel(new ColumnDatetime())
->setModel(new ColumnRelationship())
->setModel(new ColumnPoint())
->setModel(new ColumnLine())
->setModel(new ColumnPolygon())
->setModel(new Index())
->setModel(new ColumnIndex())
->setModel(new Row())
@ -937,12 +959,18 @@ class Response extends SwooleResponse
* Set Header
*
* @param string $key
* @param string $value
* @param string|array<string> $value
* @return void
*/
public function setHeader(string $key, string $value): void
public function setHeader(string $key, mixed $value): void
{
$this->sendHeader($key, $value);
if (is_array($value)) {
// Temporary solution to support proxying Set-cookie (2 cookies, 1 name)
// Ideally this would live in http library, supporting array of values in all adapters
$this->swoole->header($key, $value);
} else {
$this->sendHeader($key, $value);
}
}
/**

View file

@ -15,6 +15,7 @@ abstract class Model
public const TYPE_DATETIME_EXAMPLE = '2020-10-15T06:38:00.000+00:00';
public const TYPE_RELATIONSHIP = 'relationship';
public const TYPE_PAYLOAD = 'payload';
public const TYPE_SPATIAL = 'spatial';
/**
* @var bool

View file

@ -0,0 +1,43 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class AttributeLine extends Attribute
{
public function __construct()
{
parent::__construct();
$this
->addRule('default', [
'type' => self::TYPE_SPATIAL,
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'required' => false,
'example' => '[[0, 0], [1, 1]]'
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'AttributeLine';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_ATTRIBUTE_LINE;
}
}

View file

@ -27,6 +27,9 @@ class AttributeList extends Model
Response::MODEL_ATTRIBUTE_IP,
Response::MODEL_ATTRIBUTE_DATETIME,
Response::MODEL_ATTRIBUTE_RELATIONSHIP,
Response::MODEL_ATTRIBUTE_POINT,
Response::MODEL_ATTRIBUTE_LINE,
Response::MODEL_ATTRIBUTE_POLYGON,
Response::MODEL_ATTRIBUTE_STRING // needs to be last, since its condition would dominate any other string attribute
],
'description' => 'List of attributes.',

View file

@ -0,0 +1,43 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class AttributePoint extends Attribute
{
public function __construct()
{
parent::__construct();
$this
->addRule('default', [
'type' => self::TYPE_SPATIAL,
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'required' => false,
'example' => '[0, 0]'
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'AttributePoint';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_ATTRIBUTE_POINT;
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class AttributePolygon extends Attribute
{
public function __construct()
{
parent::__construct();
$this
->addRule('default', [
'type' => self::TYPE_SPATIAL,
'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.',
'default' => null,
'required' => false,
'example' => '[[[0, 0], [0, 10]], [[10, 10], [0, 0]]]'
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'AttributePolygon';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_ATTRIBUTE_POLYGON;
}
}

View file

@ -70,6 +70,9 @@ class Collection extends Model
Response::MODEL_ATTRIBUTE_IP,
Response::MODEL_ATTRIBUTE_DATETIME,
Response::MODEL_ATTRIBUTE_RELATIONSHIP,
Response::MODEL_ATTRIBUTE_POINT,
Response::MODEL_ATTRIBUTE_LINE,
Response::MODEL_ATTRIBUTE_POLYGON,
Response::MODEL_ATTRIBUTE_STRING, // needs to be last, since its condition would dominate any other string attribute
],
'description' => 'Collection attributes.',

View file

@ -0,0 +1,43 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class ColumnLine extends Column
{
public function __construct()
{
parent::__construct();
$this
->addRule('default', [
'type' => self::TYPE_SPATIAL,
'description' => 'Default value for column when not provided. Cannot be set when column is required.',
'default' => null,
'required' => false,
'example' => '[[0, 0], [1, 1]]'
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'ColumnLine';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_COLUMN_LINE;
}
}

View file

@ -27,6 +27,9 @@ class ColumnList extends Model
Response::MODEL_COLUMN_IP,
Response::MODEL_COLUMN_DATETIME,
Response::MODEL_COLUMN_RELATIONSHIP,
Response::MODEL_COLUMN_POINT,
Response::MODEL_COLUMN_LINE,
Response::MODEL_COLUMN_POLYGON,
Response::MODEL_COLUMN_STRING // needs to be last, since its condition would dominate any other string attribute
],
'description' => 'List of columns.',

View file

@ -0,0 +1,43 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class ColumnPoint extends Column
{
public function __construct()
{
parent::__construct();
$this
->addRule('default', [
'type' => self::TYPE_SPATIAL,
'description' => 'Default value for column when not provided. Cannot be set when column is required.',
'default' => null,
'required' => false,
'example' => '[0, 0]'
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'ColumnPoint';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_COLUMN_POINT;
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
class ColumnPolygon extends Column
{
public function __construct()
{
parent::__construct();
$this
->addRule('default', [
'type' => self::TYPE_SPATIAL,
'description' => 'Default value for column when not provided. Cannot be set when column is required.',
'default' => null,
'required' => false,
'example' => '[[[0, 0], [0, 10]], [[10, 10], [0, 0]]]'
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'ColumnPolygon';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_COLUMN_POLYGON;
}
}

View file

@ -71,6 +71,9 @@ class Table extends Model
Response::MODEL_COLUMN_IP,
Response::MODEL_COLUMN_DATETIME,
Response::MODEL_COLUMN_RELATIONSHIP,
Response::MODEL_COLUMN_POINT,
Response::MODEL_COLUMN_LINE,
Response::MODEL_COLUMN_POLYGON,
Response::MODEL_COLUMN_STRING, // needs to be last, since its condition would dominate any other string attribute
],
'description' => 'Table columns.',

View file

@ -9,6 +9,12 @@ use Utopia\System\System;
class Executor
{
// 0.8.6 is last version with object-based headers
public const RESPONSE_FORMAT_OBJECT_HEADERS = '0.10.0';
// 0.9.0 is first version with array-based headers
public const RESPONSE_FORMAT_ARRAY_HEADERS = '0.11.0';
public const METHOD_GET = 'GET';
public const METHOD_POST = 'POST';
public const METHOD_PUT = 'PUT';
@ -170,6 +176,7 @@ class Executor
* @param string $entrypoint
* @param string $runtimeEntrypoint
* @param bool $logging
* @param string $responseFormat
*
* @return array
*/
@ -190,7 +197,8 @@ class Executor
int $memory,
bool $logging,
string $runtimeEntrypoint = '',
?int $requestTimeout = null
?int $requestTimeout = null,
string $responseFormat = self::RESPONSE_FORMAT_OBJECT_HEADERS
) {
if (empty($headers['host'])) {
$headers['host'] = System::getEnv('_APP_DOMAIN', '');
@ -232,7 +240,7 @@ class Executor
$requestTimeout = $timeout + 15;
}
$response = $this->call($this->endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId, 'content-type' => 'multipart/form-data', 'accept' => 'multipart/form-data' ], $params, true, $requestTimeout);
$response = $this->call($this->endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId, 'content-type' => 'multipart/form-data', 'accept' => 'multipart/form-data', 'x-executor-response-format' => $responseFormat ], $params, true, $requestTimeout);
$status = $response['headers']['status-code'];
if ($status >= 400) {

File diff suppressed because it is too large Load diff

View file

@ -6172,4 +6172,561 @@ class DatabasesCustomServerTest extends Scope
'x-appwrite-key' => $this->getProject()['apiKey']
]));
}
public function testSpatialBulkOperations(): void
{
// Create database
$database = $this->client->call(Client::METHOD_POST, '/databases', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'databaseId' => ID::unique(),
'name' => 'Spatial Bulk Operations Test Database'
]);
$this->assertNotEmpty($database['body']['$id']);
$databaseId = $database['body']['$id'];
// Create collection with spatial attributes
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Spatial Bulk Operations Collection',
'documentSecurity' => true,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::delete(Role::any()),
Permission::update(Role::any()),
],
]);
$this->assertEquals(201, $collection['headers']['status-code']);
$collectionId = $collection['body']['$id'];
// Create string attribute
$nameAttribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'name',
'size' => 256,
'required' => true,
]);
$this->assertEquals(202, $nameAttribute['headers']['status-code']);
// Create point attribute
$pointAttribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/point', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'location',
'required' => true,
]);
$this->assertEquals(202, $pointAttribute['headers']['status-code']);
// Create polygon attribute
$polygonAttribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/polygon', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'area',
'required' => false,
]);
$this->assertEquals(202, $polygonAttribute['headers']['status-code']);
// Wait for attributes to be created
sleep(2);
// Test 1: Bulk create with spatial data
$spatialDocuments = [];
for ($i = 0; $i < 5; $i++) {
$spatialDocuments[] = [
'$id' => ID::unique(),
'name' => 'Location ' . $i,
'location' => [10.0 + $i, 20.0 + $i], // POINT
'area' => [
[10.0 + $i, 20.0 + $i],
[11.0 + $i, 20.0 + $i],
[11.0 + $i, 21.0 + $i],
[10.0 + $i, 21.0 + $i],
[10.0 + $i, 20.0 + $i]
] // POLYGON
];
}
$response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => $spatialDocuments,
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertCount(5, $response['body']['documents']);
// Verify created documents have proper spatial data
foreach ($response['body']['documents'] as $index => $document) {
$this->assertNotEmpty($document['$id']);
$this->assertNotEmpty($document['name']);
$this->assertIsArray($document['location']);
$this->assertIsArray($document['area']);
$this->assertCount(2, $document['location']); // POINT has 2 coordinates
// Check polygon structure - it might be stored as an array of arrays
if (is_array($document['area'][0])) {
$this->assertGreaterThan(1, count($document['area'][0])); // POLYGON has multiple points
} else {
$this->assertGreaterThan(1, count($document['area'])); // POLYGON has multiple points
}
$this->assertEquals('Location ' . $index, $document['name']);
$this->assertEquals([10.0 + $index, 20.0 + $index], $document['location']);
}
// Test 2: Bulk update with spatial data
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'name' => 'Updated Location',
'location' => [15.0, 25.0], // New POINT
'area' => [
[15.0, 25.0],
[16.0, 25.0],
[16.0, 26.0],
[15.0, 26.0],
[15.0, 25.0]
] // New POLYGON
],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(5, $response['body']['documents']);
// Verify updated documents
foreach ($response['body']['documents'] as $document) {
$this->assertEquals('Updated Location', $document['name']);
$this->assertEquals([15.0, 25.0], $document['location']);
// The area might be stored as an array of arrays, so check the first element
$this->assertIsArray($document['area']);
if (is_array($document['area'][0])) {
// If it's an array of arrays, check the first polygon
$this->assertEquals([
[15.0, 25.0],
[16.0, 25.0],
[16.0, 26.0],
[15.0, 26.0],
[15.0, 25.0]
], $document['area'][0]);
} else {
// If it's a direct array, check the whole thing
$this->assertEquals([
[15.0, 25.0],
[16.0, 25.0],
[16.0, 26.0],
[15.0, 26.0],
[15.0, 25.0]
], $document['area']);
}
}
// Test 3: Bulk upsert with spatial data
$upsertDocuments = [
[
'$id' => 'upsert1',
'name' => 'Upsert Location 1',
'location' => [30.0, 40.0],
'area' => [
[30.0, 40.0],
[31.0, 40.0],
[31.0, 41.0],
[30.0, 41.0],
[30.0, 40.0]
]
],
[
'$id' => 'upsert2',
'name' => 'Upsert Location 2',
'location' => [35.0, 45.0],
'area' => [
[35.0, 45.0],
[36.0, 45.0],
[36.0, 46.0],
[35.0, 46.0],
[35.0, 45.0]
]
]
];
$response = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => $upsertDocuments,
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(2, $response['body']['documents']);
// Verify upserted documents
foreach ($response['body']['documents'] as $document) {
$this->assertNotEmpty($document['$id']);
$this->assertIsArray($document['location']);
$this->assertIsArray($document['area']);
// Verify the spatial data structure
$this->assertCount(2, $document['location']); // POINT has 2 coordinates
if (is_array($document['area'][0])) {
$this->assertGreaterThan(1, count($document['area'][0])); // POLYGON has multiple points
} else {
$this->assertGreaterThan(1, count($document['area'])); // POLYGON has multiple points
}
}
// Test 4: Edge cases for spatial bulk operations
// Test 4a: Invalid point coordinates (should fail)
$response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => [
[
'$id' => ID::unique(),
'name' => 'Invalid Point',
'location' => [1000.0, 2000.0],
'area' => [10.0 , 10.0]
]
],
]);
// invalid polygon
$this->assertEquals(400, $response['headers']['status-code']);
$response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => [
[
'$id' => ID::unique(),
'name' => 'Invalid Polygon',
'location' => [10.0, 20.0],
'area' => [
[10.0, 20.0],
[11.0, 20.0]
]
]
],
]);
$this->assertEquals(400, $response['headers']['status-code']);
$response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => [
[
'$id' => ID::unique(),
'name' => 'Missing Location',
// Missing required 'location' attribute
'area' => [
[10.0, 20.0],
[11.0, 20.0],
[11.0, 21.0],
[10.0, 21.0],
[10.0, 20.0]
]
]
],
]);
// This should fail due to missing required attribute
$this->assertEquals(400, $response['headers']['status-code']);
// Test 4e: Mixed valid and invalid documents in bulk (should fail)
$response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => [
[
'$id' => ID::unique(),
'name' => 'Valid Document',
'location' => [10.0, 20.0],
'area' => [
[10.0, 20.0],
[11.0, 20.0],
[11.0, 21.0],
[10.0, 21.0],
[10.0, 20.0]
]
],
[
'$id' => ID::unique(),
'name' => 'Invalid Document',
// Missing required 'location' attribute
'area' => [
[10.0, 20.0],
[11.0, 20.0],
[11.0, 21.0],
[10.0, 21.0],
[10.0, 20.0]
]
]
],
]);
// This should fail due to mixed valid/invalid documents
$this->assertEquals(400, $response['headers']['status-code']);
// Test 4f: Very large spatial data (stress test)
$largePolygon = [];
for ($i = 0; $i < 1000; $i++) {
$largePolygon[] = [$i * 0.001, $i * 0.001];
}
$largePolygon[] = [0, 0]; // Close the polygon
$response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => [
[
'$id' => ID::unique(),
'name' => 'Large Polygon Test',
'location' => [0.0, 0.0],
'area' => $largePolygon
]
],
]);
$this->assertEquals(201, $response['headers']['status-code']);
// Test 4g: Null values in spatial attributes (should fail)
$response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => [
[
'$id' => ID::unique(),
'name' => 'Null Values Test',
'location' => null, // Null point
'area' => null // Null polygon
]
],
]);
// This should fail due to null values
$this->assertEquals(400, $response['headers']['status-code']);
// Test 4h: Bulk operations with spatial data exceeding limits
$largeBulkDocuments = [];
for ($i = 0; $i < 100; $i++) {
$largeBulkDocuments[] = [
'$id' => ID::unique(),
'name' => 'Bulk Test ' . $i,
'location' => [$i * 0.1, $i * 0.1],
'area' => [
[$i * 0.1, $i * 0.1],
[($i + 1) * 0.1, $i * 0.1],
[($i + 1) * 0.1, ($i + 1) * 0.1],
[$i * 0.1, ($i + 1) * 0.1],
[$i * 0.1, $i * 0.1]
]
];
}
$response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => $largeBulkDocuments,
]);
$this->assertEquals(201, $response['headers']['status-code']);
// Cleanup
$this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $collectionId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
}
public function testSpatialBulkOperationsWithLineStrings(): void
{
// Create database
$database = $this->client->call(Client::METHOD_POST, '/databases', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'databaseId' => ID::unique(),
'name' => 'Spatial LineString Bulk Operations Test Database'
]);
$this->assertNotEmpty($database['body']['$id']);
$databaseId = $database['body']['$id'];
// Create collection with line string attributes
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Spatial LineString Bulk Operations Collection',
'documentSecurity' => true,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::delete(Role::any()),
Permission::update(Role::any()),
],
]);
$this->assertEquals(201, $collection['headers']['status-code']);
$collectionId = $collection['body']['$id'];
// Create string attribute
$nameAttribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'name',
'size' => 256,
'required' => true,
]);
$this->assertEquals(202, $nameAttribute['headers']['status-code']);
// Create line string attribute
$lineAttribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/line', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'path',
'required' => true,
]);
// Handle both 201 (created) and 202 (accepted) status codes
$this->assertEquals(202, $lineAttribute['headers']['status-code']);
// Wait for attributes to be created
sleep(2);
// Test bulk create with line string data
$lineStringDocuments = [];
for ($i = 0; $i < 3; $i++) {
$lineStringDocuments[] = [
'$id' => ID::unique(),
'name' => 'Path ' . $i,
'path' => [
[$i * 10, $i * 10],
[($i + 1) * 10, ($i + 1) * 10],
[($i + 2) * 10, ($i + 2) * 10]
] // LINE STRING with 3 points
];
}
$response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => $lineStringDocuments,
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertCount(3, $response['body']['documents']);
// Verify created documents have proper line string data
foreach ($response['body']['documents'] as $index => $document) {
$this->assertNotEmpty($document['$id']);
$this->assertNotEmpty($document['name']);
$this->assertIsArray($document['path']);
$this->assertGreaterThan(1, count($document['path'])); // LINE STRING has multiple points
$this->assertEquals('Path ' . $index, $document['name']);
$this->assertCount(3, $document['path']); // Each line string has 3 points
}
// Test bulk update with line string data
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'name' => 'Updated Path',
'path' => [
[0, 0],
[50, 50],
[100, 100]
] // New LINE STRING
],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(3, $response['body']['documents']);
// Verify updated documents
foreach ($response['body']['documents'] as $document) {
$this->assertEquals('Updated Path', $document['name']);
$this->assertEquals([
[0, 0],
[50, 50],
[100, 100]
], $document['path']);
}
// Test: Invalid line string (single point - should fail)
$response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$collectionId}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => [
[
'$id' => ID::unique(),
'name' => 'Invalid Line String',
'path' => [[10, 20]] // Single point - invalid line string
]
],
]);
$this->assertEquals(400, $response['headers']['status-code']);
// Cleanup
$this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $collectionId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
}
}

File diff suppressed because it is too large Load diff

View file

@ -6132,4 +6132,569 @@ class DatabasesCustomServerTest extends Scope
'x-appwrite-key' => $this->getProject()['apiKey']
]));
}
public function testSpatialBulkOperations(): void
{
// Create database
$database = $this->client->call(Client::METHOD_POST, '/tablesdb', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'databaseId' => ID::unique(),
'name' => 'Spatial Bulk Operations Test Database'
]);
$this->assertNotEmpty($database['body']['$id']);
$databaseId = $database['body']['$id'];
// Create table with spatial columns
$table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'tableId' => ID::unique(),
'name' => 'Spatial Bulk Operations Table',
'rowSecurity' => true,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::delete(Role::any()),
Permission::update(Role::any()),
],
]);
$this->assertEquals(201, $table['headers']['status-code']);
$tableId = $table['body']['$id'];
// Create string column
$nameColumn = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'name',
'size' => 256,
'required' => true,
]);
$this->assertEquals(202, $nameColumn['headers']['status-code']);
// Create point column
$pointColumn = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/point', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'location',
'required' => true,
]);
$this->assertEquals(202, $pointColumn['headers']['status-code']);
// Create polygon column
$polygonColumn = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/polygon', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'area',
'required' => false,
]);
$this->assertEquals(202, $polygonColumn['headers']['status-code']);
// Wait for columns to be created
sleep(2);
// Test 1: Bulk create with spatial data
$spatialRows = [];
for ($i = 0; $i < 5; $i++) {
$spatialRows[] = [
'$id' => ID::unique(),
'name' => 'Location ' . $i,
'location' => [10.0 + $i, 20.0 + $i], // POINT
'area' => [
[10.0 + $i, 20.0 + $i],
[11.0 + $i, 20.0 + $i],
[11.0 + $i, 21.0 + $i],
[10.0 + $i, 21.0 + $i],
[10.0 + $i, 20.0 + $i]
] // POLYGON
];
}
$response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rows' => $spatialRows,
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertCount(5, $response['body']['rows']);
// Verify created rows have proper spatial data
foreach ($response['body']['rows'] as $index => $row) {
$this->assertNotEmpty($row['$id']);
$this->assertNotEmpty($row['name']);
$this->assertIsArray($row['location']);
$this->assertIsArray($row['area']);
$this->assertCount(2, $row['location']); // POINT has 2 coordinates
// Check polygon structure - it might be stored as an array of arrays
if (is_array($row['area'][0])) {
$this->assertGreaterThan(1, count($row['area'][0])); // POLYGON has multiple points
} else {
$this->assertGreaterThan(1, count($row['area'])); // POLYGON has multiple points
}
$this->assertEquals('Location ' . $index, $row['name']);
$this->assertEquals([10.0 + $index, 20.0 + $index], $row['location']);
}
// Test 2: Bulk update with spatial data
$response = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'name' => 'Updated Location',
'location' => [15.0, 25.0], // New POINT
'area' => [
[15.0, 25.0],
[16.0, 25.0],
[16.0, 26.0],
[15.0, 26.0],
[15.0, 25.0]
] // New POLYGON
],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(5, $response['body']['rows']);
// Verify updated rows
foreach ($response['body']['rows'] as $row) {
$this->assertEquals('Updated Location', $row['name']);
$this->assertEquals([15.0, 25.0], $row['location']);
// The area might be stored as an array of arrays, so check the first element
$this->assertIsArray($row['area']);
if (is_array($row['area'][0])) {
// If it's an array of arrays, check the first polygon
$this->assertEquals([
[15.0, 25.0],
[16.0, 25.0],
[16.0, 26.0],
[15.0, 26.0],
[15.0, 25.0]
], $row['area'][0]);
} else {
// If it's a direct array, check the whole thing
$this->assertEquals([
[15.0, 25.0],
[16.0, 25.0],
[16.0, 26.0],
[15.0, 26.0],
[15.0, 25.0]
], $row['area']);
}
}
// Test 3: Bulk upsert with spatial data
$upsertRows = [
[
'$id' => 'upsert1',
'name' => 'Upsert Location 1',
'location' => [30.0, 40.0],
'area' => [
[30.0, 40.0],
[31.0, 40.0],
[31.0, 41.0],
[30.0, 41.0],
[30.0, 40.0]
]
],
[
'$id' => 'upsert2',
'name' => 'Upsert Location 2',
'location' => [35.0, 45.0],
'area' => [
[35.0, 45.0],
[36.0, 45.0],
[36.0, 46.0],
[35.0, 46.0],
[35.0, 45.0]
]
]
];
$response = $this->client->call(Client::METHOD_PUT, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rows' => $upsertRows,
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(2, $response['body']['rows']);
// Verify upserted rows
foreach ($response['body']['rows'] as $row) {
$this->assertNotEmpty($row['$id']);
$this->assertIsArray($row['location']);
$this->assertIsArray($row['area']);
// Verify the spatial data structure
$this->assertCount(2, $row['location']); // POINT has 2 coordinates
if (is_array($row['area'][0])) {
$this->assertGreaterThan(1, count($row['area'][0])); // POLYGON has multiple points
} else {
$this->assertGreaterThan(1, count($row['area'])); // POLYGON has multiple points
}
}
// Test 4: Edge cases for spatial bulk operations
// Test 4a: Invalid point coordinates (should fail)
$response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rows' => [
[
'$id' => ID::unique(),
'name' => 'Invalid Point',
'location' => [1000.0, 2000.0], // Invalid coordinates
'area' => [
[10.0, 20.0],
[11.0, 20.0],
[11.0, 21.0],
[10.0, 21.0],
[10.0, 20.0]
]
]
],
]);
// Coordinates are not validated strictly; creation should succeed
$this->assertEquals(201, $response['headers']['status-code']);
// Test 4b: Invalid polygon (insufficient points - should fail)
$response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rows' => [
[
'$id' => ID::unique(),
'name' => 'Invalid Polygon',
'location' => [10.0, 20.0],
'area' => [
[10.0, 20.0],
[11.0, 20.0]
]
]
],
]);
$this->assertEquals(400, $response['headers']['status-code']);
// Test 4c: Missing required location (should fail)
$response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rows' => [
[
'$id' => ID::unique(),
'name' => 'Missing Location',
// Missing required 'location' attribute
'area' => [
[10.0, 20.0],
[11.0, 20.0],
[11.0, 21.0],
[10.0, 21.0],
[10.0, 20.0]
]
]
],
]);
// This should fail due to missing required attribute
$this->assertEquals(400, $response['headers']['status-code']);
// Test 4d: Mixed valid and invalid rows in bulk (should fail)
$response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rows' => [
[
'$id' => ID::unique(),
'name' => 'Valid Row',
'location' => [10.0, 20.0],
'area' => [
[10.0, 20.0],
[11.0, 20.0],
[11.0, 21.0],
[10.0, 21.0],
[10.0, 20.0]
]
],
[
'$id' => ID::unique(),
'name' => 'Invalid Row',
// Missing required 'location' attribute
'area' => [
[10.0, 20.0],
[11.0, 20.0],
[11.0, 21.0],
[10.0, 21.0],
[10.0, 20.0]
]
]
],
]);
// This should fail due to mixed valid/invalid rows
$this->assertEquals(400, $response['headers']['status-code']);
// Test 4e: Very large spatial data (stress test)
$largePolygon = [];
for ($i = 0; $i < 1000; $i++) {
$largePolygon[] = [$i * 0.001, $i * 0.001];
}
$largePolygon[] = [0, 0]; // Close the polygon
$response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rows' => [
[
'$id' => ID::unique(),
'name' => 'Large Polygon Test',
'location' => [0.0, 0.0],
'area' => $largePolygon
]
],
]);
$this->assertEquals(201, $response['headers']['status-code']);
// Test 4f: Null values in spatial attributes (should fail)
$response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rows' => [
[
'$id' => ID::unique(),
'name' => 'Null Values Test',
'location' => null, // Null point
'area' => null // Null polygon
]
],
]);
// This should fail due to null values
$this->assertEquals(400, $response['headers']['status-code']);
// Test 4g: Bulk operations with spatial data exceeding limits
$largeBulkRows = [];
for ($i = 0; $i < 100; $i++) {
$largeBulkRows[] = [
'$id' => ID::unique(),
'name' => 'Bulk Test ' . $i,
'location' => [$i * 0.1, $i * 0.1],
'area' => [
[$i * 0.1, $i * 0.1],
[($i + 1) * 0.1, $i * 0.1],
[($i + 1) * 0.1, ($i + 1) * 0.1],
[$i * 0.1, ($i + 1) * 0.1],
[$i * 0.1, $i * 0.1]
]
];
}
$response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rows' => $largeBulkRows,
]);
$this->assertEquals(201, $response['headers']['status-code']);
// Cleanup
$this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $tableId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
}
public function testSpatialBulkOperationsWithLineStrings(): void
{
// Create database
$database = $this->client->call(Client::METHOD_POST, '/tablesdb', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'databaseId' => ID::unique(),
'name' => 'Spatial LineString Bulk Operations Test Database'
]);
$this->assertNotEmpty($database['body']['$id']);
$databaseId = $database['body']['$id'];
// Create table with line string columns
$table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'tableId' => ID::unique(),
'name' => 'Spatial LineString Bulk Operations Table',
'rowSecurity' => true,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::delete(Role::any()),
Permission::update(Role::any()),
],
]);
$this->assertEquals(201, $table['headers']['status-code']);
$tableId = $table['body']['$id'];
// Create string column
$nameColumn = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'name',
'size' => 256,
'required' => true,
]);
$this->assertEquals(202, $nameColumn['headers']['status-code']);
// Create line string column
$lineColumn = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/line', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'path',
'required' => true,
]);
// Handle both 201 (created) and 202 (accepted) status codes
$this->assertEquals(202, $lineColumn['headers']['status-code']);
// Wait for columns to be created
sleep(2);
// Test bulk create with line string data
$lineStringRows = [];
for ($i = 0; $i < 3; $i++) {
$lineStringRows[] = [
'$id' => ID::unique(),
'name' => 'Path ' . $i,
'path' => [
[$i * 10, $i * 10],
[($i + 1) * 10, ($i + 1) * 10],
[($i + 2) * 10, ($i + 2) * 10]
] // LINE STRING with 3 points
];
}
$response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rows' => $lineStringRows,
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertCount(3, $response['body']['rows']);
// Verify created rows have proper line string data
foreach ($response['body']['rows'] as $index => $row) {
$this->assertNotEmpty($row['$id']);
$this->assertNotEmpty($row['name']);
$this->assertIsArray($row['path']);
$this->assertGreaterThan(1, count($row['path'])); // LINE STRING has multiple points
$this->assertEquals('Path ' . $index, $row['name']);
$this->assertCount(3, $row['path']); // Each line string has 3 points
}
// Test bulk update with line string data
$response = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'name' => 'Updated Path',
'path' => [
[0, 0],
[50, 50],
[100, 100]
] // New LINE STRING
],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(3, $response['body']['rows']);
// Verify updated rows
foreach ($response['body']['rows'] as $row) {
$this->assertEquals('Updated Path', $row['name']);
$this->assertEquals([
[0, 0],
[50, 50],
[100, 100]
], $row['path']);
}
// Test: Invalid line string (single point - should fail)
$response = $this->client->call(Client::METHOD_POST, "/tablesdb/{$databaseId}/tables/{$tableId}/rows", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'rows' => [
[
'$id' => ID::unique(),
'name' => 'Invalid Line String',
'path' => [[10, 20]] // Single point - invalid line string
]
],
]);
$this->assertEquals(400, $response['headers']['status-code']);
// Cleanup
$this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $tableId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
}
}

View file

@ -45,6 +45,9 @@ trait Base
public const string CREATE_IP_ATTRIBUTE = 'create_ip_attribute';
public const string CREATE_ENUM_ATTRIBUTE = 'create_enum_attribute';
public const string CREATE_DATETIME_ATTRIBUTE = 'create_datetime_attribute';
public const string CREATE_POINT_ATTRIBUTE = 'create_point_attribute';
public const string CREATE_LINE_ATTRIBUTE = 'create_line_attribute';
public const string CREATE_POLYGON_ATTRIBUTE = 'create_polygon_attribute';
public const string CREATE_RELATIONSHIP_ATTRIBUTE = 'create_relationship_attribute';
public const string UPDATE_STRING_ATTRIBUTE = 'update_string_attribute';
@ -56,6 +59,9 @@ trait Base
public const string UPDATE_IP_ATTRIBUTE = 'update_ip_attribute';
public const string UPDATE_ENUM_ATTRIBUTE = 'update_enum_attribute';
public const string UPDATE_DATETIME_ATTRIBUTE = 'update_datetime_attribute';
public const string UPDATE_POINT_ATTRIBUTE = 'update_point_attribute';
public const string UPDATE_LINE_ATTRIBUTE = 'update_line_attribute';
public const string UPDATE_POLYGON_ATTRIBUTE = 'update_polygon_attribute';
public const string UPDATE_RELATIONSHIP_ATTRIBUTE = 'update_relationship_attribute';
public const string GET_ATTRIBUTES = 'get_attributes';
@ -72,6 +78,9 @@ trait Base
public const string CREATE_IP_COLUMN = 'create_ip_column';
public const string CREATE_ENUM_COLUMN = 'create_enum_column';
public const string CREATE_DATETIME_COLUMN = 'create_datetime_column';
public const string CREATE_POINT_COLUMN = 'create_point_column';
public const string CREATE_LINE_COLUMN = 'create_line_column';
public const string CREATE_POLYGON_COLUMN = 'create_polygon_column';
public const string CREATE_RELATIONSHIP_COLUMN = 'create_relationship_column';
public const string UPDATE_STRING_COLUMN = 'update_string_column';
@ -83,6 +92,9 @@ trait Base
public const string UPDATE_IP_COLUMN = 'update_ip_column';
public const string UPDATE_ENUM_COLUMN = 'update_enum_column';
public const string UPDATE_DATETIME_COLUMN = 'update_datetime_column';
public const string UPDATE_POINT_COLUMN = 'update_point_column';
public const string UPDATE_LINE_COLUMN = 'update_line_column';
public const string UPDATE_POLYGON_COLUMN = 'update_polygon_column';
public const string UPDATE_RELATIONSHIP_COLUMN = 'update_relationship_column';
public const string GET_COLUMNS = 'get_columns';
@ -930,6 +942,35 @@ trait Base
array
}
}';
case self::CREATE_POINT_COLUMN:
return 'mutation createPointColumn($databaseId: String!, $tableId: String!, $key: String!, $required: Boolean!, $array: Boolean){
tablesDBCreatePointColumn(databaseId: $databaseId, tableId: $tableId, key: $key, required: $required, array: $array) {
key
required
array
status
}
}';
case self::CREATE_LINE_COLUMN:
return 'mutation createLineColumn($databaseId: String!, $tableId: String!, $key: String!, $required: Boolean!, $array: Boolean){
tablesDBCreateLineColumn(databaseId: $databaseId, tableId: $tableId, key: $key, required: $required, array: $array) {
key
required
array
status
}
}';
case self::CREATE_POLYGON_COLUMN:
return 'mutation createPolygonColumn($databaseId: String!, $tableId: String!, $key: String!, $required: Boolean!, $array: Boolean){
tablesDBCreatePolygonColumn(databaseId: $databaseId, tableId: $tableId, key: $key, required: $required, array: $array) {
key
required
array
status
}
}';
case self::CREATE_RELATIONSHIP_COLUMN:
return 'mutation createRelationshipColumn($databaseId: String!, $tableId: String!, $relatedTableId: String!, $type: String!, $twoWay: Boolean, $key: String, $twoWayKey: String, $onDelete: String){
tablesDBCreateRelationshipColumn(databaseId: $databaseId, tableId: $tableId, relatedTableId: $relatedTableId, type: $type, twoWay: $twoWay, key: $key, twoWayKey: $twoWayKey, onDelete: $onDelete) {
@ -1009,6 +1050,27 @@ trait Base
default
}
}';
case self::UPDATE_POINT_COLUMN:
return 'mutation updatePointColumn($databaseId: String!, $tableId: String!, $key: String!, $required: Boolean!){
tablesDBUpdatePointColumn(databaseId: $databaseId, tableId: $tableId, key: $key, required: $required) {
required
}
}';
case self::UPDATE_LINE_COLUMN:
return 'mutation updateLineColumn($databaseId: String!, $tableId: String!, $key: String!, $required: Boolean!){
tablesDBUpdateLineColumn(databaseId: $databaseId, tableId: $tableId, key: $key, required: $required) {
required
}
}';
case self::UPDATE_POLYGON_COLUMN:
return 'mutation updatePolygonColumn($databaseId: String!, $tableId: String!, $key: String!, $required: Boolean!){
tablesDBUpdatePolygonColumn(databaseId: $databaseId, tableId: $tableId, key: $key, required: $required) {
required
}
}';
case self::UPDATE_RELATIONSHIP_COLUMN:
return 'mutation updateRelationshipColumn($databaseId: String!, $tableId: String!, $key: String!, $onDelete: String){
tablesDBUpdateRelationshipColumn(databaseId: $databaseId, tableId: $tableId, key: $key, onDelete: $onDelete) {

View file

@ -1188,6 +1188,55 @@ class RealtimeCustomClientTest extends Scope
$this->assertArrayHasKey('$permissions', $response['data']['payload']);
$this->assertIsArray($response['data']['payload']['$permissions']);
// bulk upsert
$this->client->call(Client::METHOD_PUT, "/databases/{$databaseId}/collections/{$actorsId}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'documents' => [
[
'$id' => ID::unique(),
'name' => 'Robert Downey Jr.',
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
]
],
]);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(6, $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.upsert", $response['data']['events']);
$this->assertContains("databases.*.collections.*.documents.*.upsert", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.*.documents.*.upsert", $response['data']['events']);
$this->assertContains("databases.*.collections.{$actorsId}.documents.*.upsert", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']);
$this->assertContains("databases.*.collections.*.documents.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.*.documents.*", $response['data']['events']);
$this->assertContains("databases.*.collections.{$actorsId}.documents.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
$this->assertContains("databases.*.collections.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.*", $response['data']['events']);
$this->assertContains("databases.*.collections.{$actorsId}", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.*.documents.*.upsert", $response['data']['events']);
$this->assertContains("databases.*", $response['data']['events']);
$this->assertNotEmpty($response['data']['payload']);
$this->assertIsArray($response['data']['payload']);
$this->assertArrayHasKey('$id', $response['data']['payload']);
$this->assertArrayHasKey('name', $response['data']['payload']);
$this->assertArrayHasKey('$permissions', $response['data']['payload']);
$this->assertIsArray($response['data']['payload']['$permissions']);
$client->close();
}
@ -1644,6 +1693,107 @@ class RealtimeCustomClientTest extends Scope
$this->assertIsArray($response2['data']['payload']['$permissions']);
}
// bulk upsert
$this->client->call(Client::METHOD_PUT, "/databases/{$databaseId}/collections/{$actorsId}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'documents' => [
[
'$id' => ID::unique(),
'name' => 'Robert Downey Jr.',
'$permissions' => [
Permission::read(Role::user($user1Id)),
],
],
[
'$id' => ID::unique(),
'name' => 'Thor',
'$permissions' => [
Permission::read(Role::user($user2Id)),
],
]
],
]);
$response = json_decode($client1->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(6, $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.upsert", $response['data']['events']);
$this->assertContains("databases.*.collections.*.documents.*.upsert", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.*.documents.*.upsert", $response['data']['events']);
$this->assertContains("databases.*.collections.{$actorsId}.documents.*.upsert", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']);
$this->assertContains("databases.*.collections.*.documents.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.*.documents.*", $response['data']['events']);
$this->assertContains("databases.*.collections.{$actorsId}.documents.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
$this->assertContains("databases.*.collections.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.*", $response['data']['events']);
$this->assertContains("databases.*.collections.{$actorsId}", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.*.documents.*.upsert", $response['data']['events']);
$this->assertContains("databases.*", $response['data']['events']);
$this->assertNotEmpty($response['data']['payload']);
$this->assertIsArray($response['data']['payload']);
$this->assertArrayHasKey('$id', $response['data']['payload']);
$this->assertArrayHasKey('name', $response['data']['payload']);
$this->assertArrayHasKey('$permissions', $response['data']['payload']);
$this->assertIsArray($response['data']['payload']['$permissions']);
// client1 shouldnot receive more than 1 event
try {
json_decode(json_decode($client1->receive(), true));
$this->fail('Expected TimeoutException was not thrown.');
} catch (Exception $e) {
$this->assertInstanceOf(TimeoutException::class, $e);
}
$response = json_decode($client2->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(6, $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.{$response['data']['payload']['$id']}.upsert", $response['data']['events']);
$this->assertContains("databases.*.collections.*.documents.*.upsert", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.*.documents.*.upsert", $response['data']['events']);
$this->assertContains("databases.*.collections.{$actorsId}.documents.*.upsert", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.documents.*", $response['data']['events']);
$this->assertContains("databases.*.collections.*.documents.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.*.documents.*", $response['data']['events']);
$this->assertContains("databases.*.collections.{$actorsId}.documents.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
$this->assertContains("databases.*.collections.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.*", $response['data']['events']);
$this->assertContains("databases.*.collections.{$actorsId}", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.*.documents.*.upsert", $response['data']['events']);
$this->assertContains("databases.*", $response['data']['events']);
$this->assertNotEmpty($response['data']['payload']);
$this->assertIsArray($response['data']['payload']);
$this->assertArrayHasKey('$id', $response['data']['payload']);
$this->assertArrayHasKey('name', $response['data']['payload']);
$this->assertArrayHasKey('$permissions', $response['data']['payload']);
$this->assertIsArray($response['data']['payload']['$permissions']);
// client2 shouldnot receive more than 1 event
try {
json_decode(json_decode($client2->receive(), true));
$this->fail('Expected TimeoutException was not thrown.');
} catch (Exception $e) {
$this->assertInstanceOf(TimeoutException::class, $e);
}
$client1->close();
$client2->close();
}

View file

@ -2775,11 +2775,13 @@ class SitesCustomServerTest extends Scope
$proxyClient->setEndpoint('http://' . $domain);
$response = $proxyClient->call(Client::METHOD_GET, '/cookies', [
'cookie' => 'custom-session-id=abcd123'
'cookie' => 'custom-session-id=abcd123; custom-user-id=efgh456'
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals("abcd123", $response['body']);
$this->assertEquals("abcd123;efgh456", $response['body']);
$this->assertEquals("value-one", $response['cookies']['my-cookie-one']);
$this->assertEquals("value-two", $response['cookies']['my-cookie-two']);
$this->cleanupSite($siteId);
}

View file

@ -1,4 +1,9 @@
export async function GET(context) {
const sessionId = context.cookies.get("custom-session-id")?.value ?? 'Custom session ID missing';
return new Response(sessionId);
const userId = context.cookies.get("custom-user-id")?.value ?? 'Custom user ID missing';
context.cookies.set('my-cookie-one', 'value-one');
context.cookies.set('my-cookie-two', 'value-two');
return new Response(sessionId + ";" + userId);
}