50d5be6f32
Added many, many tests for the plugin system. Refactored some of the plugin system queries. Fixed a bug where prepared statements would build up and crash Gosora. Removed the inline CSS from the plugin rows.
296 lines
7.2 KiB
Go
296 lines
7.2 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 {
|
|
table string
|
|
set string
|
|
where string
|
|
|
|
build *Accumulator
|
|
}
|
|
|
|
func (update *accUpdateBuilder) Set(set string) *accUpdateBuilder {
|
|
update.set = set
|
|
return update
|
|
}
|
|
|
|
func (update *accUpdateBuilder) Where(where string) *accUpdateBuilder {
|
|
if update.where != "" {
|
|
update.where += " AND "
|
|
}
|
|
update.where += where
|
|
return update
|
|
}
|
|
|
|
func (update *accUpdateBuilder) Prepare() *sql.Stmt {
|
|
return update.build.SimpleUpdate(update.table, update.set, update.where)
|
|
}
|
|
|
|
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 (selectItem *AccSelectBuilder) Columns(columns string) *AccSelectBuilder {
|
|
selectItem.columns = columns
|
|
return selectItem
|
|
}
|
|
|
|
func (selectItem *AccSelectBuilder) Where(where string) *AccSelectBuilder {
|
|
if selectItem.where != "" {
|
|
selectItem.where += " AND "
|
|
}
|
|
selectItem.where += where
|
|
return selectItem
|
|
}
|
|
|
|
// 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 (selectItem *AccSelectBuilder) DateCutoff(column string, quantity int, unit string) *AccSelectBuilder {
|
|
selectItem.dateCutoff = &dateCutoff{column, quantity, unit}
|
|
return selectItem
|
|
}
|
|
|
|
func (selectItem *AccSelectBuilder) Orderby(orderby string) *AccSelectBuilder {
|
|
selectItem.orderby = orderby
|
|
return selectItem
|
|
}
|
|
|
|
func (selectItem *AccSelectBuilder) Limit(limit string) *AccSelectBuilder {
|
|
selectItem.limit = limit
|
|
return selectItem
|
|
}
|
|
|
|
func (selectItem *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 selectItem.dateCutoff != nil || selectItem.inChain != nil {
|
|
selectBuilder := selectItem.build.GetAdapter().Builder().Select().FromAcc(selectItem)
|
|
return selectItem.build.prepare(selectItem.build.GetAdapter().ComplexSelect(selectBuilder))
|
|
}
|
|
return selectItem.build.SimpleSelect(selectItem.table, selectItem.columns, selectItem.where, selectItem.orderby, selectItem.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", builder.table, builder.columns, builder.where, builder.orderby, builder.limit)
|
|
}
|
|
|
|
func (selectItem *AccSelectBuilder) Query(args ...interface{}) (*sql.Rows, error) {
|
|
stmt := selectItem.Prepare()
|
|
if stmt != nil {
|
|
return stmt.Query(args...)
|
|
}
|
|
return nil, selectItem.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", 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", 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
|
|
|
|
build *Accumulator
|
|
}
|
|
|
|
func (count *accCountBuilder) Where(where string) *accCountBuilder {
|
|
if count.where != "" {
|
|
count.where += " AND "
|
|
}
|
|
count.where += where
|
|
return count
|
|
}
|
|
|
|
func (count *accCountBuilder) Limit(limit string) *accCountBuilder {
|
|
count.limit = limit
|
|
return count
|
|
}
|
|
|
|
// TODO: Add QueryRow for this and use it in statistics.go
|
|
func (count *accCountBuilder) Prepare() *sql.Stmt {
|
|
return count.build.SimpleCount(count.table, count.where, count.limit)
|
|
}
|
|
|
|
// TODO: Add a Sum builder for summing viewchunks up into one number for the dashboard?
|