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