gosora/query_gen/lib/mysql.go
Azareal 21d623cba4 Static CSS files are now processed by the template system. This will be most likely be superseded by a more sophisticated system in the future.
Added the authentication interface and associated struct to better organise / escapsulate the authentication logic.
Added the LogError function. We'll put together a custom logger at some point which will supersede this.
Reorganised things a little.
Moved two queries into the query generator and inlined four with the query builder.
Added SimpleInsertLeftJoin to the query generator.
Added SimpleInsertSelect, SimpleInsertLeftJoin, and SimpleInsertInnerJoin to the query builder.
Removed the undocumented password reset mechanism for security reasons. We have a proper interface for this in the control panel.
Made one of the template compiler errors less cryptic.
Fixed the CSS on widget_simple.
Fixed a glitch in the weak password detector regarding unique character detection.
Moved the responsive CSS in media.partial.css for Tempra Conflux, and Tempra Simple.
Added the CreateUser method to the UserStore.
Users are no longer logged out on all devices when logging out.

Took the first step towards SEO URLs.
2017-06-25 10:56:39 +01:00

779 lines
22 KiB
Go

/* WIP Under Construction */
package qgen
//import "fmt"
import "strings"
import "errors"
func init() {
DB_Registry = append(DB_Registry,
&Mysql_Adapter{Name:"mysql",Buffer:make(map[string]string)},
)
}
type Mysql_Adapter struct
{
Name string
Buffer map[string]string
BufferOrder []string // Map iteration order is random, so we need this to track the order, so we don't get huge diffs every commit
}
func (adapter *Mysql_Adapter) GetName() string {
return adapter.Name
}
func (adapter *Mysql_Adapter) GetStmt(name string) string {
return adapter.Buffer[name]
}
func (adapter *Mysql_Adapter) GetStmts() map[string]string {
return adapter.Buffer
}
func (adapter *Mysql_Adapter) SimpleInsert(name string, table string, columns string, fields string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
if len(columns) == 0 {
return "", errors.New("No columns found for SimpleInsert")
}
if len(fields) == 0 {
return "", errors.New("No input data found for SimpleInsert")
}
var querystr string = "INSERT INTO `" + table + "`("
// Escape the column names, just in case we've used a reserved keyword
for _, column := range _process_columns(columns) {
if column.Type == "function" {
querystr += column.Left + ","
} else {
querystr += "`" + column.Left + "`,"
}
}
// Remove the trailing comma
querystr = querystr[0:len(querystr) - 1]
querystr += ") VALUES ("
for _, field := range _process_fields(fields) {
querystr += field.Name + ","
}
querystr = querystr[0:len(querystr) - 1]
adapter.push_statement(name,querystr + ")")
return querystr + ")", nil
}
func (adapter *Mysql_Adapter) SimpleReplace(name string, table string, columns string, fields string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
if len(columns) == 0 {
return "", errors.New("No columns found for SimpleInsert")
}
if len(fields) == 0 {
return "", errors.New("No input data found for SimpleInsert")
}
var querystr string = "REPLACE INTO `" + table + "`("
// Escape the column names, just in case we've used a reserved keyword
for _, column := range _process_columns(columns) {
if column.Type == "function" {
querystr += column.Left + ","
} else {
querystr += "`" + column.Left + "`,"
}
}
// Remove the trailing comma
querystr = querystr[0:len(querystr) - 1]
querystr += ") VALUES ("
for _, field := range _process_fields(fields) {
querystr += field.Name + ","
}
querystr = querystr[0:len(querystr) - 1]
adapter.push_statement(name,querystr + ")")
return querystr + ")", nil
}
func (adapter *Mysql_Adapter) SimpleUpdate(name string, table string, set string, where string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
if set == "" {
return "", errors.New("You need to set data in this update statement")
}
var querystr string = "UPDATE `" + table + "` SET "
for _, item := range _process_set(set) {
querystr += "`" + item.Column + "` ="
for _, token := range item.Expr {
switch(token.Type) {
case "function","operator","number","substitute":
querystr += " " + token.Contents + ""
case "column":
querystr += " `" + token.Contents + "`"
case "string":
querystr += " '" + token.Contents + "'"
}
}
querystr += ","
}
// Remove the trailing comma
querystr = querystr[0:len(querystr) - 1]
// Add support for BETWEEN x.x
if len(where) != 0 {
querystr += " WHERE"
for _, loc := range _process_where(where) {
for _, token := range loc.Expr {
switch(token.Type) {
case "function","operator","number","substitute":
querystr += " " + token.Contents + ""
case "column":
querystr += " `" + token.Contents + "`"
case "string":
querystr += " '" + token.Contents + "'"
default:
panic("This token doesn't exist o_o")
}
}
querystr += " AND"
}
querystr = querystr[0:len(querystr) - 4]
}
adapter.push_statement(name,querystr)
return querystr, nil
}
func (adapter *Mysql_Adapter) SimpleDelete(name string, table string, where string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
if where == "" {
return "", errors.New("You need to specify what data you want to delete")
}
var querystr string = "DELETE FROM `" + table + "` WHERE"
// Add support for BETWEEN x.x
for _, loc := range _process_where(where) {
for _, token := range loc.Expr {
switch(token.Type) {
case "function","operator","number","substitute":
querystr += " " + token.Contents + ""
case "column":
querystr += " `" + token.Contents + "`"
case "string":
querystr += " '" + token.Contents + "'"
default:
panic("This token doesn't exist o_o")
}
}
querystr += " AND"
}
querystr = strings.TrimSpace(querystr[0:len(querystr) - 4])
adapter.push_statement(name,querystr)
return querystr, nil
}
// We don't want to accidentally wipe tables, so we'll have a seperate method for purging tables instead
func (adapter *Mysql_Adapter) Purge(name string, table string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
adapter.push_statement(name,"DELETE FROM `" + table + "`")
return "DELETE FROM `" + table + "`", nil
}
func (adapter *Mysql_Adapter) SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
if len(columns) == 0 {
return "", errors.New("No columns found for SimpleSelect")
}
// Slice up the user friendly strings into something easier to process
var colslice []string = strings.Split(strings.TrimSpace(columns),",")
var querystr string = "SELECT "
// Escape the column names, just in case we've used a reserved keyword
for _, column := range colslice {
querystr += "`" + strings.TrimSpace(column) + "`,"
}
// Remove the trailing comma
querystr = querystr[0:len(querystr) - 1]
querystr += " FROM `" + table + "`"
// Add support for BETWEEN x.x
if len(where) != 0 {
querystr += " WHERE"
for _, loc := range _process_where(where) {
for _, token := range loc.Expr {
switch(token.Type) {
case "function","operator","number","substitute":
querystr += " " + token.Contents + ""
case "column":
querystr += " `" + token.Contents + "`"
case "string":
querystr += " '" + token.Contents + "'"
default:
panic("This token doesn't exist o_o")
}
}
querystr += " AND"
}
querystr = querystr[0:len(querystr) - 4]
}
if len(orderby) != 0 {
querystr += " ORDER BY "
for _, column := range _process_orderby(orderby) {
querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
}
querystr = querystr[0:len(querystr) - 1]
}
if limit != "" {
querystr += " LIMIT " + limit
}
querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,querystr)
return querystr, nil
}
func (adapter *Mysql_Adapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table1 == "" {
return "", errors.New("You need a name for the left table")
}
if table2 == "" {
return "", errors.New("You need a name for the right table")
}
if len(columns) == 0 {
return "", errors.New("No columns found for SimpleLeftJoin")
}
if len(joiners) == 0 {
return "", errors.New("No joiners found for SimpleLeftJoin")
}
var querystr string = "SELECT "
for _, column := range _process_columns(columns) {
var source, alias string
// Escape the column names, just in case we've used a reserved keyword
if column.Table != "" {
source = "`" + column.Table + "`.`" + column.Left + "`"
} else if column.Type == "function" {
source = column.Left
} else {
source = "`" + column.Left + "`"
}
if column.Alias != "" {
alias = " AS `" + column.Alias + "`"
}
querystr += source + alias + ","
}
// Remove the trailing comma
querystr = querystr[0:len(querystr) - 1]
querystr += " FROM `" + table1 + "` LEFT JOIN `" + table2 + "` ON "
for _, joiner := range _process_joiner(joiners) {
querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND "
}
// Remove the trailing AND
querystr = querystr[0:len(querystr) - 4]
// Add support for BETWEEN x.x
if len(where) != 0 {
querystr += " WHERE"
for _, loc := range _process_where(where) {
for _, token := range loc.Expr {
switch(token.Type) {
case "function","operator","number","substitute":
querystr += " " + token.Contents + ""
case "column":
halves := strings.Split(token.Contents,".")
if len(halves) == 2 {
querystr += " `" + halves[0] + "`.`" + halves[1] + "`"
} else {
querystr += " `" + token.Contents + "`"
}
case "string":
querystr += " '" + token.Contents + "'"
default:
panic("This token doesn't exist o_o")
}
}
querystr += " AND"
}
querystr = querystr[0:len(querystr) - 4]
}
if len(orderby) != 0 {
querystr += " ORDER BY "
for _, column := range _process_orderby(orderby) {
querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
}
querystr = querystr[0:len(querystr) - 1]
}
if limit != "" {
querystr += " LIMIT " + limit
}
querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,querystr)
return querystr, nil
}
func (adapter *Mysql_Adapter) SimpleInnerJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table1 == "" {
return "", errors.New("You need a name for the left table")
}
if table2 == "" {
return "", errors.New("You need a name for the right table")
}
if len(columns) == 0 {
return "", errors.New("No columns found for SimpleInnerJoin")
}
if len(joiners) == 0 {
return "", errors.New("No joiners found for SimpleInnerJoin")
}
var querystr string = "SELECT "
for _, column := range _process_columns(columns) {
var source, alias string
// Escape the column names, just in case we've used a reserved keyword
if column.Table != "" {
source = "`" + column.Table + "`.`" + column.Left + "`"
} else if column.Type == "function" {
source = column.Left
} else {
source = "`" + column.Left + "`"
}
if column.Alias != "" {
alias = " AS `" + column.Alias + "`"
}
querystr += source + alias + ","
}
// Remove the trailing comma
querystr = querystr[0:len(querystr) - 1]
querystr += " FROM `" + table1 + "` INNER JOIN `" + table2 + "` ON "
for _, joiner := range _process_joiner(joiners) {
querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND "
}
// Remove the trailing AND
querystr = querystr[0:len(querystr) - 4]
// Add support for BETWEEN x.x
if len(where) != 0 {
querystr += " WHERE"
for _, loc := range _process_where(where) {
for _, token := range loc.Expr {
switch(token.Type) {
case "function","operator","number","substitute":
querystr += " " + token.Contents + ""
case "column":
halves := strings.Split(token.Contents,".")
if len(halves) == 2 {
querystr += " `" + halves[0] + "`.`" + halves[1] + "`"
} else {
querystr += " `" + token.Contents + "`"
}
case "string":
querystr += " '" + token.Contents + "'"
default:
panic("This token doesn't exist o_o")
}
}
querystr += " AND"
}
querystr = querystr[0:len(querystr) - 4]
}
if len(orderby) != 0 {
querystr += " ORDER BY "
for _, column := range _process_orderby(orderby) {
querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
}
querystr = querystr[0:len(querystr) - 1]
}
if limit != "" {
querystr += " LIMIT " + limit
}
querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,querystr)
return querystr, nil
}
func (adapter *Mysql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel DB_Select) (string, error) {
/* Insert Portion */
var querystr string = "INSERT INTO `" + ins.Table + "`("
// Escape the column names, just in case we've used a reserved keyword
for _, column := range _process_columns(ins.Columns) {
if column.Type == "function" {
querystr += column.Left + ","
} else {
querystr += "`" + column.Left + "`,"
}
}
querystr = querystr[0:len(querystr) - 1] + ") SELECT"
/* Select Portion */
for _, column := range _process_columns(sel.Columns) {
var source, alias string
// Escape the column names, just in case we've used a reserved keyword
if column.Type == "function" || column.Type == "substitute" {
source = column.Left
} else {
source = "`" + column.Left + "`"
}
if column.Alias != "" {
alias = " AS `" + column.Alias + "`"
}
querystr += " " + source + alias + ","
}
querystr = querystr[0:len(querystr) - 1]
querystr += " FROM `" + sel.Table + "`"
// Add support for BETWEEN x.x
if len(sel.Where) != 0 {
querystr += " WHERE"
for _, loc := range _process_where(sel.Where) {
for _, token := range loc.Expr {
switch(token.Type) {
case "function","operator","number","substitute":
querystr += " " + token.Contents + ""
case "column":
querystr += " `" + token.Contents + "`"
case "string":
querystr += " '" + token.Contents + "'"
default:
panic("This token doesn't exist o_o")
}
}
querystr += " AND"
}
querystr = querystr[0:len(querystr) - 4]
}
if len(sel.Orderby) != 0 {
querystr += " ORDER BY "
for _, column := range _process_orderby(sel.Orderby) {
querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
}
querystr = querystr[0:len(querystr) - 1]
}
if sel.Limit != "" {
querystr += " LIMIT " + sel.Limit
}
querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,querystr)
return querystr, nil
}
func (adapter *Mysql_Adapter) SimpleInsertLeftJoin(name string, ins DB_Insert, sel DB_Join) (string, error) {
/* Insert Portion */
var querystr string = "INSERT INTO `" + ins.Table + "`("
// Escape the column names, just in case we've used a reserved keyword
for _, column := range _process_columns(ins.Columns) {
if column.Type == "function" {
querystr += column.Left + ","
} else {
querystr += "`" + column.Left + "`,"
}
}
querystr = querystr[0:len(querystr) - 1] + ") SELECT"
/* Select Portion */
for _, column := range _process_columns(sel.Columns) {
var source, alias string
// Escape the column names, just in case we've used a reserved keyword
if column.Table != "" {
source = "`" + column.Table + "`.`" + column.Left + "`"
} else if column.Type == "function" {
source = column.Left
} else {
source = "`" + column.Left + "`"
}
if column.Alias != "" {
alias = " AS `" + column.Alias + "`"
}
querystr += " " + source + alias + ","
}
querystr = querystr[0:len(querystr) - 1]
querystr += " FROM `" + sel.Table1 + "` LEFT JOIN `" + sel.Table2 + "` ON "
for _, joiner := range _process_joiner(sel.Joiners) {
querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND "
}
querystr = querystr[0:len(querystr) - 4]
// Add support for BETWEEN x.x
if len(sel.Where) != 0 {
querystr += " WHERE"
for _, loc := range _process_where(sel.Where) {
for _, token := range loc.Expr {
switch(token.Type) {
case "function","operator","number","substitute":
querystr += " " + token.Contents + ""
case "column":
halves := strings.Split(token.Contents,".")
if len(halves) == 2 {
querystr += " `" + halves[0] + "`.`" + halves[1] + "`"
} else {
querystr += " `" + token.Contents + "`"
}
case "string":
querystr += " '" + token.Contents + "'"
default:
panic("This token doesn't exist o_o")
}
}
querystr += " AND"
}
querystr = querystr[0:len(querystr) - 4]
}
if len(sel.Orderby) != 0 {
querystr += " ORDER BY "
for _, column := range _process_orderby(sel.Orderby) {
querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
}
querystr = querystr[0:len(querystr) - 1]
}
if sel.Limit != "" {
querystr += " LIMIT " + sel.Limit
}
querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,querystr)
return querystr, nil
}
func (adapter *Mysql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert, sel DB_Join) (string, error) {
/* Insert Portion */
var querystr string = "INSERT INTO `" + ins.Table + "`("
// Escape the column names, just in case we've used a reserved keyword
for _, column := range _process_columns(ins.Columns) {
if column.Type == "function" {
querystr += column.Left + ","
} else {
querystr += "`" + column.Left + "`,"
}
}
querystr = querystr[0:len(querystr) - 1] + ") SELECT"
/* Select Portion */
for _, column := range _process_columns(sel.Columns) {
var source, alias string
// Escape the column names, just in case we've used a reserved keyword
if column.Table != "" {
source = "`" + column.Table + "`.`" + column.Left + "`"
} else if column.Type == "function" {
source = column.Left
} else {
source = "`" + column.Left + "`"
}
if column.Alias != "" {
alias = " AS `" + column.Alias + "`"
}
querystr += " " + source + alias + ","
}
querystr = querystr[0:len(querystr) - 1]
querystr += " FROM `" + sel.Table1 + "` INNER JOIN `" + sel.Table2 + "` ON "
for _, joiner := range _process_joiner(sel.Joiners) {
querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND "
}
querystr = querystr[0:len(querystr) - 4]
// Add support for BETWEEN x.x
if len(sel.Where) != 0 {
querystr += " WHERE"
for _, loc := range _process_where(sel.Where) {
for _, token := range loc.Expr {
switch(token.Type) {
case "function","operator","number","substitute":
querystr += " " + token.Contents + ""
case "column":
halves := strings.Split(token.Contents,".")
if len(halves) == 2 {
querystr += " `" + halves[0] + "`.`" + halves[1] + "`"
} else {
querystr += " `" + token.Contents + "`"
}
case "string":
querystr += " '" + token.Contents + "'"
default:
panic("This token doesn't exist o_o")
}
}
querystr += " AND"
}
querystr = querystr[0:len(querystr) - 4]
}
if len(sel.Orderby) != 0 {
querystr += " ORDER BY "
for _, column := range _process_orderby(sel.Orderby) {
querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
}
querystr = querystr[0:len(querystr) - 1]
}
if sel.Limit != "" {
querystr += " LIMIT " + sel.Limit
}
querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,querystr)
return querystr, nil
}
func (adapter *Mysql_Adapter) SimpleCount(name string, table string, where string, limit string) (string, error) {
if name == "" {
return "", errors.New("You need a name for this statement")
}
if table == "" {
return "", errors.New("You need a name for this table")
}
var querystr string = "SELECT COUNT(*) AS `count` FROM `" + table + "`"
// Add support for BETWEEN x.x
if len(where) != 0 {
querystr += " WHERE"
//fmt.Println("SimpleCount:",name)
//fmt.Println("where:",where)
//fmt.Println("_process_where:",_process_where(where))
for _, loc := range _process_where(where) {
for _, token := range loc.Expr {
switch(token.Type) {
case "function","operator","number","substitute":
querystr += " " + token.Contents + ""
case "column":
querystr += " `" + token.Contents + "`"
case "string":
querystr += " '" + token.Contents + "'"
default:
panic("This token doesn't exist o_o")
}
}
querystr += " AND"
}
querystr = querystr[0:len(querystr) - 4]
}
if limit != "" {
querystr += " LIMIT " + limit
}
querystr = strings.TrimSpace(querystr)
adapter.push_statement(name,querystr)
return querystr, nil
}
func (adapter *Mysql_Adapter) Write() error {
var stmts, body string
for _, name := range adapter.BufferOrder {
stmts += "var " + name + "_stmt *sql.Stmt\n"
body += `
log.Print("Preparing ` + name + ` statement.")
` + name + `_stmt, err = db.Prepare("` + adapter.Buffer[name] + `")
if err != nil {
return err
}
`
}
out := `// Code generated by Gosora. More below:
/* This file was generated by Gosora's Query Generator. Please try to avoid modifying this file, as it might change at any time. */
// +build !pgsql !sqlite !mssql
package main
import "log"
import "database/sql"
` + stmts + `
func gen_mysql() (err error) {
if debug {
log.Print("Building the generated statements")
}
` + body + `
return nil
}
`
return write_file("./gen_mysql.go", out)
}
// Internal method, not exposed in the interface
func (adapter *Mysql_Adapter) push_statement(name string, querystr string ) {
adapter.Buffer[name] = querystr
adapter.BufferOrder = append(adapter.BufferOrder,name)
}