Meet Korma, a SQL DSL for Clojure
sqlkorma.com
sqlkorma.com
I work with a number of legacy data sources that often have models that are not ORM-friendly.
I fall into SQL code patterns that seem left behind by tools like this on a regular basis, particularly using SQL functions without default grouping because I'll select other stuff higher up.
A simple example:
select
col1,
col2,
some_function() over (partition by col1,col2 order by col1,col2) as some_ck
where <some conditions>
This is often part of a larger query (e.g., this is embedded in bowels of a some longer "chain" of SQL where I will be using some_ck to decide on which tuples I want in the end).I get pretty significant mileage out of this - meaning I write much less other code around it to handle the data.
I guess my complaint is that I often find the SQL DSLs lacking in expressiveness that I use regularly enough to miss.
Of course, this could be because I've spent too much time hacking around in legacy data in "real soon now" mode rather than in "hmm . . . let's clean up this data model" mode.
SQL is already a declarative query DSL, why not just use it? Putting another declarative layer on top buys you little (syntactic sugar for most libraries, this one additionally seems to deal with one-to-many relations, and I hope that support is read-write, not just read-only) — and isolates you from the database below.
In every serious application I've worked on there was a need to use something which the DSL did not support. So why do it at all?
For example, if your PERSON entity has TITLE, FIRST_NAME and LAST_NAME fields, that little bit of logic that concatenates them together in the right way to form a full name is something that is useful to define. Likewise, setting up a join of tables that is used many times in your application is something that Korma will let you do.
Even though these components may be implementable on the database side (as user defined functions or views), it may not always be possible (or desirable) to do that way.
I'm not a Clojure user but as I have some authority on this subject I can illustrate your query as SQLAlchemy:
select([table.c.col1, table.c.col2,
func.some_function().
over(
partition_by=[table.c.col1, table.c.col2],
order_by=[table.c.col1,table.c.col2])
.label('some_ck')])
.where(<some condition>)But perhaps it's just a matter of getting used to a different format.
[1] Legacy environment.
[2] Edited to add: most of our newer apps don't actually invoke huge queries from the app layer. We now tend to layer a view or pl/sql table function on top of the hairy query, so that at the app level it's mostly "select * from blah." For that simple a query, a DSL like this is probably fine, albeit, unnecessary.
And if so, can I use SQLAlchemy as a standalone pre-processor? My app's in Rails so I can't exactly go integrate it..
yes. many DB specific constructs are included and you can make your own: http://www.sqlalchemy.org/docs/core/compiler.html
philosophically SQLA does not consider "abstraction" to be "hiding", so ultimately no features of the backend store are concealed
> like create a Postgres user-defined type
sure, the ENUM type does this right now automatically (i.e. CREATE TYPE...AS ENUM). For other kinds of PG types, you can emit the CREATE TYPE yourself then link to it with a SQLAlchemy-side custom type. You can integrate those two steps also.
> or a SQL function?
Again a SQL function you're going to be using PG's PL/pgSQL or other embedded language to create it, so you roll the CREATE FUNCTION yourself, then call it on the Python side using func: http://www.sqlalchemy.org/docs/core/tutorial.html#functions
Apart from that, I like what I see, especially how it use c3p0 for connection pooling by default.
But - what do people have against SQL? As far as I can see, it's a pretty good language for relational data manipulation. But lots of people act like actually writing SQL is dirty work. It's not. It's an elegant, expressive language, for what it does.
Of course, use something like this if you have a documentable need for the flexibility/composability. If you're generating lots of custom queries, it may be justified.
But in nine out of ten projects I've seen which use a RDBMS, the system could be made substantially simpler, more performant and more flexible by just writing raw SQL queries instead of screwing around with ORMs.
It's up to you whether you use korma to "just write SQL" or you use the magic :)
I just think a lot of people will have a kneejerk reaction to use something like this because they have a distaste for raw SQL, rather than a considered analysis of the pros and cons.
Because there are cons - every time you use something like this, however well designed, you introduce another layer of abstraction into your software, one that is bound to have some leaks. Maybe it's justified, maybe it isn't.
Just do the analysis to see if this can benefit you, instead of avoiding SQL by default just because it's SQL.
Without something like Korma, your process looks something like:
structural-description-of-query --(string operations)--> query-string-to-db --(careful unpacking of response)--> structured-results
With Korma and its ilk, this process becomes:
structural-description-of-query --(somebody else's problem)--> structured-results
The SQL community doesn't spend much time talking about writing elegant code in the way the functional, concurrent, OOP, and whatever-Ruby-is communities do. When programmers talk about "SQL Style" or "SQL Idioms", they mean whether you make keywords uppercase and where you indent and wrap to a new line. There is an entire book on the SQL equivalent of "K&R braces, or GNU style?", and should you or should you not alias long tables to short aliases.
There's a book on "SQL Refactoring"; it's about database design, not coding.
The pain goes on. I've been wishing for something like "CoffeeScript for SQL" for a while; I'm not a lisphead, but this may just have to do, especially if it supports PG-isms like SQL procedures and user-defined types.
Let me give you an example.
select vendor, model, avg(price) as price from allCars group by vendor, model;
So far, this is simple and clear. Now let's imagine someone has a requirement to calculate this for different set of cars:
select vendor, model, avg(price) as price from allCars where vendor = 'BMW' group by vendor, model;
or
select vendor, model, avg(price) as price from allCars where year > 1990 group by vendor, model;
This approach only works if I'm always selecting from the same table - allCars, and I know all the condition in advance. To reuse the actual code which calculates the average prices (select ... group by) I'd need something like this:
select vendor, model, avg(price) as price from $cars -- $cars is a variable with at least three attributes (vendor, model, price) group by vendor, model;
This way I could write my own relational operator which calculates avg prices of cars per vendor and a model no matter where the cars are coming from.
Does anyone else have the same problem as me or am I just not smart enough to figure out how to do it in SQL?
http://clojureql.org/documentation.html
Which doesn't have as nice a website but may have superior semantics.
korma seems to done this right.
Also, it would be nice to allow some raw SQL in where clause - so one won't have to wrestle with DSL just to occasionally use proprietary features or db-specific functions.
It used ParseTree to parse idiomatic Ruby code against Enumerable APIs into SQL.
It uses sourcify (which uses ParseTree on 1.8 and Ripper on 1.9). The semantics are identical to standard Enumerable API. In fact, it's not recommended, but you can turn off dm-ambition and the expressions will still work as-is -- only instead of generating a query from the block it iterates over the results and filter in-memory.
However, with sourcify you can sort of emulate the 1.8 behaviour, but it's not perfect. It uses ripper to parse the file where the Proc is defined, and then provides the same callbacks that ParseTree does. It mostly seems to work, but it does run into problems if two Procs are defined on the same line, eg: users.select { ... }.select { ... }.
I'm actually a little disappointed that ruby-core decided to go in this direction. I think that decision will end up costing the community because it makes it harder for runtime introspection of code. It means less tools will be written on top of the runtime, and things like Ambition become harder if not impossible to do reliably. I'm glad to see Rubinius is going in the opposite direction though.
In a nutshell, how sourcify works is it gets the source_location for the Proc, which gives you the file name and line number. It uses Ripper to parse the file, and the Proc starting at that line number. It provides an object that can be fed into SexpProcessor to extract information about the block of code within the Proc.
Since it's bound to Proc#source_location one of the limitations is it can't distinguish between two or more Proc objects defined on one line (eg users.select { |u| u.id == 1 }.select{ |u| u.name == 'John Doe' }). However, that doesn't seem to be too much of a limitation.
The way that's worded just sounds a little odd.
Aside from that, this looks really cool.