@@ -303,6 +303,114 @@ public function test_select_without_with_is_unaffected(): void
303303 $ this ->assertInstanceOf (RelUser::class, $ user );
304304 $ this ->assertSame ('Alice ' , $ user ->name );
305305 }
306+
307+ // -----------------------------------------------------------------------
308+ // N+1 verification — proves eager loading runs a constant number of
309+ // queries (2: one for parents, one whereIn() for the relation) no
310+ // matter how many parent rows are involved, instead of one extra
311+ // query per row (the classic N+1 problem).
312+ // -----------------------------------------------------------------------
313+
314+ /**
315+ * Seed many users (more than the small fixture in seedAll()) so a
316+ * per-row query difference is unambiguous and not a fluke of a
317+ * tiny dataset.
318+ *
319+ * @return int Number of users seeded
320+ */
321+ private function seedManyUsersWithPosts (int $ userCount = 10 , int $ postsPerUser = 3 ): int
322+ {
323+ for ($ i = 1 ; $ i <= $ userCount ; $ i ++) {
324+ $ user = RelUser::create (['name ' => "Bulk User {$ i }" ]);
325+ for ($ j = 1 ; $ j <= $ postsPerUser ; $ j ++) {
326+ RelPost::create (['user_id ' => $ user ->getKey (), 'title ' => "Bulk Post {$ j }" ]);
327+ }
328+ }
329+
330+ return $ userCount ;
331+ }
332+
333+ public function test_lazy_loading_issues_one_query_per_row (): void
334+ {
335+ $ userCount = $ this ->seedManyUsersWithPosts (userCount: 10 );
336+
337+ DB ::flushQueryLog ();
338+ DB ::enableQueryLog ();
339+
340+ $ users = RelUser::all ();
341+ foreach ($ users as $ user ) {
342+ $ user ->posts ->count (); // lazy: one query per iteration
343+ }
344+
345+ $ queryCount = count (DB ::getQueryLog ());
346+
347+ // 1 query to fetch the users + 1 query per user for posts.
348+ $ this ->assertSame (
349+ 1 + $ userCount ,
350+ $ queryCount ,
351+ 'Lazy loading should issue exactly one query per parent row (the N+1 pattern), confirming the baseline this test contrasts eager loading against. ' ,
352+ );
353+ }
354+
355+ public function test_eager_loading_issues_constant_query_count_regardless_of_row_count (): void
356+ {
357+ $ userCount = $ this ->seedManyUsersWithPosts (userCount: 10 );
358+
359+ DB ::flushQueryLog ();
360+ DB ::enableQueryLog ();
361+
362+ $ users = RelUser::with ('posts ' )->get ();
363+ foreach ($ users as $ user ) {
364+ $ user ->posts ->count (); // already eager-loaded — must not query again
365+ }
366+
367+ $ queryCount = count (DB ::getQueryLog ());
368+
369+ // Exactly 2 queries total: 1 for users, 1 whereIn(...) for all posts —
370+ // independent of how many users were fetched. This is the N+1 fix.
371+ $ this ->assertSame (
372+ 2 ,
373+ $ queryCount ,
374+ 'Eager loading via with() must use a constant 2 queries (parents + one whereIn for the relation), not one query per row. ' ,
375+ );
376+
377+ $ log = DB ::getQueryLog ();
378+ $ this ->assertStringContainsString ('IN ( ' , $ log [1 ]->sql , 'The eager relation query should batch all parent keys into a single whereIn(...) clause. ' );
379+ }
380+
381+ public function test_eager_loading_after_select_also_avoids_n_plus_1 (): void
382+ {
383+ // Same as above, but through the select()->with() chain that was
384+ // previously broken — confirms the fix didn't just make the call
385+ // succeed, but that it still avoids N+1 once it does.
386+ $ userCount = $ this ->seedManyUsersWithPosts (userCount: 10 );
387+
388+ DB ::flushQueryLog ();
389+ DB ::enableQueryLog ();
390+
391+ $ users = RelUser::select ('id ' , 'name ' )->with ('posts ' )->get ();
392+ foreach ($ users as $ user ) {
393+ $ user ->posts ->count ();
394+ }
395+
396+ $ this ->assertSame (2 , count (DB ::getQueryLog ()));
397+ }
398+
399+ public function test_eager_loading_belongs_to_avoids_n_plus_1 (): void
400+ {
401+ // Inverse direction: many posts eager-loading their single author.
402+ $ this ->seedManyUsersWithPosts (userCount: 10 , postsPerUser: 2 );
403+
404+ DB ::flushQueryLog ();
405+ DB ::enableQueryLog ();
406+
407+ $ posts = RelPost::with ('author ' )->get ();
408+ foreach ($ posts as $ post ) {
409+ $ post ->author ->name ;
410+ }
411+
412+ $ this ->assertSame (2 , count (DB ::getQueryLog ()));
413+ }
306414}
307415
308416// -----------------------------------------------------------------------
0 commit comments