gosora/query_gen/accumulator.go

324 lines
9.5 KiB
Go
Raw Normal View History

/* WIP: A version of the builder which accumulates errors, we'll see if we can't unify the implementations at some point */
package qgen
import (
"database/sql"
"log"
"strings"
)
var LogPrepares = true
// So we don't have to do the qgen.Builder.Accumulator() boilerplate all the time
func NewAcc() *Accumulator {
return Builder.Accumulator()
}
type Accumulator struct {
conn *sql.DB
adapter Adapter
firstErr error
}
func (acc *Accumulator) SetConn(conn *sql.DB) {
acc.conn = conn
}
func (acc *Accumulator) SetAdapter(name string) error {
adap, err := GetAdapter(name)
if err != nil {
return err
}
acc.adapter = adap
return nil
}
func (acc *Accumulator) GetAdapter() Adapter {
return acc.adapter
}
func (acc *Accumulator) FirstError() error {
return acc.firstErr
}
func (acc *Accumulator) RecordError(err error) {
if err == nil {
return
}
if acc.firstErr == nil {
acc.firstErr = err
}
}
func (acc *Accumulator) prepare(res string, err error) *sql.Stmt {
// TODO: Can we make this less noisy on debug mode?
if LogPrepares {
log.Print("res: ", res)
}
if err != nil {
acc.RecordError(err)
return nil
}
stmt, err := acc.conn.Prepare(res)
acc.RecordError(err)
return stmt
}
func (acc *Accumulator) RawPrepare(res string) *sql.Stmt {
return acc.prepare(res, nil)
The Search and Filter Widget is now partly implemented. Just Search to go in the basic implementation. Added AJAX Pagination for the Topic List and Forum Page. A new log file pair is now created every-time Gosora starts up. Added proper per-theme template overrides. Added EasyJSON to make JSON serialisation faster. Moved a bit of boilerplate into paginator.html Improved paginator.html with a richer template with first, last and symbols instead of text. Phased out direct access to Templates.ExecuteTemplate across the software. Fixed the Live Topic List so it should work again. Added MicroAvatar to WsJSONUser for topic list JSON requests. An instance of the plugin is now passed to plugin handlers rather than having the plugins manipulate the globals directly. Added the pre_render_panel_forum_edit and pre_render_panel_forum_edit_perms hooks to replace pre_render_panel_edit_forum. Renamed the pre_render_panel_edit_user hook to pre_render_panel_user_edit Reduced the amount of noise from fsnotify. Added RawPrepare() to qgen.Accumulator. Added a temporary phrase whitelist to the phrase endpoint. Moved the location of the zone data assignments in the topic list to reduce the chances of security issues in the future. Changed the signature of routes/panel/renderTemplate() requiring some changes across the panel routes. Removed bits of boilerplate in some of the panel routes with renderTemplate() Added a BenchmarkTopicsGuestJSRouteParallelWithRouter benchmark. Removed a fair bit of boilerplate for each page struct by generating a couple of interface casts for each template file instead. Added the profile_comments_row_alt template. Added the topics_quick_topic template to reuse part of the quick topic logic for both the topic list and forum page. Tweaked the CSS for the Online Users Widget. Tweaked the CSS for Widgets in every theme with a sidebar. Refactored the template initialisers to hopefully reduce the amount of boilerplate and make things easier to maintain and follow. Add genIntTmpl in the template initialiser file to reduce the amount of boilerplate needed for the fallback template bindings. Removed the topics_head phrase. Moved the paginator_ phrases into the paginator. namespace and renamed them accordingly. Added the paginator.first_page phrase. Added the paginator.first_page_aria phrase. Added the paginator.last_page phrase. Added the paginator.last_page_aria phrase. Added the panel_forum_delete_are_you_sure phrase. Fixed a data race in LogWarning()
2019-02-10 05:52:26 +00:00
}
func (acc *Accumulator) query(q string, args ...interface{}) (rows *sql.Rows, err error) {
err = acc.FirstError()
if err != nil {
return rows, err
}
return acc.conn.Query(q, args...)
}
func (acc *Accumulator) exec(q string, args ...interface{}) (res sql.Result, err error) {
err = acc.FirstError()
if err != nil {
return res, err
}
return acc.conn.Exec(q, args...)
}
func (acc *Accumulator) Tx(handler func(*TransactionBuilder) error) {
tx, err := acc.conn.Begin()
if err != nil {
acc.RecordError(err)
return
}
err = handler(&TransactionBuilder{tx, acc.adapter, nil})
if err != nil {
tx.Rollback()
acc.RecordError(err)
return
}
acc.RecordError(tx.Commit())
}
func (acc *Accumulator) SimpleSelect(table, columns, where, orderby, limit string) *sql.Stmt {
return acc.prepare(acc.adapter.SimpleSelect("", table, columns, where, orderby, limit))
}
func (acc *Accumulator) SimpleCount(table, where, limit string) *sql.Stmt {
return acc.prepare(acc.adapter.SimpleCount("", table, where, limit))
}
func (acc *Accumulator) SimpleLeftJoin(table1, table2, columns, joiners, where, orderby, limit string) *sql.Stmt {
return acc.prepare(acc.adapter.SimpleLeftJoin("", table1, table2, columns, joiners, where, orderby, limit))
}
func (acc *Accumulator) SimpleInnerJoin(table1, table2, columns, joiners, where, orderby, limit string) *sql.Stmt {
return acc.prepare(acc.adapter.SimpleInnerJoin("", table1, table2, columns, joiners, where, orderby, limit))
}
func (acc *Accumulator) CreateTable(table, charset, collation string, columns []DBTableColumn, keys []DBTableKey) *sql.Stmt {
return acc.prepare(acc.adapter.CreateTable("", table, charset, collation, columns, keys))
}
func (acc *Accumulator) SimpleInsert(table, columns, fields string) *sql.Stmt {
return acc.prepare(acc.adapter.SimpleInsert("", table, columns, fields))
}
func (acc *Accumulator) SimpleBulkInsert(table, cols string, fieldSet []string) *sql.Stmt {
return acc.prepare(acc.adapter.SimpleBulkInsert("", table, cols, fieldSet))
}
func (acc *Accumulator) SimpleInsertSelect(ins DBInsert, sel DBSelect) *sql.Stmt {
return acc.prepare(acc.adapter.SimpleInsertSelect("", ins, sel))
}
func (acc *Accumulator) SimpleInsertLeftJoin(ins DBInsert, sel DBJoin) *sql.Stmt {
return acc.prepare(acc.adapter.SimpleInsertLeftJoin("", ins, sel))
}
func (acc *Accumulator) SimpleInsertInnerJoin(ins DBInsert, sel DBJoin) *sql.Stmt {
return acc.prepare(acc.adapter.SimpleInsertInnerJoin("", ins, sel))
You can now manage the attachments for an opening post by hitting edit. The update system now uses the database as the source of truth for the last version rather than lastSchema.json Refactored several structs and bits of code, so we can avoid allocations for contexts where we never use a relative time. Clicking on the relative times on the topic list and the forum page should now take you to the post on the last page rather than just the last page. Added the reltime template function. Fixed some obsolete bits of code. Fixed some spelling mistakes. Fixed a bug where MaxBytesReader was capped at the maxFileSize rather than r.ContentLength. All of the client side templates should work again now. Shortened some statement names to save some horizontal space. accUpdateBuilder and SimpleUpdate now use updatePrebuilder behind the scenes to simplify things. Renamed selectItem to builder in AccSelectBuilder. Added a Total() method to accCountBuilder to reduce the amount of boilerplate used for row count queries. The "_builder" strings have been replaced with empty strings to help save memory, to make things slightly faster and to open the door to removing the query name in many contexts down the line. Added the open_edit and close_edit client hooks. Removed many query name checks. Split the attachment logic into separate functions and de-duplicated it between replies and topics. Improved the UI for editing topics in Nox. Used type aliases to reduce the amount of boilerplate in tables.go and patches.go Reduced the amount of boilerplate in the action post logic. Eliminated a map and a slice in the topic page for users who haven't given any likes. E.g. Guests. Fixed some long out-dated parts of the update instructions. Updated the update instructions to remove mention of the obsolete lastSchema.json Fixed a bug in init.js where /api/me was being loaded for guests. Added the MiniTopicGet, GlobalCount and CountInTopic methods to AttachmentStore. Added the MiniAttachment struct. Split the mod floaters out into their own template to reduce duplication. Removed a couple of redundant ParseForms. Added the common.skipUntilIfExistsOrLine function. Added the NotFoundJS and NotFoundJSQ functions. Added the lastReplyID and attachCount columns to the topics table.
2018-12-27 05:42:41 +00:00
}
func (acc *Accumulator) SimpleUpdate(table, set, where string) *sql.Stmt {
return acc.prepare(acc.adapter.SimpleUpdate(qUpdate(table, set, where)))
}
func (acc *Accumulator) SimpleUpdateSelect(table, set, table2, cols, where, orderby, limit string) *sql.Stmt {
pre := qUpdate(table, set, "").WhereQ(acc.GetAdapter().Builder().Select().Table(table2).Columns(cols).Where(where).Orderby(orderby).Limit(limit))
return acc.prepare(acc.adapter.SimpleUpdateSelect(pre))
}
func (acc *Accumulator) SimpleDelete(table, where string) *sql.Stmt {
return acc.prepare(acc.adapter.SimpleDelete("", table, where))
}
// I don't know why you need this, but here it is x.x
func (acc *Accumulator) Purge(table string) *sql.Stmt {
return acc.prepare(acc.adapter.Purge("", table))
}
func (acc *Accumulator) prepareTx(tx *sql.Tx, res string, err error) (stmt *sql.Stmt) {
if err != nil {
acc.RecordError(err)
return nil
}
stmt, err = tx.Prepare(res)
acc.RecordError(err)
return stmt
}
// These ones support transactions
func (acc *Accumulator) SimpleSelectTx(tx *sql.Tx, table, columns, where, orderby, limit string) (stmt *sql.Stmt) {
res, err := acc.adapter.SimpleSelect("", table, columns, where, orderby, limit)
return acc.prepareTx(tx, res, err)
}
func (acc *Accumulator) SimpleCountTx(tx *sql.Tx, table, where, limit string) (stmt *sql.Stmt) {
res, err := acc.adapter.SimpleCount("", table, where, limit)
return acc.prepareTx(tx, res, err)
}
func (acc *Accumulator) SimpleLeftJoinTx(tx *sql.Tx, table1, table2, columns, joiners, where, orderby, limit string) (stmt *sql.Stmt) {
res, err := acc.adapter.SimpleLeftJoin("", table1, table2, columns, joiners, where, orderby, limit)
return acc.prepareTx(tx, res, err)
}
func (acc *Accumulator) SimpleInnerJoinTx(tx *sql.Tx, table1, table2, columns, joiners, where, orderby, limit string) (stmt *sql.Stmt) {
res, err := acc.adapter.SimpleInnerJoin("", table1, table2, columns, joiners, where, orderby, limit)
return acc.prepareTx(tx, res, err)
}
func (acc *Accumulator) CreateTableTx(tx *sql.Tx, table, charset, collation string, columns []DBTableColumn, keys []DBTableKey) (stmt *sql.Stmt) {
res, err := acc.adapter.CreateTable("", table, charset, collation, columns, keys)
return acc.prepareTx(tx, res, err)
}
func (acc *Accumulator) SimpleInsertTx(tx *sql.Tx, table, columns, fields string) (stmt *sql.Stmt) {
res, err := acc.adapter.SimpleInsert("", table, columns, fields)
return acc.prepareTx(tx, res, err)
}
func (acc *Accumulator) SimpleInsertSelectTx(tx *sql.Tx, ins DBInsert, sel DBSelect) (stmt *sql.Stmt) {
res, err := acc.adapter.SimpleInsertSelect("", ins, sel)
return acc.prepareTx(tx, res, err)
}
func (acc *Accumulator) SimpleInsertLeftJoinTx(tx *sql.Tx, ins DBInsert, sel DBJoin) (stmt *sql.Stmt) {
res, err := acc.adapter.SimpleInsertLeftJoin("", ins, sel)
return acc.prepareTx(tx, res, err)
}
func (acc *Accumulator) SimpleInsertInnerJoinTx(tx *sql.Tx, ins DBInsert, sel DBJoin) (stmt *sql.Stmt) {
res, err := acc.adapter.SimpleInsertInnerJoin("", ins, sel)
return acc.prepareTx(tx, res, err)
}
func (acc *Accumulator) SimpleUpdateTx(tx *sql.Tx, table, set, where string) (stmt *sql.Stmt) {
res, err := acc.adapter.SimpleUpdate(qUpdate(table, set, where))
return acc.prepareTx(tx, res, err)
}
func (acc *Accumulator) SimpleDeleteTx(tx *sql.Tx, table, where string) (stmt *sql.Stmt) {
res, err := acc.adapter.SimpleDelete("", table, where)
return acc.prepareTx(tx, res, err)
}
// I don't know why you need this, but here it is x.x
func (acc *Accumulator) PurgeTx(tx *sql.Tx, table string) (stmt *sql.Stmt) {
res, err := acc.adapter.Purge("", table)
return acc.prepareTx(tx, res, err)
}
func (acc *Accumulator) Delete(table string) *accDeleteBuilder {
return &accDeleteBuilder{table, "", nil, acc}
}
func (acc *Accumulator) Update(table string) *accUpdateBuilder {
return &accUpdateBuilder{qUpdate(table, "", ""), acc}
}
func (acc *Accumulator) Select(table string) *AccSelectBuilder {
return &AccSelectBuilder{table, "", "", "", "", nil, nil, "", acc}
}
func (acc *Accumulator) Exists(tbl, col string) *AccSelectBuilder {
return acc.Select(tbl).Columns(col).Where(col + "=?")
}
func (acc *Accumulator) Insert(table string) *accInsertBuilder {
return &accInsertBuilder{table, "", "", acc}
}
func (acc *Accumulator) BulkInsert(table string) *accBulkInsertBuilder {
return &accBulkInsertBuilder{table, "", nil, acc}
}
func (acc *Accumulator) Count(table string) *accCountBuilder {
return &accCountBuilder{table, "", "", nil, nil, "", acc}
}
type SimpleModel struct {
delete *sql.Stmt
create *sql.Stmt
update *sql.Stmt
}
func (acc *Accumulator) SimpleModel(tbl, colstr, primary string) SimpleModel {
var qlist, uplist string
for _, col := range strings.Split(colstr, ",") {
qlist += "?,"
uplist += col + "=?,"
}
if len(qlist) > 0 {
qlist = qlist[0 : len(qlist)-1]
uplist = uplist[0 : len(uplist)-1]
}
where := primary + "=?"
return SimpleModel{
delete: acc.Delete(tbl).Where(where).Prepare(),
create: acc.Insert(tbl).Columns(colstr).Fields(qlist).Prepare(),
update: acc.Update(tbl).Set(uplist).Where(where).Prepare(),
}
}
func (m SimpleModel) Delete(keyVal interface{}) error {
_, err := m.delete.Exec(keyVal)
return err
}
func (m SimpleModel) Update(args ...interface{}) error {
_, err := m.update.Exec(args...)
return err
}
func (m SimpleModel) Create(args ...interface{}) error {
_, err := m.create.Exec(args...)
return err
}
func (m SimpleModel) CreateID(args ...interface{}) (int, error) {
res, err := m.create.Exec(args...)
if err != nil {
return 0, err
}
lastID, err := res.LastInsertId()
return int(lastID), err
}
func (acc *Accumulator) Model(table string) *accModelBuilder {
return &accModelBuilder{table, "", acc}
}
type accModelBuilder struct {
table string
primary string
build *Accumulator
}
func (b *accModelBuilder) Primary(col string) *accModelBuilder {
b.primary = col
return b
}