Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to prefetch @. relationships? #417

Closed
FCO opened this issue Dec 23, 2019 · 8 comments
Closed

How to prefetch @. relationships? #417

FCO opened this issue Dec 23, 2019 · 8 comments
Labels
question Further information is requested

Comments

@FCO
Copy link
Owner

FCO commented Dec 23, 2019

No description provided.

@FCO FCO added the question Further information is requested label Dec 23, 2019
@FCO
Copy link
Owner Author

FCO commented Dec 23, 2019

I think the biggest problem is that the query will return multiple lines for each object... out or order...

@FCO
Copy link
Owner Author

FCO commented Jun 27, 2020

Maybe:

model Book {
   has UInt $!id        is serial;
   has Str  $.name      is column;
   has UInt $!author-id is referencing(*.id, :model<Person>);
   has      $.author    is relationship(*.author-id, :model<Person>);
}

model Person {
   has UInt $!id    is serial;
   has Str  $.name  is column;
   has      @.books is relationship(*.author-id, :model<Book>, :prefetch);
}

for Person.^all.grep: *.books.elems > 0 -> $author {
   say { $author.name }: { $author.books.join: , “ }”
}

Should it run:

SELECT person.id, person.name, book.id as ‘book__id’, book.name as ‘book__name’, book.author_id as ‘book__author_id’ FROM person JOIN book ON person.id = book.author_id ORDER BY person.id

And on Seq, loop generating and accumulating Book objects while Person.id is the same, and when it changes, create the Person obj using the accumulated objects?

@FCO
Copy link
Owner Author

FCO commented Jan 2, 2023

I'm working on it now, and for now I have this:

➜  Red git:(master) ✗ raku -I. -MRed -e '

model Bla { has $.id is serial; has $.value is column; has @.bles is relationship(*.bla-id, :model<Ble>, :optional, :prefetch) }
model Ble { has $.id is serial; has $.value is column; has $.bla-id is referencing(*.id, :model(Bla)) }
my $*RED-DB = database "SQLite";
Bla.^create-table; Ble.^create-table; Bla.^create(:value(<test1 test2>.pick)) xx 10; Ble.^create(:value(<t1 t2>.pick), :bla-id((1 .. 10).pick)) xx 20;
my $*RED-DEBUG = True;

.say for Bla.^all

'
SQL : SELECT
   "bla".id , "bla".value , "bla_bles".id as "bla_bles.id", "bla_bles".value as "bla_bles.value", "bla_bles".bla_id as "bla_bles.bla-id"
FROM
   "bla"
    LEFT JOIN "ble" as bla_bles ON "bla_bles".bla_id = "bla".id
BIND: []
Bla.new(id => 1, value => "test1")
Bla.new(id => 1, value => "test1")
Bla.new(id => 1, value => "test1")
Bla.new(id => 2, value => "test1")
Bla.new(id => 2, value => "test1")
Bla.new(id => 3, value => "test2")
Bla.new(id => 3, value => "test2")
Bla.new(id => 3, value => "test2")
Bla.new(id => 3, value => "test2")
Bla.new(id => 3, value => "test2")
Bla.new(id => 4, value => "test2")
Bla.new(id => 5, value => "test1")
Bla.new(id => 6, value => "test2")
Bla.new(id => 6, value => "test2")
Bla.new(id => 7, value => "test2")
Bla.new(id => 7, value => "test2")
Bla.new(id => 7, value => "test2")
Bla.new(id => 8, value => "test2")
Bla.new(id => 9, value => "test2")
Bla.new(id => 9, value => "test2")
Bla.new(id => 10, value => "test1")
Bla.new(id => 10, value => "test1")
Bla.new(id => 10, value => "test1")

it's not grouping the prefetches inside the object and returning an object a single time yet. But I was thinking... we have 2 possibilities here, the first one is doing the query as it is above and then iterate over all results to to create each object with all preaches inside of it. I was thinking on iterating until we find a different object, but we have no control over ORDER BY, so it may be on the wrong order. Then I thought on a different possibility, the query, instead, could be something like this:

➜  Red git:(master) ✗ sqlite3 test.db
SQLite version 3.37.0 2021-12-09 01:34:53
Enter ".help" for usage hints.
sqlite> SELECT
   ...>    "bla".id , "bla".value , "bla_bles".json as "bla_bles"
   ...> FROM
   ...>    "bla"
   ...>     LEFT JOIN (SELECT bla_id, json_group_array(json_object('id', id, 'value', value, 'bla_id', bla_id)) as json FROM ble GROUP BY bla_id) as bla_bles ON "bla_bles".bla_id = "bla".id
   ...> ;
