all repos — honk @ a03fe08a61518d59be749d9918337cd86b1fd7f1

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	"sort"
 25	"strconv"
 26	"strings"
 27	"time"
 28
 29	"humungus.tedunangst.com/r/webs/cache"
 30	"humungus.tedunangst.com/r/webs/httpsig"
 31	"humungus.tedunangst.com/r/webs/login"
 32)
 33
 34var someusers = cache.New(cache.Options{Filler: func(name string) (*WhatAbout, bool) {
 35	row := stmtWhatAbout.QueryRow(name)
 36	user := new(WhatAbout)
 37	var options string
 38	err := row.Scan(&user.ID, &user.Name, &user.Display, &user.About, &user.Key, &options)
 39	if err != nil {
 40		return nil, false
 41	}
 42	user.URL = fmt.Sprintf("https://%s/%s/%s", serverName, userSep, user.Name)
 43	user.SkinnyCSS = strings.Contains(options, " skinny ")
 44	return user, true
 45}})
 46
 47var oldserveruser *WhatAbout
 48
 49func getserveruser() *WhatAbout {
 50	if oldserveruser == nil {
 51		db := opendatabase()
 52		row := db.QueryRow("select userid, username, displayname, about, pubkey, seckey from users where userid = ?", serverUID)
 53		user := new(WhatAbout)
 54		var seckey string
 55		err := row.Scan(&user.ID, &user.Name, &user.Display, &user.About, &user.Key, &seckey)
 56		if err == nil {
 57			user.SecKey, _, err = httpsig.DecodeKey(seckey)
 58		}
 59		if err != nil {
 60			log.Panicf("trouble getting server user: %s", err)
 61		}
 62		user.URL = fmt.Sprintf("https://%s/server", serverName)
 63		oldserveruser = user
 64	}
 65	return oldserveruser
 66}
 67
 68func butwhatabout(name string) (*WhatAbout, error) {
 69	var user *WhatAbout
 70	ok := someusers.Get(name, &user)
 71	if !ok {
 72		return nil, fmt.Errorf("no user: %s", name)
 73	}
 74	return user, nil
 75}
 76
 77var honkerinvalidator cache.Invalidator
 78
 79func gethonkers(userid int64) []*Honker {
 80	rows, err := stmtHonkers.Query(userid)
 81	if err != nil {
 82		log.Printf("error querying honkers: %s", err)
 83		return nil
 84	}
 85	defer rows.Close()
 86	var honkers []*Honker
 87	for rows.Next() {
 88		h := new(Honker)
 89		var combos string
 90		err = rows.Scan(&h.ID, &h.UserID, &h.Name, &h.XID, &h.Flavor, &combos)
 91		h.Combos = strings.Split(strings.TrimSpace(combos), " ")
 92		if err != nil {
 93			log.Printf("error scanning honker: %s", err)
 94			return nil
 95		}
 96		honkers = append(honkers, h)
 97	}
 98	return honkers
 99}
