From e86773220937e92b55edcfc7b70a202ef59f33cb Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Fri, 20 Feb 2015 17:27:05 -0600 Subject: [PATCH] Created DB CRUD interface. --- .../nlsongs/db/.NLSongsDB.groovy.swp | Bin 12288 -> 0 bytes .../com/jdbernard/nlsongs/db/NLSongsDB.groovy | 199 +++++++++++++++++- .../.NLSongsContextListener.groovy.swp | Bin 12288 -> 0 bytes .../servlet/NLSongsContextListener.groovy | 19 +- src/main/sql/create-db.sql | 3 +- 5 files changed, 197 insertions(+), 24 deletions(-) delete mode 100644 src/main/groovy/com/jdbernard/nlsongs/db/.NLSongsDB.groovy.swp delete mode 100644 src/main/groovy/com/jdbernard/nlsongs/servlet/.NLSongsContextListener.groovy.swp diff --git a/src/main/groovy/com/jdbernard/nlsongs/db/.NLSongsDB.groovy.swp b/src/main/groovy/com/jdbernard/nlsongs/db/.NLSongsDB.groovy.swp deleted file mode 100644 index 314c8d3979376e551d537229b8050c7ae5649379..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI2O>Z1E7{^^WlokpIBu)sKv{Gk7){{^WD3G=^3A7?1D4P_C?W&rc@g`1ZXQsAy z^TvXNkPza^7pTMq1jlmXhTtPW#i2n6xBz!9RQx@!$+itbJ)zjrKa;Va$Is)RUskJi zd;H>sdHMo-j-WhB$k*Thw)FPlr^t`b6S5Yxxr{s+n6`Zi?CMHtuO9IJK4#?IvO_nK z@f!EFawEQ84@HO9RUEA-S4rRPdLnXHWE}T5T|e%+l~)vIRzcf+W3FKwX3nw9a=*B? z%@R291bVttfAJVO_3XsBVR3xqY5MqM%Lg8d1z-tS0+xU!UIypq% z!+`J02YoJo9vXV)ul8XHSOS)SC143y0+xU!U z`~m$AeFa^Iu0b2n9CSbQ?L&lo4t)lF3|)j6RELg2I}f4_YC~tC8stE?juP@K^b2$y z`Uvu&C!xQN5b_hW1Kof=fdsS)%|J(>pE3SVq4%M8pdMtHoPunz1S|ndz!I=IvCuWKcXaBH76LWPqdgHhav6}(rqd< z^Swl9FBDtcsnG#ji$&zrh{>xc4J}2qlBJ0Rk)P>nyhWcOg^77)X3dsqg^lB66*Vfk zAIkuqHKvkKXy@eRlT`#6uQTrXtH`D_90P&+X-dle7ux3HW0nH?_Cs%Li{TdJg-B^H z@{xLKiWe<>np5u#C@D2!_?uCbwy6!nT~g$r1y=?A!Z(9oL8zi5 zbaS$mc9fGgZK>Z&6Y7V>CCuNqhr z^h^1sl^WUa@@Bn!7DommWu<%UWQ2ZPHFJ^0kN~ zRmJ9^;`u_LQ7;U4O@diB*bK!wp-zL#zVLamaf=Mn4O6u3VKma1otvFrqNDWH#f5pQ zGJi$iIzPKOOGSY3o*5aVO%b%Hr|6te`sHkop03V;JM2&RsjDID2&ptSu9F5$dhHN< zJWqsnL=@!zJ0jAER&L~!N@19@$i4e9vQn!(9mMN6?r`=5p7t7XPx_qgsvqE!3yUBv zdQ~Wfe2^7R9@@AXI+YWSPLkWv)UaxTef5$WsOe!k*-T87S}a=yr?gIEwHQgIxn+o`1z#;dkK>w8=lQvcLKO diff --git a/src/main/groovy/com/jdbernard/nlsongs/db/NLSongsDB.groovy b/src/main/groovy/com/jdbernard/nlsongs/db/NLSongsDB.groovy index c99b805..2198b23 100644 --- a/src/main/groovy/com/jdbernard/nlsongs/db/NLSongsDB.groovy +++ b/src/main/groovy/com/jdbernard/nlsongs/db/NLSongsDB.groovy @@ -3,9 +3,9 @@ package com.jdbernard.nlsongs.db import java.sql.Connection import java.sql.PreparedStatement import java.sql.ResultSet +import java.text.SimpleDateFormat import com.zaxxer.hikari.HikariDataSource -import groovy.sql.GroovyRowResult import groovy.sql.Sql import groovy.transform.CompileStatic @@ -24,39 +24,216 @@ public class NLSongsDB { public void shutdown() { dataSource.shutdown() } - // ### Services - public Service findService(int id) { - GroovyRowResult row = sql.firstRow( - "SELECT * FROM services WHERE id = ?", [id] as List) - if (row) return (Service) recordToModel(row, Service) - else return null } + /// ### Common + public def save(def model) { + if (model.id > 0) return update(model) + else { + if (create(model) > 0) return model + else return null } } + + + /// ### Services + public Service findService(int id) { + def row = sql.firstRow("SELECT * FROM services WHERE id = ?", [id]) + recordToModel(row, Service) } + + public List findAllServices() { + return sql.rows("SELECT * FROM services"). + collect { recordToModel(it, Service) } } + + public List findServicesForSongId(int songId) { + return sql.rows("SELECT svc.* " + + "FROM services svc JOIN " + + "performances prf ON " + + "svc.id = prf.service_id " + + "WHERE prf.song_id = ?", [songId]). + collect { recordToModel(it, Service) } } + + public List findServicesAfter(Date d) { + def sdf = new SimpleDateFormat("YYYY-MM-dd") + return sql.rows('SELECT * FROM services WHERE "date" > ?', + [sdf.format(d)]). collect { recordToModel(it, Service) } } + + public List findServicesBefore(Date d) { + def sdf = new SimpleDateFormat("YYYY-MM-dd") + return sql.rows('SELECT * FROM services WHERE "date" < ?', + [sdf.format(d)]).collect { recordToModel(it, Service) } } + + public List findServicesBetween(Date b, Date e) { + def sdf = new SimpleDateFormat("YYYY-MM-dd") + return sql.rows('SELECT * FROM services WHERE "date" BETWEEN ? AND ?', + [sdf.format(b),sdf.format(e)]). + collect { recordToModel(it, Service) } } + + public Service create(Service service) { + def sdf = new SimpleDateFormat("YYYY-MM-dd") + int newId = sql.executeInsert( + 'INSERT INTO services ("date", service_type) VALUES (?, ?)', + [sdf.format(service.date), service.serviceType])[0][0] + + service.id = newId + return service } + + public int update(Service service) { + def sdf = new SimpleDateFormat("YYYY-MM-dd") + return sql.executeUpdate( + 'UPDATE services SET "date" = ?, service_type = ? WHERE id = ?', + [sdf.format(service.date), service.serviceType, service.id] ) } + + /// ### Songs + public Song findSong(int id) { + def row = sql.firstRow("SELECT * FROM songs WHERE id = ?", [id]) + return recordToModel(row, Song) } + + public List findAllSongs() { + return sql.rows("SELECT * FROM songs"). + collect { recordToModel(it, Song) } } + + public List findSongsForServiceId(int serviceId) { + return sql.rows("SELECT sng.* " + + "FROM songs sng JOIN " + + "performances prf ON " + + "sng.id = prf.song_id " + + "WHERE prf.service_id = ?", [serviceId]). + collect { recordToModel(it, Song) } } + + public List findSongsByName(String name) { + return sql.rows("SELECT * FROM songs WHERE name = ?", [name]). + collect { recordToModel(it, Song) } } + + public List findSongsLikeName(String name) { + return sql.rows("SELECT * FROM songs WHERE name LIKE ?", ["%$name%"]). + collect { recordToModel(it, Song) } } + + public List findSongsByArtist(String artist) { + return sql.rows("SELECT * FROM songs WHERE artists LIKE ?", ["%$artist%"]). + collect { recordToModel(it, Song) } } + + public List findSongsByNameAndArtist(String name, String artist) { + return sql.rows("SELECT * FROM songs WHERE name = ? AND artists LIKE ?", + [name,"%$artist%"]).collect { recordToModel(it, Song) } } + + public Song create(Song song) { + int newId = sql.executeInsert( + "INSERT INTO songs (name, artists) VALUES (?, ?)", + [song.name, wrapArtists(song.artists)])[0][0] + + song.id = newId + return song } + + public int update(Song song) { + return sql.executeUpdate( + "UPDATE songs SET name = ?, artists = ? WHERE id = ?", + [song.name, wrapArtists(song.artists), song.id] ) } + + /// ### Performances + public Performance findPerformance(int serviceId, int songId) { + def perf = sql.firstRow( + "SELECT * FROM performances WHERE service_id = ? AND song_id = ?", + [serviceId, songId]) + return recordToModel(perf, Performance) } + + public List findPerformancesForServiceId(int serviceId) { + return sql.rows("SELECT * FROM performances WHERE service_id = ?", + [serviceId]).collect { recordToModel(it, Performance) } } + + public List findPerformancesForSongId(int songId) { + return sql.rows("SELECT * FROM performances WHERE song_id = ?", + [songId]).collect { recordToModel(it, Performance) } } + + public Performance create(Performance perf) { + // TODO: handle constraint violation (same service and song ids) + sql.executeInsert( + "INSERT INTO performances (service_id, song_id, pianist, " + + "organist, bassist, drummer, guitarist, leader) VALUES " + + "(?, ?, ?, ?, ?, ?, ?, ?)", [perf.serviceId, perf.songId, + perf.pianist, perf.organist, perf.bassist, perf.drummer, + perf.guitarist, perf.leader]) + return perf } + + public int update(Performance perf) { + // TODO: handle constraint violation (same service and song ids) + return sql.executeUpdate( + "UPDATE performances SET pianist = ?, organist = ?, " + + "bassist = ?, drummer = ?, guitarist = ?, leader = ? " + + "WHERE service_id = ? AND song_id = ?", + [perf.pianist, perf.organist, perf.bassist, perf.drummer, + perf.guitarist, perf.leader, perf.serviceId, perf.songId]) } + + public int delete(Performance perf) { + sql.execute( + "DELETE FROM performances WHERE service_id = ? AND song_id = ?") + return sql.getUpdateCount() } + + /// ### API Keys + public ApiKey findKey(String key) { + def row = sql.firstRow("SELECT * FROM api_keys WHERE key = ?", [key]) + return recordToModel(row, ApiKey) } + + public ApiKey save(ApiKey apiKey) { + if (findKey(apiKey.key)) { + update(apiKey) + return apiKey } + else return create(apiKey) } + + public ApiKey create(ApiKey apiKey) { + sql.executeInsert( + "INSERT INTO api_keys (key, description) VALUES (?, ?)", + [apiKey.key, apiKey.description] ) + + return apiKey } + + public int update(ApiKey apiKey) { + return sql.executeUpdate( + "UPDATE api_keys SET description = ? WHERE key = ?", + [apiKey.description, apiKey.key]) } + + /// ### User management + // TODO + + /// ### Utility functions + static def recordToModel(def record, Class clazz) { + if (record == null) return null - // #### Utility functions - static def recordToModel(def row, Class clazz) { def model = clazz.newInstance() - row.each { recordKey, v -> + record.each { recordKey, v -> def pts = recordKey.split('_') def modelKey = pts.length == 1 ? pts[0] : pts[0] + pts[1..-1].collect { it.capitalize() }.join() + + // Hacky, there should be a better way + if (recordKey == "artists") v = unwrapArtists(v); + model[modelKey] = v } return model } static def modelToRecord(def model) { + if (model == null) return null + def record = [:] model.properties.each { modelKey, v -> if (modelKey == "class") return def recordKey = modelKey. replaceAll(/(\p{javaUpperCase})/, /_$1/).toLowerCase() + + // Hack + if (modelKey == "artists") v = wrapArtists(v) + record[recordKey] = v } return record } + public static List unwrapArtists(String artists) { + return artists.split(';') as List } + + public static String wrapArtists(List artists) { + return artists.join(':') } /* static Object recordToModel(GroovyRowResult row, Class clazz) { Object model = clazz.newInstance() - row.each { recordKey, v -> + row.each { recordKey, v -> String[] pts = ((String) recordKey).split('_') String modelKey = pts[0] + pts[1..-1].collect { it.capitalize() }.join() diff --git a/src/main/groovy/com/jdbernard/nlsongs/servlet/.NLSongsContextListener.groovy.swp b/src/main/groovy/com/jdbernard/nlsongs/servlet/.NLSongsContextListener.groovy.swp deleted file mode 100644 index 28b960ecd20bd02685951468d64132b3388c5fec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI2%Wl&^6ov}R(Etl4sIddIq6@m92@*)Ku<4v9zL*m0bsgtTG@J(hl1&YatPGh-?8sh4kGxysMmvkb2zjO~8^ zefj2|@uKS)5}$NiUk8N0I&i^uQ(ga7|u4m0){ z^$zs}bq{qHHHVr-9YTFO#MoEVbJR1`GOC1{LG40)IEenJ7bqX)pne`;>qr~Qn5M7>76LV2hK)CJTJtnV%A1?nm49xAQ*A7oA?69FPX1c(3;AOb|-|0VE{ z@$7Q@J({+HAnL$HW0b774HT@jqqDkb75UIO_sRxs-D(>z>V}14K9u!-Ru_3@#25G= zNfXSZ47so4@LCp;n-=i2i+=2+Y@3E);E+$IJ3! z1<--e*t*}Jn)RioW^udjOP6;d=?%)Pf;K8z1FujUv%i$Y=tnB@2ft;(=#yqQGvJVb z%qyEj30rP?{(GV2=^<1EQ&Aucq_a(p^az%bDPNYWLdh!fsHHeK#=-d~u{RDCT5h9R z9M+UP+QRdO&NoyPgEDIctF0k{0qTZ=xMl*Qtr>0^)dp)=<+K2C=2sDJ3u^cMB^gMQ z#5h}?o&T#Cavf6tx$+j>-6Zv<^(R}YkFMeit*?myke{`Dy?WismoJ@ZjKkeXd4Cv2 zWZ0xV$-BQ0xC9l{Z5;slKLeG8y Ddu}o4 diff --git a/src/main/groovy/com/jdbernard/nlsongs/servlet/NLSongsContextListener.groovy b/src/main/groovy/com/jdbernard/nlsongs/servlet/NLSongsContextListener.groovy index 828078a..d260388 100644 --- a/src/main/groovy/com/jdbernard/nlsongs/servlet/NLSongsContextListener.groovy +++ b/src/main/groovy/com/jdbernard/nlsongs/servlet/NLSongsContextListener.groovy @@ -12,31 +12,26 @@ import com.zaxxer.hikari.HikariDataSource public final class NLSongsContextListener implements ServletContextListener { public void contextInitialized(ServletContextEvent event) { - context = event.servletContext + def context = event.servletContext // Create the pooled data source - HikariConfig hcfg = new HikariConfig() - hcfg.username = 'TODO' - hcfg.password = 'TODO' - hcfg.dataSourceClassName = 'TODO' - hcfg.addDataSourceProperty('cachePrepStmts', 'true') - hcfg.addDataSourceProperty('prepStmtCacheSize', '250') - hcfg.addDataSourceProperty('prepStmtCacheSqlLimit', '2048') - hcfg.addDataSourceProperty('useServerPrepStmts', 'true') + HikariConfig hcfg = new HikariConfig( + context.getInitParameter("context.config.file")) HikariDataSource hds = new HikariDataSource(hcfg) // Create the NLSonsDB instance. NLSongsDB songsDB = new NLSongsDB(hds) - context.setAttribute('songsDB', songsDB) } + context.setAttribute('songsDB', songsDB) + NLSongsContext.songsDB = songsDB } public void contextDestroyed(ServletContextEvent event) { - context = event.servletContext + def context = event.servletContext // Shutdown the Songs DB instance (it will shut down the data source). NLSongsDB songsDB = context.getAttribute('songsDB') - songsDB.shutdown() + if (songsDB) songsDB.shutdown() context.removeAttribute('songsDB') } } diff --git a/src/main/sql/create-db.sql b/src/main/sql/create-db.sql index 0245745..c892650 100644 --- a/src/main/sql/create-db.sql +++ b/src/main/sql/create-db.sql @@ -4,12 +4,13 @@ -- PostgreSQL database creation sript. -- DROP DATABASE IF EXISTS nlsongs; -CREATE DATABASE IF NOT EXISTS nlsongs +CREATE DATABASE nlsongs ENCODING = 'UTF8' LC_COLLATE = 'en_US.UTF-8' LC_CTYPE = 'en_US.UTF-8' CONNECTION LIMIT = 1; +\c nlsongs -- Services table DROP TABLE IF EXISTS services;