I like the idea of DB convention interfaces but that wasn't what I was thinking about specifically.
For tables, fields and relations in an application there may be conventions established by a framework but there are also likely to be exceptions on a table-by-table basis. An schema interface provides a mechanism to default to a convention but also make an exception when necessary. (Another case this supports is querying the database schema tables dynamically rather than hard coding) I have some old code lying around from when I was experimenting with this a few weeks bacl, not saying it's perfect but perhaps it'll explain better. In this case, pk defaults to :Id but in the case of the user table it is :UserId. (defrecord SimpleSchema [pks rels] IDBSchema (pk [_ table-name] (get pks table-name :Id)) (rel [_ from-table rel-name] (if-let [[to-table field-mapping] (get-in rels [from-table rel-name])] {:from from-table :to to-table :on field-mapping})) (has-many? [db-schema table-name rel-name] (if-let [{:keys [on]} (rel db-schema table-name rel-name)] (not= [(pk db-schema table-name)] (vals on))))) (let [test-schema (map->TestSchema {:pks {:Races :Id :Meetings :Id :Users :UserId} :rels {:Races {:Meeting [:Meetings {:MeetingId :Id}]} :Meetings {:Races [:Races {:Id :MeetingId}]}}})] ...) On Monday, 4 April 2016 19:25:16 UTC+10, Krzysiek Herod wrote: > > Thanks :-) > > I was thinking about using protocols for defining interfaces of different > types of databases (e.g. MySQL, PostgreSQL), but with conventions I'm not > sure. > Most of the time conventions would not change, and once in a while > somebody will want to change only one of them (probably foreign-key). I > wouldn't want him to need to define a new record with all the conventions > (foreign-key, private-key) in such case. Or did I get it wrong? > > On Thursday, March 31, 2016 at 2:59:56 AM UTC+2, Oliver George wrote: >> >> Fantastic. Keep up the good work. >> >> Schema conventions could be made flexible with a protocol. >> >> (defprotocol IDBSchema (pk [_ table-name]) (rel [_ table-name rel-name]) >> (has-many? [_ table-name rel-name])) >> >> >> >> >> On Thursday, 31 March 2016 09:23:00 UTC+11, Krzysiek Herod wrote: >>> >>> I just released version 0.3.0 of Relational Mapper. Customization of >>> keys and foreign keys is done now, as well as possibility to specify >>> relation with a different name than the corresponding table ( >>> https://github.com/netizer/relational_mapper#different-name-of-an-association-than-a-table-name). >>> >>> >>> >>> @Oliver George: your example with SupervisorId, AnalystId would work >>> now, but have in mind that postgreSQL by default lowercases column names, >>> so I'd still recommend supervisor_id and analyst_id. >>> >>> Cheers, >>> Krzysiek >>> >>> On Tuesday, March 1, 2016 at 11:35:46 PM UTC+1, Oliver George wrote: >>>> >>>> >>>> Both those ideas seem sensible to me. Look foward to hearing more. >>>> >>>> On Tuesday, 1 March 2016 23:38:43 UTC+11, Krzysiek Herod wrote: >>>>> >>>>> I went through the paper very briefly, so I might be wrong, but from >>>>> the first look it seems like the algorithm would generate the actual SQL >>>>> queries . If so, although the idea seems interesting, I wouldn't go in >>>>> this >>>>> direction because of the loss of flexibility for the user of the library. >>>>> For example sometimes it happens, that the slowest SQL query called by >>>>> the >>>>> application is the one where database picked a sub-optimal index, or >>>>> sometimes combining data by adding one more join has a great performance >>>>> impact. >>>>> >>>>> Actually I was thinking about giving the programmer more flexibility, >>>>> and maybe splitting the whole code into query part and stitch part, so >>>>> the >>>>> developer would choose the most efficient queries, but the stitching part >>>>> would put all those data together (with deep result structure). I'm >>>>> curious >>>>> what do you think about this direction. I'll comment on your issue ( >>>>> https://github.com/netizer/relational_mapper/issues/3) with more >>>>> details about the idea. >>>>> >>>>> Cheers, >>>>> Krzysiek >>>>> >>>>> On Tue, Mar 1, 2016 at 6:03 AM, Oliver George <oli...@condense.com.au> >>>>> wrote: >>>>> >>>>>> Awesome, thanks. >>>>>> >>>>>> I did a little research last night looking for techniques for turning >>>>>> recursive queries into efficient SQL queries. I came across an >>>>>> interesting >>>>>> paper: >>>>>> >>>>>> Cheney, James, Sam Lindley, and Philip Wadler. "Query shredding: >>>>>> Efficient relational evaluation of queries over nested multisets >>>>>> (extended >>>>>> version)."*arXiv preprint arXiv:1404.7078* (2014). >>>>>> >>>>>> >>>>>> The details are obscured behind some intimidating equations but the >>>>>> concept seems pretty simple: The nested query gets normalised and then >>>>>> shredded into a set of sql queries and the results of those queries are >>>>>> stitched back together. >>>>>> >>>>>> There seem to be two version >>>>>> <https://scholar.google.com.au/scholar?hl=en&q=Query+shredding%3A+Efficient+relational+evaluation+of+queries+over+nested+multisets+%28extended+version%29&btnG=&as_sdt=1%2C5&as_sdtp=> >>>>>> >>>>>> of the paper. This one looks to be more detailed (26 pages): >>>>>> >>>>>> https://scholar.google.com/citations?view_op=view_citation&hl=en&user=Iz-3VFQAAAAJ&sortby=pubdate&citation_for_view=Iz-3VFQAAAAJ:9pM33mqn1YgC >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> On Monday, 29 February 2016 21:06:23 UTC+11, Krzysiek Herod wrote: >>>>>>> >>>>>>> Thanks a lot for detailed notes. >>>>>>> >>>>>>> The problem with customization of foreign keys is on my TODO list. I >>>>>>> hope to fix that before releasing version 1.0. That would solve the >>>>>>> problem >>>>>>> with SupervisorId and AnalystId. >>>>>>> >>>>>>> What you said about deeper result structure (race -> meeting -> >>>>>>> venue) is very inspiring. You can't do that with this library (you can >>>>>>> fetch records with their - potentially indirect - relations, but those >>>>>>> relations won't have own relations included), but definitely it's >>>>>>> something >>>>>>> worth considering. I added it to my TODO list in the README but I don't >>>>>>> have a clear idea about how to do it well yet. >>>>>>> >>>>>>> Cheers, >>>>>>> Krzysiek >>>>>>> >>>>>>> On Monday, February 29, 2016 at 12:54:31 PM UTC+8, Oliver George >>>>>>> wrote: >>>>>>>> >>>>>>>> Oops, one more. >>>>>>>> >>>>>>>> There was also a Users table (Id, Username, ...) >>>>>>>> >>>>>>>> I didn't see a way to handle join from Races to Users based on >>>>>>>> SupervisorId and AnalystId. >>>>>>>> >>>>>>>> >>>>>>>> On Monday, 29 February 2016 15:52:48 UTC+11, Oliver George wrote: >>>>>>>>> >>>>>>>>> Thanks for the details. >>>>>>>>> >>>>>>>>> I did a little experimenting and it works as advertised. Notes >>>>>>>>> below show what I did and found. >>>>>>>>> >>>>>>>>> I was interested to see if this might be suitable as a simple >>>>>>>>> om.next remote for a relational database. Potentially fanciful but >>>>>>>>> it's a >>>>>>>>> topic of interest for me at the moment. >>>>>>>>> >>>>>>>>> I used an existing database so I had a semi interesting dataset to >>>>>>>>> play with. >>>>>>>>> >>>>>>>>> Races (Id, RaceNumber, RaceTime, MeetingId, SupervisorId, >>>>>>>>> AnalystId...) >>>>>>>>> Meetings (Id, MeetingDate, MeetingTypeId, VenueId, JurisdictionId, >>>>>>>>> ...) >>>>>>>>> Venues (Id, Name) >>>>>>>>> Jurisdiction (Id, Name, Code) >>>>>>>>> >>>>>>>>> >>>>>>>>> The table and foreign key naming conventions didn't match so I >>>>>>>>> created views for each table. If that was configurable then you'd >>>>>>>>> open >>>>>>>>> yourself to a wider audience. (e.g. MeetingId vs meetings_id) >>>>>>>>> >>>>>>>>> It was easy to setup some associations >>>>>>>>> >>>>>>>>> (def associations >>>>>>>>> {:meeting {:race :has-many >>>>>>>>> :jurisdiction :belongs-to >>>>>>>>> :venue :belongs-to} >>>>>>>>> :race {:meeting :belongs-to >>>>>>>>> :jurisdiction [:through :meeting :belongs-to]} >>>>>>>>> :venue {}}) >>>>>>>>> >>>>>>>>> My queries all worked as expected. >>>>>>>>> >>>>>>>>> (find-one db-state :meeting #{:race} [[:= :meeting.id 5617]]) >>>>>>>>> (find-one db-state :meeting #{:venue} [[:= :meeting.id 5617]]) >>>>>>>>> (find-one db-state :race #{:meeting :jurisdiction} [[:= :race.id >>>>>>>>> 42792]]) >>>>>>>>> >>>>>>>>> I couldn't see how I might pull data which requires three levels >>>>>>>>> of information (e.g. race -> meeting -> venue). I didn't dig deep >>>>>>>>> enough >>>>>>>>> to be sure. >>>>>>>>> >>>>>>>>> Incidentally, in case you haven't come across the datomic pull >>>>>>>>> inspired om.next remote pull syntax this is what it might look like: >>>>>>>>> >>>>>>>>> [{:meeting [:race]}] >>>>>>>>> (find-one db-state :meeting #{:race} []) >>>>>>>>> >>>>>>>>> [({:meeting [:race]} [:= :meeting.id 5617])] >>>>>>>>> (find-one db-state :meeting #{:race} [[:= :meeting.id 5617]]) >>>>>>>>> >>>>>>>>> [{:meeting [:venue]}] >>>>>>>>> (find-one db-state :meeting #{:venue} [[:= :meeting.id 5617]]) >>>>>>>>> >>>>>>>>> [{:race [{:meeting [{:venue :jurisdiction}]}]}] >>>>>>>>> >>>>>>>>> Not prettier necessarily but allows for composing multiple queries >>>>>>>>> into a request and for drilling deeper into available data. >>>>>>>>> >>>>>>>>> cheers, Oliver >>>>>>>>> >>>>>>>>> >>>>>>>>> >>>>>>>>> >>>>>>>>> >>>>>>>>> >>>>>>>>> >>>>>>>>> On Sunday, 28 February 2016 20:02:15 UTC+11, Krzysiek Herod wrote: >>>>>>>>>> >>>>>>>>>> Thanks Oliver for the feedback, >>>>>>>>>> >>>>>>>>>> actually I came up with the idea of relational_mapper while >>>>>>>>>> working on a project in which I had one "data-model" that contained >>>>>>>>>> all the >>>>>>>>>> database related information, but the database related code >>>>>>>>>> contained a lot >>>>>>>>>> of features, and I really like working with small, focused clojure >>>>>>>>>> libraries, so in the end relational_mapper is as small as I could >>>>>>>>>> think of >>>>>>>>>> it. >>>>>>>>>> >>>>>>>>>> Also as you can see in this commit: >>>>>>>>>> https://github.com/netizer/relational_mapper/commit/6b4d79f92570bf723e4092d329978d484c01d2ab#diff-2b44df73d826687086fd1972295f8bd0L8 >>>>>>>>>> >>>>>>>>>> I actually was storing both: relations and fields in the same >>>>>>>>>> structure, >>>>>>>>>> but I changed that because I needed "fields" only for migrations >>>>>>>>>> that I >>>>>>>>>> used in tests, and because the whole structure was unnecessarily >>>>>>>>>> complex >>>>>>>>>> (it was much easier to make mistake modifying the >>>>>>>>>> fields/associations >>>>>>>>>> structure). >>>>>>>>>> >>>>>>>>>> Relational Mapper is meant only for reading data because whenever >>>>>>>>>> I tried to use complex structures to write data, I was unhappy with >>>>>>>>>> the >>>>>>>>>> result (often you have to update indexes of related records after >>>>>>>>>> one of >>>>>>>>>> them - with auto-increment field - is created, and there is a >>>>>>>>>> problem of >>>>>>>>>> determining if the related record has to be created or updated). >>>>>>>>>> >>>>>>>>>> I didn't write compare/contrast points because I couldn't find >>>>>>>>>> similar libraries in clojure. I mentioned ActiveRecord in README >>>>>>>>>> mostly >>>>>>>>>> because of the wording in types of relations, but even ActiveRecord >>>>>>>>>> is very >>>>>>>>>> far from Relational Mapper (it's much bigger, and has features that >>>>>>>>>> go way >>>>>>>>>> beyond simple relational mapping). >>>>>>>>>> >>>>>>>>>> Thanks again, >>>>>>>>>> Krzysiek >>>>>>>>>> >>>>>>>>>> On Sunday, February 28, 2016 at 10:54:57 AM UTC+8, Oliver George >>>>>>>>>> wrote: >>>>>>>>>>> >>>>>>>>>>> >>>>>>>>>>> Seems pretty nice to me. Like a light weight version of the >>>>>>>>>>> Django's migrate and queryset features which build on model >>>>>>>>>>> definitions. >>>>>>>>>>> >>>>>>>>>>> It seems like this would allow me to define a database schema >>>>>>>>>>> (tables, relations and fields) as data and use it to both create >>>>>>>>>>> the >>>>>>>>>>> database and run select/insert/update/delete queries against it. >>>>>>>>>>> >>>>>>>>>>> Is that your intention for the library? >>>>>>>>>>> >>>>>>>>>>> I've not explored the options in this space before. It might be >>>>>>>>>>> good to have a section in the README pointing out to other related >>>>>>>>>>> tools >>>>>>>>>>> with some compare/contrast points. >>>>>>>>>>> >>>>>>>>>>> Thanks. >>>>>>>>>>> >>>>>>>>>>> >>>>>>>>>>> On Friday, 26 February 2016 17:51:10 UTC+11, Krzysiek Herod >>>>>>>>>>> wrote: >>>>>>>>>>>> >>>>>>>>>>>> I created Relational Mapper, for situations where there is a >>>>>>>>>>>> relational database with certain amount of relations between >>>>>>>>>>>> tables and >>>>>>>>>>>> it's just not cool to fetch data from each table separately nor to >>>>>>>>>>>> write >>>>>>>>>>>> custom code for each such project so, with this library, you can >>>>>>>>>>>> just call: >>>>>>>>>>>> >>>>>>>>>>>> (find_all db-state :posts #{:authors :attachments} [:= post.id 1]) >>>>>>>>>>>> >>>>>>>>>>>> and assuming you have appropriate relations between these tables, >>>>>>>>>>>> you'll get: >>>>>>>>>>>> >>>>>>>>>>>> {:posts {:title "Christmas" >>>>>>>>>>>> :body "Merry Christmas!" >>>>>>>>>>>> :id 1 >>>>>>>>>>>> :authors_id 10 >>>>>>>>>>>> :authors {:name "Rudolf" :id 10} >>>>>>>>>>>> :attachments [{:name "rudolf.png" :id 100 :posts_id 1} >>>>>>>>>>>> {:name "santa.png" :id 101 :posts_id 1}] >>>>>>>>>>>> >>>>>>>>>>>> >>>>>>>>>>>> The code is here: https://github.com/netizer/relational_mapper >>>>>>>>>>>> >>>>>>>>>>>> Please, guys, let me know what do you think, and if you have >>>>>>>>>>>> any ideas about improvements. If somebody would be so kind to take >>>>>>>>>>>> a look >>>>>>>>>>>> at the code, it would be awesome to read some feedback. >>>>>>>>>>>> >>>>>>>>>>>> Krzysiek HerĂ³d >>>>>>>>>>>> >>>>>>>>>>> -- >>>>>> You received this message because you are subscribed to the Google >>>>>> Groups "Clojure" group. >>>>>> To post to this group, send email to clo...@googlegroups.com >>>>>> Note that posts from new members are moderated - please be patient >>>>>> with your first post. >>>>>> To unsubscribe from this group, send email to >>>>>> clojure+u...@googlegroups.com >>>>>> For more options, visit this group at >>>>>> http://groups.google.com/group/clojure?hl=en >>>>>> --- >>>>>> You received this message because you are subscribed to a topic in >>>>>> the Google Groups "Clojure" group. >>>>>> To unsubscribe from this topic, visit >>>>>> https://groups.google.com/d/topic/clojure/g6Yxk-o6_rQ/unsubscribe. >>>>>> To unsubscribe from this group and all its topics, send an email to >>>>>> clojure+u...@googlegroups.com. >>>>>> For more options, visit https://groups.google.com/d/optout. >>>>>> >>>>> >>>>> -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.