1|test1|[{"id":20,"value":"t1","bla_id":"1"}]
2|test1|[{"id":2,"value":"t1","bla_id":"2"},{"id":11,"value":"t1","bla_id":"2"}]
3|test1|[{"id":7,"value":"t2","bla_id":"3"},{"id":9,"value":"t1","bla_id":"3"},{"id":12,"value":"t1","bla_id":"3"},{"id":13,"value":"t1","bla_id":"3"},{"id":19,"value":"t2","bla_id":"3"}]
4|test1|
5|test2|[{"id":5,"value":"t2","bla_id":"5"},{"id":14,"value":"t2","bla_id":"5"}]
6|test1|[{"id":1,"value":"t1","bla_id":"6"}]
7|test1|[{"id":3,"value":"t2","bla_id":"7"}]
8|test1|[{"id":6,"value":"t2","bla_id":"8"},{"id":8,"value":"t1","bla_id":"8"},{"id":15,"value":"t2","bla_id":"8"}]
9|test1|[{"id":4,"value":"t1","bla_id":"9"},{"id":10,"value":"t2","bla_id":"9"},{"id":16,"value":"t1","bla_id":"9"},{"id":18,"value":"t2","bla_id":"9"}]
10|test1|[{"id":17,"value":"t1","bla_id":"10"}]
sqlite>

And it would be much easier to create each object and its prefetches. But much harder to only select the needed prefetch rows.

Any idea/suggestion here?

@FCO
Copy link
Owner Author

FCO commented Jan 3, 2023

