Posts tagged orm
Posts tagged orm
0 notes &
Well, it’s been quite an effort to develop an SQL-like DSL for data access for Scala. The model we’ve came up to is already good and beautiful, but I’ve summarized all the issues (custom PK types, cyclic dependency problems between Record and Relation, evaluation of uuids on each record instantiation, etc.) and I think we cannot maintain this model as it is anymore.
The main issue is of course the Record-Relation cyclic dependency (the so-called chicken-or-the-egg issue of Circumflex ORM 1.1). I don’t think it’s been raised by the community, so I’ll explain a bit. In short, we have two abstractions, Relation and Record. Relations are meant to be singletons, which produce record instances. In turn, records need to be aware of their relation. That’s fine. Now in order to build predicates in a DSL fashion, we need to reference fields declared in records from relation-level scope:
val co = Country as "co" // give our table an SQL alias
co.name EQ "United Kingdom" // construct a predicate
In the example above name field is declared inside record, not inside relation. So how does this compile and work? It’s because we use some magic here. Internally, every relation instantiates a sample record inside during it’s initialization. Relation then can be implicitly converted into Record to point to it’s fields (there are actually lots of things we do about this recordSample, but they do not matter for now). Now the issue. Because initialization code of relation instantiates a record, you cannot reference this relation inside record’s initialization block. Try it:
class Country extends Record[Country] {
println(Country.toString)
}
object Country extends Table[Country]
You’ll get NullPointerException as soon as you attempt to access either the Country singleton or instantiate the Country record.
Why would you use the relation object inside record? This question is out of my scope. The only thing that matters is the fact that you can’t do that. It’s a limitation. And we should get rid of it at any cost.
Fortunately, I’ve came up with a solution, but the cost is significant. It will require not only complete library rewriting, but will also eliminate all backward compatibility of users code. The data definition code will look like that:
class Country extends Record[Country](Country) { ... }
object Country extends Country with Table[Long, Country]
You see, the Country singleton now extends Country itself, thus it has all it’s fields (this eliminates the need for implicit conversions when constructing predicates). This approach also eliminates that notorious cyclic dependency problem.
But apart from the hard work on complete rewriting and backward compatibility problems, there are some open questions on this solution.
Since relation extends record now, it will inherit ambiguous methods applicable only to record instances (such as insert, update or delete) — what should we do with them? Throw exceptions?
Just like with recordSample, assignment to the fields of the relation does not make any sense:
Country.name := "New Zealand" // wut?
It is even possible to mistakenly add a relation into the collection of records and include it into mass processing which would make absolutely no sense (and possibly introduce runtime errors):
var countries = Country.all()
countries ++= List(Country)
countries.foreach { c =>
c.code := c.code().toLowerCase // no sense for relation
c.update // unacceptable for relation
}
That’s kinda it, thanks for your attention. I would appreciate all your opinions and ideas.