LauJensen / clojureql

ClojureQL is superior SQL integration for Clojure
https://clojureql.sabrecms.com
Eclipse Public License 1.0
285 stars 39 forks source link

The project+ fn #87

Open r0man opened 13 years ago

r0man commented 13 years ago

Sometimes I want to add artificial fields to a query but can't use project, because it overwrites previous selections. What about adding something like:

(defn project+
  [relation fields]
  (assoc relation :tcols (concat (:tcols relation) fields)))

to ClojureQL? With this function I can add a "distance calculation" to the query with something like this:

(-> spots-with-address
    (select-in-bounding-box :spots.location bounding-box)
    (project+ [[(str "distance(spots.location, ST_GeomFromText('" (make-point-2d 0 0) "'))") :as :distance]])
    (sort [:distance]))

Thoughts? Suggestions?

LauJensen commented 13 years ago

We debated this quite a bit when doing the initial implementation. What it came down to is that project is by definition non-additive. Since I cant offer deeper insights into RA than the creators of it, I decided to adhere by their standards. I think this is a good example of how easy it is to extend ClojureQL, without the need to modify ClojureQL itself.

Your thoughts?

r0man commented 13 years ago

I can understand that you want to keep it clean. On the other hand I can see me copying this fn to all of my ClojureQL projects. And I hate copying :) Maybe somthing for clojureql.utils?

LauJensen commented 13 years ago

Thats not a bad idea. We really should compile a list of things for cql.utils

ninjudd commented 13 years ago

What about making it so this does what you want?

(project [:* ["distance(...)" :as :distance]])
ninjudd commented 13 years ago

Agreed. I think :* should mean: all columns projected so far. If you want all columns in a particular table, then use :spots.*

ninjudd commented 13 years ago

Oops. Was trying to fix formatting on r0man's post, but accidentally hit delete (fingers are too big for my iPhone).

ninjudd commented 13 years ago

Here is r0man's message:

I tried this, but the spots-with-address is actually defined like this:

(def spots-with-address
  (-> spots
    (join (project (table :countries) [[:name :as :address_country]])
          (where (= :spots.country_id :countries.id)))
    (join (project (table :regions) [[:name :as :address_region]])
          (where (= :spots.region_id :regions.id)))
    (outer-join (project (table :addresses)
                         [[:locality :as :address_locality]
                          [:postal_code :as :address_postal_code]
                          [:street_address :as :address_street_address]
                          [:extended_address :as :address_extended_address]])
                :left (where (geo= :spots.location :addresses.location)))))

When using spots-with-address defined like above and applying the project operator in another function like this:

(project [:* ["distance(...)" :as :distance]])

... it selects everything from the spots table (spots.*) and the distance. All the other columns I have joined above get thrown away.

The shortcut :* in this example is really :spots.. Applying `:` doesn't honor my previous selection, but starts back looking at the spots table and not my new relation spots-with-address.

I think since :* is not defined in RA (you give all the attributes you want to project) it's questionalble if the shortcut :* applys to the relation (spots-with-address) at hand or the original one (spots). I think the first one is the route to true composability ;)