Изменение поведения функции 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))
Результат как в первом примере, код работает, но насколько это стало неудобным.
Вопрос
Может быть я чего-то не понял? Неверно прочитал доку? Не увидел какой-то другой более адекватный способ отправить в запрос список кортежей. Кто знает?