Skip to content

Commit daf0276

Browse files
authored
Merge pull request #715 from utopia-php/fix-relationship-top-level-filter
fix relationship top level filter
2 parents 732ffef + 616b04e commit daf0276

2 files changed

Lines changed: 345 additions & 0 deletions

File tree

src/Database/Database.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6750,6 +6750,8 @@ public function decode(Document $collection, Document $document, array $selectio
67506750
fn ($attribute) => $attribute['type'] === self::VAR_RELATIONSHIP
67516751
);
67526752

6753+
$filteredValue = [];
6754+
67536755
foreach ($relationships as $relationship) {
67546756
$key = $relationship['$id'] ?? '';
67556757

@@ -6798,6 +6800,8 @@ public function decode(Document $collection, Document $document, array $selectio
67986800
$value[$index] = $node;
67996801
}
68006802

6803+
$filteredValue[$key] = ($array) ? $value : $value[0];
6804+
68016805
if (
68026806
empty($selections)
68036807
|| \in_array($key, $selections)
@@ -6807,6 +6811,29 @@ public function decode(Document $collection, Document $document, array $selectio
68076811
}
68086812
}
68096813

6814+
$hasRelationshipSelections = false;
6815+
if (!empty($selections)) {
6816+
foreach ($selections as $selection) {
6817+
if (\str_contains($selection, '.')) {
6818+
$hasRelationshipSelections = true;
6819+
break;
6820+
}
6821+
}
6822+
}
6823+
6824+
if ($hasRelationshipSelections && !empty($selections) && !\in_array('*', $selections)) {
6825+
foreach ($collection->getAttribute('attributes', []) as $attribute) {
6826+
$key = $attribute['$id'] ?? '';
6827+
6828+
if ($attribute['type'] === self::VAR_RELATIONSHIP || $key === '$permissions') {
6829+
continue;
6830+
}
6831+
6832+
if (!in_array($key, $selections) && isset($filteredValue[$key])) {
6833+
$document->setAttribute($key, $filteredValue[$key]);
6834+
}
6835+
}
6836+
}
68106837
return $document;
68116838
}
68126839

