0f7bf2b9379386611ed6cc6feec81175025eff45
[quanweb.git] / main / db.go
1 package main
2
3 import (
4   "database/sql"
5   "fmt"
6   _ "github.com/lib/pq"
7   "strconv"
8   "sync"
9 )
10
11 // ---------------------------------------------------------------------------
12 type Book struct {
13   Id             int
14   Age            string  // recommended age, e.g. "beginner", "junior", "ya" (Young Adult), "adult"
15   AuthorGrouping string  // unique rendering of the author's name, used for internal grouping 
16   AuthorReading  string  // reading order of author's name, e.g. "Charles Dickens"
17   AuthorSort     string  // sort order of author's name, e.g. "Dickens, Charles"
18   DDC            string  // Dewey Decimal Classification
19   Description    string  // Back cover / inside flap blurb, describing the book
20   Genre          string  // e.g. "adventure", "historical", "mystery", "romance", "sf" (Science Fiction)
21   LCC            string  // Library of Congress Classification
22   SeriesName     string  
23   Title          string
24   Volume         string
25 }
26
27 // ---------------------------------------------------------------------------
28 type Field string
29 const (
30   Author, Series, Title Field = "aut", "ser", "tit"
31 )
32
33 func (f Field) String() string {
34   return string(f)
35 }
36
37 // ---------------------------------------------------------------------------
38 type SearchTerm struct {
39   Attribute Field
40   Text      string
41 }
42
43 var g_db *sql.DB = nil
44 var g_mutex = &sync.Mutex{}
45
46 // ============================================================================
47 func dbShutdown() {
48   if nil != g_db {
49     g_db.Close()
50   }
51 }
52
53 func getDb() (*sql.DB) {
54   if nil == g_db {
55     g_mutex.Lock()
56     defer g_mutex.Unlock()
57     if nil == g_db {
58       config := getConfig()
59       g_db = openDb(config.user, config.pass, config.dbName)
60     }
61   }
62
63   return g_db
64 }
65
66 func nsVal(ns sql.NullString) string {
67   if ns.Valid {
68     return ns.String
69   }
70   return ""
71 }
72
73 func openDb(user, pass, dbName string) (*sql.DB) {
74   db, err := sql.Open("postgres","user=" + user + " password=" + pass + " dbname=" + dbName + " sslmode=disable")
75   if nil != err {
76     report("Error:  DB arguments incorrect?", err)
77     return nil
78   }
79
80   err = db.Ping()
81   if nil != err {
82     report("Error:  could not connect to DB.", err)
83     db.Close()
84     return nil
85   }
86
87   return db
88 }
89
90 func queryBooksByIds(ids []int) []Book {
91   query := `SELECT s.age,a.grouping,a.reading,a.sort,c.ddc,b.description,s.genre,c.lcc,s.descr,b.title,b.volume
92             FROM Authors a 
93             INNER JOIN Books b ON a.id=b.author
94             LEFT OUTER JOIN Classifications c ON c.id=b.classification
95             LEFT OUTER JOIN Series s ON s.id=b.series
96             WHERE b.id=$1`
97
98   ps, err := getDb().Prepare(query)
99   if nil != err {
100     report("Error:  failed to prepare statement:  " + query, err)
101     return nil
102   }
103   defer ps.Close()
104
105   var count int = 0
106   for _, id := range ids {
107     if 0 != id {
108       count++
109     }
110   }
111     
112   res := make([]Book, count)
113   
114   count = 0
115   for _, id := range ids {
116     if 0 == id {
117       continue
118     }
119
120     row := ps.QueryRow(id)
121
122     var age, grouping, reading, sort, ddc, description, genre, lcc, name, title, volume sql.NullString
123
124     err = row.Scan(&age, &grouping, &reading, &sort, &ddc, &description, &genre, &lcc, &name, &title, &volume)
125     if err != nil {
126       report("Error:  Failed to read book:" + strconv.Itoa(id) + ":", err)
127     } else {
128       var b Book
129       b.Id = id
130       b.Age = nsVal(age)
131       b.AuthorGrouping = nsVal(grouping)
132       b.AuthorReading = nsVal(reading)
133       b.AuthorSort = nsVal(sort)
134       b.DDC = nsVal(ddc)
135       b.Description = nsVal(description)
136       b.Genre = nsVal(genre)
137       b.LCC = nsVal(lcc)
138       b.SeriesName = nsVal(name)
139       b.Title = nsVal(title)
140       b.Volume = nsVal(volume)
141
142       res[count] = b
143       count++
144     }
145   }
146
147   if count < len(res) {
148     res = res[:count]
149   }
150
151   return res
152 }
153
154 func queryIds(criteria []SearchTerm) []int {
155   query := "SELECT b.id FROM Books b" +
156            " INNER JOIN Authors a ON a.id=b.author" +
157            " LEFT OUTER JOIN Series s ON s.id=b.series"
158
159   args := make([]interface{}, len(criteria))
160
161   for i, criterion := range criteria {
162     if 0 == i {
163       query += " WHERE "
164     } else {
165       query += " AND "
166     }
167     switch criterion.Attribute {
168     case Author:
169       query += " a.grouping LIKE $" + strconv.Itoa(i + 1) 
170     case Series:
171       query += " s.descr LIKE $" + strconv.Itoa(i + 1)
172     case Title:
173       query += " b.title LIKE $" + strconv.Itoa(i + 1)
174     default:
175       report("Error:  unrecognized search field in queryIds():  " + criterion.Attribute.String(), nil)
176       return nil
177     }
178     args[i] = criterion.Text
179   }
180
181   res := []int{}
182
183   ps, err := getDb().Prepare(query)
184   if nil != err {
185     report("Failed to Prepare query:  " + query, err)
186     return nil
187   }
188   defer ps.Close()
189
190   var rows *sql.Rows
191   rows, err = ps.Query(args...)
192   if nil != err {
193     report("Failed to execute query:  " + query, err)
194     return nil
195   }
196   defer rows.Close()
197
198   for rows.Next(); rows.Next(); {
199     var id int
200     rows.Scan(&id)
201     res = append(res, id)
202   }
203
204   return res
205 }
206
207 func report(msg string, err error) {
208   fmt.Println("Error:  " + msg, err)
209 }