gosora/query_gen/acc_builders.go
Azareal 414d9c4817 Tighter ratelimiting for password resets.
Eliminated a line in agents.go
Added the DateCutoff method to accCountBuilder.
Function columns should now work for ComplexSelect.
Added type=search to the Search and Filter Widget search box.

Began cleaning some prebuilder logic up.
Began work on the generic ratelimit interface.
2019-03-12 19:13:57 +10:00

337 lines
8.4 KiB
Go

package qgen
import (
"database/sql"
"strconv"
)
type accDeleteBuilder struct {
table string
where string
build *Accumulator
}
func (builder *accDeleteBuilder) Where(where string) *accDeleteBuilder {
if builder.where != "" {
builder.where += " AND "
}
builder.where += where
return builder
}
func (builder *accDeleteBuilder) Prepare() *sql.Stmt {
return builder.build.SimpleDelete(builder.table, builder.where)
}
func (builder *accDeleteBuilder) Run(args ...interface{}) (int, error) {
stmt := builder.Prepare()
if stmt == nil {
return 0, builder.build.FirstError()
}
res, err := stmt.Exec(args...)
if err != nil {
return 0, err
}
lastID, err := res.LastInsertId()
return int(lastID), err
}
type accUpdateBuilder struct {
up *updatePrebuilder
build *Accumulator
}
func (update *accUpdateBuilder) Set(set string) *accUpdateBuilder {
update.up.set = set
return update
}
func (update *accUpdateBuilder) Where(where string) *accUpdateBuilder {
if update.up.where != "" {
update.up.where += " AND "
}
update.up.where += where
return update
}
func (update *accUpdateBuilder) WhereQ(sel *selectPrebuilder) *accUpdateBuilder {
update.up.whereSubQuery = sel
return update
}
func (builder *accUpdateBuilder) Prepare() *sql.Stmt {
if builder.up.whereSubQuery != nil {
return builder.build.prepare(builder.build.adapter.SimpleUpdateSelect(builder.up))
}
return builder.build.prepare(builder.build.adapter.SimpleUpdate(builder.up))
}
func (builder *accUpdateBuilder) Exec(args ...interface{}) (res sql.Result, err error) {
query, err := builder.build.adapter.SimpleUpdate(builder.up)
if err != nil {
return res, err
}
return builder.build.exec(query, args...)
}
type AccSelectBuilder struct {
table string
columns string
where string
orderby string
limit string
dateCutoff *dateCutoff // We might want to do this in a slightly less hacky way
inChain *AccSelectBuilder
inColumn string
build *Accumulator
}
func (builder *AccSelectBuilder) Columns(columns string) *AccSelectBuilder {
builder.columns = columns
return builder
}
func (builder *AccSelectBuilder) Cols(columns string) *AccSelectBuilder {
builder.columns = columns
return builder
}
func (builder *AccSelectBuilder) Where(where string) *AccSelectBuilder {
if builder.where != "" {
builder.where += " AND "
}
builder.where += where
return builder
}
// TODO: Don't implement the SQL at the accumulator level but the adapter level
func (selectItem *AccSelectBuilder) In(column string, inList []int) *AccSelectBuilder {
if len(inList) == 0 {
return selectItem
}
var where = column + " IN("
for _, item := range inList {
where += strconv.Itoa(item) + ","
}
where = where[:len(where)-1] + ")"
if selectItem.where != "" {
where += " AND " + selectItem.where
}
selectItem.where = where
return selectItem
}
func (selectItem *AccSelectBuilder) InQ(column string, subBuilder *AccSelectBuilder) *AccSelectBuilder {
selectItem.inChain = subBuilder
selectItem.inColumn = column
return selectItem
}
func (builder *AccSelectBuilder) DateCutoff(column string, quantity int, unit string) *AccSelectBuilder {
builder.dateCutoff = &dateCutoff{column, quantity, unit}
return builder
}
func (builder *AccSelectBuilder) Orderby(orderby string) *AccSelectBuilder {
builder.orderby = orderby
return builder
}
func (builder *AccSelectBuilder) Limit(limit string) *AccSelectBuilder {
builder.limit = limit
return builder
}
func (builder *AccSelectBuilder) Prepare() *sql.Stmt {
// TODO: Phase out the procedural API and use the adapter's OO API? The OO API might need a bit more work before we do that and it needs to be rolled out to MSSQL.
if builder.dateCutoff != nil || builder.inChain != nil {
selectBuilder := builder.build.GetAdapter().Builder().Select().FromAcc(builder)
return builder.build.prepare(builder.build.GetAdapter().ComplexSelect(selectBuilder))
}
return builder.build.SimpleSelect(builder.table, builder.columns, builder.where, builder.orderby, builder.limit)
}
func (builder *AccSelectBuilder) query() (string, error) {
// TODO: Phase out the procedural API and use the adapter's OO API? The OO API might need a bit more work before we do that and it needs to be rolled out to MSSQL.
if builder.dateCutoff != nil || builder.inChain != nil {
selectBuilder := builder.build.GetAdapter().Builder().Select().FromAcc(builder)
return builder.build.GetAdapter().ComplexSelect(selectBuilder)
}
return builder.build.adapter.SimpleSelect("", builder.table, builder.columns, builder.where, builder.orderby, builder.limit)
}
func (builder *AccSelectBuilder) Query(args ...interface{}) (*sql.Rows, error) {
stmt := builder.Prepare()
if stmt != nil {
return stmt.Query(args...)
}
return nil, builder.build.FirstError()
}
type AccRowWrap struct {
row *sql.Row
err error
}
func (wrap *AccRowWrap) Scan(dest ...interface{}) error {
if wrap.err != nil {
return wrap.err
}
return wrap.row.Scan(dest...)
}
// TODO: Test to make sure the errors are passed up properly
func (builder *AccSelectBuilder) QueryRow(args ...interface{}) *AccRowWrap {
stmt := builder.Prepare()
if stmt != nil {
return &AccRowWrap{stmt.QueryRow(args...), nil}
}
return &AccRowWrap{nil, builder.build.FirstError()}
}
// Experimental, reduces lines
func (builder *AccSelectBuilder) Each(handle func(*sql.Rows) error) error {
query, err := builder.query()
if err != nil {
return err
}
rows, err := builder.build.query(query)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
err = handle(rows)
if err != nil {
return err
}
}
return rows.Err()
}
func (builder *AccSelectBuilder) EachInt(handle func(int) error) error {
query, err := builder.query()
if err != nil {
return err
}
rows, err := builder.build.query(query)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var theInt int
err = rows.Scan(&theInt)
if err != nil {
return err
}
err = handle(theInt)
if err != nil {
return err
}
}
return rows.Err()
}
type accInsertBuilder struct {
table string
columns string
fields string
build *Accumulator
}
func (insert *accInsertBuilder) Columns(columns string) *accInsertBuilder {
insert.columns = columns
return insert
}
func (insert *accInsertBuilder) Fields(fields string) *accInsertBuilder {
insert.fields = fields
return insert
}
func (insert *accInsertBuilder) Prepare() *sql.Stmt {
return insert.build.SimpleInsert(insert.table, insert.columns, insert.fields)
}
func (builder *accInsertBuilder) Exec(args ...interface{}) (res sql.Result, err error) {
query, err := builder.build.adapter.SimpleInsert("", builder.table, builder.columns, builder.fields)
if err != nil {
return res, err
}
return builder.build.exec(query, args...)
}
func (builder *accInsertBuilder) Run(args ...interface{}) (int, error) {
query, err := builder.build.adapter.SimpleInsert("", builder.table, builder.columns, builder.fields)
if err != nil {
return 0, err
}
res, err := builder.build.exec(query, args...)
if err != nil {
return 0, err
}
lastID, err := res.LastInsertId()
return int(lastID), err
}
type accCountBuilder struct {
table string
where string
limit string
dateCutoff *dateCutoff // We might want to do this in a slightly less hacky way
inChain *AccSelectBuilder
inColumn string
build *Accumulator
}
func (b *accCountBuilder) Where(where string) *accCountBuilder {
if b.where != "" {
b.where += " AND "
}
b.where += where
return b
}
func (b *accCountBuilder) Limit(limit string) *accCountBuilder {
b.limit = limit
return b
}
func (b *accCountBuilder) DateCutoff(column string, quantity int, unit string) *accCountBuilder {
b.dateCutoff = &dateCutoff{column, quantity, unit}
return b
}
// TODO: Fix this nasty hack
func (b *accCountBuilder) Prepare() *sql.Stmt {
// TODO: Phase out the procedural API and use the adapter's OO API? The OO API might need a bit more work before we do that and it needs to be rolled out to MSSQL.
if b.dateCutoff != nil || b.inChain != nil {
selBuilder := b.build.GetAdapter().Builder().Count().FromCountAcc(b)
selBuilder.columns = "COUNT(*)"
return b.build.prepare(b.build.GetAdapter().ComplexSelect(selBuilder))
}
return b.build.SimpleCount(b.table, b.where, b.limit)
}
func (b *accCountBuilder) Total() (total int, err error) {
stmt := b.Prepare()
if stmt == nil {
return 0, b.build.FirstError()
}
err = stmt.QueryRow().Scan(&total)
return total, err
}
// TODO: Add a Sum builder for summing viewchunks up into one number for the dashboard?