Merge branch '1.8.x' into dat-571

This commit is contained in:
ArnabChatterjee20k 2025-07-18 10:28:27 +05:30
commit e97d10061f
10 changed files with 378 additions and 41 deletions

View file

@ -22,6 +22,7 @@ use Appwrite\Utopia\Request\Filters\V16 as RequestV16;
use Appwrite\Utopia\Request\Filters\V17 as RequestV17;
use Appwrite\Utopia\Request\Filters\V18 as RequestV18;
use Appwrite\Utopia\Request\Filters\V19 as RequestV19;
use Appwrite\Utopia\Request\Filters\V20 as RequestV20;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Filters\V16 as ResponseV16;
use Appwrite\Utopia\Response\Filters\V17 as ResponseV17;
@ -851,6 +852,10 @@ App::init()
if (version_compare($requestFormat, '1.7.0', '<')) {
$request->addFilter(new RequestV19());
}
if (version_compare($requestFormat, '1.8.0', '<')) {
$dbForProject = $getProjectDB($project);
$request->addFilter(new RequestV20($dbForProject, $route));
}
}
$domain = $request->getHostname();

View file

@ -87,7 +87,15 @@ class Get extends Action
}
try {
$document = $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId, $queries);
$selects = Query::groupByType($queries)['selections'] ?? [];
if (! empty($selects)) {
// has selects, allow relationship on documents!
$document = $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId, $queries);
} else {
// has no selects, disable relationship looping on documents!
$document = $dbForProject->skipRelationships(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId, $queries));
}
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}

View file

