I fully agree that using Postgres’ native capabilities consciously gives a huge advantage in terms of performance when executing code. However, I don’t agree with the statement that ORM is “bad”. You just need to know how to use ORM properly. It’s like saying: why use a jackhammer if it costs electricity, when I can do the same manually with a hammer and a chisel?
Some things that I think are missing in this perspective:
maintainability – in a project with dozens of tables, the need to maintain raw SQL (whether as templates or – even worse – strings in Python) is a recipe for a nightmare. Not to mention SQL validation, a syntax checker or at least a formatter. Even the smallest schema change (e.g. column name) requires updating dozens of places in the codebase.
migrations and validation – this is another area we have to watch very carefully when a table schema changes, and manually handle additional serialization, eg. we cannot simply pass Python True into a boolean column, instead we must convert it into SQL literal TRUE.
exception handling – ORM gives more semantic domain-specific errors instead of generic driver messages like: ERROR: duplicate key value violates unique constraint.
consistent domain model – without ORM, we still map records into objects, only that mapping happens… inside the developer’s head.
For me the right approach should rather look like: using ORM as the default tool for CRUD and the domain model, and raw SQL where it gives a real advantage (aggregations, optimization, reporting). But even in the second case, you can create advanced queries in a clean and efficient way, however, you must know and understand the specific ORM at an advanced level.