all repos — honk @ 8dadee71f96f6f7be168a4ba624b3c6c5bd3f199

my fork of honk

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