colopl / laravel-spanner

Laravel database driver for Google Cloud Spanner
Apache License 2.0
97 stars 16 forks source link

Models with $with and BelongsToMany #146

Closed matthewjumpsoffbuildings closed 12 months ago

matthewjumpsoffbuildings commented 12 months ago

If I have two models, say, Movies and Directors, which have a two-way BelongsToMany relationship, eg Movies can have many Directors, and Directors can have many Movies, and I want to use the $with property on both to preload the related data, this seems to cause an infinite loop/timeout. With other DB drivers its not an issue.

Heres a brief example

Movie Model:

class Movie extends Model
{
  protected $with = ['directors'];

  public function directors() {
    return $this->belongsToMany(Director::class);
  }
}

Director Model:

class Director extends Model
{
  protected $with = ['movies'];

  public function movies() {
    return $this->belongsToMany(Movie::class);
  }
}

This will hang till timeout. However if I comment out one of the $withs (on either Model), it works fine

taka-oyama commented 12 months ago

Can you point out where the infinite loop is occurring?

matthewjumpsoffbuildings commented 12 months ago

I am not sure, but if I put Log calls in the constructor of both Models, as well as on all DB calls, I see millions of the following:

[2023-11-27 16:35:53] local.INFO: select `directors`.*, `director_movie`.`movie_id` as `pivot_movie_id`, `director_movie`.`director_id` as `pivot_director_id` from `directors` inner join `director_movie` on `directors`.`id` = `director_movie`.`director_id` where `director_movie`.`movie_id` in (?) ["fdfca507-5b6d-459d-9855-719aa974d347"] 
[2023-11-27 16:35:53] local.INFO: director construct  
[2023-11-27 16:35:53] local.INFO: director construct  
[2023-11-27 16:35:53] local.INFO: director construct  
[2023-11-27 16:35:53] local.INFO: movie construct  
[2023-11-27 16:35:53] local.INFO: select `movies`.*, `director_movie`.`director_id` as `pivot_director_id`, `director_movie`.`movie_id` as `pivot_movie_id` from `movies` inner join `director_movie` on `movies`.`id` = `director_movie`.`movie_id` where `director_movie`.`director_id` in (?) ["0a027a9a-c9d7-44b9-a8c1-fbe34b7f0734"] 
[2023-11-27 16:35:53] local.INFO: movie construct  
[2023-11-27 16:35:53] local.INFO: movie construct  
[2023-11-27 16:35:53] local.INFO: movie construct  
[2023-11-27 16:35:53] local.INFO: director construct  
[2023-11-27 16:35:53] local.INFO: select `directors`.*, `director_movie`.`movie_id` as `pivot_movie_id`, `director_movie`.`director_id` as `pivot_director_id` from `directors` inner join `director_movie` on `directors`.`id` = `director_movie`.`director_id` where `director_movie`.`movie_id` in (?) ["fdfca507-5b6d-459d-9855-719aa974d347"] 
[2023-11-27 16:35:53] local.INFO: director construct  
[2023-11-27 16:35:53] local.INFO: director construct  
[2023-11-27 16:35:53] local.INFO: director construct  
[2023-11-27 16:35:53] local.INFO: movie construct  
[2023-11-27 16:35:53] local.INFO: select `movies`.*, `director_movie`.`director_id` as `pivot_director_id`, `director_movie`.`movie_id` as `pivot_movie_id` from `movies` inner join `director_movie` on `movies`.`id` = `director_movie`.`movie_id` where `director_movie`.`director_id` in (?) ["0a027a9a-c9d7-44b9-a8c1-fbe34b7f0734"] 
matthewjumpsoffbuildings commented 12 months ago

It should be relatively simple to reproduce? Just two base tables plus one pivot table, then put a belongsToMany on both models and include each on both Models using $with

taka-oyama commented 12 months ago

With other DB drivers its not an issue.

Which driver are you referring to? I tried it with the sqlite driver and got the same result.

taka-oyama commented 12 months ago
$builder = Schema::connection('sqlite');

$builder->create('movies', function (Blueprint $table) {
    $table->string('id', 36)->primary();
});
$builder->create('directors', function (Blueprint $table) {
    $table->string('id', 36)->primary();
});
$builder->create('director_movie', function (Blueprint $table) {
    $table->string('movie_id', 36);
    $table->string('director_id', 36);
    $table->primary(['movie_id', 'director_id']);
});

$movie = new Movie();
$movie->id = Uuid::uuid4()->toString();
$director = new Director();
$director->id = Uuid::uuid4()->toString();
$movie->save();
$director->save();
$movie->directors()->attach($director);

Movie::query()->find($movie->id);
matthewjumpsoffbuildings commented 12 months ago

My mistake, it also fails in MySQL too, I guess this is just a Laravel/Eloquent thing