all repos — honk @ f8566e0a7823ea6eb90c4bfbbc36e6a3f929b8ea

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	"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}