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

Add an assert function to the standard library #5040

Merged
merged 2 commits into from
Feb 21, 2023
Merged

Add an assert function to the standard library #5040

merged 2 commits into from
Feb 21, 2023

Conversation

msullivan
Copy link
Member

@msullivan msullivan commented Feb 15, 2023

Using error raising functions in postgres (and thus in edgeql) is
somewhat fraught, since the semantics of when errors are raised and
when they may be "optimized out" is somewhat unclear.

We should document some recommended usage patterns:

In triggers

This is probably the most important usage pattern, and works without
complication.
Just put it at the top level or in a FOR loop:

for obj in __new__ union (
  assert(
    not exists (obj.friends intersect obj.enemies),
    message := "invalid frenemies",
  )
)

On some condition of each object you are returning

Put the assertion in an ORDER BY clause:

select File { name, size }
filter .name in array_unpack(<array<str>>$names)
order by assert(.size <= 64*1024, message := "file too big")

Putting the assert call in an order by clause ensures it is only
evaluated on objects that passed the filter.
(In practice, putting the assert as part of the filter will
probably work, if it depends on the object itself, but order by
should be less fragile.)

When you have a condition not directly related to the data

Here things get kind of ugly, there are a couple options.

  • Put the assertion in a FOR iterator:
for _ in assert(not DatabaseStatus.closed, message := "db closed") union (
  select User { name, email } filter .name = 'Yury'
)

This should always work if the data is non-empty, but in some cases
might not raise the assertion if the object is empty.

  • Return a free object, and put a FILTER or ORDER BY on the free
    object:
select { user := (select User { name, email } filter .name = 'Yury') }
order by assert(not DatabaseStatus.closed, message := "db closed");

This works but means the data is wrapped in a free object.

  • Make DML depend on the value:
with cond := assert(not DatabaseStatus.closed, message := "db closed"),
     _  := (for _ in (select 0 filter not cond) union (insert Dummy)),
select User { name, email } filter .name = 'Yury'

This is solid, but kind of atrocious. It will be less nasty maybe once
we have DML-supporting IF.

Copy link
Contributor

@aljazerzen aljazerzen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like it should be two separate commits (one for assert and one for type stuff).

edb/pgsql/compiler/dml.py Outdated Show resolved Hide resolved
Using error raising functions in postgres (and thus in edgeql) is
somewhat fraught, since the semantics of when errors are raised and
when they may be "optimized out" is somewhat unclear.

We should document some recommended usage patterns:

This is probably the most important usage pattern, and works without
complication.
Just put it at the top level or in a `FOR` loop:
```
for obj in __new__ union (
  assert(
    not exists (obj.friends intersect obj.enemies),
    message := "invalid frenemies",
  )
)
```

Put the assertion in an `ORDER BY` clause:
```
select File { name, size }
filter .name in array_unpack(<array<str>>$names)
order by assert(.size <= 64*1024, message := "file too big")
```

Putting the `assert` call in an `order by` clause ensures it is only
evaluated on objects that passed the `filter`.
(In practice, putting the `assert` as part of the `filter` will
probably work, if it depends on the object itself, but `order by`
should be less fragile.)

Here things get kind of ugly, there are a couple options.

Put the assertion in a `FOR` iterator:
```
for _ in assert(not DatabaseStatus.closed, message := "db closed") union (
  select User { name, email } filter .name = 'Yury'
)
```
This should always work if the data is non-empty, but in some cases
might not raise the assertion if the object is empty.

Return a free object, and put a `FILTER` or `ORDER BY` on the free
object:
```
select { user := (select User { name, email } filter .name = 'Yury') }
order by assert(not DatabaseStatus.closed, message := "db closed");
```
This works but means the data is wrapped in a free object.

Make DML depend on the value:
```
with cond := assert(not DatabaseStatus.closed, message := "db closed"),
     _  := (for _ in (select 0 filter not cond) union (insert Dummy)),
select User { name, email } filter .name = 'Yury'
```
This is solid, but kind of atrocious. It will be less nasty maybe once
we have DML-supporting `IF`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants