Skip to content

Commit

Permalink
Catch aggregate calls earlier in constraints.
Browse files Browse the repository at this point in the history
  • Loading branch information
dnwpark committed May 15, 2024
1 parent b150e12 commit 18638e5
Show file tree
Hide file tree
Showing 5 changed files with 379 additions and 21 deletions.
20 changes: 20 additions & 0 deletions edb/ir/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
from edb.common import ordered

from edb.edgeql import qltypes as ft
from edb.schema import name as sn

from . import ast as irast
from . import typeutils
Expand Down Expand Up @@ -498,6 +499,25 @@ def flt(n: irast.Call) -> bool:
return bool(ast.find_children(ir, irast.Call, flt, terminate_early=True))


def find_set_of_op(
ir: irast.Base,
ignore_names: set[sn.Name],
) -> Optional[irast.Call]:
def flt(n: irast.Call) -> bool:
return (
n.func_shortname not in ignore_names
and (
any(
arg.param_typemod == ft.TypeModifier.SetOfType
for arg in n.args.values()
)
or n.typemod == ft.TypeModifier.SetOfType
)
)
calls = ast.find_children(ir, irast.Call, flt, terminate_early=True)
return next(iter(calls or []), None)


T = TypeVar('T')


Expand Down
21 changes: 18 additions & 3 deletions edb/schema/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -942,12 +942,27 @@ def _populate_concrete_constraint_attrs(
span=sourcectx
)

if has_any_multi and ir_utils.contains_set_of_op(
final_subjectexpr.irast):
# Some set functions and operators are allowed in singleton mode
# as long as their inputs are singletons
ignore_aggregate_names: set[sn.Name] = (
set() if has_any_multi else
{
sn.QualName('std', 'IN'),
sn.QualName('std', 'NOT IN'),
sn.QualName('std', 'EXISTS'),
sn.QualName('std', '??'),
sn.QualName('std', 'IF'),
}
)

if set_of_op := ir_utils.find_set_of_op(
final_subjectexpr.irast,
ignore_aggregate_names,
):
raise errors.InvalidConstraintDefinitionError(
"cannot use aggregate functions or operators "
"in a non-aggregating constraint",
span=sourcectx
span=set_of_op.span
)

if (
Expand Down
144 changes: 144 additions & 0 deletions tests/test_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -2119,3 +2119,147 @@ async def test_constraints_abstract_object_03(self):
await self.con.execute("""
update ChatBase set { messages := 'hello world' };
""")

async def test_constraints_singleton_set_ops_01(self):
await self.con.execute(
"""
create type X {
create property a -> int64 {
create constraint expression on (
__subject__ in {1}
);
}
};
""")

async with self.assertRaisesRegexTx(
edgedb.InvalidConstraintDefinitionError,
"cannot use aggregate functions or operators in a non-aggregating "
"constraint"
):
await self.con.execute(
"""
create type Y {
create multi property a -> int64 {
create constraint expression on (
__subject__ in {1}
);
}
};
""")

async def test_constraints_singleton_set_ops_02(self):
await self.con.execute(
"""
create type X {
create property a -> int64 {
create constraint expression on (
__subject__ not in {1}
);
}
};
""")

async with self.assertRaisesRegexTx(
edgedb.InvalidConstraintDefinitionError,
"cannot use aggregate functions or operators in a non-aggregating "
"constraint"
):
await self.con.execute(
"""
create type Y {
create multi property a -> int64 {
create constraint expression on (
__subject__ not in {1}
);
}
};
""")

async def test_constraints_singleton_set_ops_03(self):
await self.con.execute(
"""
create type X {
create property a -> int64 {
create constraint expression on (
exists(__subject__)
);
}
};
""")

async with self.assertRaisesRegexTx(
edgedb.InvalidConstraintDefinitionError,
"cannot use aggregate functions or operators in a non-aggregating "
"constraint"
):
await self.con.execute(
"""
create type Y {
create multi property a -> int64 {
create constraint expression on (
exists(__subject__)
);
}
};
""")

async def test_constraints_singleton_set_ops_04(self):
await self.con.execute(
"""
create type X {
create property a -> int64 {
create constraint expression on (
__subject__ ?? 1 = 0
);
}
};
""")

async with self.assertRaisesRegexTx(
edgedb.InvalidConstraintDefinitionError,
"cannot use aggregate functions or operators in a non-aggregating "
"constraint"
):
await self.con.execute(
"""
create type Y {
create multi property a -> int64 {
create constraint expression on (
__subject__ ?? 1 = 0
);
}
};
""")

async def test_constraints_singleton_set_ops_05(self):
await self.con.execute(
"""
create type X {
create property a -> tuple<bool, int64> {
create constraint expression on (
__subject__.1 < 0
if __subject__.0 else
__subject__.1 >= 0
);
}
};
""")

async with self.assertRaisesRegexTx(
edgedb.InvalidConstraintDefinitionError,
"cannot use aggregate functions or operators in a non-aggregating "
"constraint"
):
await self.con.execute(
"""
create type Y {
create multi property a -> tuple<bool, int64> {
create constraint expression on (
__subject__.1 < 0
if __subject__.0 else
__subject__.1 >= 0
);
}
};
""")
18 changes: 9 additions & 9 deletions tests/test_edgeql_ddl.py
Original file line number Diff line number Diff line change
Expand Up @@ -10851,9 +10851,9 @@ async def test_edgeql_ddl_constraint_26(self):

async def test_edgeql_ddl_constraint_27(self):
async with self.assertRaisesRegexTx(
edgedb.UnsupportedFeatureError,
'set returning operator std::DISTINCT is not supported '
'in singleton expressions'):
edgedb.InvalidConstraintDefinitionError,
'cannot use aggregate functions or operators in a '
'non-aggregating constraint'):
await self.con.execute(r"""
CREATE TYPE default::ConstraintNonSingletonTest {
CREATE PROPERTY has_bad_constraint: std::str {
Expand All @@ -10866,9 +10866,9 @@ async def test_edgeql_ddl_constraint_27(self):

async def test_edgeql_ddl_constraint_28(self):
async with self.assertRaisesRegexTx(
edgedb.UnsupportedFeatureError,
'set returning operator std::DISTINCT is not supported '
'in singleton expressions'):
edgedb.InvalidConstraintDefinitionError,
'cannot use aggregate functions or operators in a '
'non-aggregating constraint'):
await self.con.execute(r"""
CREATE TYPE default::ConstraintNonSingletonTest {
CREATE PROPERTY has_bad_constraint: std::str {
Expand All @@ -10881,9 +10881,9 @@ async def test_edgeql_ddl_constraint_28(self):

async def test_edgeql_ddl_constraint_29(self):
async with self.assertRaisesRegexTx(
edgedb.UnsupportedFeatureError,
'set returning operator std::DISTINCT is not supported '
'in singleton expressions'):
edgedb.InvalidConstraintDefinitionError,
'cannot use aggregate functions or operators in a '
'non-aggregating constraint'):
await self.con.execute(r"""
CREATE TYPE default::ConstraintNonSingletonTest {
CREATE PROPERTY has_bad_constraint: std::str;
Expand Down
Loading

0 comments on commit 18638e5

Please sign in to comment.