tests/e2e/Adapter/Scopes/DocumentTests.php

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6020,4 +6020,322 @@ public function testCreateUpdateDocumentsMismatch(): void
60206020
}
60216021
$database->deleteCollection($colName);
60226022
}
6023+
public function testDecodeWithDifferentSelectionTypes(): void
6024+
{
6025+
/** @var Database $database */
6026+
$database = static::getDatabase();
6027+
6028+
if (!$database->getAdapter()->getSupportForRelationships()) {
6029+
$this->expectNotToPerformAssertions();
6030+
return;
6031+
}
6032+
6033+
if (!$database->getAdapter()->getSupportForSpatialAttributes()) {
6034+
$this->expectNotToPerformAssertions();
6035+
return;
6036+
}
6037+
6038+
$database->addFilter(
6039+
'encrypt',
6040+
function (mixed $value) {
6041+
return json_encode([
6042+
'data' => base64_encode($value),
6043+
'method' => 'base64',
6044+
'version' => 'v1',
6045+
]);
6046+
},
6047+
function (mixed $value) {
6048+
if (is_null($value)) {
6049+
return;
6050+
}
6051+
$value = json_decode($value, true);
6052+
return base64_decode($value['data']);
6053+
}
6054+
);
6055+
6056+
$citiesId = 'TestCities';
6057+
$storesId = 'TestStores';
6058+
6059+
$database->createCollection($citiesId);
6060+
$database->createCollection($storesId);
6061+
6062+
$database->createAttribute($citiesId, 'name', Database::VAR_STRING, 255, required: true);
6063+
$database->createAttribute($citiesId, 'area', Database::VAR_POLYGON, 0, required: true);
6064+
$database->createAttribute($citiesId, 'population', Database::VAR_INTEGER, 0, required: false, default: 0);
6065+
$database->createAttribute($citiesId, 'secretCode', Database::VAR_STRING, 255, required: false, default: 'default-secret', filters: ['encrypt']);
6066+
$database->createAttribute($citiesId, 'center', Database::VAR_POINT, 0, required: false, default: [0.0, 0.0]);
6067+
6068+
$database->createAttribute($storesId, 'name', Database::VAR_STRING, 255, required: true);
6069+
$database->createAttribute($storesId, 'revenue', Database::VAR_FLOAT, 0, required: false, default: 0.0);
6070+
$database->createAttribute($storesId, 'location', Database::VAR_POINT, 0, required: false, default: [1.0, 1.0]);
6071+
6072+
$database->createRelationship(
6073+
collection: $storesId,
6074+
relatedCollection: $citiesId,
6075+
type: Database::RELATION_MANY_TO_ONE,
6076+
twoWay: true,
6077+
id: 'city',
6078+
twoWayKey: 'stores'
6079+
);
6080+
6081+
$cityDoc = new Document([
6082+
'$id' => 'city-1',
6083+
'name' => 'Test City',
6084+
'area' => [[[40.7128, -74.0060], [40.7589, -74.0060], [40.7589, -73.9851], [40.7128, -73.9851], [40.7128, -74.0060]]],
6085+
'population' => 1000000,
6086+
'secretCode' => 'super-secret-code',
6087+
'center' => [40.7282, -73.9942],
6088+
'$permissions' => [Permission::read(Role::any())],
6089+
]);
6090+
$createdCity = $database->createDocument($citiesId, $cityDoc);
6091+
6092+
$storeDoc = new Document([
6093+
'$id' => 'store-1',
6094+
'name' => 'Main Store',
6095+
'revenue' => 50000.75,
6096+
'location' => [40.7300, -73.9900],
6097+
'city' => $createdCity->getId(),
6098+
'$permissions' => [Permission::read(Role::any())],
6099+
]);
6100+
$createdStore = $database->createDocument($storesId, $storeDoc);
6101+
6102+
$cityWithSelection = $database->getDocument($citiesId, 'city-1', [
6103+
Query::select(['name', 'population'])
6104+
]);
6105+
6106+
$this->assertEquals('Test City', $cityWithSelection->getAttribute('name'));
6107+
$this->assertEquals(1000000, $cityWithSelection->getAttribute('population'));
6108+
6109+
$this->assertNull($cityWithSelection->getAttribute('area'));
6110+
$this->assertNull($cityWithSelection->getAttribute('secretCode'));
6111+
$this->assertNull($cityWithSelection->getAttribute('center'));
6112+
6113+
$cityWithSpatial = $database->getDocument($citiesId, 'city-1', [
6114+
Query::select(['name', 'area', 'center'])
6115+
]);
6116+
6117+
$this->assertEquals('Test City', $cityWithSpatial->getAttribute('name'));
6118+
$this->assertNotNull($cityWithSpatial->getAttribute('area'));
6119+
$this->assertEquals([[[40.7128, -74.0060], [40.7589, -74.0060], [40.7589, -73.9851], [40.7128, -73.9851], [40.7128, -74.0060]]], $cityWithSpatial->getAttribute('area'));
6120+
$this->assertEquals([40.7282, -73.9942], $cityWithSpatial->getAttribute('center'));
6121+
// Null -> not selected
6122+
$this->assertNull($cityWithSpatial->getAttribute('population'));
6123+
$this->assertNull($cityWithSpatial->getAttribute('secretCode'));
6124+
6125+
$cityWithEncrypted = $database->getDocument($citiesId, 'city-1', [
6126+
Query::select(['name', 'secretCode'])
6127+
]);
6128+
6129+
$this->assertEquals('Test City', $cityWithEncrypted->getAttribute('name'));
6130+
$this->assertEquals('super-secret-code', $cityWithEncrypted->getAttribute('secretCode')); // Should be decrypted
6131+
6132+
$this->assertNull($cityWithEncrypted->getAttribute('area'));
6133+
$this->assertNull($cityWithEncrypted->getAttribute('population'));
6134+
6135+
$cityWithStores = $database->getDocument($citiesId, 'city-1', [
6136+
Query::select(['stores.name'])
6137+
]);
6138+
6139+
$this->assertNotNull($cityWithStores->getAttribute('stores'));
6140+
$this->assertCount(1, $cityWithStores->getAttribute('stores'));
6141+
$this->assertEquals('Main Store', $cityWithStores->getAttribute('stores')[0]['name']);
6142+
6143+
$this->assertEquals('super-secret-code', $cityWithStores->getAttribute('secretCode'));
6144+
$this->assertNotNull($cityWithStores->getAttribute('area'));
6145+
$this->assertEquals([40.7282, -73.9942], $cityWithStores->getAttribute('center'));
6146+
6147+
$cityWithMultipleStoreFields = $database->getDocument($citiesId, 'city-1', [
6148+
Query::select(['stores.name', 'stores.revenue'])
6149+
]);
6150+
6151+
$this->assertNotNull($cityWithMultipleStoreFields->getAttribute('stores'));
6152+
$this->assertEquals('Main Store', $cityWithMultipleStoreFields->getAttribute('stores')[0]['name']);
6153+
$this->assertEquals(50000.75, $cityWithMultipleStoreFields->getAttribute('stores')[0]['revenue']);
6154+
6155+
$this->assertEquals('super-secret-code', $cityWithMultipleStoreFields->getAttribute('secretCode'));
6156+
6157+
$cityWithMixed = $database->getDocument($citiesId, 'city-1', [
6158+
Query::select(['name', 'population', 'stores.name'])
6159+
]);
6160+
6161+
$this->assertEquals('Test City', $cityWithMixed->getAttribute('name'));
6162+
$this->assertEquals(1000000, $cityWithMixed->getAttribute('population'));
6163+
6164+
$this->assertNotNull($cityWithMixed->getAttribute('stores'));
6165+
$this->assertEquals('Main Store', $cityWithMixed->getAttribute('stores')[0]['name']);
6166+
6167+
$citiesWithStores = $database->find($citiesId, [
6168+
Query::select(['stores.name']),
6169+
Query::equal('$id', ['city-1'])
6170+
]);
6171+
6172+
$this->assertCount(1, $citiesWithStores);
6173+
$city = $citiesWithStores[0];
6174+
$this->assertNotNull($city->getAttribute('stores'));
6175+
$this->assertEquals('Main Store', $city->getAttribute('stores')[0]['name']);
6176+
$this->assertEquals('super-secret-code', $city->getAttribute('secretCode'));
6177+
6178+
$storeWithCityArea = $database->getDocument($storesId, 'store-1', [
6179+
Query::select(['location','city.area'])
6180+
]);
6181+
6182+
$this->assertNotNull($storeWithCityArea->getAttribute('city'));
6183+
$this->assertNotNull($storeWithCityArea->getAttribute('city')['area']);
6184+
$this->assertEquals([40.7300, -73.9900], $storeWithCityArea->getAttribute('location'));
6185+
6186+
$database->deleteCollection($citiesId);
6187+
$database->deleteCollection($storesId);
6188+
}
6189+
6190+
public function testDecodeWithoutRelationships(): void
6191+
{
6192+
/** @var Database $database */
6193+
$database = static::getDatabase();
6194+
6195+
if (!$database->getAdapter()->getSupportForSpatialAttributes()) {
6196+
$this->expectNotToPerformAssertions();
6197+
return;
6198+
}
6199+
6200+
$database->addFilter(
6201+
'encryptTest',
6202+
function (mixed $value) {
6203+
return 'encrypted:' . base64_encode($value);
6204+
},
6205+
function (mixed $value) {
6206+
if (is_null($value) || !str_starts_with($value, 'encrypted:')) {
6207+
return $value;
6208+
}
6209+
return base64_decode(substr($value, 10));
6210+
}
6211+
);
6212+
6213+
$collectionId = 'TestDecodeCollection';
6214+
$database->createCollection($collectionId);
6215+
6216+
$database->createAttribute($collectionId, 'title', Database::VAR_STRING, 255, required: true);
6217+
$database->createAttribute($collectionId, 'description', Database::VAR_STRING, 1000, required: false, default: 'No description');
6218+
$database->createAttribute($collectionId, 'count', Database::VAR_INTEGER, 0, required: false, default: 0);
6219+
$database->createAttribute($collectionId, 'price', Database::VAR_FLOAT, 0, required: false, default: 0.0);
6220+
$database->createAttribute($collectionId, 'active', Database::VAR_BOOLEAN, 0, required: false, default: true);
6221+
$database->createAttribute($collectionId, 'tags', Database::VAR_STRING, 50, required: false, array: true);
6222+
$database->createAttribute($collectionId, 'secret', Database::VAR_STRING, 255, required: false, default: 'default-secret', filters: ['encryptTest']);
6223+
$database->createAttribute($collectionId, 'location', Database::VAR_POINT, 0, required: false, default: [0.0, 0.0]);
6224+
$database->createAttribute($collectionId, 'boundary', Database::VAR_POLYGON, 0, required: false);
6225+
6226+
$doc = new Document([
6227+
'$id' => 'test-1',
6228+
'title' => 'Test Document',
6229+
'description' => 'This is a test document',
6230+
'count' => 42,
6231+
'price' => 99.99,
6232+
'active' => true,
6233+
'tags' => ['tag1', 'tag2', 'tag3'],
6234+
'secret' => 'my-secret-value',
6235+
'location' => [40.7128, -74.0060],
6236+
'boundary' => [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]],
6237+
'$permissions' => [Permission::read(Role::any())],
6238+
]);
6239+
$created = $database->createDocument($collectionId, $doc);
6240+
6241+
$selected = $database->getDocument($collectionId, 'test-1', [
6242+
Query::select(['title', 'count', 'secret'])
6243+
]);
6244+
6245+
$this->assertEquals('Test Document', $selected->getAttribute('title'));
6246+
$this->assertEquals(42, $selected->getAttribute('count'));
6247+
$this->assertEquals('my-secret-value', $selected->getAttribute('secret'));
6248+
6249+
$this->assertNull($selected->getAttribute('description'));
6250+
$this->assertNull($selected->getAttribute('price'));
6251+
$this->assertNull($selected->getAttribute('location'));
6252+
6253+
$spatialSelected = $database->getDocument($collectionId, 'test-1', [
6254+
Query::select(['title', 'location', 'boundary'])
6255+
]);
6256+
6257+
$this->assertEquals('Test Document', $spatialSelected->getAttribute('title'));
6258+
$this->assertEquals([40.7128, -74.0060], $spatialSelected->getAttribute('location'));
6259+
$this->assertEquals([[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]], $spatialSelected->getAttribute('boundary'));
6260+
$this->assertNull($spatialSelected->getAttribute('secret'));
6261+
$this->assertNull($spatialSelected->getAttribute('count'));
6262+
6263+
$arraySelected = $database->getDocument($collectionId, 'test-1', [
6264+
Query::select(['title', 'tags'])
6265+
]);
6266+
6267+
$this->assertEquals('Test Document', $arraySelected->getAttribute('title'));
6268+
$this->assertEquals(['tag1', 'tag2', 'tag3'], $arraySelected->getAttribute('tags'));
6269+
$this->assertNull($arraySelected->getAttribute('active'));
6270+
6271+
$allSelected = $database->getDocument($collectionId, 'test-1', [
6272+
Query::select(['*'])
6273+
]);
6274+
6275+
$this->assertEquals('Test Document', $allSelected->getAttribute('title'));
6276+
$this->assertEquals('This is a test document', $allSelected->getAttribute('description'));
6277+
$this->assertEquals(42, $allSelected->getAttribute('count'));
6278+
$this->assertEquals('my-secret-value', $allSelected->getAttribute('secret'));
6279+
$this->assertEquals([40.7128, -74.0060], $allSelected->getAttribute('location'));
6280+
6281+
$noSelection = $database->getDocument($collectionId, 'test-1');
6282+
6283+
$this->assertEquals('Test Document', $noSelection->getAttribute('title'));
6284+
$this->assertEquals('This is a test document', $noSelection->getAttribute('description'));
6285+
$this->assertEquals('my-secret-value', $noSelection->getAttribute('secret'));
6286+
$this->assertEquals([40.7128, -74.0060], $noSelection->getAttribute('location'));
6287+
6288+
$database->deleteCollection($collectionId);
6289+
}
6290+
6291+
public function testDecodeWithMultipleFilters(): void
6292+
{
6293+
/** @var Database $database */
6294+
$database = static::getDatabase();
6295+
6296+
$database->addFilter(
6297+
'upperCase',
6298+
function (mixed $value) { return strtoupper($value); },
6299+
function (mixed $value) { return strtolower($value); }
6300+
);
6301+
6302+
$database->addFilter(
6303+
'prefix',
6304+
function (mixed $value) { return 'prefix_' . $value; },
6305+
function (mixed $value) { return str_replace('prefix_', '', $value); }
6306+
);
6307+
6308+
$collectionId = 'EdgeCaseCollection';
6309+
$database->createCollection($collectionId);
6310+
6311+
$database->createAttribute($collectionId, 'name', Database::VAR_STRING, 255, required: true);
6312+
$database->createAttribute($collectionId, 'processedName', Database::VAR_STRING, 255, required: false, filters: ['upperCase', 'prefix']);
6313+
$database->createAttribute($collectionId, 'nullableField', Database::VAR_STRING, 255, required: false);
6314+
6315+
$doc = new Document([
6316+
'$id' => 'edge-1',
6317+
'name' => 'Test Name',
6318+
'processedName' => 'test value',
6319+
'nullableField' => null,
6320+
'$permissions' => [Permission::read(Role::any())],
6321+
]);
6322+
$created = $database->createDocument($collectionId, $doc);
6323+
6324+
$selected = $database->getDocument($collectionId, 'edge-1', [
6325+
Query::select(['name', 'processedName'])
6326+
]);
6327+
6328+
$this->assertEquals('Test Name', $selected->getAttribute('name'));
6329+
$this->assertEquals('test value', $selected->getAttribute('processedName'));
6330+
$this->assertNull($selected->getAttribute('nullableField'));
6331+
6332+
$nullSelected = $database->getDocument($collectionId, 'edge-1', [
6333+
Query::select(['name', 'nullableField'])
6334+
]);
6335+
6336+
$this->assertEquals('Test Name', $nullSelected->getAttribute('name'));
6337+
$this->assertNull($nullSelected->getAttribute('nullableField'));
6338+
6339+
$database->deleteCollection($collectionId);
6340+
}
60236341
}

0 commit comments

Comments
 (0)