I just committed a new branch (to-many-prefetch) (#563) with a PoC for SQLite doing prefetch the 2nd way. I'm testing it with:

➜  Red git:(to-many-prefetch) ✗ raku -I. -MRed -e '

model Bla { has $.id is serial; has $.value is column; has @.bles is relationship(*.bla-id, :model<Ble>, :prefetch) }
model Ble { has $.id is serial; has $.value is column; has $.bla-id is referencing(*.id, :model(Bla)) }
my $*RED-DB = database "SQLite";
Bla.^create-table; Ble.^create-table; Bla.^create(:value(<test1 test2>.pick)) xx 5; Ble.^create(:value(<t1 t2>.pick), :bla-id((1 .. 10).pick)) xx 20;
my $*RED-DEBUG = True; my $*RED-DEBUG-RESULT = True;

for Bla.^all { .say for .bles }

'
SQL : SELECT
   "bla".id , "bla".value , "bla_bles".json as "bla_bles"
FROM
   "bla"
    LEFT JOIN (
       SELECT
           bla_id,
           json_group_array(json_object('id', "ble".id, 'value', "ble".value, 'bla-id', "ble".bla_id)) as json
       FROM
           "ble"
       GROUP BY
           bla_id
   )
    as bla_bles ON "bla_bles".bla_id = "bla".id
BIND: []
Ble.new(id => 8, value => "t2", bla-id => "1")
Ble.new(id => 11, value => "t2", bla-id => "1")
Ble.new(id => 14, value => "t1", bla-id => "1")
Ble.new(id => 15, value => "t1", bla-id => "1")
Ble.new(id => 20, value => "t1", bla-id => "1")
Ble.new(id => 1, value => "t1", bla-id => "2")
Ble.new(id => 19, value => "t1", bla-id => "2")
Ble.new(id => 3, value => "t1", bla-id => "3")
Ble.new(id => 17, value => "t2", bla-id => "3")
Ble.new(id => 2, value => "t2", bla-id => "4")
Ble.new(id => 6, value => "t2", bla-id => "5")

It's still in the beginning, but I seems very interesting... Opinions?

@FCO
Copy link
Owner Author

FCO commented Jan 10, 2023

Maybe that may be an option for for #25

@FCO
Copy link
Owner Author

FCO commented Jan 10, 2023

Now we can set the prefetches on a ResultSeq using the prefetch method:

➜  Red git:(to-many-prefetch) ✗ raku -I. -MRed -e '
my $*RED-FALLBACK = False;
model Bla { has $.id is serial; has $.value is column; has @.bles is relationship(*.bla-id, :model<Ble>) }
model Ble { has $.id is serial; has $.value is column; has $.bla-id is referencing(*.id, :model(Bla)) }
my $*RED-DB = database "SQLite";
Bla.^create-table; Ble.^create-table; Bla.^create(:value(<test1 test2>.pick)) xx 5; Ble.^create(:value(<t1 t2>.pick), :bla-id((1 .. 10).pick)) xx 20;
my $*RED-DEBUG = True; my $*RED-DEBUG-RESULT = True;

say .gist, "\n", .bles>>.gist.join("\n").indent: 4 for Bla.^all.prefetch: "bles"

'
SQL : SELECT
   "bla".id , "bla".value , "bla_bles".json as "bla_bles"
FROM
   "bla"
    LEFT JOIN (
       SELECT
           bla_id,
           json_group_array(json_object('id', "ble".id, 'value', "ble".value, 'bla-id', "ble".bla_id)) as json
       FROM
           "ble"
       GROUP BY
           bla_id
   )
    as bla_bles ON "bla_bles".bla_id = "bla".id
BIND: []
Bla.new(id => 1, value => "test2")
    Ble.new(id => 12, value => "t2", bla-id => "1")
    Ble.new(id => 15, value => "t1", bla-id => "1")
Bla.new(id => 2, value => "test2")
    Ble.new(id => 3, value => "t2", bla-id => "2")
    Ble.new(id => 19, value => "t2", bla-id => "2")
Bla.new(id => 3, value => "test1")
    Ble.new(id => 10, value => "t1", bla-id => "3")
Bla.new(id => 4, value => "test1")
    Ble.new(id => 2, value => "t1", bla-id => "4")
    Ble.new(id => 4, value => "t1", bla-id => "4")
    Ble.new(id => 6, value => "t1", bla-id => "4")
    Ble.new(id => 17, value => "t2", bla-id => "4")
    Ble.new(id => 20, value => "t1", bla-id => "4")
Bla.new(id => 5, value => "test2")
    Ble.new(id => 16, value => "t2", bla-id => "5")

@FCO
Copy link
Owner Author

FCO commented Jan 10, 2023

(not recursive, yet...)

@FCO
Copy link
Owner Author

FCO commented Jan 10, 2023

Pg:

➜  Red git:(to-many-prefetch) ✗ raku -I. -MRed -e '
my $*RED-FALLBACK = False;
model Bla { has $.id is serial; has $.value is column; has @.bles is relationship(*.bla-id, :model<Ble>) }
model Ble { has $.id is serial; has $.value is column; has UInt $.bla-id is referencing(*.id, :model(Bla)) }
my $*RED-DB = database "Pg";
schema(Bla, Ble).drop.create; Bla.^create(:value(<test1 test2>.pick)) xx 5; Ble.^create(:value(<t1 t2>.pick), :bla-id((1 .. 5).pick)) xx 20;
my $*RED-DEBUG = True; my $*RED-DEBUG-RESULT = True;

say .gist, "\n", .bles>>.gist.join("\n").indent: 4 for Bla.^all.prefetch: "bles"

'
SQL : SELECT
   "bla".id , "bla".value , "bla_bles".json as "bla_bles"
FROM
   "bla"
    LEFT JOIN (
       SELECT
           bla_id,
           json_agg(json_build_object('id', "ble".id, 'value', "ble".value, 'bla-id', "ble".bla_id)) as json

       FROM
           "ble"
       GROUP BY
           bla_id
   )
    as bla_bles ON "bla_bles".bla_id = "bla".id
BIND: []
Bla.new(id => 1, value => "test1")
    Ble.new(id => 19, value => "t1", bla-id => 1)
Bla.new(id => 2, value => "test2")
    Ble.new(id => 10, value => "t2", bla-id => 2)
    Ble.new(id => 20, value => "t2", bla-id => 2)
Bla.new(id => 3, value => "test1")
    Ble.new(id => 8, value => "t1", bla-id => 3)
    Ble.new(id => 12, value => "t2", bla-id => 3)
    Ble.new(id => 14, value => "t1", bla-id => 3)
    Ble.new(id => 16, value => "t2", bla-id => 3)
    Ble.new(id => 17, value => "t1", bla-id => 3)
    Ble.new(id => 18, value => "t1", bla-id => 3)
Bla.new(id => 4, value => "test1")
    Ble.new(id => 1, value => "t1", bla-id => 4)
    Ble.new(id => 2, value => "t1", bla-id => 4)
    Ble.new(id => 4, value => "t2", bla-id => 4)
    Ble.new(id => 9, value => "t1", bla-id => 4)
Bla.new(id => 5, value => "test2")
    Ble.new(id => 3, value => "t2", bla-id => 5)
    Ble.new(id => 5, value => "t2", bla-id => 5)
    Ble.new(id => 6, value => "t2", bla-id => 5)
    Ble.new(id => 7, value => "t1", bla-id => 5)
    Ble.new(id => 11, value => "t1", bla-id => 5)
    Ble.new(id => 13, value => "t2", bla-id => 5)
    Ble.new(id => 15, value => "t1", bla-id => 5)

still a bit hardcoded, but I'm going to merge it.

@FCO FCO closed this as completed Jan 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

1 participant