imageИз школьного курса арифметики всем известно что минус на минус дает плюс. Ваш покорный слуга также всю свою жизнь был уверен в этой, казалось бы незыблемой, аксиоме. Но на днях, произошло событие, перевернувшее мировоззрение, и заставившее посмотреть новым взглядом на привычные вещи.

В процессе разработки административных инструментов для клуба трельяж понадобилась функция ануллирования всех результатов конкретной игры. Казалось бы, что может быть проще. Меняем статус игры, откатывае�� денормализационные данные со статистикой игроков, инвалидируем оперативные кеши, затрагивающие эти данные, и дело в шляпе. Но у связки PostgreSQL и psycopg2 на этот счет оказалось собственное мнение, не совпадающее с мнением редакции.



Упрощенно проблемный запрос выглядел приблизительно так:

cursor.execute("update pref_player set games=games-1, rating=rating-%s where player_id=%s", (rating, player_id))

То есть нам нужно декрементировать общее количество игр и ануллировать рейтинг игрока, набранный в этой игре. Запрос параметризован. Экранируется штатными средствами psycopg2. Здесь кажется просто негде ошибиться. Но на деле, при отрицательном значени rating этот запрос превратится в:

update pref_player set games=games-1, rating=rating

То есть происходит ВЗРЫВ, БАЗУ РАЗРЫВАЕТ В КИШКИ, и далее по тексту. Другими словами, база радостно восприняла два, подряд идущих минуса как комментарий, и побила данные в капусту. А драйвер psycopg2 всю эту радость пропустил без единого возгласа. Хотя, наверное, мог бы определить что в запросе есть комментарий и количество экранируемых параметров не соответствует действительности.

Лечится это довольно просто. Достаточно вместо rating=rating-%s сказать например rating=rating-(%s) и все заработает как надо, Но сам факт безоговорочного доверия к обработке параметризированных запросов драйверами баз данных нужно ставить под сомнение.

Следите за собой, будьте осторожны, и да пребудет с вами сила.