Skip to content

Commit 1b783f7

Browse files
github-actions[bot]claude
authored andcommitted
fix(relationships): respect lowered maxQueryValues in chunked fan-out
Hardcoding the relationship fan-out chunk at RELATION_QUERY_CHUNK_SIZE (5000) matched the default validator cap, but a caller that lowered setMaxQueryValues() below 5000 would still see chunked find/update calls throw QueryException — the validator rejects any IN-list whose count exceeds the configured cap. Introduce a private relationQueryChunkSize() helper that takes min(RELATION_QUERY_CHUNK_SIZE, getMaxQueryValues()) and use it at every chunked array_chunk() site in Hook/Relationships. The constant still acts as a memory bound; the configured cap acts as the validator ceiling. Floor at 1 so a misconfigured cap of 0 cannot crash array_chunk. Also restore the docblock on Memory::decodeObjectValue so the ?array contract mirrors decodeArrayValue and matchesObject's "null === no match" guard reads as intentional rather than incidental. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 4f16422 commit 1b783f7

2 files changed

Lines changed: 31 additions & 10 deletions

File tree

src/Database/Adapter/Memory.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3150,6 +3150,14 @@ protected function matchesObject(mixed $value, Query $query): bool
31503150
throw new DatabaseException('Query method '.$method->value.' not supported for object attributes');
31513151
}
31523152

