cloudcreativity / laravel-json-api

JSON API (jsonapi.org) package for Laravel applications.
http://laravel-json-api.readthedocs.io/en/latest/
Apache License 2.0
780 stars 109 forks source link

Diagnosing Duplicate SQL queries when fetching related records #525

Closed halfbusy closed 4 years ago

halfbusy commented 4 years ago

I am trying to track down a bug of redundant sql queries being generated for each request and need some assistance to where it may be happening in the laravel-json-api source.

I am fetching related records items from pages, and including items.images Endpoint: api.com/pages/057c0657-1911-4311-9c8a-52a02443699e/items?include=images

In the SQL logs, I can see getting the page and the related items works successfully.

select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]
select * from `items` where `items`.`page_id` = ? and `items`.`page_id` is not null [057c0657-1911-4311-9c8a-52a02443699e]

After that, a whereIn query runs successfully to include item.images

select `images`.*, `images_junction`.`imagable_id` as `pivot_imagable_id`, `images_junction`.`image_id` as `pivot_image_id`, `images_junction`.`imagable_type` as `pivot_imagable_type` from `images` inner join `images_junction` on `images`.`id` = `images_junction`.`image_id` where `images_junction`.`imagable_id` in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) and `images_junction`.`imagable_type` = ? [0304e9aa-38ac-4555-891a-897eb7be960c, 1e3ec1d0-e44b-461c-a3e6-f95fb49ed34b, 54b32c57-72cf-4fa3-a063-54ff22b3de41, 57705399-9532-4de7-b569-0eac20f5f023, 58c256f8-c0d3-4409-ab96-88442c67c770, 6aa0757f-e8f7-4e88-b5c8-9dfe85fedc5f, 6ba289ec-f175-47b4-a574-87a70b00689c, 6f67da83-2043-4bc1-9f15-b5a8953556a5, 836796e7-a266-46a5-8082-13dc8c2ad1ff, 86a9e875-78c1-45e3-a6fd-f445e989100b, a523b0f8-0c90-4350-9594-21f29a59c7e0, ab7b506d-d4e2-44e5-8b71-68435c1ccd4c, b8961976-b508-47c3-81e9-7cf850771159, be4694df-62ef-4f25-9484-6b7dfcbe3e0d, bea75078-1703-4e95-91d6-390c425d46ce, cdc09fcf-477f-4851-9b79-b5c2d19fa0e1, d9d71b42-93d7-46de-a2c2-46ad73de9be0, f1001a68-fe20-45d8-bfc6-f68f35ef70fe, Web\Content\Item]

This is basically the result of searchAll in AbstractAdapter.

protected function searchAll($query)
    {
        return $query->get();
    }

After that, for each of the 18 item records, it then generates a corresponding sql query to pages.

select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]
select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]
select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]
select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]
select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]
select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]
select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]
select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]
select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]
select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]
select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]
select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]
select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]
select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]
select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]
select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]
select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]
select * from `pages` where `pages`.`id` = ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e]

These queries are redundant, Eloquent should already have all the data it needs to create the JSON.

What is the order of operations after searchAll() is run? It looks like the parent process might be Store->queryRecords() ? and then it's parent trickles up to JsonApiController->index()

If I throw a dd() in before the final return statement in JsonApiController->index(), I can see in the SQL logs that the duplicate queries eg. select * frompageswherepages.id= ? limit 1 [057c0657-1911-4311-9c8a-52a02443699e] have not been executed yet. So most likely there's nothing wrong at the Adapter level, and the bug is elsewhere?

    public function index(StoreInterface $store, FetchResources $request)
    {
        $result = $this->doSearch($store, $request);

        if ($this->isResponse($result)) {
            return $result;
        }

        dd("here");
        return $this->reply()->content($result);
    }
halfbusy commented 4 years ago

Ah, since that points to the Schema, both pages and items have self-reflexive relationships to themselves.

Must be in my implementation. Close for now.