100
101func getdubs(userid int64) []*Honker {
102	rows, err := stmtDubbers.Query(userid)
103	if err != nil {
104		log.Printf("error querying dubs: %s", err)
105		return nil
106	}
107	defer rows.Close()
108	var honkers []*Honker
109	for rows.Next() {
110		h := new(Honker)
111		err = rows.Scan(&h.ID, &h.UserID, &h.Name, &h.XID, &h.Flavor)
112		if err != nil {
113			log.Printf("error scanning honker: %s", err)
114			return nil
115		}
116		honkers = append(honkers, h)
117	}
118	return honkers
119}
120
121func allusers() []login.UserInfo {
122	var users []login.UserInfo
123	rows, _ := opendatabase().Query("select userid, username from users where userid > 0")
124	defer rows.Close()
125	for rows.Next() {
126		var u login.UserInfo
127		rows.Scan(&u.UserID, &u.Username)
128		users = append(users, u)
129	}
130	return users
131}
132
133func getxonk(userid int64, xid string) *Honk {
134	row := stmtOneXonk.QueryRow(userid, xid)
135	return scanhonk(row)
136}
137
138func getbonk(userid int64, xid string) *Honk {
139	row := stmtOneBonk.QueryRow(userid, xid)
140	return scanhonk(row)
141}
142
143func getpublichonks() []*Honk {
144	dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
145	rows, err := stmtPublicHonks.Query(dt)
146	return getsomehonks(rows, err)
147}
148func geteventhonks(userid int64) []*Honk {
149	rows, err := stmtEventHonks.Query(userid)
150	honks := getsomehonks(rows, err)
151	sort.Slice(honks, func(i, j int) bool {
152		var t1, t2 time.Time
153		if honks[i].Time == nil {
154			t1 = honks[i].Date
155		} else {
156			t1 = honks[i].Time.StartTime
157		}
158		if honks[j].Time == nil {
159			t2 = honks[j].Date
160		} else {
161			t2 = honks[j].Time.StartTime
162		}
163		return t1.After(t2)
164	})
165	now := time.Now().Add(-24 * time.Hour)
166	for i, h := range honks {
167		if h.Time.StartTime.Before(now) {
168			honks = honks[:i]
169			break
170		}
171	}
172	reversehonks(honks)
173	return honks
174}
175func gethonksbyuser(name string, includeprivate bool, wanted int64) []*Honk {
176	dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
177	whofore := 2
178	if includeprivate {
179		whofore = 3
180	}
181	rows, err := stmtUserHonks.Query(wanted, whofore, name, dt)
182	return getsomehonks(rows, err)
183}
184func gethonksforuser(userid int64, wanted int64) []*Honk {
185	dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
186	rows, err := stmtHonksForUser.Query(wanted, userid, dt, userid, userid)
187	return getsomehonks(rows, err)
188}
189func gethonksforuserfirstclass(userid int64, wanted int64) []*Honk {
190	dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
191	rows, err := stmtHonksForUserFirstClass.Query(wanted, userid, dt, userid, userid)
192	return getsomehonks(rows, err)
193}
194func gethonksforme(userid int64, wanted int64) []*Honk {
195	dt := time.Now().UTC().Add(-7 * 24 * time.Hour).Format(dbtimeformat)
196	rows, err := stmtHonksForMe.Query(wanted, userid, dt, userid)
197	return getsomehonks(rows, err)
198}
199func getsavedhonks(userid int64, wanted int64) []*Honk {
200	rows, err := stmtHonksISaved.Query(wanted, userid)
201	return getsomehonks(rows, err)
202}
203func gethonksbyhonker(userid int64, honker string, wanted int64) []*Honk {
204	rows, err := stmtHonksByHonker.Query(wanted, userid, honker, userid)
205	return getsomehonks(rows, err)
206}
207func gethonksbyxonker(userid int64, xonker string, wanted int64) []*Honk {
208	rows, err := stmtHonksByXonker.Query(wanted, userid, xonker, xonker, userid)
209	return getsomehonks(rows, err)
210}
211func gethonksbycombo(userid int64, combo string, wanted int64) []*Honk {
212	combo = "% " + combo + " %"
213	rows, err := stmtHonksByCombo.Query(wanted, userid, combo, userid)
214	return getsomehonks(rows, err)
215}
216func gethonksbyconvoy(userid int64, convoy string, wanted int64) []*Honk {
217	rows, err := stmtHonksByConvoy.Query(wanted, userid, userid, convoy)
218	honks := getsomehonks(rows, err)
219	return honks
220}
221func gethonksbysearch(userid int64, q string, wanted int64) []*Honk {
222	honker := ""
223	withhonker := 0
224	site := ""
225	withsite := 0
226	terms := strings.Split(q, " ")
227	q = "%"
228	for _, t := range terms {
229		if strings.HasPrefix(t, "site:") {
230			site = t[5:]
231			site = "%" + site + "%"
232			withsite = 1
233			continue
234		}
235		if strings.HasPrefix(t, "honker:") {
236			honker = t[7:]
237			xid := fullname(honker, userid)
238			if xid != "" {
239				honker = xid
240			}
241			withhonker = 1
242			continue
243		}
244		if len(q) != 1 {
245			q += " "
246		}
247		q += t
248	}
249	q += "%"
250	rows, err := stmtHonksBySearch.Query(wanted, userid, withsite, site, withhonker, honker, honker, q, userid)
251	honks := getsomehonks(rows, err)
252	return honks
253}
254func gethonksbyontology(userid int64, name string, wanted int64) []*Honk {
255	rows, err := stmtHonksByOntology.Query(wanted, name, userid, userid)
256	honks := getsomehonks(rows, err)
257	return honks
258}
259
260func reversehonks(honks []*Honk) {
261	for i, j := 0, len(honks)-1; i < j; i, j = i+1, j-1 {
262		honks[i], honks[j] = honks[j], honks[i]
263	}
264}
265
266func getsomehonks(rows *sql.Rows, err error) []*Honk {
267	if err != nil {
268		log.Printf("error querying honks: %s", err)
269		return nil
270	}
271	defer rows.Close()
272	var honks []*Honk
273	for rows.Next() {
274		h := scanhonk(rows)
275		if h != nil {
276			honks = append(honks, h)
277		}
278	}
279	rows.Close()
280	donksforhonks(honks)
281	return honks
282}
283
284type RowLike interface {
285	Scan(dest ...interface{}) error
286}
287
288func scanhonk(row RowLike) *Honk {
289	h := new(Honk)
290	var dt, aud string
291	err := row.Scan(&h.ID, &h.UserID, &h.Username, &h.What, &h.Honker, &h.Oonker, &h.XID, &h.RID,
292		&dt, &h.URL, &aud, &h.Noise, &h.Precis, &h.Format, &h.Convoy, &h.Whofore, &h.Flags)
293	if err != nil {
294		if err != sql.ErrNoRows {
295			log.Printf("error scanning honk: %s", err)
296		}
297		return nil
298	}
299	h.Date, _ = time.Parse(dbtimeformat, dt)
300	h.Audience = strings.Split(aud, " ")
301	h.Public = !keepitquiet(h.Audience)
302	return h
303}
304
305func donksforhonks(honks []*Honk) {
306	db := opendatabase()
307	var ids []string
308	hmap := make(map[int64]*Honk)
309	for _, h := range honks {
310		ids = append(ids, fmt.Sprintf("%d", h.ID))
311		hmap[h.ID] = h
312	}
313	// grab donks
314	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, ","))
315	rows, err := db.Query(q)
316	if err != nil {
317		log.Printf("error querying donks: %s", err)
318		return
319	}
320	defer rows.Close()
321	for rows.Next() {
322		var hid int64
323		d := new(Donk)
324		err = rows.Scan(&hid, &d.FileID, &d.XID, &d.Name, &d.Desc, &d.URL, &d.Media, &d.Local)
325		if err != nil {
326			log.Printf("error scanning donk: %s", err)
327			continue
328		}
329		h := hmap[hid]
330		h.Donks = append(h.Donks, d)
331	}
332	rows.Close()
333
334	// grab onts
335	q = fmt.Sprintf("select honkid, ontology from onts where honkid in (%s)", strings.Join(ids, ","))
336	rows, err = db.Query(q)
337	if err != nil {
338		log.Printf("error querying onts: %s", err)
339		return
340	}
341	defer rows.Close()
342	for rows.Next() {
343		var hid int64
344		var o string
345		err = rows.Scan(&hid, &o)
346		if err != nil {
347			log.Printf("error scanning donk: %s", err)
348			continue
349		}
350		h := hmap[hid]
351		h.Onts = append(h.Onts, o)
352	}
353	rows.Close()
354	// grab meta
355	q = fmt.Sprintf("select honkid, genus, json from honkmeta where honkid in (%s)", strings.Join(ids, ","))
356	rows, err = db.Query(q)
357	if err != nil {
358		log.Printf("error querying honkmeta: %s", err)
359		return
360	}
361	defer rows.Close()
362	for rows.Next() {
363		var hid int64
364		var genus, j string
365		err = rows.Scan(&hid, &genus, &j)
366		if err != nil {
367			log.Printf("error scanning honkmeta: %s", err)
368			continue
369		}
370		h := hmap[hid]
371		switch genus {
372		case "place":
373			p := new(Place)
374			err = unjsonify(j, p)
375			if err != nil {
376				log.Printf("error parsing place: %s", err)
377				continue
378			}
379			h.Place = p
380		case "time":
381			t := new(Time)
382			err = unjsonify(j, t)
383			if err != nil {
384				log.Printf("error parsing time: %s", err)
385				continue
386			}
387			h.Time = t
388		case "oldrev":
389		default:
390			log.Printf("unknown meta genus: %s", genus)
391		}
392	}
393	rows.Close()
394}
395
396func savefile(xid string, name string, desc string, url string, media string, local bool, data []byte) (int64, error) {
397	res, err := stmtSaveFile.Exec(xid, name, desc, url, media, local)
398	if err != nil {
399		return 0, err
400	}
401	fileid, _ := res.LastInsertId()
402	if local {
403		_, err = stmtSaveFileData.Exec(xid, media, data)
404		if err != nil {
405			return 0, err
406		}
407	}
408	return fileid, nil
409}
410
411func finddonk(url string) *Donk {
412	donk := new(Donk)
413	row := stmtFindFile.QueryRow(url)
414	err := row.Scan(&donk.FileID, &donk.XID)
415	if err == nil {
416		return donk
417	}
418	if err != sql.ErrNoRows {
419		log.Printf("error finding file: %s", err)
420	}
421	return nil
422}
423
424func savehonk(h *Honk) error {
425	dt := h.Date.UTC().Format(dbtimeformat)
426	aud := strings.Join(h.Audience, " ")
427
428	db := opendatabase()
429	tx, err := db.Begin()
430	if err != nil {
431		log.Printf("can't begin tx: %s", err)
432		return err
433	}
434
435	res, err := tx.Stmt(stmtSaveHonk).Exec(h.UserID, h.What, h.Honker, h.XID, h.RID, dt, h.URL,
436		aud, h.Noise, h.Convoy, h.Whofore, h.Format, h.Precis,
437		h.Oonker, h.Flags)
438	if err == nil {
439		h.ID, _ = res.LastInsertId()
440		err = saveextras(tx, h)
441	}
442	if err == nil {
443		err = tx.Commit()
444	} else {
445		tx.Rollback()
446	}
447	if err != nil {
448		log.Printf("error saving honk: %s", err)
449	}
450	return err
451}
452
453func updatehonk(h *Honk) error {
454	old := getxonk(h.UserID, h.XID)
455	oldrev := OldRevision{Precis: old.Precis, Noise: old.Noise}
456	dt := h.Date.UTC().Format(dbtimeformat)
457
458	db := opendatabase()
459	tx, err := db.Begin()
460	if err != nil {
461		log.Printf("can't begin tx: %s", err)
462		return err
463	}
464
465	err = deleteextras(tx, h.ID)
466	if err == nil {
467		_, err = tx.Stmt(stmtUpdateHonk).Exec(h.Precis, h.Noise, h.Format, dt, h.ID)
468	}
469	if err == nil {
470		err = saveextras(tx, h)
471	}
472	if err == nil {
473		var j string
474		j, err = jsonify(&oldrev)
475		if err == nil {
476			_, err = tx.Stmt(stmtSaveMeta).Exec(old.ID, "oldrev", j)
477		}
478		if err != nil {
479			log.Printf("error saving oldrev: %s", err)
480		}
481	}
482	if err == nil {
483		err = tx.Commit()
484	} else {
485		tx.Rollback()
486	}
487	if err != nil {
488		log.Printf("error updating honk %d: %s", h.ID, err)
489	}
490	return err
491}
492
493func deletehonk(honkid int64) error {
494	db := opendatabase()
495	tx, err := db.Begin()
496	if err != nil {
497		log.Printf("can't begin tx: %s", err)
498		return err
499	}
500
501	err = deleteextras(tx, honkid)
502	if err == nil {
503		_, err = tx.Stmt(stmtDeleteMeta).Exec(honkid, "nonsense")
504	}
505	if err == nil {
506		_, err = tx.Stmt(stmtDeleteHonk).Exec(honkid)
507	}
508	if err == nil {
509		err = tx.Commit()
510	} else {
511		tx.Rollback()
512	}
513	if err != nil {
514		log.Printf("error deleting honk %d: %s", honkid, err)
515	}
516	return err
517}
518
519func saveextras(tx *sql.Tx, h *Honk) error {
520	for _, d := range h.Donks {
521		_, err := tx.Stmt(stmtSaveDonk).Exec(h.ID, d.FileID)
522		if err != nil {
523			log.Printf("error saving donk: %s", err)
524			return err
525		}
526	}
527	for _, o := range h.Onts {
528		_, err := tx.Stmt(stmtSaveOnt).Exec(strings.ToLower(o), h.ID)
529		if err != nil {
530			log.Printf("error saving ont: %s", err)
531			return err
532		}
533	}
534	if p := h.Place; p != nil {
535		j, err := jsonify(p)
536		if err == nil {
537			_, err = tx.Stmt(stmtSaveMeta).Exec(h.ID, "place", j)
538		}
539		if err != nil {
540			log.Printf("error saving place: %s", err)
541			return err
542		}
543	}
544	if t := h.Time; t != nil {
545		j, err := jsonify(t)
546		if err == nil {
547			_, err = tx.Stmt(stmtSaveMeta).Exec(h.ID, "time", j)
548		}
549		if err != nil {
550			log.Printf("error saving time: %s", err)
551			return err
552		}
553	}
554	return nil
555}
556
557func deleteextras(tx *sql.Tx, honkid int64) error {
558	_, err := tx.Stmt(stmtDeleteDonks).Exec(honkid)
559	if err != nil {
560		return err
561	}
562	_, err = tx.Stmt(stmtDeleteOnts).Exec(honkid)
563	if err != nil {
564		return err
565	}
566	_, err = tx.Stmt(stmtDeleteMeta).Exec(honkid, "oldrev")
567	if err != nil {
568		return err
569	}
570	return nil
571}
572
573func jsonify(what interface{}) (string, error) {
574	var buf bytes.Buffer
575	e := json.NewEncoder(&buf)
576	e.SetEscapeHTML(false)
577	e.SetIndent("", "")
578	err := e.Encode(what)
579	return buf.String(), err
580}
581
582func unjsonify(s string, dest interface{}) error {
583	d := json.NewDecoder(strings.NewReader(s))
584	err := d.Decode(dest)
585	return err
586}
587
588func cleanupdb(arg string) {
589	db := opendatabase()
590	days, err := strconv.Atoi(arg)
591	var sqlargs []interface{}
592	var where string
593	if err != nil {
594		honker := arg
595		expdate := time.Now().UTC().Add(-3 * 24 * time.Hour).Format(dbtimeformat)
596		where = "dt < ? and honker = ?"
597		sqlargs = append(sqlargs, expdate)
598		sqlargs = append(sqlargs, honker)
599	} else {
600		expdate := time.Now().UTC().Add(-time.Duration(days) * 24 * time.Hour).Format(dbtimeformat)
601		where = "dt < ? and convoy not in (select convoy from honks where flags & 4 or whofore = 2 or whofore = 3)"
602		sqlargs = append(sqlargs, expdate)
603	}
604	doordie(db, "delete from honks where flags & 4 = 0 and whofore = 0 and "+where, sqlargs...)
605	doordie(db, "delete from donks where honkid not in (select honkid from honks)")
606	doordie(db, "delete from onts where honkid not in (select honkid from honks)")
607	doordie(db, "delete from honkmeta where honkid not in (select honkid from honks)")
608
609	doordie(db, "delete from filemeta where fileid not in (select fileid from donks)")
610	for _, u := range allusers() {
611		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)
612	}
613
614	filexids := make(map[string]bool)
615	blobdb := openblobdb()
616	rows, err := blobdb.Query("select xid from filedata")
617	if err != nil {
618		log.Fatal(err)
619	}
620	for rows.Next() {
621		var xid string
622		err = rows.Scan(&xid)
623		if err != nil {
624			log.Fatal(err)
625		}
626		filexids[xid] = true
627	}
628	rows.Close()
629	rows, err = db.Query("select xid from filemeta")
630	for rows.Next() {
631		var xid string
632		err = rows.Scan(&xid)
633		if err != nil {
634			log.Fatal(err)
635		}
636		delete(filexids, xid)
637	}
638	rows.Close()
639	tx, err := blobdb.Begin()
640	if err != nil {
641		log.Fatal(err)
642	}
643	for xid, _ := range filexids {
644		_, err = tx.Exec("delete from filedata where xid = ?", xid)
645		if err != nil {
646			log.Fatal(err)
647		}
648	}
649	err = tx.Commit()
650	if err != nil {
651		log.Fatal(err)
652	}
653}
654
655var stmtHonkers, stmtDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateHonker *sql.Stmt
656var stmtAnyXonk, stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt
657var stmtHonksByOntology, stmtHonksForUser, stmtHonksForMe, stmtSaveDub, stmtHonksByXonker *sql.Stmt
658var stmtHonksBySearch, stmtHonksByHonker, stmtSaveHonk, stmtWhatAbout *sql.Stmt
659var stmtEventHonks, stmtOneBonk, stmtFindZonk, stmtFindXonk, stmtSaveDonk *sql.Stmt
660var stmtFindFile, stmtGetFileData, stmtSaveFileData, stmtSaveFile *sql.Stmt
661var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover, stmtOneHonker *sql.Stmt
662var stmtThumbBiters, stmtDeleteHonk, stmtDeleteDonks, stmtDeleteOnts, stmtSaveZonker *sql.Stmt
663var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker *sql.Stmt
664var stmtAllOnts, stmtSaveOnt, stmtUpdateFlags, stmtClearFlags *sql.Stmt
665var stmtHonksForUserFirstClass, stmtSaveMeta, stmtDeleteMeta, stmtUpdateHonk *sql.Stmt
666var stmtHonksISaved, stmtGetFilters, stmtSaveFilter, stmtDeleteFilter *sql.Stmt
667
668func preparetodie(db *sql.DB, s string) *sql.Stmt {
669	stmt, err := db.Prepare(s)
670	if err != nil {
671		log.Fatalf("error %s: %s", err, s)
672	}
673	return stmt
674}
675
676func prepareStatements(db *sql.DB) {
677	stmtHonkers = preparetodie(db, "select honkerid, userid, name, xid, flavor, combos from honkers where userid = ? and (flavor = 'presub' or flavor = 'sub' or flavor = 'peep' or flavor = 'unsub') order by name")
678	stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos) values (?, ?, ?, ?, ?)")
679	stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and flavor = ?")
680	stmtUpdateHonker = preparetodie(db, "update honkers set name = ?, combos = ? where honkerid = ? and userid = ?")
681	stmtOneHonker = preparetodie(db, "select xid from honkers where name = ? and userid = ?")
682	stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'")
683
684	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 "
685	limit := " order by honks.honkid desc limit 250"
686	butnotthose := " and convoy not in (select name from zonkers where userid = ? and wherefore = 'zonvoy' order by zonkerid desc limit 100)"
687	stmtOneXonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ?")
688	stmtAnyXonk = preparetodie(db, selecthonks+"where xid = ? order by honks.honkid asc")
689	stmtOneBonk = preparetodie(db, selecthonks+"where honks.userid = ? and xid = ? and what = 'bonk' and whofore = 2")
690	stmtPublicHonks = preparetodie(db, selecthonks+"where whofore = 2 and dt > ?"+limit)
691	stmtEventHonks = preparetodie(db, selecthonks+"where (whofore = 2 or honks.userid = ?) and what = 'event'"+limit)
692	stmtUserHonks = preparetodie(db, selecthonks+"where honks.honkid > ? and (whofore = 2 or whofore = ?) and username = ? and dt > ?"+limit)
693	myhonkers := " and honker in (select xid from honkers where userid = ? and (flavor = 'sub' or flavor = 'peep' or flavor = 'presub') and combos not like '% - %')"
694	stmtHonksForUser = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ?"+myhonkers+butnotthose+limit)
695	stmtHonksForUserFirstClass = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ? and (what <> 'tonk')"+myhonkers+butnotthose+limit)
696	stmtHonksForMe = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
697	stmtHonksISaved = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and flags & 4 order by honks.honkid desc")
698	stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on (honkers.xid = honks.honker or honkers.xid = honks.oonker) where honks.honkid > ? and honks.userid = ? and honkers.name = ?"+butnotthose+limit)
699	stmtHonksByXonker = preparetodie(db, selecthonks+" where honks.honkid > ? and honks.userid = ? and (honker = ? or oonker = ?)"+butnotthose+limit)
700	stmtHonksByCombo = preparetodie(db, selecthonks+"join honkers on honkers.xid = honks.honker where honks.honkid > ? and honks.userid = ? and honkers.combos like ?"+butnotthose+limit)
701	stmtHonksBySearch = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and (? = 0 or xid like ?) and (? = 0 or honks.honker = ? or honks.oonker = ?) and noise like ?"+butnotthose+limit)
702	stmtHonksByConvoy = preparetodie(db, selecthonks+"where honks.honkid > ? and (honks.userid = ? or (? = -1 and whofore = 2)) and convoy = ?"+limit)
703	stmtHonksByOntology = preparetodie(db, selecthonks+"join onts on honks.honkid = onts.honkid where honks.honkid > ? and onts.ontology = ? and (honks.userid = ? or (? = -1 and honks.whofore = 2))"+limit)
704
705	stmtSaveMeta = preparetodie(db, "insert into honkmeta (honkid, genus, json) values (?, ?, ?)")
706	stmtDeleteMeta = preparetodie(db, "delete from honkmeta where honkid = ? and genus <> ?")
707	stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
708	stmtDeleteHonk = preparetodie(db, "delete from honks where honkid = ?")
709	stmtUpdateHonk = preparetodie(db, "update honks set precis = ?, noise = ?, format = ?, dt = ? where honkid = ?")
710	stmtSaveOnt = preparetodie(db, "insert into onts (ontology, honkid) values (?, ?)")
711	stmtDeleteOnts = preparetodie(db, "delete from onts where honkid = ?")
712	stmtSaveDonk = preparetodie(db, "insert into donks (honkid, fileid) values (?, ?)")
713	stmtDeleteDonks = preparetodie(db, "delete from donks where honkid = ?")
714	stmtSaveFile = preparetodie(db, "insert into filemeta (xid, name, description, url, media, local) values (?, ?, ?, ?, ?, ?)")
715	blobdb := openblobdb()
716	stmtSaveFileData = preparetodie(blobdb, "insert into filedata (xid, media, content) values (?, ?, ?)")
717	stmtGetFileData = preparetodie(blobdb, "select media, content from filedata where xid = ?")
718	stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
719	stmtFindFile = preparetodie(db, "select fileid, xid from filemeta where url = ? and local = 1")
720	stmtWhatAbout = preparetodie(db, "select userid, username, displayname, about, pubkey, options from users where username = ? and userid > 0")
721	stmtSaveDub = preparetodie(db, "insert into honkers (userid, name, xid, flavor) values (?, ?, ?, ?)")
722	stmtAddDoover = preparetodie(db, "insert into doovers (dt, tries, username, rcpt, msg) values (?, ?, ?, ?, ?)")
723	stmtGetDoovers = preparetodie(db, "select dooverid, dt from doovers")
724	stmtLoadDoover = preparetodie(db, "select tries, username, rcpt, msg from doovers where dooverid = ?")
725	stmtZapDoover = preparetodie(db, "delete from doovers where dooverid = ?")
726	stmtThumbBiters = preparetodie(db, "select userid, name, wherefore from zonkers")
727	stmtFindZonk = preparetodie(db, "select zonkerid from zonkers where userid = ? and name = ? and wherefore = 'zonk'")
728	stmtGetZonkers = preparetodie(db, "select zonkerid, name, wherefore from zonkers where userid = ? and wherefore <> 'zonk'")
729	stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
730	stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?")
731	stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor) values (?, ?, ?)")
732	stmtDeleteXonker = preparetodie(db, "delete from xonkers where name = ? and flavor = ?")
733	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")
734	stmtUpdateFlags = preparetodie(db, "update honks set flags = flags | ? where honkid = ?")
735	stmtClearFlags = preparetodie(db, "update honks set flags = flags & ~ ? where honkid = ?")
736	stmtAllOnts = preparetodie(db, "select ontology, count(ontology) from onts join honks on onts.honkid = honks.honkid where (honks.userid = ? or honks.whofore = 2) group by ontology")
737	stmtGetFilters = preparetodie(db, "select hfcsid, json from hfcs where userid = ?")
738	stmtSaveFilter = preparetodie(db, "insert into hfcs (userid, json) values (?, ?)")
739	stmtDeleteFilter = preparetodie(db, "delete from hfcs where userid = ? and hfcsid = ?")
740}