EdgeDB JavaScript客户端更新关联表报错,无需开启allow_user_specified_id的解决方法
问题背景
尝试通过basic_details关联更新social_media表,使用以下代码:
import * as edgedb from 'edgedb'; import e from '../../dbschema/edgeql-js'; const client = edgedb.createClient(); let query = await e.params({ items: e.json }, (params) => { return e.for(e.json_array_unpack(params.items), (item) => { return e.update(e.basic_details, () => ({ filter_single: { id: resumeId }, set: { name: e.cast(e.str, item.name), email: e.cast(e.str, item.email), phone: e.cast(e.str, item.phone), designation: e.cast(e.str, item.designation), summary: e.cast(e.str, item.summary), template_design_type: e.cast(e.str, item.template_design_type), address: e.update(e.address, () => ({ filter_single: { id: e.cast(e.uuid, item.address.id) }, set: { address: e.cast(e.str, item.address.address), city: e.cast(e.str, item.address.city), country: e.cast(e.str, item.address.country), state: e.cast(e.str, item.address.state), postal_code: e.cast(e.str, item.address.postal_code) } })), social_media: e.for( e.op( "distinct", e.json_array_unpack(item.social_media)), (sm) => { return e.insert(e.social_media, { id: e.cast(e.uuid, sm.id), platform: e.cast(e.str, sm.platform), url: e.cast(e.str, sm.url) }).unlessConflict((socail) => ({ on: socail.id, else: e.update(e.social_media, () => ({ filter_single: {id: e.cast(e.uuid, socail.id)}, set: { platform: e.cast(e.str, socail.platform), url: e.cast(e.str, socail.url) } })) })) } ) } })); }); }); const result = await query.run(client, { items: Array.isArray(data)? data : [data] });
执行后触发第一个错误:
web-1 | QueryError: cannot assign to property 'id'
web-1 | |
web-1 | 33 | id := std::uuid(__forVar__1["id"]),
web-1 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web-1 | Hint: consider enabling the "allow_user_specified_id" configuration parameter to allow setting custom object ids
移除social_media插入部分的id字段后,触发第二个错误:
web-1 | UnsupportedFeatureError: INSERT UNLESS CONFLICT ON does not support volatile properties
web-1 | |
web-1 | 32 | (INSERT default::social_media {
web-1 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web-1 | 33 | platform := std::str(__forVar__1["platform"]),
web-1 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web-1 | 34 | url := std::str(__forVar__1["url"])
web-1 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web-1 | 35 | }
web-1 | | ^^^^^^^^^
web-1 | 36 | UNLESS CONFLICT ON default::social_media.id
web-1 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web-1 | 37 | ELSE ((WITH
web-1 | | ^^^^^^^^^^^^^^^^^^^
web-1 | 38 | __scope_2_defaultsocialmedia := default::social_media
web-1 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web-1 | 39 | UPDATE __scope_2_defaultsocialmedia
web-1 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web-1 | 40 | FILTER (__scope_2_defaultsocialmedia.id = std::uuid(DETACHED default::social_media.id))
web-1 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web-1 | 41 | SET {
web-1 | | ^^^^^^^^^^^^^
web-1 | 42 | platform := std::str(DETACHED default::social_media.platform),
web-1 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
web-1 | 43 | url := std::str(DETACHED default::social_media.url)
web-1 | ^
错误原因分析
- 第一个错误:EdgeDB默认禁止手动赋值
id字段(这是系统自动生成的UUID),除非开启allow_user_specified_id配置,而你不想开启该配置,所以不能直接设置id。 - 第二个错误:
id属于volatile属性(每次查询会生成新值的属性),INSERT UNLESS CONFLICT ON不支持将volatile属性作为冲突判断条件,因此不能用id来做冲突检测。
解决方案
改用e.if_exists来实现social_media的 upsert 逻辑:先根据传入的sm.id查询是否存在对应的记录,存在则更新,不存在则插入(由EdgeDB自动生成id)。同时调整basic_details关联social_media的逻辑,确保关联正确。
修改后的代码:
import * as edgedb from 'edgedb'; import e from '../../dbschema/edgeql-js'; const client = edgedb.createClient(); let query = await e.params({ items: e.json }, (params) => { return e.for(e.json_array_unpack(params.items), (item) => { // 处理每个social_media的upsert逻辑 const socialMediaUpserts = e.for( e.json_array_unpack(item.social_media), (sm) => { // 尝试根据传入的id查询已有记录 const existingSM = e.select(e.social_media, () => ({ filter_single: { id: e.cast(e.uuid, sm.id) } })); // 存在则更新,不存在则插入(不指定id,由系统生成) return e.if_exists( existingSM, (existing) => e.update(existing, () => ({ set: { platform: e.cast(e.str, sm.platform), url: e.cast(e.str, sm.url) } })), () => e.insert(e.social_media, { platform: e.cast(e.str, sm.platform), url: e.cast(e.str, sm.url) }) ); } ); return e.update(e.basic_details, () => ({ filter_single: { id: resumeId }, set: { name: e.cast(e.str, item.name), email: e.cast(e.str, item.email), phone: e.cast(e.str, item.phone), designation: e.cast(e.str, item.designation), summary: e.cast(e.str, item.summary), template_design_type: e.cast(e.str, item.template_design_type), address: e.update(e.address, () => ({ filter_single: { id: e.cast(e.uuid, item.address.id) }, set: { address: e.cast(e.str, item.address.address), city: e.cast(e.str, item.address.city), country: e.cast(e.str, item.address.country), state: e.cast(e.str, item.address.state), postal_code: e.cast(e.str, item.address.postal_code) } })), // 将处理后的social_media关联到basic_details social_media: socialMediaUpserts } })); }); }); const result = await query.run(client, { items: Array.isArray(data) ? data : [data] });
关键修改点
- 移除手动设置
id的代码,由EdgeDB自动生成新记录的id。 - 用
e.if_exists替代INSERT UNLESS CONFLICT,避免使用volatile属性id作为冲突条件。 - 将
social_media的upsert逻辑抽离,使代码结构更清晰,同时确保关联到basic_details的是处理后的有效记录。
如果你的业务场景中,social_media的唯一标识不是id(比如同一basic_details下platform唯一),可以调整查询条件为:
const existingSM = e.select(e.social_media, () => ({ filter_single: { platform: e.cast(e.str, sm.platform), basic_details: e.select(e.basic_details, () => ({ filter_single: { id: resumeId } })) } }));
这样可以避免依赖传入的id,直接通过业务唯一键做upsert。
内容的提问来源于stack exchange,提问作者rishabharidas