3153+
/**
3154+
* Return the decoded array if $value is already an array or looks like a
3155+
* JSON object/array literal; null otherwise. Mirrors decodeArrayValue and
3156+
* lets matchesObject's callers rely on a single `null === no match` guard
3157+
* rather than dispatching on raw scalar types.
3158+
*
3159+
* @return array<mixed>|null
3160+
*/
31533161
protected function decodeObjectValue(mixed $value): ?array
31543162
{
31553163
if (\is_array($value)) {

src/Database/Hook/Relationships.php

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,19 @@ public function __construct(
5353
) {
5454
}
5555

56+
/**
57+
* Effective per-query chunk size for relationship fan-out reads/writes.
58+
*
59+
* Capped by RELATION_QUERY_CHUNK_SIZE as a memory bound, but never larger
60+
* than the configured maxQueryValues — otherwise a caller that lowers the
61+
* validator cap would still see relationship updates throw QueryException
62+
* on the chunked find/update fallback.
63+
*/
64+
private function relationQueryChunkSize(): int
65+
{
66+
return \max(1, \min(Database::RELATION_QUERY_CHUNK_SIZE, $this->db->getMaxQueryValues()));
67+
}
68+
5669
private function coerceToDocument(Document $document, string $key, mixed $value): mixed
5770
{
5871
if (\is_array($value) && ! \array_is_list($value)) {
@@ -485,7 +498,7 @@ public function afterDocumentUpdate(Document $collection, Document $old, Documen
485498
// Chunk to honor the validator's maxQueryValues cap; without
486499
// this a relationship update with thousands of removed
487500
// children would throw QueryException.
488-
foreach (\array_chunk($removedDocuments, Database::RELATION_QUERY_CHUNK_SIZE) as $chunk) {
501+
foreach (\array_chunk($removedDocuments, $this->relationQueryChunkSize()) as $chunk) {
489502
$this->db->getAuthorization()->skip(fn () => $this->db->skipRelationships(fn () => $this->db->updateDocuments(
490503
$relatedCollection->getId(),
491504
new Document([$twoWayKey => null]),
@@ -508,7 +521,7 @@ public function afterDocumentUpdate(Document $collection, Document $old, Documen
508521

509522
if (! empty($stringRelations)) {
510523
$existingIds = [];
511-
foreach (\array_chunk($stringRelations, Database::RELATION_QUERY_CHUNK_SIZE) as $chunk) {
524+
foreach (\array_chunk($stringRelations, $this->relationQueryChunkSize()) as $chunk) {
512525
$existing = $this->db->skipRelationships(
513526
fn () => $this->db->find($relatedCollection->getId(), [
514527
Query::select(['$id']),
@@ -522,7 +535,7 @@ public function afterDocumentUpdate(Document $collection, Document $old, Documen
522535
}
523536

524537
if (! empty($existingIds)) {
525-
foreach (\array_chunk($existingIds, Database::RELATION_QUERY_CHUNK_SIZE) as $chunk) {
538+
foreach (\array_chunk($existingIds, $this->relationQueryChunkSize()) as $chunk) {
526539
$this->db->skipRelationships(fn () => $this->db->updateDocuments(
527540
$relatedCollection->getId(),
528541
new Document([$twoWayKey => $document->getId()]),
@@ -636,7 +649,7 @@ public function afterDocumentUpdate(Document $collection, Document $old, Documen
636649
// diff with thousands of removed peers stays within the
637650
// validator's maxQueryValues ceiling.
638651
$junctionIds = [];
639-
foreach (\array_chunk($removedDocuments, Database::RELATION_QUERY_CHUNK_SIZE) as $chunk) {
652+
foreach (\array_chunk($removedDocuments, $this->relationQueryChunkSize()) as $chunk) {
640653
$junctions = $this->db->find($junction, [
641654
Query::select(['$id']),
642655
Query::equal($key, $chunk),
@@ -649,7 +662,7 @@ public function afterDocumentUpdate(Document $collection, Document $old, Documen
649662
}
650663

651664
if (! empty($junctionIds)) {
652-
foreach (\array_chunk($junctionIds, Database::RELATION_QUERY_CHUNK_SIZE) as $chunk) {
665+
foreach (\array_chunk($junctionIds, $this->relationQueryChunkSize()) as $chunk) {
653666
$this->db->getAuthorization()->skip(fn () => $this->db->deleteDocuments(
654667
$junction,
655668
[Query::equal('$id', $chunk)],
@@ -1442,7 +1455,7 @@ private function populateOneToOneRelationshipsBatch(array $documents, Relationsh
14421455
$uniqueRelatedIds = \array_unique($relatedIds);
14431456
$relatedDocuments = [];
14441457

1445-
$chunks = \array_chunk($uniqueRelatedIds, Database::RELATION_QUERY_CHUNK_SIZE);
1458+
$chunks = \array_chunk($uniqueRelatedIds, $this->relationQueryChunkSize());
14461459

14471460
if (\count($chunks) > 1) {
14481461
$collectionId = $relatedCollection->getId();
@@ -1540,7 +1553,7 @@ private function populateOneToManyRelationshipsBatch(array $documents, Relations
15401553

15411554
$relatedDocuments = [];
15421555

1543-
$chunks = \array_chunk($parentIds, Database::RELATION_QUERY_CHUNK_SIZE);
1556+
$chunks = \array_chunk($parentIds, $this->relationQueryChunkSize());
15441557

15451558
if (\count($chunks) > 1) {
15461559
$collectionId = $relatedCollection->getId();
@@ -1644,7 +1657,7 @@ private function populateManyToOneRelationshipsBatch(array $documents, Relations
16441657

16451658
$relatedDocuments = [];
16461659

1647-
$chunks = \array_chunk($childIds, Database::RELATION_QUERY_CHUNK_SIZE);
1660+
$chunks = \array_chunk($childIds, $this->relationQueryChunkSize());
16481661

16491662
if (\count($chunks) > 1) {
16501663
$collectionId = $relatedCollection->getId();
@@ -1732,7 +1745,7 @@ private function populateManyToManyRelationshipsBatch(array $documents, Relation
17321745

17331746
$junctions = [];
17341747

1735-
$junctionChunks = \array_chunk($documentIds, Database::RELATION_QUERY_CHUNK_SIZE);
1748+
$junctionChunks = \array_chunk($documentIds, $this->relationQueryChunkSize());
17361749

17371750
if (\count($junctionChunks) > 1) {
17381751
$tasks = \array_map(
@@ -1795,7 +1808,7 @@ private function populateManyToManyRelationshipsBatch(array $documents, Relation
17951808
$uniqueRelatedIds = array_unique($relatedIds);
17961809
$foundRelated = [];
17971810

1798-
$relatedChunks = \array_chunk($uniqueRelatedIds, Database::RELATION_QUERY_CHUNK_SIZE);
1811+
$relatedChunks = \array_chunk($uniqueRelatedIds, $this->relationQueryChunkSize());
17991812

18001813
if (\count($relatedChunks) > 1) {
18011814
$relatedCollectionId = $relatedCollection->getId();

0 commit comments

Comments
 (0)