diff --git a/packages/database/src/Builder/QueryBuilders/InsertQueryBuilder.php b/packages/database/src/Builder/QueryBuilders/InsertQueryBuilder.php index 1343a89c6..b0ca927d2 100644 --- a/packages/database/src/Builder/QueryBuilders/InsertQueryBuilder.php +++ b/packages/database/src/Builder/QueryBuilders/InsertQueryBuilder.php @@ -47,6 +47,12 @@ final class InsertQueryBuilder implements BuildsQuery private array $after = []; + private array $relationCallbacks = []; + + private array $resolvedRows = []; + + private array $rowCallbacks = []; + public array $bindings = []; public ModelInspector $model; @@ -90,6 +96,22 @@ public function execute(mixed ...$bindings): ?PrimaryKey } } + foreach ($this->rowCallbacks as $rowIndex => $callbacks) { + $rowId = $id; + + if ($this->model->hasUuidPrimaryKey()) { + $rowId = $this->resolveRowPrimaryKey($this->resolvedRows[$rowIndex] ?? []) ?? $id; + } + + foreach ($callbacks as $after) { + $query = $after($rowId); + + if ($query instanceof BuildsQuery) { + $query->build()->execute(); + } + } + } + return $id; } @@ -224,7 +246,7 @@ private function addHasManyRelationCallback(string $relationName, array $relatio throw ModelDidNotHavePrimaryColumn::neededForRelation($this->model->getName(), 'HasMany'); } - $this->after[] = function (PrimaryKey $parentId) use ($hasMany, $relations) { + $this->relationCallbacks[] = function (PrimaryKey $parentId) use ($hasMany, $relations) { $foreignKey = $hasMany->ownerJoin ? $this->removeTablePrefix($hasMany->ownerJoin) : $this->getDefaultForeignKeyName(); @@ -254,7 +276,7 @@ private function addHasOneRelationCallback(string $relationName, object|iterable return; } - $this->after[] = function (PrimaryKey $parentId) use ($hasOne, $relation) { + $this->relationCallbacks[] = function (PrimaryKey $parentId) use ($hasOne, $relation) { if ($hasOne->ownerJoin) { return $this->handleCustomHasOneRelation($hasOne, $relation, $parentId); } @@ -275,7 +297,7 @@ private function addBelongsToManyRelationCallback(string $relationName, iterable throw ModelDidNotHavePrimaryColumn::neededForRelation(model: $this->model->getName(), relationType: 'BelongsToMany'); } - $this->after[] = function (PrimaryKey $parentId) use ($belongsToMany, $relations) { + $this->relationCallbacks[] = function (PrimaryKey $parentId) use ($belongsToMany, $relations) { $ownerModel = inspect(model: $this->model->getName()); $targetModel = inspect(model: $belongsToMany->property->getIterableType()->asClass()); @@ -377,10 +399,34 @@ private function handleStandardHasOneRelation(HasOne $hasOne, object|array $rela private function resolveData(): array { - return Arr\map( - array: $this->rows, - map: $this->resolveModelData(...), - ); + $resolved = []; + + foreach ($this->rows as $row) { + $before = count($this->relationCallbacks); + + $data = $this->resolveModelData($row); + + $resolved[] = $data; + $this->resolvedRows[] = $data; + $this->rowCallbacks[] = array_slice($this->relationCallbacks, $before); + } + + return $resolved; + } + + private function resolveRowPrimaryKey(array $entry): ?PrimaryKey + { + $primaryKey = $this->model->getPrimaryKey(); + + if ($primaryKey === null || ! isset($entry[$primaryKey])) { + return null; + } + + $value = $entry[$primaryKey]; + + return $value instanceof PrimaryKey + ? $value + : new PrimaryKey($value); } private function resolveModelData(object|iterable $model): array diff --git a/tests/Integration/Database/UuidPrimaryKeyTest.php b/tests/Integration/Database/UuidPrimaryKeyTest.php index a3979439c..8bbebcada 100644 --- a/tests/Integration/Database/UuidPrimaryKeyTest.php +++ b/tests/Integration/Database/UuidPrimaryKeyTest.php @@ -144,6 +144,57 @@ public function uuid_primary_key_generated_for_iterable_insert(): void $this->assertSame('admin', $role->name); } + #[Test] + public function batch_insert_attaches_relations_to_their_own_parent(): void + { + $this->database->migrate( + CreateMigrationsTable::class, + CreateUuidUsersTableMigration::class, + CreateUuidRolesTableMigration::class, + CreateUuidUserRoleTableMigration::class, + CreateUuidPostsTableMigration::class, + ); + + $role = query(UuidRole::class)->create(name: 'admin'); + + $frieren = UuidUser::new(name: 'Frieren'); + $frieren->posts = [new UuidPost(title: 'Post A')]; + $frieren->roles = [$role]; + + $fern = UuidUser::new(name: 'Fern'); + $fern->posts = [new UuidPost(title: 'Post B')]; + $fern->roles = [$role]; + + query(UuidUser::class)->insert($frieren, $fern)->execute(); + + $userIds = []; + + foreach (query('uuid_users')->select()->all() as $user) { + $userIds[$user['name']] = $user['id']; + } + + $this->assertCount(2, $userIds); + $this->assertNotSame($userIds['Frieren'], $userIds['Fern']); + + $posts = query('uuid_posts')->select()->all(); + + $this->assertCount(2, $posts); + + foreach ($posts as $post) { + $this->assertSame($userIds[$post['title'] === 'Post A' ? 'Frieren' : 'Fern'], $post['uuid_user_id']); + } + + $pivotRows = query('uuid_user_role')->select()->all(); + $ownerIds = array_column($pivotRows, 'uuid_user_id'); + + sort($ownerIds); + + $expectedOwnerIds = [$userIds['Frieren'], $userIds['Fern']]; + sort($expectedOwnerIds); + + $this->assertSame($expectedOwnerIds, $ownerIds); + } + #[Test] public function uuid_primary_key_belongs_to_many_pivot_uses_generated_uuid(): void {