@ -114,8 +114,21 @@ class XList extends Action
$cursor->setValue($cursorDocument);
}
$selectQueries = [];
try {
$documents = $dbForProject->find('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $queries);
$selectQueries = Query::groupByType($queries)['selections'] ?? [];
if (! empty($selectQueries)) {
// has selects, allow relationship on documents!
$documents = $dbForProject->find('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $queries);
} else {
// has no selects, disable relationship looping on documents!
/* @type Document[] $documents */
$documents = $dbForProject->skipRelationships(fn () => $dbForProject->find('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $queries));
}
$total = $dbForProject->count('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $queries, APP_LIMIT_COUNT);
} catch (OrderException $e) {
$documents = $this->isCollectionsAPI() ? 'documents' : 'rows';
@ -150,24 +163,40 @@ class XList extends Action
}, false);
// Check if the SELECT query includes $databaseId and $collectionId
$hasWildcard = false;
$hasDatabaseId = false;
$hasCollectionId = false;
if ($select) {
$hasDatabaseId = \array_reduce($queries, function ($result, $query) {
return $result || ($query->getMethod() === Query::TYPE_SELECT && \in_array('$databaseId', $query->getValues()));
}, false);
$hasCollectionId = \array_reduce($queries, function ($result, $query) {
return $result || ($query->getMethod() === Query::TYPE_SELECT && \in_array('$collectionId', $query->getValues()));
}, false);
}
$hasSelectQueries = !empty($selectQueries);
if ($select) {
foreach ($documents as $document) {
if (!$hasDatabaseId) {
$document->removeAttribute('$databaseId');
if ($hasSelectQueries) {
foreach ($selectQueries as $query) {
if ($query->getMethod() !== Query::TYPE_SELECT) {
continue;
}
if (!$hasCollectionId) {
$document->removeAttribute('$collectionId');
$values = $query->getValues();
if (\in_array('*', $values, true)) {
$hasWildcard = true;
break;
}
if (\in_array('$databaseId', $values, true)) {
$hasDatabaseId = true;
}
if (\in_array('$collectionId', $values, true)) {
$hasCollectionId = true;
}
}
if (!$hasWildcard) {
foreach ($documents as $document) {
if (!$hasDatabaseId) {
$document->removeAttribute('$databaseId');
}
if (!$hasCollectionId) {
$document->removeAttribute('$collectionId');
}
}
}
}

View file

@ -2,8 +2,20 @@
namespace Appwrite\Utopia\Request;
use Utopia\Database\Database;
use Utopia\Route;
abstract class Filter
{
private ?Route $route;
private ?Database $dbForProject;
public function __construct(Database $dbForProject = null, Route $route = null)
{
$this->route = $route;
$this->dbForProject = $dbForProject;
}
/**
* Parse params to another format.
*
@ -13,4 +25,33 @@ abstract class Filter
* @return array
*/
abstract public function parse(array $content, string $model): array;
/**
* Get the database for the current project.
*
* @return null|Database
*/
public function getDbForProject(): ?Database
{
return $this->dbForProject;
}
/**
* Returns the value of the given route param key, or a default if not found or on error.
*
* @param string $key
* @param mixed $default
*
* @return mixed
*/
public function getParamValue(string $key, mixed $default = ''): mixed
{
try {
$value = $this->route?->getParamValue($key) ?? $default;
} catch (\Exception $e) {
$value = $default;
}
return $value;
}
}

View file

@ -39,7 +39,7 @@ class V19 extends Filter
return $content;
}
public function convertQueryAttribute(array $content, string $old, string $new)
public function convertQueryAttribute(array $content, string $old, string $new): array
{
if (isset($content['queries']) && is_array($content['queries'])) {
foreach ($content['queries'] as $index => $query) {

View file

@ -0,0 +1,124 @@
<?php
namespace Appwrite\Utopia\Request\Filters;
use Appwrite\Utopia\Request\Filter;
use Utopia\Database\Database;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
class V20 extends Filter
{
// Convert 1.7 params to 1.8
public function parse(array $content, string $model): array
{
switch ($model) {
case 'databases.getDocument':
case 'databases.listDocuments':
$content = $this->manageSelectQueries($content, $model);
break;
}
return $content;
}
/**
* From 1.8.x onward, related documents are no longer returned by default to improve performance.
*
* Use `Query::select(['related.*'])` for full documents or `Query::select(['related.key'])` for specific fields.
*
* This filter preserves 1.7.x behavior by including all related documents for backward compatibility with
* `listDocuments` and `getDocument` calls.
*/
protected function manageSelectQueries(array $content, string $model): array
{
$hasWildcard = false;
if (! isset($content['queries'])) {
$hasWildcard = true;
// only query, make it json encoded!
$content['queries'] = [Query::select(['*'])->toString()];
}
try {
$parsed = Query::parseQueries($content['queries']);
} catch (QueryException) {
// don't crash!
return $content;
}
$selections = Query::groupByType($parsed)['selections'] ?? [];
if (! $hasWildcard) {
// check if any select includes a wildcard as we added one above
foreach ($selections as $select) {
if (\in_array('*', $select->getValues(), true)) {
$hasWildcard = true;
break;
}
}
}
if ($hasWildcard && $model === 'databases.listDocuments') {
$relatedKeys = $this->getRelatedCollectionKeys();
if (! empty($relatedKeys)) {
$selects = \array_values(\array_unique(\array_merge(['*'], $relatedKeys)));
// remove previous select queries
$parsed = \array_filter(
$parsed,
fn ($query) => $query->getMethod() !== Query::TYPE_SELECT
);
// add wildcard + relationship(s) selects
$parsed[] = Query::select($selects);
}
}
$resolvedQueries = [];
foreach ($parsed as $query) {
// make em json encoded!
$resolvedQueries[] = $query->toString();
}
$content['queries'] = $resolvedQueries;
return $content;
}
/**
* Returns all relationship attribute keys in `key.*` format for use with `Query::select`.
*/
private function getRelatedCollectionKeys(): array
{
$dbForProject = $this->getDbForProject();
if ($dbForProject === null) {
return [];
}
$databaseId = $this->getParamValue('databaseId');
$collectionId = $this->getParamValue('collectionId');
if (empty($databaseId) || empty($collectionId)) {
return [];
}
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
$collection = $dbForProject->getDocument(
'database_' . $database->getSequence(),
$collectionId
);
$attributes = $collection->getAttribute('attributes', []);
return \array_values(\array_map(
fn ($attr) => $attr['key'] . '.*',
\array_filter(
$attributes,
fn ($attr) => ($attr['type'] ?? null) === Database::VAR_RELATIONSHIP
)
));
}
}

View file

@ -4563,7 +4563,11 @@ trait DatabasesBase
$response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $personCollection . '/documents/' . $person2['body']['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
], $this->getHeaders()), [
'queries' => [
Query::select(['*', 'libraries.*'])->toString()
]
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertArrayNotHasKey('$collection', $response['body']);
@ -4573,7 +4577,11 @@ trait DatabasesBase
$response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $libraryCollection . '/documents/library11', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
], $this->getHeaders()), [
'queries' => [
Query::select(['person_one_to_many.$id'])->toString()
]
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertArrayHasKey('person_one_to_many', $response['body']);
@ -4723,7 +4731,11 @@ trait DatabasesBase
$album = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $albums['body']['$id'] . '/documents/album1', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
], $this->getHeaders()), [
'queries' => [
Query::select(['*', 'artist.name', 'artist.$permissions'])->toString()
]
]);
$this->assertEquals(200, $album['headers']['status-code']);
$this->assertEquals('album1', $album['body']['$id']);
@ -4735,7 +4747,11 @@ trait DatabasesBase
$artist = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $artists['body']['$id'] . '/documents/' . $album['body']['artist']['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
], $this->getHeaders()), [
'queries' => [
Query::select(['*', 'albums.$id', 'albums.name', 'albums.$permissions'])->toString()
]
]);
$this->assertEquals(200, $artist['headers']['status-code']);
$this->assertEquals('Artist 1', $artist['body']['name']);
@ -4876,7 +4892,11 @@ trait DatabasesBase
$sport = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $sports['body']['$id'] . '/documents/sport1', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
], $this->getHeaders()), [
'queries' => [
Query::select(['*', 'players.name', 'players.$permissions'])->toString()
]
]);
$this->assertEquals(200, $sport['headers']['status-code']);
$this->assertEquals('sport1', $sport['body']['$id']);
@ -4890,7 +4910,11 @@ trait DatabasesBase
$player = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $players['body']['$id'] . '/documents/' . $sport['body']['players'][0]['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
], $this->getHeaders()), [
'queries' => [
Query::select(['*', 'sports.$id', 'sports.name', 'sports.$permissions'])->toString()
]
]);
$this->assertEquals(200, $player['headers']['status-code']);
$this->assertEquals('Player 1', $player['body']['name']);
@ -4918,6 +4942,7 @@ trait DatabasesBase
], $this->getHeaders()), [
'queries' => [
Query::isNotNull('$id')->toString(),
Query::select(['*', 'libraries.*'])->toString(),
Query::startsWith('fullName', 'Stevie')->toString(),
Query::endsWith('fullName', 'Wonder')->toString(),
Query::between('$createdAt', '1975-12-06', '2050-12-01')->toString(),

View file

@ -3794,7 +3794,11 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
]), [
'queries' => [
Query::select(['new_level_2.*'])->toString()
]
]);
$this->assertArrayHasKey('new_level_2', $newDocument['body']);
$this->assertEquals(1, count($newDocument['body']['new_level_2']));
@ -3904,7 +3908,11 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
]), [
'queries' => [
Query::select(['new_level_2.*'])->toString()
]
]);
$this->assertArrayHasKey('new_level_2', $newDocument['body']);
$this->assertNotEmpty($newDocument['body']['new_level_2']);
@ -4014,7 +4022,11 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
]), [
'queries' => [
Query::select(['new_level_2.*'])->toString()
]
]);
$this->assertArrayHasKey('new_level_2', $newDocument['body']);
$this->assertNotEmpty($newDocument['body']['new_level_2']);
@ -4025,7 +4037,11 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
]), [
'queries' => [
Query::select(['*', 'level1.*'])->toString()
]
]);
$this->assertArrayHasKey('level1', $level2Document['body']);
$this->assertNotEmpty($level2Document['body']['level1']);
@ -4124,7 +4140,11 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
]), [
'queries' => [
Query::select(['new_level_2.*'])->toString()
]
]);
$this->assertArrayHasKey('new_level_2', $newDocument['body']);
$this->assertNotEmpty($newDocument['body']['new_level_2']);
@ -4135,7 +4155,11 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
]), [
'queries' => [
Query::select(['*', 'level1.*'])->toString()
]
]);
$this->assertArrayHasKey('level1', $level2Document['body']);
$this->assertNotEmpty($level2Document['body']['level1']);
@ -4480,6 +4504,14 @@ class DatabasesCustomServerTest extends Scope
$createBulkDocuments();
/**
* Wait for database to purge cache...
*
* This test specifically failed on 1.6.x response format,
* could be due to the slow or overworked machine, but being safe here!
*/
sleep(5);
// TEST: Update all documents
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
@ -4498,6 +4530,14 @@ class DatabasesCustomServerTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(10, $response['body']['documents']);
/**
* Wait for database to purge cache...
*
* This test specifically failed on 1.6.x response format,
* could be due to the slow or overworked machine, but being safe here!
*/
sleep(5);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],

