在Postgres中更新json字段
在Postgres中更新json字段
使用Postgres 9.3通过json字段查询非常好。\n然而,我找不到一种正式的方法来更新json对象,所以我使用了一个基于先前帖子的内部函数,该函数使用plpythonu编写(参考链接:How do I modify fields inside the new PostgreSQL JSON datatype?):\nCREATE OR REPLACE FUNCTION json_update(data json, key text, value json)\nRETURNS json AS\n$BODY$\n from json import loads, dumps\n if key is None: return data\n js = loads(data)\n js[key] = value\n return dumps(js)\n$BODY$\nLANGUAGE plpythonu VOLATILE\n
\n当我的json更新保持扁平和简单时,它工作得非常好。例如,\"GO_SESSION\"表中的\"chat\"是一个json类型字段,包含{\"a\":\"1\",\"b\":\"2\"},以下代码将更改\'b\'的值,并将\"chat\"变为{\"a\":\"1\",\"b\":\"5\"}:\n
update "GO_SESSION" set chat=json_update(chat,'b','5') where id=3
\n问题是,当我尝试将\'b\'赋值为一个对象而不是一个简单的值时:\n
update "GO_SESSION" set chat=json_update(chat,'b','{"name":"steve"}') where id=3
\n数据库中的结果是,\'b\'包含一个转义的字符串,而不是一个真正的json对象:\n{\"a\": \"1\", \"b\": \"{\\\"name\\\":\\\"steve\\\"}\"}\n我尝试了不同的方法来取消转义或转储我的json,以保持\'b\'为一个对象,但是没有找到解决办法。\n谢谢。
在使用plv8(可在Heroku等服务上使用的受信任的语言)的人们中,经常需要对json字段进行迁移或更新,直接在数据库上运行查询要比下载所有数据、转换数据然后进行更新要快得多。
问题的解决方法是创建一个名为json_replace_string的函数,它使用plv8语言编写。该函数接受四个参数:obj(json对象),path(json对象的路径),value(要更新的值),force(是否强制创建路径)。函数首先检查value是否为null且force参数为false,如果是,则返回原始的json对象。然后,函数使用正则表达式对路径进行处理,并根据路径逐层更新json对象的值。最后,函数返回更新后的json对象。
使用该函数的方法是使用UPDATE语句,将json_replace_string函数应用于要更新的json字段。其中,参数blob是要更新的json字段,'some.nested.path[5].to.object'是要更新的路径,'new value'是要更新的值,false是force参数的值。同时,WHERE子句可以根据需要添加条件。
force参数有两个功能:(1)允许设置null值。如果根据不存在的其他列动态生成值,例如blob->'non_existent_value',则null将作为函数的输入,您可能不希望将值设置为null。(2)强制在要更改的json对象中创建嵌套路径,如果路径不存在的话。例如,json_replace_string函数中的例子给出了一个json对象和一个要添加的键值对,当force参数为true时,将创建一个新的键值对。
可以想象类似的函数来更新数值、删除键等。这基本上在postgres中实现了类似mongo的功能,用于快速原型设计和在架构稳定之前,将数据存储在json字段中,之后再将其拆分到独立的列和表中,以获得最佳性能。
问题的原因是在更新Postgres中的json字段时,没有正确解码json对象。解决方法是使用loads函数对值进行解码,而不是使用eval函数。此外,还要注意不要使用eval函数,因为它是不可靠和危险的。
以下是解决问题的代码示例:
CREATE OR REPLACE FUNCTION json_update(data json, key text, value json) RETURNS json AS $BODY$ from json import loads, dumps if key is None: return data js = loads(data) # you must decode 'value' with loads too: js[key] = loads(value) return dumps(js) $BODY$ LANGUAGE plpythonu VOLATILE; postgres=# SELECT json_update('{"a":1}', 'a', '{"innerkey":"innervalue"}'); json_update ----------------------------------- {"a": {"innerkey": "innervalue"}} (1 row)
另外,使用eval函数解码json是不可靠和危险的。它不可靠是因为json不是Python语言,它只是在大多数情况下类似于Python的语法。它是不安全的是因为你永远不知道你可能会eval的内容。在这种情况下,你大部分受到PostgreSQL的json解析器的保护:
postgres=# SELECT json_update( postgres(# '{"a":1}', postgres(# 'a', postgres(# '__import__(''shutil'').rmtree(''/glad_this_is_not_just_root'')' postgres(# ); ERROR: invalid input syntax for type json LINE 4: '__import__(''shutil'').rmtree(''/glad_this_is_not_... ^ DETAIL: Token "__import__" is invalid. CONTEXT: JSON data, line 1: __import__...
但如果有人能够绕过这个限制,成功地使用eval函数进行攻击,我也不会感到意外。所以,这里的教训是:不要使用eval函数。