Skip to content

Commit eaffdc1

Browse files
authored
Merge pull request #722 from utopia-php/fix-nested-selection
Fix nested selection query mutation
2 parents 398b5d5 + 7ec0238 commit eaffdc1

File tree

2 files changed

+221
-1
lines changed

2 files changed

+221
-1
lines changed

src/Database/Database.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3596,7 +3596,8 @@ private function populateDocumentRelationships(Document $collection, Document $d
35963596
$twoWayKey = $relationship['options']['twoWayKey'];
35973597
$side = $relationship['options']['side'];
35983598

3599-
$queries = $selects[$key] ?? [];
3599+
// Clone queries to avoid mutation affecting subsequent documents
3600+
$queries = array_map(fn ($query) => clone $query, $selects[$key] ?? []);
36003601

36013602
if (!empty($value)) {
36023603
$k = $relatedCollection->getId() . ':' . $value . '=>' . $collection->getId() . ':' . $document->getId();

tests/e2e/Adapter/Scopes/RelationshipTests.php

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2580,4 +2580,223 @@ public function testUpdateDocumentWithRelationships(): void
25802580
$database->deleteCollection('products');
25812581
$database->deleteCollection('appearance');
25822582
}
2583+
2584+
/**
2585+
* Test that nested relationships are populated for all documents in a multi-document query
2586+
* Covers bug: https://github.com/appwrite/appwrite/issues/10552
2587+
*/
2588+
public function testMultiDocumentNestedRelationships(): void
2589+
{
2590+
/** @var Database $database */
2591+
$database = static::getDatabase();
2592+
2593+
if (!$database->getAdapter()->getSupportForRelationships()) {
2594+
$this->expectNotToPerformAssertions();
2595+
return;
2596+
}
2597+
2598+
// Create collections: car -> customer -> inspection
2599+
$database->createCollection('car');
2600+
$database->createAttribute('car', 'plate_number', Database::VAR_STRING, 255, true);
2601+
2602+
$database->createCollection('customer');
2603+
$database->createAttribute('customer', 'name', Database::VAR_STRING, 255, true);
2604+
2605+
$database->createCollection('inspection');
2606+
$database->createAttribute('inspection', 'type', Database::VAR_STRING, 255, true);
2607+
2608+
// Create relationships
2609+
// car -> customer (many to one, one-way to avoid circular references)
2610+
$database->createRelationship(
2611+
collection: 'car',
2612+
relatedCollection: 'customer',
2613+
type: Database::RELATION_MANY_TO_ONE,
2614+
twoWay: false,
2615+
id: 'customer',
2616+
);
2617+
2618+
// customer -> inspection (one to many, one-way)
2619+
$database->createRelationship(
2620+
collection: 'customer',
2621+
relatedCollection: 'inspection',
2622+
type: Database::RELATION_ONE_TO_MANY,
2623+
twoWay: false,
2624+
id: 'inspections',
2625+
);
2626+
2627+
// Create test data - customers with inspections first
2628+
$database->createDocument('inspection', new Document([
2629+
'$id' => 'inspection1',
2630+
'$permissions' => [
2631+
Permission::read(Role::any()),
2632+
Permission::update(Role::any()),
2633+
],
2634+
'type' => 'annual',
2635+
]));
2636+
$database->createDocument('inspection', new Document([
2637+
'$id' => 'inspection2',
2638+
'$permissions' => [
2639+
Permission::read(Role::any()),
2640+
Permission::update(Role::any()),
2641+
],
2642+
'type' => 'safety',
2643+
]));
2644+
$database->createDocument('inspection', new Document([
2645+
'$id' => 'inspection3',
2646+
'$permissions' => [
2647+
Permission::read(Role::any()),
2648+
Permission::update(Role::any()),
2649+
],
2650+
'type' => 'emissions',
2651+
]));
2652+
$database->createDocument('inspection', new Document([
2653+
'$id' => 'inspection4',
2654+
'$permissions' => [
2655+
Permission::read(Role::any()),
2656+
Permission::update(Role::any()),
2657+
],
2658+
'type' => 'annual',
2659+
]));
2660+
$database->createDocument('inspection', new Document([
2661+
'$id' => 'inspection5',
2662+
'$permissions' => [
2663+
Permission::read(Role::any()),
2664+
Permission::update(Role::any()),
2665+
],
2666+
'type' => 'safety',
2667+
]));
2668+
2669+
$database->createDocument('customer', new Document([
2670+
'$id' => 'customer1',
2671+
'$permissions' => [
2672+
Permission::read(Role::any()),
2673+
Permission::update(Role::any()),
2674+
],
2675+
'name' => 'Customer 1',
2676+
'inspections' => ['inspection1', 'inspection2'],
2677+
]));
2678+
2679+
$database->createDocument('customer', new Document([
2680+
'$id' => 'customer2',
2681+
'$permissions' => [
2682+
Permission::read(Role::any()),
2683+
Permission::update(Role::any()),
2684+
],
2685+
'name' => 'Customer 2',
2686+
'inspections' => ['inspection3', 'inspection4'],
2687+
]));
2688+
2689+
$database->createDocument('customer', new Document([
2690+
'$id' => 'customer3',
2691+
'$permissions' => [
2692+
Permission::read(Role::any()),
2693+
Permission::update(Role::any()),
2694+
],
2695+
'name' => 'Customer 3',
2696+
'inspections' => ['inspection5'],
2697+
]));
2698+
2699+
$car1 = $database->createDocument('car', new Document([
2700+
'$id' => 'car1',
2701+
'$permissions' => [
2702+
Permission::read(Role::any()),
2703+
Permission::delete(Role::any()),
2704+
],
2705+
'plate_number' => 'ABC123',
2706+
'customer' => 'customer1',
2707+
]));
2708+
2709+
$car2 = $database->createDocument('car', new Document([
2710+
'$id' => 'car2',
2711+
'$permissions' => [
2712+
Permission::read(Role::any()),
2713+
Permission::delete(Role::any()),
2714+
],
2715+
'plate_number' => 'DEF456',
2716+
'customer' => 'customer2',
2717+
]));
2718+
2719+
$car3 = $database->createDocument('car', new Document([
2720+
'$id' => 'car3',
2721+
'$permissions' => [
2722+
Permission::read(Role::any()),
2723+
Permission::delete(Role::any()),
2724+
],
2725+
'plate_number' => 'GHI789',
2726+
'customer' => 'customer3',
2727+
]));
2728+
2729+
// Query all cars with nested relationship selections
2730+
$cars = $database->find('car', [
2731+
Query::select([
2732+
'*',
2733+
'customer.*',
2734+
'customer.inspections.type',
2735+
]),
2736+
]);
2737+
2738+
$this->assertCount(3, $cars);
2739+
2740+
$this->assertEquals('ABC123', $cars[0]['plate_number']);
2741+
$this->assertEquals('Customer 1', $cars[0]['customer']['name']);
2742+
$this->assertCount(2, $cars[0]['customer']['inspections']);
2743+
$this->assertEquals('annual', $cars[0]['customer']['inspections'][0]['type']);
2744+
$this->assertEquals('safety', $cars[0]['customer']['inspections'][1]['type']);
2745+
2746+
$this->assertEquals('DEF456', $cars[1]['plate_number']);
2747+
$this->assertEquals('Customer 2', $cars[1]['customer']['name']);
2748+
$this->assertCount(2, $cars[1]['customer']['inspections']);
2749+
$this->assertEquals('emissions', $cars[1]['customer']['inspections'][0]['type']);
2750+
$this->assertEquals('annual', $cars[1]['customer']['inspections'][1]['type']);
2751+
2752+
$this->assertEquals('GHI789', $cars[2]['plate_number']);
2753+
$this->assertEquals('Customer 3', $cars[2]['customer']['name']);
2754+
$this->assertCount(1, $cars[2]['customer']['inspections']);
2755+
$this->assertEquals('safety', $cars[2]['customer']['inspections'][0]['type']);
2756+
2757+
// Test with createDocuments as well
2758+
$database->deleteDocument('car', 'car1');
2759+
$database->deleteDocument('car', 'car2');
2760+
$database->deleteDocument('car', 'car3');
2761+
2762+
$database->createDocuments('car', [
2763+
new Document([
2764+
'$id' => 'car1',
2765+
'$permissions' => [Permission::read(Role::any())],
2766+
'plate_number' => 'ABC123',
2767+
'customer' => 'customer1',
2768+
]),
2769+
new Document([
2770+
'$id' => 'car2',
2771+
'$permissions' => [Permission::read(Role::any())],
2772+
'plate_number' => 'DEF456',
2773+
'customer' => 'customer2',
2774+
]),
2775+
new Document([
2776+
'$id' => 'car3',
2777+
'$permissions' => [Permission::read(Role::any())],
2778+
'plate_number' => 'GHI789',
2779+
'customer' => 'customer3',
2780+
]),
2781+
]);
2782+
2783+
$cars = $database->find('car', [
2784+
Query::select([
2785+
'*',
2786+
'customer.*',
2787+
'customer.inspections.type',
2788+
]),
2789+
]);
2790+
2791+
// Verify all cars still have nested relationships after batch create
2792+
$this->assertCount(3, $cars);
2793+
$this->assertCount(2, $cars[0]['customer']['inspections']);
2794+
$this->assertCount(2, $cars[1]['customer']['inspections']);
2795+
$this->assertCount(1, $cars[2]['customer']['inspections']);
2796+
2797+
// Clean up
2798+
$database->deleteCollection('inspection');
2799+
$database->deleteCollection('car');
2800+
$database->deleteCollection('customer');
2801+
}
25832802
}

0 commit comments

Comments
 (0)