update/upsert 可能会将不在 `raw` 参数里的字段从 undefined 设为 null
chuan6 opened this issue · 2 comments
实际发生的场景:
-
受到影响的那个字段是 OBJECT 类型
-
raw
参数只包含主键和一个与受影响字段无关(不存在关联)的字段 -
手动 upsert
{ 主键: 'xxx', 受影响字段: undefined }
,可以将该字段值重置为 undefined -
构建一个失败的单元测试以确认问题
在 test/schemas/Post 里,添加 attachments 字段,设置其类型为 RDBType.OBJECT,
it.only("should keep an `undefined` attribute unchanged if the attribute is absent from the upsert body", function*() {
const mockPost = postGen(1, null).pop()
const { _id, content, created } = mockPost
// 需要先有这个条目才能复现原来 undefined 的属性被改为 null 的行为。
yield database.upsert('Post', { _id, created })
const post = { _id, content }
const execRet = yield database.update('Post', post) // upsert 也能重现,insert 不会
const [ret] = yield database
.get<PostSchema>('Post', {
where: {
_id: post._id,
},
})
.values()
expect(ret).to.deep.equal({ _id, content, created })
checkExecutorResult(execRet, 1)
})
得:
AssertionError: expected { Object (_id, content, ...) } to deeply equal { Object (_id, content, ...) }
+ expected - actual
{
"_id": "47675ecd"
- "attachments": null
"content": "posts content:ab8920dd"
"created": "1969-12-31T16:00:00.000Z"
}
看了一下,这应该是 lovefield 对 nullable 列的预期行为,而 OBJECT 类型的列被定死为 nullable。
感觉这样的行为对我们的应用而言有些问题;主要在于,它拿掉了原生表达不完整数据的能力。
本来,我们遇到某一单元格的值为 undefined 时,假设这个单元格的值在前端还没有获取到(比如 websocket 推送里没有这个字段),而当值为 null,我们会假设后端数据源对应的值就是 null。
但如果类型为 OBJECT (还有 ARRAY_BUFFER)的列会在更新时把 undefined 变为 null,那上层的代码就不能自然地依赖 undefined 和 null 的不同来分辨(缺值 vs 空值)上述两种情况了。
/cc @Saviio @Brooooooklyn
lovefield 代码上,可以追踪到 rowClass 上的 toDbPayload 方法,将其中对 OBJECT 类型的判断由
if (type == lf.Type.OBJECT) {
obj[key] = goog.isDefAndNotNull(value) ? value : null;
}
改为
if (type == lf.Type.OBJECT) {
obj[key] = !goog.isNull(value) ? value : null
}
就可以避免这里我们遇到的问题。
由于这个目标与 lovefield 的意图相悖(见上面的评论),我们可能需要 fork 才能用上上边的做法,@Saviio @Brooooooklyn 你们有什么看法吗?