View file

@ -4572,7 +4572,11 @@ trait DatabasesBase
$response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/tables/' . $personCollection . '/rows/' . $person2['body']['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
], $this->getHeaders()), [
'queries' => [
Query::select(['*', 'libraries.*'])->toString()
]
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertArrayNotHasKey('$table', $response['body']);
@ -4582,7 +4586,11 @@ trait DatabasesBase
$response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/tables/' . $libraryCollection . '/rows/library11', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
], $this->getHeaders()), [
'queries' => [
Query::select(['person_one_to_many.$id'])->toString()
]
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertArrayHasKey('person_one_to_many', $response['body']);
@ -4732,7 +4740,11 @@ trait DatabasesBase
$album = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/tables/' . $albums['body']['$id'] . '/rows/album1', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
], $this->getHeaders()), [
'queries' => [
Query::select(['*', 'artist.name', 'artist.$permissions'])->toString()
]
]);
$this->assertEquals(200, $album['headers']['status-code']);
$this->assertEquals('album1', $album['body']['$id']);
@ -4744,7 +4756,11 @@ trait DatabasesBase
$artist = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/tables/' . $artists['body']['$id'] . '/rows/' . $album['body']['artist']['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
], $this->getHeaders()), [
'queries' => [
Query::select(['*', 'albums.$id', 'albums.name', 'albums.$permissions'])->toString()
]
]);
$this->assertEquals(200, $artist['headers']['status-code']);
$this->assertEquals('Artist 1', $artist['body']['name']);
@ -4885,7 +4901,11 @@ trait DatabasesBase
$sport = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/tables/' . $sports['body']['$id'] . '/rows/sport1', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
], $this->getHeaders()), [
'queries' => [
Query::select(['*', 'players.name', 'players.$permissions'])->toString()
]
]);
$this->assertEquals(200, $sport['headers']['status-code']);
$this->assertEquals('sport1', $sport['body']['$id']);
@ -4899,7 +4919,11 @@ trait DatabasesBase
$player = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/tables/' . $players['body']['$id'] . '/rows/' . $sport['body']['players'][0]['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
], $this->getHeaders()), [
'queries' => [
Query::select(['*', 'sports.$id', 'sports.name', 'sports.$permissions'])->toString()
]
]);
$this->assertEquals(200, $player['headers']['status-code']);
$this->assertEquals('Player 1', $player['body']['name']);
@ -4927,6 +4951,7 @@ trait DatabasesBase
], $this->getHeaders()), [
'queries' => [
Query::isNotNull('$id')->toString(),
Query::select(['*', 'libraries.*'])->toString(),
Query::startsWith('fullName', 'Stevie')->toString(),
Query::endsWith('fullName', 'Wonder')->toString(),
Query::between('$createdAt', '1975-12-06', '2050-12-01')->toString(),

View file

@ -3740,7 +3740,11 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
]), [
'queries' => [
Query::select(['new_level_2.*'])->toString()
]
]);
$this->assertArrayHasKey('new_level_2', $newRow['body']);
$this->assertEquals(1, count($newRow['body']['new_level_2']));
@ -3850,7 +3854,11 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
]), [
'queries' => [
Query::select(['new_level_2.*'])->toString()
]
]);
$this->assertArrayHasKey('new_level_2', $newRow['body']);
$this->assertNotEmpty($newRow['body']['new_level_2']);
@ -3960,7 +3968,11 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
]), [
'queries' => [
Query::select(['new_level_2.*'])->toString()
]
]);
$this->assertArrayHasKey('new_level_2', $newRow['body']);
$this->assertNotEmpty($newRow['body']['new_level_2']);
@ -3971,7 +3983,11 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
]), [
'queries' => [
Query::select(['*', 'level1.*'])->toString()
]
]);
$this->assertArrayHasKey('level1', $level2Row['body']);
$this->assertNotEmpty($level2Row['body']['level1']);
@ -4070,7 +4086,11 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
]), [
'queries' => [
Query::select(['new_level_2.*'])->toString()
]
]);
$this->assertArrayHasKey('new_level_2', $newRow['body']);
$this->assertNotEmpty($newRow['body']['new_level_2']);
@ -4081,7 +4101,11 @@ class DatabasesCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
]), [
'queries' => [
Query::select(['*', 'level1.*'])->toString()
]
]);
$this->assertArrayHasKey('level1', $level2Row['body']);
$this->assertNotEmpty($level2Row['body']['level1']);
@ -4426,6 +4450,14 @@ class DatabasesCustomServerTest extends Scope
$createBulkRows();
/**
* Wait for database to purge cache...
*
* This test specifically failed on 1.6.x response format,
* could be due to the slow or overworked machine, but being safe here!
*/
sleep(5);
// TEST: Update all rows
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $data['databaseId'] . '/tables/' . $data['$id'] . '/rows', array_merge([
'content-type' => 'application/json',
@ -4444,6 +4476,14 @@ class DatabasesCustomServerTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(10, $response['body']['rows']);
/**
* Wait for database to purge cache...
*
* This test specifically failed on 1.6.x response format,
* could be due to the slow or overworked machine, but being safe here!
*/
sleep(5);
$rows = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/tables/' . $data['$id'] . '/rows', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],