database.go (view raw)
1//
2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
3//
4// Permission to use, copy, modify, and distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16package main
17
18import (
19 "database/sql"
20 "fmt"
21 "log"
22 "strconv"
23 "strings"
24 "time"
25
26 "humungus.tedunangst.com/r/webs/login"
27)
28
29func butwhatabout(name string) (*WhatAbout, error) {
30 row := stmtWhatAbout.QueryRow(name)
31 var user WhatAbout
32 var options string
33 err := row.Scan(&user.ID, &user.Name, &user.Display, &user.About, &user.Key, &options)
34 user.URL = fmt.Sprintf("https://%s/%s/%s", serverName, userSep, user.Name)
35 user.SkinnyCSS = strings.Contains(options, " skinny ")
36 return &user, err
37}
38
39func gethonkers(userid int64) []*Honker {
40 rows, err := stmtHonkers.Query(userid)
41 if err != nil {
42 log.Printf("error querying honkers: %s", err)
43 return nil
44 }
45 defer rows.Close()
46 var honkers []*Honker
47 for rows.Next() {
48 var f Honker
49 var combos string
50 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor, &combos)
51 f.Combos = strings.Split(strings.TrimSpace(combos), " ")
52 if err != nil {
53 log.Printf("error scanning honker: %s", err)
54 return nil
55 }
56 honkers = append(honkers, &f)
57 }
58 return honkers
59}
60
61func getdubs(userid int64) []*Honker {
62 rows, err := stmtDubbers.Query(userid)
63 if err != nil {
64 log.Printf("error querying dubs: %s", err)
65 return nil
66 }
67 defer rows.Close()
68 var honkers []*Honker
69 for rows.Next() {
70 var f Honker
71 err = rows.Scan(&f.ID, &f.UserID, &f.Name, &f.XID, &f.Flavor)
72 if err != nil {
73 log.Printf("error scanning honker: %s", err)
74 return nil
75 }
76 honkers = append(honkers, &f)
77 }
78 return honkers
79}
80
81func allusers() []login.UserInfo {
82 var users []login.UserInfo
83 rows, _ := opendatabase().Query("select userid, username from users")
84 defer rows.Close()
85 for rows.Next() {
86 var u login.UserInfo
87 rows.Scan(&u.UserID, &u.Username)
88 users = append(users, u)
89 }
90 return users
91}
92
93func getxonk(userid int64, xid string) *Honk {
94 row := stmtOneXonk.QueryRow(userid, xid)
95 return scanhonk(row)
96}
97
98func getbonk(userid int64, xid string) *Honk {
99 row := stmtOneBonk.QueryRow(userid, xid)
100 return scanhonk(row)
101}
102
103func getpublichonks() []*Honk {
104 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
105 rows, err := stmtPublicHonks.Query(dt)
106 return getsomehonks(rows, err)
107}
108func gethonksbyuser(name string, includeprivate bool) []*Honk {
109 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
110 whofore := 2
111 if includeprivate {
112 whofore = 3
113 }
114 rows, err := stmtUserHonks.Query(whofore, name, dt)
115 return getsomehonks(rows, err)
116}
117func gethonksforuser(userid int64) []*Honk {
118 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
119 rows, err := stmtHonksForUser.Query(userid, dt, userid, userid)
120 return getsomehonks(rows, err)
121}
122func gethonksforuserfirstclass(userid int64) []*Honk {
123 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
124 rows, err := stmtHonksForUserFirstClass.Query(userid, dt, userid, userid)
125 return getsomehonks(rows, err)
126}
127func gethonksforme(userid int64) []*Honk {
128 dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
129 rows, err := stmtHonksForMe.Query(userid, dt, userid)
130 return getsomehonks(rows, err)
131}
132func gethonksbyhonker(userid int64, honker string) []*Honk {
133 rows, err := stmtHonksByHonker.Query(userid, honker, userid)
134 return getsomehonks(rows, err)
135}
136func gethonksbyxonker(userid int64, xonker string) []*Honk {
137 rows, err := stmtHonksByXonker.Query(userid, xonker, xonker, userid)
138 return getsomehonks(rows, err)
139}
140func gethonksbycombo(userid int64, combo string) []*Honk {
141 combo = "% " + combo + " %"
142 rows, err := stmtHonksByCombo.Query(userid, combo, userid)
143 return getsomehonks(rows, err)
144}
145func gethonksbyconvoy(userid int64, convoy string) []*Honk {
146 rows, err := stmtHonksByConvoy.Query(userid, userid, convoy)
147 honks := getsomehonks(rows, err)
148 for i, j := 0, len(honks)-1; i < j; i, j = i+1, j-1 {
149 honks[i], honks[j] = honks[j], honks[i]
150 }
151 return honks
152}
153func gethonksbysearch(userid int64, q string) []*Honk {
154 q = "%" + q + "%"
155 rows, err := stmtHonksBySearch.Query(userid, q)
156 honks := getsomehonks(rows, err)
157 return honks
158}
159func gethonksbyontology(userid int64, name string) []*Honk {
160 rows, err := stmtHonksByOntology.Query(name, userid, userid)
161 honks := getsomehonks(rows, err)
162 return honks
163}
164
165func getsomehonks(rows *sql.Rows, err error) []*Honk {
166 if err != nil {
167 log.Printf("error querying honks: %s", err)
168 return nil
169 }
170 defer rows.Close()
171 var honks []*Honk
172 for rows.Next() {
173 h := scanhonk(rows)
174 if h != nil {
175 honks = append(honks, h)
176 }
177 }
178 rows.Close()
179 donksforhonks(honks)
180 return honks
181}
182
183type RowLike interface {
184 Scan(dest ...interface{}) error
185}
186
187func scanhonk(row RowLike) *Honk {
188 h := new(Honk)
189 var dt, aud string
190 err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
191 &dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Format, &h.Convoy, &h.Whofore, &h.Flags)
192 if err != nil {
193 if err != sql.ErrNoRows {
194 log.Printf("error scanning honk: %s", err)
195 }
196 return nil
197 }
198 h.Date, _ = time.Parse(dbtimeformat, dt)
199 h.Audience = strings.Split(aud, " ")
200 h.Public = !keepitquiet(h.Audience)
201 return h
202}
203
204func donksforhonks(honks []*Honk) {
205 db := opendatabase()
206 var ids []string
207 hmap := make(map[int64]*Honk)
208 for _, h := range honks {
209 ids = append(ids, fmt.Sprintf("%d", h.ID))
210 hmap[h.ID] = h
211 }
212 // grab donks
213 q := fmt.Sprintf("select honkid, donks.fileid, xid, name, description, url, media, local from donks join filemeta on donks.fileid = filemeta.fileid where honkid in (%s)", strings.Join(ids, ","))
214 rows, err := db.Query(q)
215 if err != nil {
216 log.Printf("error querying donks: %s", err)
217 return
218 }
219 defer rows.Close()
220 for rows.Next() {
221 var hid int64
222 var d Donk
223 err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.Desc, &d.URL, &d.Media, &d.Local)
224 if err != nil {
225 log.Printf("error scanning donk: %s", err)
226 continue
227 }
228 h := hmap[hid]
229 h.Donks = append(h.Donks, &d)
230 }
231 rows.Close()
232
233 // grab onts
234 q = fmt.Sprintf("select honkid, ontology from onts where honkid in (%s)", strings.Join(ids, ","))
235 rows, err = db.Query(q)
236 if err != nil {
237 log.Printf("error querying onts: %s", err)
238 return
239 }
240 defer rows.Close()
241 for rows.Next() {
242 var hid int64
243 var o string
244 err = rows.Scan(&hid, &o)
245 if err != nil {
246 log.Printf("error scanning donk: %s", err)
247 continue
248 }
249 h := hmap[hid]
250 h.Onts = append(h.Onts, o)
251 }
252 rows.Close()
253 // grab places
254 q = fmt.Sprintf("select honkid, name, latitude, longitude, url from places where honkid in (%s)", strings.Join(ids, ","))
255 rows, err = db.Query(q)
256 if err != nil {
257 log.Printf("error querying places: %s", err)
258 return
259 }
260 defer rows.Close()
261 for rows.Next() {
262 var hid int64
263 p := new(Place)
264 err = rows.Scan(&hid, &p.Name, &p.Latitude, &p.Longitude, &p.Url)
265 if err != nil {
266 log.Printf("error scanning place: %s", err)
267 continue
268 }
269 h := hmap[hid]
270 h.Place = p
271 }
272 rows.Close()
273}
274
275func savefile(xid string, name string, desc string, url string, media string, local bool, data []byte) (int64, error) {
276 res, err := stmtSaveFile.Exec(xid, name, desc, url, media, local)
277 if err != nil {
278 return 0, err
279 }
280 fileid, _ := res.LastInsertId()
281 _, err = stmtSaveFileData.Exec(xid, media, data)
282 if err != nil {
283 return 0, err
284 }
285 return fileid, nil
286}
287
288func savehonk(h *Honk) error {
289 dt := h.Date.UTC().Format(dbtimeformat)
290 aud := strings.Join(h.Audience, " ")
291
292 res, err := stmtSaveHonk.Exec(h.UserID, h.What, h.Honker, h.XID, h.RID, dt, h.URL,
293 aud, h.Noise, h.Convoy, h.Whofore, h.Format, h.Precis,
294 h.Oonker, h.Flags)
295 if err != nil {
296 log.Printf("err saving honk: %s", err)
297 return err
298 }
299 h.ID, _ = res.LastInsertId()
300 err = saveextras(h)
301 return err
302}
303
304func saveextras(h *Honk) error {
305 for _, d := range h.Donks {
306 _, err := stmtSaveDonk.Exec(h.ID, d.FileID)
307 if err != nil {
308 log.Printf("err saving donk: %s", err)
309 return err
310 }
311 }
312 for _, o := range h.Onts {
313 _, err := stmtSaveOnt.Exec(strings.ToLower(o), h.ID)
314 if err != nil {
315 log.Printf("error saving ont: %s", err)
316 return err
317 }
318 }
319 if p := h.Place; p != nil {
320 _, err := stmtSavePlace.Exec(h.ID, p.Name, p.Latitude, p.Longitude, p.Url)
321 if err != nil {
322 log.Printf("error saving place: %s", err)
323 return err
324 }
325 }
326
327 return nil
328}
329
330func deleteextras(honkid int64) {
331 _, err := stmtDeleteDonks.Exec(honkid)
332 if err != nil {
333 log.Printf("error deleting: %s", err)
334 }
335 _, err = stmtDeleteOnts.Exec(honkid)
336 if err != nil {
337 log.Printf("error deleting: %s", err)
338 }
339 _, err = stmtDeletePlace.Exec(honkid)
340 if err != nil {
341 log.Printf("error deleting: %s", err)
342 }
343}
344
345func deletehonk(honkid int64) {
346 deleteextras(honkid)
347 _, err := stmtDeleteHonk.Exec(honkid)
348 if err != nil {
349 log.Printf("error deleting: %s", err)
350 }
351}
352
353func updatehonk(h *Honk) {
354 old := getxonk(h.UserID, h.XID)
355 _, err := stmtSaveOld.Exec(old.ID, old.Precis, old.Noise)
356 if err != nil {
357 log.Printf("error saving old: %s", err)
358 return
359 }
360 deleteextras(h.ID)
361
362 dt := h.Date.UTC().Format(dbtimeformat)
363 stmtUpdateHonk.Exec(h.Precis, h.Noise, h.Format, dt, h.ID)
364
365 saveextras(h)
366}
367
368func cleanupdb(arg string) {
369 db := opendatabase()
370 days, err := strconv.Atoi(arg)
371 var sqlargs []interface{}
372 var where string
373 if err != nil {
374 honker := arg
375 expdate := time.Now().UTC().Add(-3 * 24 * time.Hour).Format(dbtimeformat)
376 where = "dt < ? and whofore = 0 and honker = ?"
377 sqlargs = append(sqlargs, expdate)
378 sqlargs = append(sqlargs, honker)
379 } else {
380 expdate := time.Now().UTC().Add(-time.Duration(days) * 24 * time.Hour).Format(dbtimeformat)
381 where = "dt < ? and whofore = 0 and convoy not in (select convoy from honks where whofore = 2 or whofore = 3)"
382 sqlargs = append(sqlargs, expdate)
383 }
384 doordie(db, "delete from honks where " + where, sqlargs...)
385 doordie(db, "delete from donks where honkid not in (select honkid from honks)")
386 doordie(db, "delete from onts where honkid not in (select honkid from honks)")
387 doordie(db, "delete from places where honkid not in (select honkid from honks)")
388
389 doordie(db, "delete from filemeta where fileid not in (select fileid from donks)")
390 for _, u := range allusers() {
391 doordie(db, "delete from zonkers where userid = ? and wherefore = 'zonvoy' and zonkerid < (select zonkerid from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 1 offset 200)", u.UserID, u.UserID)
392 }
393
394 filexids := make(map[string]bool)
395 blobdb := openblobdb()
396 rows, err := blobdb.Query("select xid from filedata")
397 if err != nil {
398 log.Fatal(err)
399 }
400 for rows.Next() {
401 var xid string
402 err = rows.Scan(&xid)
403 if err != nil {
404 log.Fatal(err)
405 }
406 filexids[xid] = true
407 }
408 rows.Close()
409 rows, err = db.Query("select xid from filemeta")
410 for rows.Next() {
411 var xid string
412 err = rows.Scan(&xid)
413 if err != nil {
414 log.Fatal(err)
415 }
416 delete(filexids, xid)
417 }
418 rows.Close()
419 tx, err := blobdb.Begin()
420 if err != nil {
421 log.Fatal(err)
422 }
423 for xid, _ := range filexids {
424 _, err = tx.Exec("delete from filedata where xid = ?", xid)
425 if err != nil {
426 log.Fatal(err)
427 }
428 }
429 err = tx.Commit()
430 if err != nil {
431 log.Fatal(err)
432 }
433}
434
435var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateCombos *sql.Stmt
436var stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
437var stmtHonksByOntology, stmtHonksForUser, stmtHonksForMe, stmtSaveDub, stmtHonksByXonker *sql.Stmt
438var stmtHonksBySearch, stmtHonksByHonker, stmtSaveHonk, stmtWhatAbout *sql.Stmt
439var stmtOneBonk, stmtFindZonk, stmtFindXonk, stmtSaveDonk *sql.Stmt
440var stmtFindFile, stmtGetFileData, stmtSaveFileData, stmtSaveFile *sql.Stmt
441var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover, stmtOneHonker *sql.Stmt
442var stmtThumbBiters, stmtDeleteHonk, stmtDeleteDonks, stmtDeleteOnts, stmtSaveZonker *sql.Stmt
443var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker *sql.Stmt
444var stmtSelectOnts, stmtSaveOnt, stmtUpdateFlags, stmtClearFlags *sql.Stmt
445var stmtSavePlace, stmtDeletePlace, stmtHonksForUserFirstClass, stmtSaveOld, stmtUpdateHonk *sql.Stmt
446
447func preparetodie(db *sql.DB, s string) *sql.Stmt {
448 stmt, err := db.Prepare(s)
449 if err != nil {
450 log.Fatalf("error %s: %s", err, s)
451 }
452 return stmt
453}
454
455func prepareStatements(db *sql.DB) {
456 stmtHonkers = preparetodie(db, "select honkerid, userid, name, xid, flavor, combos from honkers where userid = ? and (flavor = 'sub' or flavor = 'peep' or flavor = 'unsub') order by name")
457 stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
458 stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
459 stmtUpdateCombos = preparetodie(db, "update honkers set combos = ? where honkerid = ? and userid = ?")
460 stmtOneHonker = preparetodie(db, "select xid from honkers where name = ? and userid = ?")
461 stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
462
463 selecthonks := "select honks.honkid, honks.userid, username, what, honker, oonker, honks.xid, rid, dt, url, audience, noise, precis, format, convoy, whofore, flags from honks join users on honks.userid = users.userid "
464 limit := " order by honks.honkid desc limit 250"
465 butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
466 stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
467 stmtOneBonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ? and what = 'bonk' and whofore = 2")
468 stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+limit)
469 stmtUserHonks = preparetodie(db, selecthonks+"where (whofore = 2 or whofore = ?) and username = ? and dt > ?"+limit)
470 stmtHonksForUser = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and honker in (select xid from honkers where userid = ? and flavor = 'sub' and combos not like '% - %')"+butnotthose+limit)
471 stmtHonksForUserFirstClass = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and (what <> 'tonk') and honker in (select xid from honkers where userid = ? and flavor = 'sub' and combos not like '% - %')"+butnotthose+limit)
472 stmtHonksForMe = preparetodie(db, selecthonks+"where honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
473 stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on (honkers.xid = honks.honker or honkers.xid = honks.oonker) where honks.userid = ? and honkers.name = ?"+butnotthose+limit)
474 stmtHonksByXonker = preparetodie(db, selecthonks+" where honks.userid = ? and (honker = ? or oonker = ?)"+butnotthose+limit)
475 stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
476 stmtHonksBySearch = preparetodie(db, selecthonks+"where honks.userid = ? and noise like ?"+limit)
477 stmtHonksByConvoy = preparetodie(db, selecthonks+"where (honks.userid = ? or (? = -1 and whofore = 2)) and convoy = ?"+limit)
478 stmtHonksByOntology = preparetodie(db, selecthonks+"join onts on honks.honkid = onts.honkid where onts.ontology = ? and (honks.userid = ? or (? = -1 and honks.whofore = 2))"+limit)
479
480 stmtSaveOld = preparetodie(db, "insert into forsaken (honkid, precis, noise) values (?, ?, ?)")
481 stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
482 stmtDeleteHonk = preparetodie(db, "delete from honks where honkid = ?")
483 stmtUpdateHonk = preparetodie(db, "update honks set precis = ?, noise = ?, format = ?, dt = ? where honkid = ?")
484 stmtSavePlace = preparetodie(db, "insert into places (honkid, name, latitude, longitude, url) values (?, ?, ?, ?, ?)")
485 stmtDeletePlace = preparetodie(db, "delete from places where honkid = ?")
486 stmtSaveOnt = preparetodie(db, "insert into onts (ontology, honkid) values (?, ?)")
487 stmtDeleteOnts = preparetodie(db, "delete from onts where honkid = ?")
488 stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
489 stmtDeleteDonks = preparetodie(db, "delete from donks where honkid = ?")
490 stmtSaveFile = preparetodie(db, "insert into filemeta (xid, name, description, url, media, local) values (?, ?, ?, ?, ?, ?)")
491 blobdb := openblobdb()
492 stmtSaveFileData = preparetodie(blobdb, "insert into filedata (xid, media, content) values (?, ?, ?)")
493 stmtGetFileData = preparetodie(blobdb, "select media, content from filedata where xid = ?")
494 stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
495 stmtFindFile = preparetodie(db, "select fileid from filemeta where url = ? and local = 1")
496 stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey, options from users where username = ?")
497 stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
498 stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
499 stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
500 stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
501 stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
502 stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers")
503 stmtFindZonk = preparetodie(db, "select zonkerid from zonkers where userid = ? and name = ? and wherefore = 'zonk'")
504 stmtGetZonkers = preparetodie(db, "select zonkerid, name, wherefore from zonkers where userid = ? and wherefore <> 'zonk'")
505 stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
506 stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?")
507 stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor) values (?, ?, ?)")
508 stmtDeleteXonker = preparetodie(db, "delete from xonkers where name = ? and flavor = ?")
509 stmtRecentHonkers = preparetodie(db, "select distinct(honker) from honks where userid = ? and honker not in (select xid from honkers where userid = ? and flavor = 'sub') order by honkid desc limit 100")
510 stmtUpdateFlags = preparetodie(db, "update honks set flags = flags | ? where honkid = ?")
511 stmtClearFlags = preparetodie(db, "update honks set flags = flags & ~ ? where honkid = ?")
512 stmtSelectOnts = preparetodie(db, "select distinct(ontology) from onts join honks on onts.honkid = honks.honkid where (honks.userid = ? or honks.whofore = 2)")
513}