I don't want to learn your query language (2018)
erikbern.com
erikbern.com
I rely a lot on SQL and in general advocate for it, but that’s too simplistic of a view IMHO. SQL makes it easy to write and read simple queries, and ridiculously complicated and arcane to write slightly more complex logic.
More than once I’ve been asked to help fix giant SQL queries written by a business or analytics team and that’s a terrible experience.
Also, the tooling around SQL is often terrible and almost didn’t evolve during the past 15+ years. SQL queries from another programming language without an ORM means that you do everything by manipulating strings by hand, with zero type safety.
Edit: My favorite library in Go is reform, a generator that generates types and implement the Scanner/Valuer interfaces. You annotate your structs, generate the types, add the generates files to git, done. That way you have very limited ORM magic but gain some type safety. And you still have complete control over your SQL queries (which is often frustrating to do with classic ORMs).
https://github.com/go-reform/reform
Edit 2: the most promising ORM I’ve seen is Prisma, https://www.prisma.io/. I haven’t tried yet, I’m still quite attached to writing most of my SQL by hand because that’s what I know well, but their pitch seems good to me.
This is consistent with my experience, and exactly backward of what you want a language to be - which is good scaling of solution complexity as a function of problem complexity, even (although ideally not) at the cost of upfront effort required to learn.
That sounds absurd, but my dream would be to write Go (just my personal favorite), then whenever I want to interact with the DB I can directly write actual SQL, not as a string, but as an actual valid expression. I have zero idea how that would work in practice but that’s the dev experience I would love to have. SQL as a DSL, written in my go file, without the need to change context, with syntax highlighting, type check, linting, etc.
Instead of having SQL as a second-class language it could be first class and that would be fantastic.
Not that something like this will ever happen, I’m well aware that standard SQL isn’t an actual thing in the real world and all the other issues around that idea, but I would LOVE this!
Then along came entity framework which also supports LINQ querying syntax (or you can use expressions).
[1] https://en.wikipedia.org/wiki/ABAP
[2] https://help.sap.com/viewer/fe24b0146c551014891ad42d6b2789e5...
The declarative layer gives you the handy ORM syntax for simple operations.
The core layer is lower level, and allows to composes all possible SQL operations you can dream, but from the comfort of your programming language, including typing, completion, and so on.
Does something like that exist for Go ? I don't know the golang ecosystem very well.
Do you have some examples of SQLAlchemy core layer you're talking about, just to have an idea of what you have in mind?
[0] https://github.com/sqlalchemy/sqlalchemy/tree/master/example...
[1] https://docs.sqlalchemy.org/en/14/core/tutorial.html#common-...
[2] https://github.com/atlasacademy/fgo-game-data-api/tree/maste...
Sure, it is. See how regexes in some languages are first class datatypes, so you can have a regex literal in your code (with backreferences too, I believe).
There is no good reason that SQL statements can't also be a datatype, with SQL literals in the code like the way regexes do. I'm imagining something like this:
sql_t stmt = /SELECT #1, #2 FROM #3, #4 WHERE #3.#1 = #4.#2/;
sql_res_t res_cursor = sql_exec (stmt, t1_col1, t2_col2, T1, T2);
The stmt above is not a string, hence is part of the AST and can be checked at compile time. The execution might be a problem (returned columns have to be checked too).This is the thing that makes ORMs sound appealing: let's make the complicated things simple! In my experience, the complicated things are truly-complicated, not just because SQL makes them seem to be. So by the time you make Use Case A "simple," you've run into Use Case B and Use Case C, and by the time your ORM handles all of those, well, there's a good chance it still doesn't handle all of them, and however many it does handle, the syntax ends up being... complicated.
There is so much to be said for a known and well-supported standard, and we are so quick to assume we can do better. Most of them, we really can't.
But hey, keep trying! Coming up on 50 years, we should manage to surpass SQL at some point, right? And in the meantime, we'll have our five-thousandth ORM or DSL to reach the limits of and have to revert to SQL anyway for just those last two or three queries.
myFun param =
for each param do...
Something like that has given me WAY more headaches than i ever expected, and that's before i saw vendor code (for systems that are used by millions) handling business logic in the stored proc with a 70 case switch statement...47 years on, most experienced developers seem to agree that SQL is better extended than replaced, but innovations like graph databases and "NoSQL" document stores have been received very well.
We shouldn't stop innovating but we should sure as hell should stop chucking half baked shit into production.
One measly example: Cypress. Everyone raves about it in blog posts, some dev immediately downloads it into their project and does a POC and calls it good. Fast forward to mid-late project and you are upgrading, downgrading, refactoring, and spending hours and hours trying to work around the issues. It's a great innovation, but my god. Self-induced stress.
Or you can do it the smart way:
Save an awful lot of time and hassle by using an ORM for all the boring stuff and have a couple of plain SQL queries for when you need it.
> And in the meantime, we'll have our five-thousandth ORM or DSL to reach the limits of and have to revert to SQL anyway for just those last two or three queries.
Or you can do it the smart way as I mentioned above and also just continue to use JPA.
Thanks for sharing go-structured-query, I didn't know about it.
This is not true at all. If you connect IntelliJ to your database (which you should - the query tools are fantastic), it becomes incredibly good at validating SQL strings in code. Even when doing crazy conditional construction.
I like ORMs. I use Hibernate a lot. But I write all my queries in native SQL.
https://blog.jetbrains.com/idea/2020/06/language-injections-... (Overview with link to the full docs)
It’s been a while since I’ve used it but it works great (and not just for SQL but pretty much anything IntelliJ knows about).
But this is all beside the point. Yes, SQL kind of sucks. But what sucks more is having a leaky query language on top of SQL such that you have to learn the query language AND SQL. Because- let's face it- you almost always end up printing out the SQL that the damn thing generated to figure out why it's doing something weird. You almost always end up needing to drop to SQL anyway.
And next year there will be a new awesome ORM/query-language that you're expected to learn all the pitfalls of.
My current comfort zone is if I can find a query builder that has enough static typing that it has all of the keywords of my preferred SQL flavor, has prepared statements with placeholders- mainly for safety/security, and basically returns a string when you're done.
At least then I'm just dealing with SQL instead of figuring out Hibernate's arcane caching feature or why in the FUCKING FUCK JDBC will return a `0` if a column was an int value that was `null` in the result set. IT WAS NULL- GIVE ME NULL.
Here's the code from the current version as of this message. It's in mysql-connector-java-8.0.23.jar. Class com.mysql.cj.jdbc.result.ResultSetImpl. Lines 814-818:
@Override
public int getInt(int columnIndex) throws SQLException {
Integer res = getObject(columnIndex, Integer.TYPE);
return res == null ? 0 : res;
}
Notice that the Java guys set the return type to `int` and not `Integer` in the `ResultSet` interface. So, if you call this method after retrieving a null value from a nullable column, you can either throw an exception or return something. I think it's absolutely insane to return a 0 here, but this is the way the MySQL connector has been forever.EDIT: Postgres does the same thing: https://github.com/pgjdbc/pgjdbc/blob/master/pgjdbc/src/main...
Some self-promotion (shameless, I know): https://github.com/lelanthran/libsqldb/tree/v1.0.0-rc2
I'm intending to rewrite it ("the first one is always to throw away" - I put too much unnecessary functionality into it and not enough RDBMS server backends) but I've used it in a few projects (use the latest branch) and am happy with it for postgres or sqlite usage.
See https://github.com/lelanthran/libsqldb/blob/v1.0.0-rc2/src/s... for example usage, but the basic premise is:
1. Send parameterised string to DB.
2. Get back records one at a time as an array of strings (NULLs are empty strings, as are empty strings) no matter the datatype of the column.
This may be true in some lines of work, but as a Rails developer I find it amusing. I've been happily using ActiveRecord for over a decade. I get that chasing the new shiny can be fun and look good on the resume, but the ORM shouldn't be a fad you chase that you need to swap out every couple of years.
You're not wrong that sometimes the ORM gets in the way and slows you down. There are certainly times I've been frustrated by having to figure out the right incantations to satisfy the APIs of ActiveRecord or Arel. However, this tradeoff is well worth it, because these tools protect us from so many footguns present with raw query string manipulation.
cough Parler springs to mind.
Or you can just stick with a JPA implementation (like Hibernate) which is highly performant and has been around for over a decade.
Someone will probably mention poor performance and yes, if you routinely query multiple millions of rows you'll probably want to hand code som SQL (something you can easily do without ditching JPA).
Otherwise, if you just happen to do the famous n+1 query, just get over it and actually learn how to use your ORM.
(Sibling comment also mentions ActiveRecord. I think the canonical .Net ORM has been stable for a few years already. Not everything is a Javascript/NoSQL :-)
Recently I had a chance to work on a simple 6-months 4-developers Java project from scratch. We had to interface with legacy software through an Oracle db and I was getting ready to use Hibernate again but our team lead asked if we really want to use it or just do it cause it's the "best practice". The project was pretty simple from data POV and we were mostly fetching lots of records to be processed and bulk updating a few "status" fields, but I've seen simpler projects using Hibernate for no good reason.
Using SQL directly was a breath of fresh air and I estimate it cut at least a month from our schedule.
Sounds like a perfect example for when you just want to whip up a few SQL queries, yes.
I'm not against raw sql.
I'm just against people presenting it as either/or and claiming that there is no gopd use case for ORMs.
You're probably a Hibernate expert. I'm not. I used it once, several years ago. I'm a polyglot, Jack of all trades, developer- I know lots of languages reasonably well, I know several high profile frameworks well enough that I at least know what to look up when I need to, etc.
Hibernate has a steeper learning curve than you might remember if you've been using it extensively or for a long time. I can't give specific examples, because it's been some time, but I just vaguely remember it having some of the same rough edges as other annotation-heavy Java frameworks- some of the annotations didn't actually work together the way I expected, it's mostly only checkable at runtime, remembering to use `@Column(nullable = false)` instead of just `@NotNull` (I just looked that one up because I remembered it), etc.
I also vaguely remember some issue where I had accidentally defined a class field as `int` when the column in the database was nullable. Then, instead of exploding when the ORM pulled a null out, I think it just magically converted the null to 0 and set my field to 0. Took a while to figure out why we had such strange results for some data. I'm not totally positive if I'm remembering that right, though.
You can accuse me of laziness for not wanting to learn the intricacies, shortcomings, opinions, and foot-guns of an ORM like Hibernate (and keep up to date with it even when I'm not doing Java this month/year). But I don't see it that way. I see it as being defensive. There is no way I'm going to be able to remember Hibernate's weird parts, AND Symfony's weird parts, AND ActiveRecord's weird parts, AND whoever else's weird parts. If I can just put the thinnest possible layer over SQL, map the result columns to specific types, and then build my object(s) from those "by hand", I'm perfectly happy. And I don't believe that my dev speed is slowed down by a meaningful amount. Will that part of the code take me longer to write compared to an expert in Hibernate? Probably. But that's probably not a significant amount of time relative to the whole project. And it saves me a lot of ramp up and debugging time. This is what I've convinced myself of, anyway.
Plus there's basically always an inefficiency in modeling an entity class per table. What if one of my domain types is constructed by taking a subset of multiple tables' columns joined together? Do I have to define an Entity class for every subset of columns I plan to use? That would be tedious, and POSSIBLY even more so than just writing some SQL and getting a typed tuple out per result row. Or can Hibernate et al understand pulling partial entities somehow? Is this easy and simple or does it require me to spend a couple of hours reading documentation and figuring out tricky annotations?
Everyone talks about these 80% rules: ORMs make 80% of stuff trivial and then you just break out the SQL when you need it. To me, that's a losing proposition. That 80% was already trivial to me. Writing a basic JOIN is trivial. It's the more complex stuff that I'm worried about and it just doesn't seem like ORMs do much for us there.
Yes, on your case there's hardly any reason to use an ORM.
I have worked with Symfony but these days it is always .Net Core or Java it seems and I can live with that (thankfully none of the clients I work for use Javascript or on the backend. One used Python with plain SQL and secrets stored in the application code though :-)
I think this speaks to the sad state of ORMs, but not the value of SQL.
I think SQL is rubbish: The two biggest things I hate about SQL are (1) that it's unstructured, so getting data into or out of SQL involves strings, (2) the syntax of those strings. Seriously. I really don't want to program in COBOL either.
ORMs are better on both these points, but as you point out, they are rife with pitfalls and I'll add I find their APIs absolutely reek of the SQL implementation they hide. I certainly want for better.
What we really need are languages to be better at dealing with data (especially data that is backed by permanent storage or distributed across multiple machines), but it's difficult to do this without (basically) creating a whole new language, and I think the decision to bring a new language into the world isn't one to be taken lightly.
Especially when you're asking people to trust their data to it. Bad software just gets turned off and on, and it resets, but people don't put up with a bad database for very long.
ORMs are a doomed proposition. There is simply no way to write a database-agnostic ORM API while also taking full advantage of the underlying tech.
Very simple examples include whether to return rows that have been inserted. In Postgres, you can do that in a single query. In MySQL, it's two queries because INSERT does not return the inserted row. So if I'm using MySQL and a popular "agnostic" ORM I have to realize that I'm (probably) doing an extra query on every insert, even if I don't use the result of the second query.
Then, of course, some of these ORMs don't even implement any of the database-specific features, like MySQL's fulltext search, because they're catering to the least common denominator.
Not every application needs to take full advantage of the underlying DBMS. Sometimes it's just a place to put data and not lose it, e.g. a key-value store with transactions. ORMs handle that use case perfectly well.
(I'm not a fan of ORMs personally, but they seem to work fine for logs of applications.)
Okay, yes... But an ORM is a TON of baggage for such a trivial use case, is it not? And these data have no interesting relationships to each other at all?
Not to mention that transactions work differently across DBMSs, too. So what is your ORM's default? Is it the same default as your DBMS? Does it know that MySQL doesn't support nested transactions, but Postgres does? Do you (metaphorical "you")? Do you fully understand what happens if you try to nest transactions in your ORM?
Also, do you understand how your ORM is going to convert data types and which ones are supported? Remember that JavaScript numbers don't even have the full range of a MySQL/Postgres BIGINT? Probably your programming language doesn't even support DECIMAL types natively- is your ORM going to just cast a DECIMAL to a float for you, or give you an error? Which is worse (I have my opinion)?
What about Dates and Times? How will your ORM handle timezones? How does your DB handle timezones?
ORMs don't seem to help with any of this. In fact, it seems like it only complicates things even more because you have to figure out how your DBMS works AND what your ORM is going to do about it.
Using an ORM seems like a reasonable choice for multi-platform use cases or at least some of them. The alternative would be to implement something more or less from scratch.
Structured types are in SQL since 1999.
People should really look into JOOQ to see what a SQL friendly ORM looks like.
Your "parameterized" queries are still serialized strings. Many language bindings for SQL interfaces don't have good support for serializing data types, so you will often see things like ?::datetime in the template string. Some don't have the ability to parameterize column names (or have subtle bugs) either.
> People should really look into JOOQ to see what a SQL friendly ORM looks like.
I've never heard of JOOQ. Do you know of a good introduction?
How else would you do it, short of a binary query format?
The primary benefit of pggen and sqlc is that you don't need a different query model; it's just SQL and the tools automate the mapping between database rows and Go structs.
It had a great developer experience but was slow and had poor pagespeed and usability scores compared to the standard Wordpress sites they were providing clients. Their next site was a bog standard all local Wordpress job although they've since moved onto React/Gatsby.
Also, I think this is what's going on with NextJS and React's server-side-components stuff. Having solved client-side webapps, the world has now turned to reinventing Cold Fusion...
In the codegen mode, it scans your DB schema and generates record classes + a lot of utilities. If the DB is well done (and it should be), it interprets many constructs, including relationships, domain types and various constraints. It can also generate activerecord-like classes if needed.
It allows far better safety and composability than raw strings and a lot of control on the query. Most DSL functions are called the same as in standard SQL, and the docs always shows the DSL next to the SQL version.
Everyone in the Java world seems to reach for JPA directly, but for me working with something closer to the DB is really a breath of fresh air. The DB-firat approach really works wonderfully.
If you always want the vendor specific SQL string, use DSLContext.render(QueryPart)
Obviously, I bought into the whole ORM concept, but I did have doubts in the back of my mind, some of which showed up in this article. You have to retrieve all of the columns needed to constitute an object. There is no subtlety in controlling the SELECT clause. Getting object identity right is hard, (e.g. you don't want to distinct Customer objects representing the same real-world Customer just because you ran two queries that happened to identify the same Customer). Caching is hard, especially when there are stored procedures doing updates and your ORM has no insight into them. Sometimes it would be nice to just write the damned SQL. Schema and/or object model changes are difficult, because you need to keep the two in sync. When I wrote my own database applications, I didn't want an ORM, not even mine. I wanted to write SQL. Having some tools to manage PreparedStatements and Connections would have been nice, but for my own work, I really preferred writing my own SQL.
But what finally changed my mind completely about ORMs was a hallway conversation with a developer at a prospect on Wall Street. (I think it was one of the firms to go under in the 2008 crash.) She told me that her resume looks a lot better with "5 years Oracle experience", than with "5 years XYZ ORM experience". This, combined with the doubts that were accumulating in my mind, finally pushed me over the edge.
In later years, playing with various web development frameworks, I dreaded dealing with ORMs. It's as the article said: I don't want to learn yet another query language, only to have that translated, badly, into SQL. Just let me write the damned SQL, it isn't that hard. I know exactly what SQL I want to run. Why should I create work for myself in convincing the ORM to do what I already know how to do?
In case someone else is wondering:
https://stackoverflow.com/questions/97197/what-is-the-n1-sel...
It's just not typed but that is true of JPQL as well. And the criteria API is, well, ...special.
I really don't mind SQL, I mind all the perceived boilerplate around it.
The "ORM/SQL dichotomy" is greatly exaggerated.
Furthermore, those objects are "live" and editing them updates the database. Navigating the object graph navigates the database. All this is very convenient. When it's not convenient or performant, I write updates or tuple queries by hand. But mostly the approach of "select entities using complex SQL queries but use the ORM approach afterward" is a sweet spot.
Classic example is an event booking system:
(a) Event Management is basic CRUD, so you use an ORM everywhere. There are no complicated queries here so ORM objections don't count.
(b) Ticket Management has multiple users, race conditions, performance requirements, etc. so maybe go the command-query separation (CQRS) route and use a full ORM for your write model and raw SQL, possibly a micro-ORM, for your eventually-consistent read model.
Why use a full ORM? You get to write your domain logic in your high level language with full unit testing, and the ORM worries about mapping this back to the database, dealing with concurrency and retry strategies automatically. For this to work you have to carefully design your business transactions against small aggregates containing a handful of entities, which is difficult to get right initially but has many advantages.
(c) Payments/Refunds are transactional, reliant on external services, and need a full audit history so maybe consider an event sourced approach with no ORM visible.
(d) Reports have no write model, so an ORM holds little advantage. Use SSRS, Power Bi, QlikView, etc.
etc.
Sure, your select and join is mostly the same, maybe with a few different commas here or there. Once you get into complex large data operations though it's a whole new ballgame.
Imports happen in wildly different ways, temp tables, choices of inserts vs. updates is a per-platform choice in some cases.
On top of all that, an ORM like entity framework (C#) handles things like making sure the statements are prepared in a way that "handles" major security issues like dependency injection out of the box.
If you make your team use an ORM you don't have to worry quite as much about someone named ';DROP tables ' signing up to your system. (Or whatever)
ORM has it's place. SQL isn't a monolith that everyone* knows.
The discussion is worth having, but I think the OP's argument lacks nuance.
The first step was ditching the ORM and replacing it with a well-defined repository layer. Because the ORM was encouraging interaction with the database to be scattered everywhere, and that was making it impossible to understand scope of impact.
With the data access layer, though, we had a relatively small, clearly defined chunk of code that we could isolate and test, and that made the migration manageable.
We didn't need to ditch the ORM, of course. And, at first, we weren't going to. But we saw that, with things so clearly isolated like that, the ORM library wasn't really making the code any easier to understand, so we decided it wasn't really worth the added indirection or the performance cost.
I also worked on a product that simultaneously supported two different DBMSes. It didn't use an ORM, either.
I had to remind him that, in the few years I worked for him, we had actually used five different databases - MS SQL, VistaDB, Excel-as-a-database (using ODBC), LiteDB (a NoSql database) and Sqlite. He knew about each and everyone of those, but the "we're never going to change databases" reaction is so ingrained apparently that he just went with it.
And just in case you're going to argue "sure, you used different databases but in different applications" - I just had a programmer change Sqlite to LiteDB, because Sqlite wasn't working properly on his system and it was faster to just change the database.
You can't be using any advanced features if you're supporting SQL, NoSQL and even Excel, so if you only need basic features why are you bothering with switching so much?
I know of one good reason to support multiple databases, when you're writing software for several enterprise clients and each of them wants to use whatever databases their teams are used to managing.
All of these bits of SQL, though, are things that ORMs typically handle by simply not using them. If you're limiting yourself to the subset of SQL that you can access with a DSL, it's pretty standard.
I agree that ORMs help defend against SQL injection, but so does any linter worth its salt.
To me, the real advantage of these DSLs is that they can get you some compile-time guarantees that your queries have some minimum level of validity. Raw SQL needs to be explicitly tested, otherwise you don't even have a guarantee that it's syntactically valid. I'm personally on the fence about whether that outweighs the cost of having a custom DSL, but I'm biased - I tend to avoid ORMs, anyway, so that I can have nice things like common table expressions and temp tables and PIVOT.
But if you're using a standard DSL such as .NET's LINQ, which everyone working on the platform already needs to know, anyway, then I can definitely see the attraction. That also eliminates a lot of the core complaint in TFA.
sql`SELECT foo FROM bar WHERE id = ${userEnteredId}`
gets converted into an object that is basically:
{ query: 'SELECT foo FROM bar WHERE id = ?', params: [userEnteredId] }
It gives a centralized place to understand what data is in play in the application and adding a new column to the database means updating a class / struct / type in one location and checking the use of that within the code base.
Without an ORM and with poor discipline I end up having to go find every query in the app referencing that table and check if I need to update the query which usually means changing the columns, the ? value placeholder and putting the correct value in the associated values container.
It all seems like a lot of tedium and ripe for simple typos in the queries that is easily avoided by using beneficial tooling.
It's great if your talent allows you to do much more than that, but I wouldn't deride it, and much less hate it, when your own success is likely going to have to rely on competent people around you doing that job extremely well.
ORM is necessary, otherwise we will find ourselves in the old days again, a large part of string concatenations, security problems, etc,.
The hate for ORMs comes from more advanced applications, not simple CRUD apps. When you have 5 tables with no relations, or maybe a simple one-to-many relation at most, an ORM is the perfect solution.
The problem comes when you have 25 tables, complex relations, complex updates and need to optimize your queries for better performance. You now need to know the inner workings of you ORM, and pray that there are sane ways of configuring these things. Knowing the inner workings of these enormous beasts are not always easy – at least not easier than writing the SQL yourself.
I ended up using raw queries in many of the projects I worked on for more than 2 or 3 years. It's more cost effective than trying to find the right combination of ORM statements.
If I had an ORM like that maybe I would write all SELECTs directly in SQL.
What makes that impractical in projects with multiple developers is that about half of them don't know SQL nowadays. They know how to use the ORM and the language to write CRUD queries and that's all. If they see SELECT * FROM invoices WHERE customer_id = ? they probably understand it. Nested queries, HAVING, GROUP BY... oops! CTEs... OMG! This means that sometimes they write three or four queries when one would be enough.
For the scenario you're describing, you could still use a mix of an ORM and raw SQL as needed.
Compromise, best tool for the job, nuance, etc - all tend to get tossed out when people make tech decisions. Someone using an ORM may not even be aware that raw SQL is possible.
I am not saying that ORMs never work. They sure do in a lot of cases, and some ORMs more than others. But an ORM is not a silver bullet – far from it. There are gotchas, and you need to invest time in understanding how they work. Just as you need to understand how SQL works.
In Python/Node: I'd never use an ORM for handling 5 tables. I'm just fine writing/maintaining my 5-10 queries in plain SQL. Centralize all the queries in a single file and that's it.
In Java/C#: Do I even have the choice? Even for 1 table it's probably easier to let the ORM do its job than fighting the typing system.
Perfect solution to what problem?
New table columns should never cause problems for existing code because you should never use `SELECT *` and you should never use positional semantics in your SQL, put a linter on it.
As for typos, that's what SQL builder libraries are for, they allow you to work with your own language constructs instead of mashing together strings manually.
ORMs are this thing that developers seem to think is a life preserver but as time goes on morphs into an anchor.
I have different experience. There are situations where you work with database given of course, but in many projects ORM enforces good and unified practices (rows have unique id, constraints and indexes are properly named, relationships are done in always the same way), and generally database is just a servant to developer idea of data model, not a god to worship and bend to.
If I'm understanding you, that along with your mention of a SQL builder means, for example, in Python, you'd expect
CreateUser(name="blah")
As opposed to something like
db.Exec("INSERT INTO users (name) values (?)", "blah")
Is that correct? I think we're agreeing on reducing the amount of raw SQL being tossed around within the code.
And to be frank, CRUD apps created with an ORM that remain as simple CRUD apps are the vast minority of the work that I’ve done in my career. Everything else evolves into something where we inevitably end up working around the ORM more than using it as-is.
The solution in my opinion: SQL generators (like linq and such). ORMs often include these as an after thought, but pulling in a full ORM for the sql generation is like pulling in a semi truck for its generator; it’s too easy to start experimenting with the toys stored in the trailer.
Add to that, as someone mentioned above ORMs tend to be "common least set of features". And if you for some reason want to use a specific feature of your RDBMS you have to jump through hoops and the ORM "illusion" ends.
EF Core -can- be better than the older, much more reviled EF as far as generated SQL, but in EF core 3 they did a pretty big breaking change on joins, bringing behavior closer to EF6's tendency to do a cartesian asplosion
It's when things get complicated and performance is important that they start to feel like a hindrance. I've had to wrestle with ORMs only to figure out that it's generating bad queries or lots of queries when it could do it with one query. I've usually had to end up subverting the ORM or throwing it away entirely in almost every case.
This sounds like bad design, why would you have SQL queries for the same table littered across a project, rather than in context with each other in the same file/class/interface/etc.?
Most of the CRUD experience I have is in Java, and I've preferred using JDBI over ORM's like Hibernate. You write a CRUD interface for the table and annotate the methods with (something very close to) plain old SQL. I can define how a SQL row gets mapped to a POJO, though in many cases the default mapping logic is fine. I know exactly what SQL will be executed, at compile time. I use Liquibase to manage the database schema. It's about as painless as it gets; I've had many obtuse errors to resolve attempting to use an ORM. I think most importantly, I'm familiar enough with the schema that if and when I need to do an ad-hoc query to give some data to an analyst, or debug an issue, I know what I'm working with.
I've used both ORMs and direct ADO queries (in C#), and while ORMs can trip you in special cases like the N+1 SELECT problem, the raw queries are a pain everywhere. It's just to easy to say "ah, I need a quick SELECT here, I'll just return this bunch of records and I know that record[5] is a string with the value I need". Hello, versioning hell.
> Their alleged benefit is they cut down development time
> Let's dispel with the myth that ORMs make code cleaner
This is very narrow view of what ORMs do; without them, access to the DB layer becomes a black box.
Actually, not even the black box definition is appropriate - APIs to access the DB layer are needed in one way or another, so one ends up writing their own ORM.
ORM APIs do a lot more than just executing queries. The first thing that pop into my mind is persistence/state management, but also, composing queries is much more than bashing together a series of strings.
EDIT: elaborated on ORM APIs role.
OOP and SQL are different paradigms. An OOP abstraction over SQL is never going to be perfect so to properly and responsibly use an ORM framework you have to understand what's going on under the hood. If you don't, then yes, you will create a mess for anything other than the most trivial use-cases.
ORMs try to make SQL seem more programmer-oriented by adding an extra layer of indirection over SQL. Unfortunately, what they actually tend to do is to add an additional black box enclosing SQL's black box, and that usually makes everything worse because it is now almost impossible to reason about how the database is actually going to execute a particular query.
There doesn't seem to be an ideal solution to this problem. I think it is why so many databases tend to tweak SQL into something that better fits their implementation detail, and we end up with the explosion of almost-SQL languages that the author is complaining about. Personally, the least-bad solution I have found is the one you mention in your post: write your own SQL abstraction layer every time. At least that way, you can poke your screwdriver into the box and reason about what is going on in there.
In the end after all this frustration I wished I could have written the query plan directly, especially when I used Postgres with no query hints. And yes I'm aware of Postgres and all the tricks that you can do to make it do certain types of joins and such and I employed many of them (adding statistics, loose index scans, all the index types and others). IMO the potential this could open up is quite large given many databases all have the indexes/algorithms and many data structure types these days. Gluing them together in a performant way where you use the appropriate algorithm/data structure index for the data on tables/JSON blobs/etc seems to be the hard part right now that requires a lot of trial and error and learnings of the SQL optimizer to get right.
Seriously, the idea that everything maps well onto relational databases is incredibly misplaced. Not everything is tables, tuples, and relationships. Most RDBMSs graft a million slightly incompatible features onto SQL to attempt to handle all the things SQL doesn't understand, like GIS and full text search.
The thing that's nice about RDMSes, and the reason why they've been so successful, is that they're built on top of a strong theoretical basis. The relational model is a bit like the lambda calculus. It's simple and relatively easy to understand, but can still provably handle just about anything.
- Documents
- Spatio-temporal
- Graphs
- K/V
- Trees
- CRDTS
- Images
- Arrays
I'm sure there are plenty of others but those come to mind first. Most DB researchers I know would argue that if any database model is equivalent to the lambda calculus it would be graph theoretic model, because all other models can be expressed by graphs, but other models cannot universally express the graph model.If you want to use a non-RDBMS, then by all means, SQL won't be a good fit. I use Amazon's DynamoDB all day, every day, and I wouldn't dream of using SQL there.
I don't want to learn your query language - https://news.ycombinator.com/item?id=17890760 - Sept 2018 (153 comments)
I don't want to learn your garbage query language - https://news.ycombinator.com/item?id=17888930 - Aug 2018 (9 comments)
I had to fight a battle to remove a very slow ORM that was used for a SQLite database with 4 tables. After I removed the ORM from a critical code path, startup went from 24 hours to about 2 minutes. Raw SQL was "worth it" because the schema was brain-dead simple.
I more recently optimized some C# Entity Framework code that took minutes to run and brought it down to seconds. In this case, there were many tables and many joins, so going down to raw SQL wasn't "worth it." All I had to do was recognize that the code was relying on lazy-loading, and then I just pre-loaded the entities.
BUT: I should point out that ORMs can be extremely powerful. Using a tool like Linqpad with C# and Entity Framework, you can write ad-hoc queries much more easily than SQL.
SaaS platforms build _filter languages_ that are (sometimes vaguely) inspired by SQL. I really wish there was a standard among filter languages that was optimized for interactive search. Interactive search is different from application search because in interactive search you don't know what fields there are so you are mostly counting on substring matches. But you still want to be able to include multiple substring matches (AND) and exclude things as you find them that aren't what you're looking for (AND NOT).
I think Google's search language is one of the best filter languages out there in terms of simplicity/effectiveness. Splunk's language is good as well (but maybe they're just making up for it in good typeahead support).
I'd like a standard for Google's search language with implementations for in-memory filtering in every major programming language and implementations for generating SQL from the filter for every major programming language.
Until this happens there's not really much hope for standardizing on filters across applications.
Not just typeahead. A lot of Splunk's power comes from data transformations and filters.
get_logs
| apply_transform
| merge with other logs (which can also be log|transform|filter|transform)
| apply more transforms
| filter
| expose as a specific structure (that is, transform)
| filter more
This would be anywhere from pain to impossible with SQL.ORMs, though... I tried to get on board with those, and it's done nothing but bite me. As the article says, I end up spending more time in the documentation, and then my boss ends up questioning the choice as well when things (like in the article) happen.
In the end, I think the ORM has been as much pain as help, and I'd have been better off just not bothering.
It felt as good as building Ikea furniture without checking the manual and it's what user/developer experience should be about.
1) ORMs (which are used in cases where you have control over the DB) are bad and you should just use SQL
2) DSLs (in non-SQL DBs) are bad and the devs should have just exposed SQL
3) DSLs (in opaque services) are bad and the devs should have just exposed SQL
I tend to agree with #1, though it's complicated and there's lots to be said on both sides.
#2 seems semi-reasonable, if the semantics are close enough to those of a SQL DB, but the problem is that DBs like Mongo have wildly different semantics from SQL.
#3 is harder because an opaque service usually specifically wants to insulate users from its implementation details. The great majority will not want to give you direct access to a SQL database. So the only alternative would be to "fake it" and pretty much base their DSL on SQL, which may or may not line up with the semantics of their service.
For both #2 and #3, exposing SQL when the underlying semantics may or may not be a good match for the query language seems questionable at best. It encourages assumptions to be carried over that may not hold, especially around performance characteristics (which the OP specifically mentions as an advantage, surprisingly). I just don't see how this is tractable in the general case.
I can't agree more. Bespoke query languages and back-ends are generally only good for two groups: the vendors and consultants. SaaS apps with their own query languages and NoSQL flavored back-ends end up being nothing but headaches for the poor FTEs that have to spend their days in the drudgery of having to figure out anything beyond a basic query.
For example, I want to write simple SQL queries this way:
(-> "table" (cols col1 col2 col3) (where (= col1 10)) (order-by col3 :desc))This allows "normal" C++ code, which by the compiler is converted into the query string, allowing code like
for (const auto& row : db(select(all_of(foo)).from(foo).where(foo.hasFun or foo.name == "joker")))
{
int64_t id = row.id; // numeric fields are implicitly convertible to numeric c++ types
}
(Random pick from the README)https://engineering.fb.com/2016/03/18/data-infrastructure/dr...
There is an example under "Functional programming primitives".
It was a C++ implementation that fell victim to Greenspun's 10th rule. So I wrote a specification for it, first in Clojure and then in python.
The C++ execution engine is open source:
https://github.com/facebookarchive/iterlib/
Main problems writing such code:
* The output of SQL is generally flat. GraphQL makes it nested, but doesn't support all the operators SQL does natively. * Anyone trying to implement such an engine needs to ensure a fundamental property - the shape of the output can be inferred from the shape of the query. * Writing async code in C++ with futures and promises is like pulling teeth. At some point the complexity explodes and the code becomes unmaintainable.
My most recent attempt is here:
https://adsharma.github.io/fquery/
It shouldn't be hard to put a wrapper around fquery that looks like the query string you present above. In fact, there is a s-expression parser in iterlib/python above that could be tweaked to do this.
I'm hoping that we can build a community around such languages and once the specification is agreed on, a more performant implementation can be written in a systems programming language.
Why not test it at the Clojure REPL?
Also as far as I know React has native components, not sure how much HTML decoupled are these.
>Author seems to take for granted that not everyone knows sql.
Then ORM is incredibly dangerous for them to use. ORM frameworks are useful but their abstraction of SQL is very leaky. You have to have a reasonable understanding of SQL in order to use an ORM framework effectively, otherwise you will get yourself in trouble. I bet this is where a lot of ORM criticism comes from ... namely developers who don't know SQL and therefore use ORM as crutch when working with a relational database. They can create a very nice object model with an ORM framework that is shit when it comes to the actual queries it generates.
Even if you use a framework like MaterialUI which abstracts all the HTML tags into React components, working on layout creates a recurring feedback loop between source and output code in the browser / devtools.
If you've done that for some time, you end up knowing a fair bit of semantic HTML.
So I suspect either your friend was exaggerating, the devs were lying about their React proficiency, or maybe they work on everything but layout in React (highly unlikely). At any rate, forgive my cynicism, but I can't help but feel that this is a classic HN cheap shot at React developers at large.
ORM to SQL may be another matter. I've been using the Django ORM in the last couple of months and it's true that I haven't touched SQL but once. I know SQL but I've barely felt my skills progress since.
knowing html is a somewhat nebulous claim, there are so many elements, attributes, and relationships, and the specification is incredibly dense. A design-focused front-end developer may almost exclusively use parts of HTML that a 'back-of-front-end' developer might not even know exist.
I do think you could do back-of-front-end very competently and have only a vague understanding of HTML. However, you'd likely also be interacting strictly with a subset of React.
Which of them? There are as many SQLs as there are implementations. You can't take an SQL schema from one DBMS and drop it into a different one, without conversion. Also you can't take a query written for particular SQL implementation and expect it to work and return the same results, except for very simple queries.
On smaller scale projects, cut out as many frameworks as possible. Stick with the bare minimum of tools. Pure Ruby, versus Rails. Pure Javascript versus jQuery. SQL queries versus ORM. Etc. It's less to maintain, less to learn, AND it forces you to learn the underlying technology.
On larger-scale and longer-term projects where you expect to work with others and/or will need to hop back into the code on a regular basis for years, cautiously introduce frameworks (and ORMs). In those cases, frameworks solve more problems in the long run. I'm usually annoyed early on, as frameworks force you to learn their DSLs (etc). But there's always a point where the benefits start to outweigh the initial negatives.
The DSLs are generally better to work with. Complex queries can be broken up into composable functions that can be mixed and matched to perform different analyses. Functions are easier to unit test, document, deal with quoting better, can be type safe, etc.
Some super-complex queries are still better to write with regular SQL.
New query DSLs don't need to reinvent the wheel. They can implement their query capabilities using the Row / Column / DataFrame abstractions that are elegant and familiar in popular projects like Spark. We need an ANSI query engine DSL.
Eg. the security rules and query lang around Firestore locks down what the develop can ask for in a way that keeps queries run fast. Not doing so could pose a security risk. However, for an on premise setup with just a limited number of trained people it seems reasonable that they should have full power.
Ie. I am pro DSLs as they allow to mitigate other concerns.
That being said, ORMs still don't enjoy the level of trust that optimizing compilers have enjoyed for decades. That's always going to be a barrier for wide adoption, especially from folks who are experienced in SQL. It's similar to how in the 1950s (and maybe somewhat 1960s) there was resistance for "automatic coding" by compilers; those who were experienced in assembly thought that compilers will always produce sub-optimal code. But it's clear that high-level languages have won (at least outside embedded/low-level drivers).
The point is: any form of code generation will always find resistance until it proves itself.
This is also something light weight orms or query generators handle fairly well, if not better.
But to be clear, either you use an ORM or you're going to invent a new ORM because your app doesn't talk in SQL, it talks in Objects - so whatever you do, there will be some mapping between the SQL query response, and objects that you will need to create to pass around in your app. If your app is expected to work with different databases, each with their own SQL flavor, you're going to have abstract that as well.
And of course, ORMs tend to have good sets of defaults built in (like proper escapes). So whatever ORM-like framework you build, you'll have to take care to cover that case, otherwise you're back in the bad old world of SQL injection.
Speak for yourself. My app talks in dataframes.
I'm just complaining about the "everything is a webapp backend written in a scripting language" mentality that pervades everything in IT now.
"Learn how to master {flavor} ORM and when you've spent hours digging through documentation, stack overflow posts, and src code to find it isn't simple to do in {flavor}, use raw SQL."
This argument that "you can always drop into raw SQL" skips the fact that the code will not be merged if it can be done in the ORM because ThisIsTheWay(tm).
I find query builders to be a nice blend between raw SQL and the ability to write programmatic queries. The APIs tend to be relatively consistent between libraries since they map directly to SQL statements.
Maybe that's the problem? It might just be that OOP is a poor fit for large datasets :-/
Using OOP for results from a RDBMS is banging a square peg into a round hole: with a big enough hammer you'll eventually get it in, but the results are not going to be pretty.
If all the data is relational, and you're trying to enforce some sort of hierarchy on it, whether you're using an ORM or not is irrelevant, you've already lost the aesthetics war.
In that discussion, I made this comment¹, which I still stand by:
If you’re developing a DSL which is just a query language, you are reinventing the wheel, and you should ask yourself if any benefit of your language over SQL is worth the effort of all your users to learn your new query language. It may be worth it of your data can not be usefully be modeled by tables; e.g. document query languages like XQuery and even simple XPath are useful and can not be easily replaced with SQL.
The author has a beef with two things and claims to have a problem with a third. The author seems to dislike ORMs for their abstractness and general inefficiency when interacting with a SQL database (this is the nature of an ORM, either stop writing OOP code and as a result stop requiring an ORM, or use a graph database for your graph data (object state)). The author also seems to dislike SaaS query languages or something like that, a topic I don't know much about.
This gets all rounded up under the topic of "Query DSLs".
When I hear "Query DSL" I think SQLAlchemy Core. A library designed to provide an API to a SQL database which looks like you're writing python (implemented through overloading tricks and some fancy OOP metaprogramming tricks). SQLAlchemy Core is great, the author would not hate it because you're effectively writing SQL without all the footguns.
SELECT * FROM user; -> select(m.user)
SELECT * FROM user WHERE name == 'test' ORDER BY surname; -> select(m.user).where(m.user.c.name == 'test').order_by(m.user.c.surname)
By having python native code you can generate SQL statements using idiomatic python. If you do some kind of coverage tests (no idea what the kids these days call these but the idea is to run all the python code to ensure there isn't a low hanging fruit which would get caught at runtime) you can find 'select' typoed as 'slect' early on in development rather than it ruining your Friday when such a bug gets into production. This also makes parametrized queries painless which prevents many potential security issues with generating SQL.
If you want to you can also use the generic subset of SQL this way and SQLAlchemy Core will iron out the creases. But personally I think it's pointless to make a choice of SQL database if you're then going to not use all its special features. It's like writing some kind of polyglot-lang which can be translated to C, Java, Pascal, prolog, Lua, scheme and rust. You pick a language for the task, pick a database for the task too.
So it's important to isolate these features mentioned above from "ORM" and whatever the other thing the author was talking about. People use ORMs and they realise they suck and it's important to remember that although the core concept of an ORM is flawed, not all the features which come bundled with an ORM are bad ideas (or at least it doesn't mean that those ideas can't be done well when you are not trying to implement an ORM).
Now the design was far from easy, and keeping things reasonably simple without abstracting the SQL part was my biggest challenge. And this would not have been possible without the template-string tagging feature of modern JS, so it wouldn't be doable for many other languages.
Are you a human and want to query a database? SQL is probably it. (a SQL of some sort, but a pretty good shared understanding) Even if it's not relational exactly, it probably comes with some project with things like records/rows and columns/fields.
Do you have some kind of relational model that needs accessing? SQL sounds pretty good, and you know it from writing queries as a human...
But should you construct queries with string concatenation? No! Absolutely not.
Do you have to write the queries yourself? Like what if you want to stick an object in a database? Can't it write the query for you? It's appealing, but leads to sadness and anger at some point. (Joins? Conditions of more than 2 degrees? lots of dark corners very close by)
AND whatever model objects are used become a defacto database schema, so you can't really refactor them in the same way you would other models. So you better not expose them past whatever datastore touching interface you have. So why not just define them in SQL DDL in the first place?
What if you want to write the query (or need to to be sure you're getting exactly what you expect) but type-safely? LINQ? Absolutely! jOOQ? Sounds good! Arel? Sure! And maybe you get some amount of programmatic composability as well with any of these. Thankfully they're not really ORMs or extremely thin ones at that.
In my experience, features such as a search engine that supports many filters are much easier to maintain if they are written with an ORM rather than with raw SQL. I don't have a preference for simple queries.
On the other hand, I find MongoDB's query language and its popular Python ORMs (Pymongo and Mongoengine) painful to work with.
I actually still had a little bit of code in production using it when they shut down.
That is essential reading for anyone arguing against or for ORMs.
That! Totally agree!
Seriously, what is so ambiguous with “GROUP BY” that you had to invent your own keyword for it?
Interested in how DLS like expression evolving on other industries like Music, Science, etc
You can view source to see the HTTP parameters or just WireShark it.
https://www.youtube.com/watch?v=LEZv-kQUSi4
Has some great insights and quips. "Nothing says 'screw you' like a DSL"
The author is correct, sql does everything they need, cause it's so expressive. (Mysql8 is turing complete with it's domain specific syntax duh-duh-daah...) But that power comes at the cost of scrutiny and composition.
ORMS are fish in a barrel--they fail because they are the wrong abstraction. Queries shouldn't be the language, they should be the verbs in the language.
With a very restricted query AST, introspection and composition are possible. If queries point to the same table, you can combine them into a new query whose result cells are union or intersection of the input result cells. You can automate joins and subquery indexing. You can recover from a dropped column by replacing references to it with null, and limp by with partial results instead of no results.
I agree with the author's hate on DSLs as a substitute for sql... when they have the same level of abstraction. But if you want queries to be lego bricks that snap together, the bricks can't have NP hard surface area. DSLs fill that need.
This is plenty.
One's I've used:
knex - node.js
sqlx - rust
Or Microsoft's Graph API, which is kind of SQL-y, kind of LDAPy, but not systematized enough that you can be confident anything you try will work without reading the doc on the particular resource you're trying to get.