Joining more than once on the same table produces invalid SQL
See original GitHub issueFirst of all - super awesome library!
Just running into a problem relating to joining on the same table more than once in a query. For example, given the following postgresql schema:
CREATE TABLE public.user
(
user_pk uuid NOT NULL DEFAULT gen_random_uuid(),
username character varying NOT NULL,
PRIMARY KEY (user_pk)
);
CREATE TABLE public.message
(
message_pk uuid NOT NULL DEFAULT gen_random_uuid(),
from_fk uuid NOT NULL,
to_fk uuid NOT NULL,
body character varying NOT NULL,
CONSTRAINT message__from_fk__fk FOREIGN KEY (from_fk)
REFERENCES public."user" (user_pk) MATCH SIMPLE
ON UPDATE CASCADE,
CONSTRAINT message__to_fk__fk FOREIGN KEY (to_fk)
REFERENCES public."user" (user_pk) MATCH SIMPLE
ON UPDATE CASCADE
);
And the following code:
let userTable = table<``public``.message>
let messageTable = table<``public``.message>
let! messages =
selectTask Dto.HydraReader.Read (Shared queryCtx) {
for m in messageTable do
join fromUser in userTable on (m.from_fk = fromUser.user_pk)
join toUser in userTable on (m.to_fk = toUser.user_pk)
select (m.body, fromUser.username, toUser.username)
}
You end up with the following SQL error: table name "user" specified more than once
This is because it gets compiled to the following SQL:
"message"."body", "user"."username", "user"."username" FROM "message"
INNER JOIN "user" ON ("message"."from_fk" = "user"."user_pk")
INNER JOIN "user" ON ("message"."to_fk" = "user"."user_pk")
SQL hydra can solve this by adding some AS clauses to disambiguate the two different usages of this table. It could either be an arbitrary alias, or it could even use the same name as the variable in the F# query (in this case fromUser
and toUser
).
It should be running something more like this:
"message"."body", "fromUser"."username", "toUser"."username" FROM "message"
INNER JOIN "user" AS fromUser ON ("message"."from_fk" = "user"."user_pk")
INNER JOIN "user" AS toUser ON ("message"."to_fk" = "user"."user_pk")
Unfortunately I can’t figure out a workaround, so I am having to drop to a lower level and generating a query directly.
Issue Analytics
- State:
- Created a year ago
- Comments:12 (12 by maintainers)
Released in v1.1.0! 🎉
I’ve been putting this one off for a while because it was so involved to finish. But I doubled down over the weekend, and I think it’s pretty much done now. 😅 I cherrypicked some key pieces from your PR (very well done, btw), but I still had to go through a lot of tedious implementation details. Specifically, the:
QuotationVisitor
and the[<ReflectedDefinition>] forExpr: FSharp.Quotations.Expr<'T -> QuerySource<'T>>
in theFor
builder methods.I don’t know how you figured out the
[<ReflectedDefinition>]
thing; I would have never found that in a million years 😄, but that made the whole thing possible.Integration tests are all passing, so only some query unit tests still need to be changed to reflect the new table alias qualified columns.
EDIT: All tests passing now! Only exception is that Oracle aggregate functions used in
having
andorderBy
clauses no longer fail because Oracle. But I’m not willing to halt progress for one fringe feature on a provider that is 1/20th the downloads of the others.I think I will add support for .NET 7 in the generators and then release (probably as v1.1.0).