【问题标题】:Updating json field in Postgres在 Postgres 中更新 json 字段
【发布时间】:2014-02-02 05:39:18
【问题描述】:

通过 json 字段查询 Postgres 9.3 真的很棒。 但是我找不到更新 json 对象的正式方法,为此我使用了基于以前的帖子 (How do I modify fields inside the new PostgreSQL JSON datatype?) 用 plpythonu 编写的内部函数:

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)
   js[key] = value
   return dumps(js)
$BODY$
LANGUAGE plpythonu VOLATILE

当我的 json 更新保持扁平和简单时,它的效果非常好。假设“chat”是“GO_SESSION”表中的json类型,并且包含{"a":"1","b":"2"},下面的代码会改变 'b' 值并将 "chat" 变为 {"a":"1","b":"5"}

update "GO_SESSION" set chat=json_update(chat,'b','5') where id=3

问题是当我试图为 'b' 另一个对象而不是一个简单的值赋值时:

update "GO_SESSION" set chat=json_update(chat,'b','{"name":"steve"}') where id=3

数据库中的结果是 'b' 包含转义字符串而不是真正的 json 对象:

{"a": "1", "b": "{\"name\":\"steve\"}"}

我尝试了不同的方法来取消转义或转储我的 json 以保留 'b' 一个对象,但找不到解决方案。

谢谢

【问题讨论】:

  • 很想知道......谁在编写教程来教用户创建这些疯狂的模式?
  • 这个问题是基于以前的帖子:stackoverflow.com/questions/18209625/…
  • 可能是这样,但以这种方式更新 json 仍然是可疑的,委婉地说。如果您需要更新存储在 json 中的个别数据,则该数据几乎肯定应该是表中的 实际 字段。
  • 这种结构的效率很大程度上取决于它的使用方式。例如,如果我当前的 json 字段被指定用于处理大量的“读取”请求,而使用 postgres json 数据类型的“写入”则更少,这样可以很好地达到目的。
  • 如果您的“读取”并不意味着“读取整个 json 数据,但在从数据库中取出之前根本不知道里面有什么”,我认为您会在路上大吃一惊。说到这一点,从内部角度来看,您可能会阅读应用程序中的整个字段,在其中进行编辑,然后更新整个字段……这就是 Postgres 在使用函数时所做的事情;唯一的区别是,您试图从 PG 中松散地操纵 json 相当于重新发明了性能不佳的 update 语句版本。

标签: python json postgresql


【解决方案1】:

适用于需要 plv8(可在 Heroku 等服务上使用的可信语言)的人。我经常需要对 json blob 进行迁移或更新,直接在数据库上运行查询比下载所有数据、转换数据然后发布更新要快得多。

CREATE EXTENSION plv8;
CREATE OR REPLACE FUNCTION json_replace_string(obj json, path text, value text, force boolean)
RETURNS json AS $$
if (value === null && !force) {
  return obj;
}
var nestedRe = /(\.|\[)/;
var scrub = /]/g;
path = path.replace(scrub, '');
var pathBits = path.split(nestedRe);
var len = pathBits.length;
var layer = obj;
for (var i = 0; i < len; i += 2) {
  if (layer === null || layer === undefined) return obj;
  var key = pathBits[i];
  if (key === '') continue;
  if (i === len - 1) {
    layer[key] = value;
  } else {
    if (force && typeof layer[key] === 'undefined') {
      layer[key] = pathBits[i+1] === '.' ? {} : [];
    }
    layer = layer[key];
  }
}
return obj;
$$ LANGUAGE plv8 IMMUTABLE;

你可以这样使用

UPDATE my_table
SET blob=json_replace_string(blob, 'some.nested.path[5].to.object', 'new value', false)
WHERE some_condition;

force 参数有两个功能 - (1) 让您设置 null 值。如果您基于其他不存在的列动态生成值 - 例如blob-&gt;'non_existent_value' 然后 null 将被输入到函数中,您可能并不意味着将值设置为 null。 (2) 目的是强制创建嵌套路径,如果它不存在于您正在变异的 json 对象中。例如

json_replace(string('{"some_key": "some_val"}', 'other_key', 'new_val', true)

给予

{"some_key": "some_val", "other_key": "new_val"}

您可以想象类似的函数来更新数字、删除键等。这基本上可以在新特性的早期阶段在 postgres 中启用类似 mongo 的功能,以进行快速原型设计,并且随着我们的架构稳定,我们将事情分解为独立的列和表以获得最好的表现。

【讨论】:

    【解决方案2】:

    不需要eval。您的问题是您没有将值解码为 json 对象。

    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,它只是碰巧在很多时候评估有点像它。这是不安全的,因为你永远不知道你可能会评估什么。在这种情况下,您在很大程度上受到 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。

    【讨论】:

    【解决方案3】:

    已解决

    上述plpythonu函数的问题在于,它与“value”作为字符串相关,无论它是否实际上是一个复杂的json对象。解决它的关键是在 value 周围添加 eval():

    js[key] = eval(value)
    

    这样,json 字符串(在本例中名为 'value')会失去其外层双引号 "{...}" 并成为一个对象。

    【讨论】:

    • 那根本不需要。
    • ... 不是,请参阅我的回答。如果您发现自己在使用eval,请想一想“哎呀,这样做的安全方法是什么”。 eval() 几乎永远不是正确答案
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-06
    • 2023-01-29
    相关资源
    最近更新 更多