rows aren't objects.
tables aren't even objectsnote 1.
a relational database (management system) is an object, but ORMs don't model the entire database note 2.
your database is at least a separate process; most commonly, it's running on a completely separate machine.
your database is an application, one that has an application programming interface.
this low-level API includes things like escaping content and connection management. there are client libraries written for it; your ORM depends on one or more of them, opaquely.
your database is not your server. data in your database is not data in your process. that data is not coming from the cache, or from local memory at all. moving data from or to your database is an API call.
the client library your ORM hides from you knows this. that is one reason your ORM hides the client library it uses from you.
to let you treat data that lives in the database the same as data that lives in local memory—to erase the distance between that separate entity and your code—is a promise your ORM makes.
the outcome of a database query is a snapshot of some portion of that database at the time of the query. once a query has been executed and its result made visible, the connection to "the truth" has already been severed.
objects initialized with the results of queries also lack that epistemic connection—the `email` field of a `Author` object varies in complete independence from the `email` column of the `authors` table.
initializing objects with the results of queries is one of two things
your ORM does.
aside: mitigation
transactions
are one affordance your database offers to mitigate issues this
transiency can cause; there may be others depending on implementation.
since ORMs don't model the database, taking advantage of said
affordances is often unidiomatic and awkward, and not infrequently
wholly infeasible.
to write a changed field back into the database involves tearing the object apart and overwriting rows in the database, based on what is hoped to be a stable and unique set of identifiers.
Your database has another API: SQL.
SQL is the only way of interacting with the kind of RDBMS under discussion. there is no bytecode, no assembly. there are no macros or compiler flags.
There Is Only SQL.
psql
's backslash commands, sqlite3
's dot
commands, or mysql
's "Database Administration
Statements"—but since those commands are only available when using the
interactive client, there's no way or need to make use of them when
writing code.
SQL is a programming language that is interpreted by the database itself. there is more readable SQL and less readable SQL; there is more performant SQL and less performant SQL. there are ways you can architect your tables etc. that make it easier or harder to write more or less readable or performant SQL in much the same way that there are ways you can architect your application towards similar ends.
EXPLAIN
, available in both psql
and
mysql
, that can help you write at least more performant
SQL.
your ORM produces SQL based on your code that it then sends to your database via a client library.
producing SQL based on your code and sending it to your database via a client library is the second of two things your ORM does.
you have no direct control over the SQL your ORM produces based on the code you write.
your ORM does not show you the SQL it produces based on your code unless you specifically ask it to. it does not show you the SQL it produces because it is technically unnecessary to do so, and because the SQL it produces is usually neither readable nor performant.
because ORMs model tables, maintaining the illusion that its objects are
"normal" usually results in producing SQL that retrieves every column,
usually by name. your ORM may also unexpectedly produce SQL that join on
foreign keys, leaving your HTTP server or machine learning application
or CLI tool to deal with having a few hundredBook
s in
memory when it just wanted an Author
's phone number.
you can change your code to get your ORM not to produce SQL like that. if you don't change your code to get your ORM not to produce SQL like that, it will produce SQL like that. the exact ways to change your code to get your ORM not to produce SQL like that might be unidiomatic and ill-documented.
because ORMs model tables, idiomaticity and quality of documentation often correlates negatively to the conceptual distance from that focus.
database-native functions are treated in a lax and piebald manner. an ORM may elevate favored SQL functions to named methods or other documented constructs; the rest are invoked "stringly" or ignored in favor of equivalent functions run as part of your code.
another promise your ORM makes is to let you ignore which specific RDBMS implementation is running your database. people like this because despite the existence of an ANSI standard every RDBMS implements SQL a little bit differently.
your ORM most values not being misunderstood.
this concern both limits its output to a kind of "simplified SQL" that often lacks in both readability and performance, and proscribes its "blessed" (idiomatic, ergonomic, well-documented) API surface to favor easily-universalizable SQL constructs.
these constructs may or may not be well-suited for your use-cases. your RDBMS may offer different and more fitting constructs.
you may be able to change your code to get your ORM to produce different, better-suited SQL. you may be able to change your code to get your ORM to make use of features specific to your RDBMS. to do so is to force your ORM to break a promise; the ways in which you need to change your code will likely be unidiomatic and ill-documented, if they exist at all.
your ORM is not a compiler.
once your application progresses beyond the most trivial CRUD, there will come a day when Life and its exigencies will present to you and your ORM a problem.
it may be a problem of complexity or of scale or of performance; it may be a problem of presenting the contents of your database in a different way.
whatever its nature, it will only be solved by you writing SQL.
your ORM will fail you.
instead: learn SQL. understand the relational model. befriend a client library.
instead: recognize the non-monism of your models. know that to create is not to read is not update is not to delete.
instead: acknowledge separation. accept distance. embrace a third party.
instead: think about Domains. think about Repositories. think about the Law of Demeter and the Single-Responsibility Principle.
instead: be thoughtful. be deliberate. be consistent. be clear.
© 2025 sam raker