Portable shapes
I would like to see portable shapes being implemented to EdgeQL, this would reduce the duplication of queries that operate on the same type.
A very simple demonstration of the issue:
select {
oldest := (select Foo { id, name, created, updated, ...many others } order by .created ASC limit 5),
newest := (select Foo { id, name, created, updated, ...many others } order by .created DESC limit 5)
};
Proposed shape command
Allows EdgeQL to store a shape of an object type for later use.
This command requires the object type as an argument so EdgeQL can ensure the shape fits the type. It might also be possible to have anonymous shapes, however I don't know how well they would play with EdgeQL.
shape Foo {
name,
created
};
# Returns a portable shape to be used for any query with `Foo`
Proposed ... operator for "spreading" shapes
Allows EdgeQL to "spread" the above shapes to queries. There needs to be a way to tell EdgeQL we're trying to use a portable shape (value) instead of the referring to the literal value.
In the simplest form this is the spread operator from JS:
myShape := (
shape Foo {
name,
created
};
);
# Select with just the portable shape
select Foo {
...myShape # Just "myShape" wouldn't work here, as EdgeQL has no idea if we're trying to select "myShape" as a literal or a value.
};
# Select with the portable shape and also pick updated
select Foo {
...myShape,
updated
};
Link to thread: https://discord.com/channels/841451783728529451/1126251245657669743/1126251249831002202
For reference, see earlier discussions: #180 and #5259.
IMO, now that we have support for splats, we should probably look into using that machinery instead. For example we could make the following work:
with
myShape := Foo { bar, baz := 1 }
select
Foo {
(typeof myShape).*
};
Right now it fails because myShape is not a supertype of MyCls (rather a subtype), but that check can be fixed to allow aliases like this.
Another option that could let us move the typeof into the definition to implement #1578, so
with
myShape as type typeof(Foo { bar, baz := 1 })
select
Foo {
myShape.*
}
On a second thought, splats might not be ideal here as they'd pick up properties not specified in the typeof shape, so :thinking:
I suggest using
myShape := (
shape Foo {
name,
created
};
);
select.spread(myShape){
updated
}
This would translate nicely to Inserts with "spread operator like semantics" https://github.com/edgedb/edgedb/issues/5795
insert.copy(select Foo Limit 1){
name := 'new one'
}
And of course shapes will be useful for things like insert.copy(select Foo Limit 1, myShape){ } to copy only what's in the shape.
That would be nice, but seems like it's possible via aliases and splats:
abstract type ChatBase {
required num: ChatsNumericSequence { readonly := true; constraint exclusive; };
multi members: User;
multi messages: Message;
}
alias BasicChatShape := (for chat in ChatBase union { num := chat.num, member_nums := chat.members.num });
# query
select BasicChatShape { ** }
will return:
[
{
"member_nums": [
"1",
"2"
],
"num": "1",
"id": "a3e61727-633c-4af9-9c06-5e225d8cb76d"
},
{
"member_nums": [
"3",
"1"
],
"num": "2",
"id": "ec70104b-ebc1-4fe0-8a33-bd88fc65af70"
}
]
One drawback here: it returns id field. Is there a way to omit id field with splat? And not sure, does it affect performance?
Any news on this? Would highly appreciate such a feature.
I am facing some scaling issues in my application, having lots of duplicated queries in my edgeql files. Reusable Shapes would make a huge difference in development and maintenance of my application.