-
Notifications
You must be signed in to change notification settings - Fork 96
WIP:subquery exists #2932
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
WIP:subquery exists #2932
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
packages/zql/src/zql/ivm/exists.ts
Outdated
const relationship = node.relationships[this.#relationshipName]; | ||
assert(relationship); | ||
let size = 0; | ||
// an alternative to make relationship callable is to use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aboodman realized this while implementing this. It actually can be done without changing the Change api.
a115676
to
55aa817
Compare
relationship: string, | ||
format: Format, | ||
) { | ||
if (format.hidden) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note from video review with Aaron:
It seems fine to have the subquery filter subqueries in the output view, just not in the TypeScript type. It may even help with debugging/understanding. Suggests not doing this hidden format for now.
// is <= bound. | ||
assert(change.type !== 'child', 'child changes are not supported'); | ||
|
||
const {compareRows} = this.getSchema(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This enhancement to take should land first as a standalone change.
type: v.literal('NOT EXISTS'), | ||
}); | ||
|
||
export const correlatedSubqueryConditionConditionSchema = v.union( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be
correlatedSubqueryConditionConditionSchema
or
correlatedSubqueryConditionOperatorSchema
for EXISTS and NOT EXISTS there are no other operands other than the subquery, but for other subquery conditions there may be additional operands.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be an operator
. It is more symmetrical with SimpleCondition
that way.
type Condition = SimpleCondition | Cojunction | Discjunction | CorrelatedSubqueryCondition;
type CorrelatedSubqueryCondition = {
type: 'correlatedSubquery';
related: CorrelatedSubquery;
op: 'EXISTS' | 'NOT EXISTS'
};
assert(related, 'Invalid relationship'); | ||
const related1 = related; | ||
|
||
// TODO what if they have multiple exists on same relationship? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How should we name these for subquery filters, and how should we fix the naming collision issue for all subqueries?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Each whereExists
gets an incrementing value appended to its name? The user never needs to interact with the results of a whereExists
relationships so we can generate any name we like for them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
related
calls should be able to take an alias
parameter. If related
calls collide, that's on the developer to deal with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need some way to avoid collision between the user's aliases and the ones we create for subquery filters.
Reserved character or prefix?
push(change: Change) { | ||
assert(this.#output, 'Output not set'); | ||
|
||
switch (change.type) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative design is for push to always fetch the relationship with code like:
const relationship = must(
first(
this.#input.fetch({
start: {row: node.row, basis: 'at'},
}),
),
).relationships[this.#relationshipName]
and to then check the relationships size.
This alternative approach is simpler, stateless, and it also generalizes to support other subquery condition types, it could allow implementing a general SubqueryFilter operator like:
export class SubqueryFilter implements Operator {
constructor(
input: Input,
storage: Storage,
relationshipName: string,
prediate: (relationship: Stream<Node>) => boolean
)
The downside is it is less efficient.
throw new Error(`Invalid relationship ${relationship as string}`); | ||
} | ||
|
||
whereExists<TRelationship extends keyof TSchema['relationships']>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a better API for this at the Query level, something that supports subquery conditions nested in not/and/or conditions.
Perhaps the ideas in the "or
API improvements" bug can apply here https://bugs.rocicorp.dev/issue/3104
let q = z.issue.where({cmp, or, and, not}=> or(
filters.map(f => cmp('category', f))
));
could be extend to have:
let q = z.issue.where({cmp, or, and, not, exists}=> or(
filters.map(f => cmp('category', f))
));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the latter is also how Kysely handles exists
: https://kysely.dev/docs/recipes/expressions#expression-builder
Good to know the path is well trodden there. I'm in favor of that form.
I know @aboodman was hoping to remove the nomenclature of exists
and notExists
entirely. That we'd instead do something like:
z.issue.related('comments').where('comments.length', '>', 0)
Or an alternative formulation:
z.issue.where(
related('comments').length, '>', 0
);
That's too much for me to entertain at the moment without making it my main focus. At first glance, an API of that form greatly expands the surface area (in terms of features) that we would need to cover. It pulls in aggregating over the subquery then comparing rather than simple existence checks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should do exists and non-exist for now and unblock read permissions.
constructor( | ||
input: Input, | ||
format: Format = {singular: false, relationships: {}}, | ||
format: Format = {singular: false, hidden: false, relationships: {}}, |
No description provided.