postgres plpgsql插入jsonb而不转义双引号

postgres plpgsql insert jsonb without escaping double quotes

我正在使用 psycopg2 与 postgres (v13) 数据库交互。我打算动态创建 sql 脚本来创建 tables、函数等,以便在数据库中执行。

我创建了脚本来对具有两个 boolean 和一个 jsonb 列的 table 执行更新插入。剧本和玩具table在这个db<>fiddle。它完美运行。

问题是当我尝试使用 psycopg2 动态地获得相同的结果时。我创建了一个 'toy' 示例。下面的设置代码创建了一个简单的 3 列 table,其中包含 2 个布尔值和一个 jsonb 列。连接设置加上一些 sql 脚本来创建 upsert 函数(如上面 fiddle 中使用的)和 call/test 函数:

from psycopg2 import connect
from psycopg2.extras import Json
import pandas as pd


conn_graphstruct = connect(host='localhost', database='graphstruct_data',
                                            port='5665', user='postgres', password='postgres')

cursor_graphstruct = conn_graphstruct.cursor()

def graph_script(x):
    cursor = cursor_graphstruct
    conn = conn_graphstruct
    try:
        cursor.execute(x)
        conn.commit()
    except Exception as e:
        print(e)
        conn.rollback()

def graph_query(x):
    temp = pd.read_sql(x, conn_graphstruct)
    if not temp.empty:
        return temp
    
def graph_fetch(x):
    cursor = cursor_graphstruct
    conn = conn_graphstruct
    try:
        cursor.execute(x)
        records = cursor.fetchall()
        return records
    except Exception as e:
        print(e)

make_table = '''
    drop table if exists graphy;
    create temporary table graphy (
          directed boolean, 
          multigraph boolean,
          graph jsonb);
    create unique index unique_db_name on graphy ((graph->>'name'));

    insert into graphy(directed, multigraph, graph) values(FALSE,FALSE, '{"node_default": {},
    "edge_default": {}, "name": "test_8"}');
'''

make_procedure = '''
    drop procedure if exists p_upsert_meta();
    CREATE OR REPLACE PROCEDURE  p_upsert_meta(x bool, y bool, z jsonb) LANGUAGE plpgsql
    as $$
    begin
        INSERT into graphy (directed, multigraph, graph) values (x,y,z)
        ON CONFLICT ((graph->>'name'))
        DO update
        set (directed, multigraph, graph) = (x,y,z);
    END 
    $$;
'''

run_procedure = '''
    call p_upsert_meta(FALSE,TRUE, '{"node_default": {}, "edge_default": {},
    "name": "test_10"}');
    call p_upsert_meta(FALSE,TRUE, '{"node_default": {}, "edge_default": {},
    "name": "test_10"}');    
'''

然后我运行脚本。首先使用定义的脚本,然后使用动态 sql 方法编写查询。

graph_script(make_table)
graph_script(make_procedure)
graph_script(run_procedure)

directed = False
multi = False
graph_name = 'proto_1'

graph = '{"node_default": Null, "edge_default": True,"name": "' + graph_name + '"}'
print(graph)
graph_script('call p_upsert_meta({},{},{})'.format(directed, multi, Json(graph)))
graph_script('call p_upsert_meta({},{},{})'.format(directed, multi, Json(graph)))

应该 结果是 table 中的三个条目。而是查询 jsonb 列 (graph):

query = '''
        select graph
        from graphy
    '''

graph_fetch(query)

结果:

{"node_default": Null, "edge_default": True,"name": "proto_1"}  -- composed string

[({'name': 'test_8', 'edge_default': {}, 'node_default': {}},),
 ({'name': 'test_10', 'edge_default': {}, 'node_default': {}},),
 ('{"node_default": Null, "edge_default": True,"name": "proto_1"}',), -- text not jsonb
 ('{"node_default": Null, "edge_default": True,"name": "proto_1"}',)] -- text not jsonb

如果我不使用 psycopg2.extras.Json,我会收到此错误 - syntax error at or near "{" LINE 1: call p_upsert_meta(False,False,{"node_default": Null, "edge_...。但是使用 Json 转义所有双引号。发生这种情况时 CONFLICT 将不起作用。我试过在函数中使用 zgraph 并使用 import json 方法。

我错过了什么来正确插入没有转义双引号的 jsonb?

备注:

  1. 是的,我必须使用 psycopg2

发生这种情况是因为您将字符串传递给 JsonJson 将 Python 类型改编为 Postgres 中的 Json(b)。一个简单的例子:

cur.execute('insert into json_test (fld_jsonb) values(%s)', [Json({"node_default": None, "edge_default": True,"name": "graph_name"})])

cur.execute('insert into json_test (fld_jsonb) values(%s)', [Json('{"node_default": None, "edge_default": True,"name": "graph_name"}')]) 

con.commit()

cur.execute("select fld_jsonb from json_test")                                                                                                                            

rs = cur.fetchall()                          

# Python dict as Json value
rs[0][0]                                                                                                                                                                  
{'name': 'graph_name', 'edge_default': True, 'node_default': None}

# Python string as Json value
rs[1][0]                                                                                                                                                                  
'{"node_default": None, "edge_default": True,"name": "graph_name"}'

如果您希望 Json 适应正常工作,请使用适当的 Python 类型:list 用于 json arraydict 用于 json object.这也适用于包含的类型,因此 None 而不是 Python 侧的 Null

更新

如果您想使用字符串,请不要使用 Json 改编:

cur.execute('insert into json_test (fld_jsonb) values(%s)', ['{"node_default": null, "edge_default": true,"name": "graph_name"}'])
con.commit()

rs[2][0]                                                                                                                                                                  
{'name': 'graph_name', 'edge_default': True, 'node_default': None}

不过您将需要使用 JSON 值,例如nulltrue 而不是 NoneTrue.