Skip to content

Commit aa4eb0b

Browse files
authored
Make SET REQUIRED USING (...) work on link properties (#9015)
The SET REQUIRED USING path always used the `produce_ctes=True` mode of `_compile_conversion_expression`, which depends on using `id` as the key. That doesn't work for us here, since there's no `id` for a link property. If we supported using ctid or source, target as the key (see #5050) we could make it work, but until then, use the `produce_ctes=False` mode for link properties. This means DML won't work there, but whatever.
1 parent d38555d commit aa4eb0b

File tree

4 files changed

+128
-4
lines changed

4 files changed

+128
-4
lines changed

edb/pgsql/compiler/pathctx.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1108,7 +1108,10 @@ def _get_rel_path_output(
11081108
ptr_info = None
11091109
if ptrref and not isinstance(ptrref, irast.TypeIntersectionPointerRef):
11101110
ptr_info = pg_types.get_ptrref_storage_info(
1111-
ptrref, resolve_type=False, link_bias=False)
1111+
ptrref,
1112+
resolve_type=False,
1113+
link_bias=bool(rel.path_id and rel.path_id.is_ptr_path()),
1114+
)
11121115

11131116
if (rptr_dir is not None and
11141117
rptr_dir != s_pointers.PointerDirection.Outbound):

edb/pgsql/delta.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4333,7 +4333,7 @@ def _alter_pointer_optionality(
43334333

43344334
source_rel_alias = f'source_{uuidgen.uuid1mc()}'
43354335

4336-
(conv_expr_ctes, _, _) = self._compile_conversion_expr(
4336+
(conv_expr_ctes, conv_expr, _) = self._compile_conversion_expr(
43374337
ptr,
43384338
fill_expr,
43394339
source_rel_alias,
@@ -4342,9 +4342,30 @@ def _alter_pointer_optionality(
43424342
context=context,
43434343
check_non_null=is_required and not is_multi,
43444344
allow_globals=is_default,
4345+
produce_ctes=not is_lprop,
43454346
)
43464347

4347-
if not is_multi:
4348+
if is_lprop:
4349+
# The produce_ctes=True flow really wants to key
4350+
# everything based on id, which doesn't work for link
4351+
# properties. If produce_ctes=True was able to use
4352+
# ctid or (source, target) for keying, we could use
4353+
# that here, but for now we will just use the
4354+
# old-school version. See #5050, which would require
4355+
# that in order to support DML in cast expressions.
4356+
assert conv_expr
4357+
update_with = (
4358+
f'WITH {conv_expr_ctes}' if conv_expr_ctes else ''
4359+
)
4360+
update_qry = f'''
4361+
{update_with}
4362+
UPDATE {tab} AS {qi(source_rel_alias)}
4363+
SET {qi(target_col)} = ({conv_expr})
4364+
WHERE {qi(target_col)} IS NULL
4365+
'''
4366+
self.pgops.add(dbops.Query(update_qry))
4367+
4368+
elif not is_multi:
43484369
update_qry = textwrap.dedent(f'''\
43494370
WITH
43504371
"{source_rel_alias}" AS ({source_rel}),
@@ -4898,12 +4919,14 @@ def _compile_conversion_expr(
48984919
)
48994920
# Concat to string which is a JSON. Great. Equivalent to SQL:
49004921
# '{"object_id": "' || {obj_id_ref} || '"}'
4922+
# We report 'source' for linkprops. Seems OK, I guess.
4923+
id_col = 'source' if is_lprop else 'id'
49014924
detail = pgast.Expr(
49024925
name='||',
49034926
lexpr=pgast.StringConstant(val='{"object_id": "'),
49044927
rexpr=pgast.Expr(
49054928
name='||',
4906-
lexpr=pgast.ColumnRef(name=('id',)),
4929+
lexpr=pgast.ColumnRef(name=(id_col,)),
49074930
rexpr=pgast.StringConstant(val='"}'),
49084931
)
49094932
)

edb/schema/pointers.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2936,6 +2936,8 @@ def _alter_begin(
29362936
schema: s_schema.Schema,
29372937
context: sd.CommandContext,
29382938
) -> s_schema.Schema:
2939+
from edb.ir import utils as irutils
2940+
29392941
orig_schema = schema
29402942
schema = super()._alter_begin(schema, context)
29412943
scls = self.scls
@@ -2988,6 +2990,17 @@ def _alter_begin(
29882990
span=self.span,
29892991
)
29902992

2993+
if (
2994+
self.scls.is_link_property(schema)
2995+
and irutils.contains_dml(self.fill_expr.ir_statement)
2996+
):
2997+
raise errors.UnsupportedFeatureError(
2998+
f'USING clause for the alteration of optionality of '
2999+
f'{vn} cannot include mutating statements, '
3000+
'because it is a link property',
3001+
span=self.span,
3002+
)
3003+
29913004
schema = self._propagate_if_expr_refs(
29923005
schema,
29933006
context,

tests/test_edgeql_ddl.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19284,6 +19284,91 @@ async def test_edgeql_ddl_new_required_multi_pointer_04(self):
1928419284
ALTER TYPE Foo ALTER LINK link SET REQUIRED;
1928519285
""")
1928619286

19287+
async def test_edgeql_ddl_set_required_lprop_01(self):
19288+
await self.con.execute(r"""
19289+
CREATE TYPE default::User {
19290+
CREATE PROPERTY name: std::str;
19291+
};
19292+
CREATE TYPE default::Post {
19293+
CREATE MULTI LINK author: default::User {
19294+
CREATE PROPERTY created_at: std::str;
19295+
};
19296+
CREATE PROPERTY title: std::str;
19297+
};
19298+
INSERT Post { author := {
19299+
(insert User { name := "1" }),
19300+
(insert User { name := "2" }),
19301+
} };
19302+
""")
19303+
19304+
await self.con.execute(r"""
19305+
ALTER TYPE default::Post {
19306+
ALTER LINK author {
19307+
ALTER PROPERTY created_at {
19308+
SET REQUIRED USING (.name);
19309+
};
19310+
};
19311+
};
19312+
""")
19313+
19314+
async with self.assertRaisesRegexTx(
19315+
edgedb.MissingRequiredError,
19316+
"missing value"
19317+
):
19318+
await self.con.execute(r"""
19319+
INSERT Post { author := {(insert User), (insert User)} };
19320+
""")
19321+
19322+
async def test_edgeql_ddl_set_required_lprop_02(self):
19323+
await self.con.execute(r"""
19324+
CREATE TYPE default::User {
19325+
CREATE PROPERTY name: std::str;
19326+
};
19327+
CREATE TYPE default::Post {
19328+
CREATE LINK author: default::User {
19329+
CREATE PROPERTY created_at: std::str;
19330+
};
19331+
CREATE PROPERTY title: std::str;
19332+
};
19333+
INSERT Post { author := (insert User { name := "asdf" }) };
19334+
""")
19335+
19336+
await self.con.execute(r"""
19337+
ALTER TYPE default::Post {
19338+
ALTER LINK author {
19339+
ALTER PROPERTY created_at {
19340+
SET REQUIRED USING (<str>.name);
19341+
};
19342+
};
19343+
};
19344+
""")
19345+
19346+
async def test_edgeql_ddl_set_required_lprop_03(self):
19347+
await self.con.execute(r"""
19348+
CREATE TYPE default::User;
19349+
CREATE TYPE default::Post {
19350+
CREATE MULTI LINK author: default::User {
19351+
CREATE PROPERTY created_at: std::str;
19352+
};
19353+
CREATE PROPERTY title: std::str;
19354+
};
19355+
INSERT Post { author := {(insert User), (insert User)} };
19356+
""")
19357+
19358+
async with self.assertRaisesRegexTx(
19359+
edgedb.UnsupportedFeatureError,
19360+
"cannot include mutating statements"
19361+
):
19362+
await self.con.execute(r"""
19363+
ALTER TYPE default::Post {
19364+
ALTER LINK author {
19365+
ALTER PROPERTY created_at {
19366+
SET REQUIRED USING (<str>(insert User).id);
19367+
};
19368+
};
19369+
};
19370+
""")
19371+
1928719372
async def test_edgeql_ddl_link_union_delete_01(self):
1928819373
await self.con.execute(r"""
1928919374
CREATE TYPE default::M;

0 commit comments

Comments
 (0)