Изменение поведения функции sql.Literal при апгрейде psycopg2->psycopg3

Коллеги, есть у меня код, написанный с применением модуля psycopg2 для общения с Postgresql. Решил я добавить в код асинхронности и заменил psycopg2 на psycopg3 (разработчики убрали номер версии из названия, теперь это просто psycopg). Но вот с чем я столкнулся. Есть в этом модуле метод .sql создающий объекты типа Composed, которые, по утверждению разработчиков, являются самым безопасным и правильным способом передачи переменных (названий полей, таблиц, значений полей...) в SQL запросы. Исходя из этого я и использовал этот метод. Приведу иллюстрирующий пример, как это было в psycopg2.

import psycopg2
from psycopg2 import sql

conn = psycopg2.connect(
        dbname=dbname,
        user=user,
        password=password,
        host=host
    )

fields = ('field1', 'field2', 'field3')
vals = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
table_name = 'test_table'

with conn.cursor() as cursor:
    conn.autocommit = True
    insert = (
        sql.SQL(
            'INSERT INTO {table} ({fields}) VALUES {values}'
        ).format(table=sql.Identifier(table_name),
                 fields=sql.SQL(',').join(map(sql.Identifier, fields)),
                 values=sql.SQL(',').join((map(sql.Literal, vals))))
    )
    print(insert.as_string(conn))

Результат

>>INSERT INTO "test_table" ("field1","field2","field3") VALUES (1,2,3),(4,5,6),(7,8,9)

Что тут происходит - мы собираем запрос из переменных: название таблицы, кортеж с названием полей и список кортежей со строками значений. Каждое значение из кортежа полей мы оборачиваем в sql.Identifier (функция работает с названиями полей и таблиц) с помощью map и вставляем в запрос. Каждый кортеж из списка значений мы оборачиваем в sql.Literal (функция работает с значениями переменных) и также вставляем в запрос. В результате легко и приятно получаем запрос, который вставит в таблицу список кортежей любой длины.

Теперь смотрим, что происходит в psycopg версии 3. Весь код копировать не буду он совершено тот же самый, кроме импорта (import psycopg).

Результат:

>>INSERT INTO "test_table" ("field1","field2","field3") VALUES '(1,2,3)','(4,5,6)','(7,8,9)'

И я получаю от Postgres ошибку синтаксиса где-то рядом c VALUES '(1,2,3)... Что произошло: во второй версии psycopg.sql.Literal возвращал кортеж как есть - скобка-значения через запятую-скобка. В третьей версии он окружает кортеж одинарными кавычками, что приводит вот к этому вот. И мне пришлось, вместо лаконичного map, писать длиннющую макаронину из двух вложенных списковых включений, одно из которых разбирает/собирает кортежи по каждому значению, а второе уже эти собранные значения разделяет запятыми и вставляет в запрос. Вот так:

with conn.cursor() as cursor:
    conn.autocommit = True
    insert = (
        sql.SQL(
            'INSERT INTO {table} ({fields}) VALUES {values}'
        ).format(table=sql.Identifier(table_name),
                 fields=sql.SQL(',').join(map(sql.Identifier, fields)),
                 values=sql.SQL(',').join([sql.SQL('({one_row})').format(
                     one_row=sql.SQL(',').join([sql.Literal(field_value) for field_value in row])) for row in vals])))
    )
    print(insert.as_string(conn))

Результат как в первом примере, код работает, но насколько это стало неудобным.

Вопрос

Может быть я чего-то не понял? Неверно прочитал доку? Не увидел какой-то другой более адекватный способ отправить в запрос список кортежей. Кто знает?


Ответы (0 шт):