github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/query_gen/mysql.go (about)

     1  /* WIP Under Construction */
     2  package qgen
     3  
     4  import (
     5  	"database/sql"
     6  	"errors"
     7  
     8  	//"fmt"
     9  	"os"
    10  	"runtime"
    11  	"strconv"
    12  	"strings"
    13  
    14  	_ "github.com/go-sql-driver/mysql"
    15  )
    16  
    17  var ErrNoCollation = errors.New("You didn't provide a collation")
    18  
    19  func init() {
    20  	Registry = append(Registry,
    21  		&MysqlAdapter{Name: "mysql", Buffer: make(map[string]DBStmt)},
    22  	)
    23  }
    24  
    25  type MysqlAdapter struct {
    26  	Name        string // ? - Do we really need this? Can't we hard-code this?
    27  	Buffer      map[string]DBStmt
    28  	BufferOrder []string // Map iteration order is random, so we need this to track the order, so we don't get huge diffs every commit
    29  }
    30  
    31  // GetName gives you the name of the database adapter. In this case, it's mysql
    32  func (a *MysqlAdapter) GetName() string {
    33  	return a.Name
    34  }
    35  
    36  func (a *MysqlAdapter) GetStmt(name string) DBStmt {
    37  	return a.Buffer[name]
    38  }
    39  
    40  func (a *MysqlAdapter) GetStmts() map[string]DBStmt {
    41  	return a.Buffer
    42  }
    43  
    44  // TODO: Add an option to disable unix pipes
    45  func (a *MysqlAdapter) BuildConn(config map[string]string) (*sql.DB, error) {
    46  	dbCollation, ok := config["collation"]
    47  	if !ok {
    48  		return nil, ErrNoCollation
    49  	}
    50  	var dbpassword string
    51  	if config["password"] != "" {
    52  		dbpassword = ":" + config["password"]
    53  	}
    54  
    55  	// First try opening a pipe as those are faster
    56  	if runtime.GOOS == "linux" {
    57  		dbsocket := "/tmp/mysql.sock"
    58  		if config["socket"] != "" {
    59  			dbsocket = config["socket"]
    60  		}
    61  
    62  		// The MySQL adapter refuses to open any other connections, if the unix socket doesn't exist, so check for it first
    63  		_, err := os.Stat(dbsocket)
    64  		if err == nil {
    65  			db, err := sql.Open("mysql", config["username"]+dbpassword+"@unix("+dbsocket+")/"+config["name"]+"?collation="+dbCollation+"&parseTime=true")
    66  			if err == nil {
    67  				// Make sure that the connection is alive
    68  				return db, db.Ping()
    69  			}
    70  		}
    71  	}
    72  
    73  	// Open the database connection
    74  	db, err := sql.Open("mysql", config["username"]+dbpassword+"@tcp("+config["host"]+":"+config["port"]+")/"+config["name"]+"?collation="+dbCollation+"&parseTime=true")
    75  	if err != nil {
    76  		return db, err
    77  	}
    78  
    79  	// Make sure that the connection is alive
    80  	return db, db.Ping()
    81  }
    82  
    83  func (a *MysqlAdapter) DbVersion() string {
    84  	return "SELECT VERSION()"
    85  }
    86  
    87  func (a *MysqlAdapter) DropTable(name, table string) (string, error) {
    88  	if table == "" {
    89  		return "", errors.New("You need a name for this table")
    90  	}
    91  	q := "DROP TABLE IF EXISTS `" + table + "`;"
    92  	// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
    93  	a.pushStatement(name, "drop-table", q)
    94  	return q, nil
    95  }
    96  
    97  func (a *MysqlAdapter) CreateTable(name, table, charset, collation string, cols []DBTableColumn, keys []DBTableKey) (string, error) {
    98  	if table == "" {
    99  		return "", errors.New("You need a name for this table")
   100  	}
   101  	if len(cols) == 0 {
   102  		return "", errors.New("You can't have a table with no columns")
   103  	}
   104  
   105  	var qsb strings.Builder
   106  	//q := "CREATE TABLE `" + table + "`("
   107  	w := func(s string) {
   108  		qsb.WriteString(s)
   109  	}
   110  	w("CREATE TABLE `")
   111  	w(table)
   112  	w("`(")
   113  	for i, col := range cols {
   114  		if i != 0 {
   115  			w(",\n\t`")
   116  		} else {
   117  			w("\n\t`")
   118  		}
   119  		col, size, end := a.parseColumn(col)
   120  		//q += "\n\t`" + col.Name + "` " + col.Type + size + end + ","
   121  		w(col.Name)
   122  		w("` ")
   123  		w(col.Type)
   124  		w(size)
   125  		w(end)
   126  	}
   127  
   128  	if len(keys) > 0 {
   129  		/*if len(cols) > 0 {
   130  			w(",")
   131  		}*/
   132  		for _, k := range keys {
   133  			/*if ii != 0 {
   134  				w(",\n\t")
   135  			} else {
   136  				w("\n\t")
   137  			}*/
   138  			w(",\n\t")
   139  			//q += "\n\t" + key.Type
   140  			w(k.Type)
   141  			if k.Type != "unique" {
   142  				//q += " key"
   143  				w(" key")
   144  			}
   145  			if k.Type == "foreign" {
   146  				cols := strings.Split(k.Columns, ",")
   147  				//q += "(`" + cols[0] + "`) REFERENCES `" + k.FTable + "`(`" + cols[1] + "`)"
   148  				w("(`")
   149  				w(cols[0])
   150  				w("`) REFERENCES `")
   151  				w(k.FTable)
   152  				w("`(`")
   153  				w(cols[1])
   154  				w("`)")
   155  				if k.Cascade {
   156  					//q += " ON DELETE CASCADE"
   157  					w(" ON DELETE CASCADE")
   158  				}
   159  				//q += ","
   160  			} else {
   161  				//q += "("
   162  				w("(")
   163  				for i, col := range strings.Split(k.Columns, ",") {
   164  					/*if i != 0 {
   165  						q += ",`" + col + "`"
   166  					} else {
   167  						q += "`" + col + "`"
   168  					}*/
   169  					if i != 0 {
   170  						w(",`")
   171  					} else {
   172  						w("`")
   173  					}
   174  					w(col)
   175  					w("`")
   176  				}
   177  				//q += "),"
   178  				w(")")
   179  			}
   180  		}
   181  	}
   182  
   183  	//q = q[0:len(q)-1] + "\n)"
   184  	w("\n)")
   185  	if charset != "" {
   186  		//q += " CHARSET=" + charset
   187  		w(" CHARSET=")
   188  		w(charset)
   189  	}
   190  	if collation != "" {
   191  		//q += " COLLATE " + collation
   192  		w(" COLLATE ")
   193  		w(collation)
   194  	}
   195  
   196  	// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
   197  	//q += ";"
   198  	w(";")
   199  	q := qsb.String()
   200  	a.pushStatement(name, "create-table", q)
   201  	return q, nil
   202  }
   203  
   204  func (a *MysqlAdapter) DropColumn(name, table, colName string) (string, error) {
   205  	q := "ALTER TABLE `" + table + "` DROP COLUMN `" + colName + "`;"
   206  	a.pushStatement(name, "drop-column", q)
   207  	return q, nil
   208  }
   209  
   210  // ! Currently broken in MariaDB. Planned.
   211  func (a *MysqlAdapter) RenameColumn(name, table, oldName, newName string) (string, error) {
   212  	q := "ALTER TABLE `" + table + "` RENAME COLUMN `" + oldName + "` TO `" + newName + "`;"
   213  	a.pushStatement(name, "rename-column", q)
   214  	return q, nil
   215  }
   216  
   217  func (a *MysqlAdapter) ChangeColumn(name, table, colName string, col DBTableColumn) (string, error) {
   218  	col.Default = ""
   219  	col, size, end := a.parseColumn(col)
   220  	q := "ALTER TABLE `" + table + "` CHANGE COLUMN `" + colName + "` `" + col.Name + "` " + col.Type + size + end
   221  	a.pushStatement(name, "change-column", q)
   222  	return q, nil
   223  }
   224  
   225  func (a *MysqlAdapter) SetDefaultColumn(name, table, colName, colType, defaultStr string) (string, error) {
   226  	if defaultStr == "" {
   227  		defaultStr = "''"
   228  	}
   229  	// TODO: Exclude the other variants of text like mediumtext and longtext too
   230  	expr := ""
   231  	/*if colType == "datetime" && defaultStr[len(defaultStr)-1] == ')' {
   232  		end += defaultStr
   233  	} else */if a.stringyType(colType) && defaultStr != "''" {
   234  		expr += "'" + defaultStr + "'"
   235  	} else {
   236  		expr += defaultStr
   237  	}
   238  	q := "ALTER TABLE `" + table + "` ALTER COLUMN `" + colName + "` SET DEFAULT " + expr + ";"
   239  	a.pushStatement(name, "set-default-column", q)
   240  	return q, nil
   241  }
   242  
   243  func (a *MysqlAdapter) parseColumn(col DBTableColumn) (ocol DBTableColumn, size, end string) {
   244  	// Make it easier to support Cassandra in the future
   245  	if col.Type == "createdAt" {
   246  		col.Type = "datetime"
   247  		// MySQL doesn't support this x.x
   248  		/*if col.Default == "" {
   249  			col.Default = "UTC_TIMESTAMP()"
   250  		}*/
   251  	} else if col.Type == "json" {
   252  		col.Type = "text"
   253  	}
   254  	if col.Size > 0 {
   255  		size = "(" + strconv.Itoa(col.Size) + ")"
   256  	}
   257  
   258  	// TODO: Exclude the other variants of text like mediumtext and longtext too
   259  	if col.Default != "" && col.Type != "text" {
   260  		end = " DEFAULT "
   261  		/*if col.Type == "datetime" && col.Default[len(col.Default)-1] == ')' {
   262  			end += column.Default
   263  		} else */if a.stringyType(col.Type) && col.Default != "''" {
   264  			end += "'" + col.Default + "'"
   265  		} else {
   266  			end += col.Default
   267  		}
   268  	}
   269  
   270  	if col.Null {
   271  		end += " null"
   272  	} else {
   273  		end += " not null"
   274  	}
   275  	if col.AutoIncrement {
   276  		end += " AUTO_INCREMENT"
   277  	}
   278  	return col, size, end
   279  }
   280  
   281  // TODO: Support AFTER column
   282  // TODO: Test to make sure everything works here
   283  func (a *MysqlAdapter) AddColumn(name, table string, col DBTableColumn, key *DBTableKey) (string, error) {
   284  	if table == "" {
   285  		return "", errors.New("You need a name for this table")
   286  	}
   287  
   288  	col, size, end := a.parseColumn(col)
   289  	q := "ALTER TABLE `" + table + "` ADD COLUMN " + "`" + col.Name + "` " + col.Type + size + end
   290  
   291  	if key != nil {
   292  		q += " " + key.Type
   293  		if key.Type != "unique" {
   294  			q += " key"
   295  		} else if key.Type == "primary" {
   296  			q += " first"
   297  		}
   298  	}
   299  
   300  	// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
   301  	a.pushStatement(name, "add-column", q)
   302  	return q, nil
   303  }
   304  
   305  // TODO: Test to make sure everything works here
   306  func (a *MysqlAdapter) AddIndex(name, table, iname, colname string) (string, error) {
   307  	if table == "" {
   308  		return "", errors.New("You need a name for this table")
   309  	}
   310  	if iname == "" {
   311  		return "", errors.New("You need a name for the index")
   312  	}
   313  	if colname == "" {
   314  		return "", errors.New("You need a name for the column")
   315  	}
   316  
   317  	q := "ALTER TABLE `" + table + "` ADD INDEX " + "`i_" + iname + "` (`" + colname + "`);"
   318  	// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
   319  	a.pushStatement(name, "add-index", q)
   320  	return q, nil
   321  }
   322  
   323  // TODO: Test to make sure everything works here
   324  // Only supports FULLTEXT right now
   325  func (a *MysqlAdapter) AddKey(name, table, cols string, key DBTableKey) (string, error) {
   326  	if table == "" {
   327  		return "", errors.New("You need a name for this table")
   328  	}
   329  	if cols == "" {
   330  		return "", errors.New("You need to specify columns")
   331  	}
   332  
   333  	var colstr string
   334  	for _, col := range strings.Split(cols, ",") {
   335  		colstr += "`" + col + "`,"
   336  	}
   337  	if len(colstr) > 1 {
   338  		colstr = colstr[:len(colstr)-1]
   339  	}
   340  
   341  	var q string
   342  	if key.Type == "fulltext" {
   343  		q = "ALTER TABLE `" + table + "` ADD FULLTEXT(" + colstr + ")"
   344  	} else {
   345  		return "", errors.New("Only fulltext is supported by AddKey right now")
   346  	}
   347  
   348  	// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
   349  	a.pushStatement(name, "add-key", q)
   350  	return q, nil
   351  }
   352  
   353  func (a *MysqlAdapter) RemoveIndex(name, table, iname string) (string, error) {
   354  	if table == "" {
   355  		return "", errors.New("You need a name for this table")
   356  	}
   357  	if iname == "" {
   358  		return "", errors.New("You need a name for the index")
   359  	}
   360  	q := "ALTER TABLE `" + table + "` DROP INDEX `" + iname + "`"
   361  
   362  	// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
   363  	a.pushStatement(name, "remove-index", q)
   364  	return q, nil
   365  }
   366  
   367  func (a *MysqlAdapter) AddForeignKey(name, table, col, ftable, fcolumn string, cascade bool) (out string, e error) {
   368  	c := func(str string, val bool) {
   369  		if e != nil || !val {
   370  			return
   371  		}
   372  		e = errors.New("You need a " + str + " for this table")
   373  	}
   374  	c("name", table == "")
   375  	c("col", col == "")
   376  	c("ftable", ftable == "")
   377  	c("fcolumn", fcolumn == "")
   378  	if e != nil {
   379  		return "", e
   380  	}
   381  
   382  	q := "ALTER TABLE `" + table + "` ADD CONSTRAINT `fk_" + col + "` FOREIGN KEY(`" + col + "`) REFERENCES `" + ftable + "`(`" + fcolumn + "`)"
   383  	if cascade {
   384  		q += " ON DELETE CASCADE"
   385  	}
   386  
   387  	// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
   388  	a.pushStatement(name, "add-foreign-key", q)
   389  	return q, nil
   390  }
   391  
   392  const silen1 = len("INSERT INTO``()VALUES() ")
   393  
   394  func (a *MysqlAdapter) SimpleInsert(name, table, cols, fields string) (string, error) {
   395  	if table == "" {
   396  		return "", errors.New("You need a name for this table")
   397  	}
   398  
   399  	var sb *strings.Builder
   400  	ii := queryStrPool.Get()
   401  	if ii == nil {
   402  		sb = &strings.Builder{}
   403  	} else {
   404  		sb = ii.(*strings.Builder)
   405  		sb.Reset()
   406  	}
   407  
   408  	sb.Grow(silen1 + len(table))
   409  	sb.WriteString("INSERT INTO`")
   410  	sb.WriteString(table)
   411  	if cols != "" {
   412  		sb.WriteString("`(")
   413  		sb.WriteString(a.buildColumns(cols))
   414  		sb.WriteString(")VALUES(")
   415  		fs := processFields(fields)
   416  		sb.Grow(len(fs) * 3)
   417  		for i, field := range fs {
   418  			if i != 0 {
   419  				sb.WriteString(",")
   420  			}
   421  			nameLen := len(field.Name)
   422  			if field.Name[0] == '"' && field.Name[nameLen-1] == '"' && nameLen >= 3 {
   423  				sb.WriteRune('\'')
   424  				sb.WriteString(field.Name[1 : nameLen-1])
   425  				sb.WriteRune('\'')
   426  			} else if field.Name[0] == '\'' && field.Name[nameLen-1] == '\'' && nameLen >= 3 {
   427  				sb.WriteRune('\'')
   428  				sb.WriteString(strings.Replace(field.Name[1:nameLen-1], "'", "''", -1))
   429  				sb.WriteRune('\'')
   430  			} else {
   431  				sb.WriteString(field.Name)
   432  			}
   433  		}
   434  		sb.WriteString(")")
   435  	} else {
   436  		sb.WriteString("`()VALUES()")
   437  	}
   438  
   439  	// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
   440  	q := sb.String()
   441  	queryStrPool.Put(sb)
   442  	a.pushStatement(name, "insert", q)
   443  	return q, nil
   444  }
   445  
   446  func (a *MysqlAdapter) SimpleBulkInsert(name, table, cols string, fieldSet []string) (string, error) {
   447  	if table == "" {
   448  		return "", errors.New("You need a name for this table")
   449  	}
   450  
   451  	var sb *strings.Builder
   452  	ii := queryStrPool.Get()
   453  	if ii == nil {
   454  		sb = &strings.Builder{}
   455  	} else {
   456  		sb = ii.(*strings.Builder)
   457  		sb.Reset()
   458  	}
   459  	sb.Grow(silen1 + len(table))
   460  	sb.WriteString("INSERT INTO`")
   461  	sb.WriteString(table)
   462  	if cols != "" {
   463  		sb.WriteString("`(")
   464  		sb.WriteString(a.buildColumns(cols))
   465  		sb.WriteString(")VALUES(")
   466  		for oi, fields := range fieldSet {
   467  			if oi != 0 {
   468  				sb.WriteString(",(")
   469  			}
   470  			fs := processFields(fields)
   471  			sb.Grow(len(fs) * 3)
   472  			for i, field := range fs {
   473  				if i != 0 {
   474  					sb.WriteString(",")
   475  				}
   476  				nameLen := len(field.Name)
   477  				if field.Name[0] == '"' && field.Name[nameLen-1] == '"' && nameLen >= 3 {
   478  					//field.Name = "'" + field.Name[1:nameLen-1] + "'"
   479  					sb.WriteString("'")
   480  					sb.WriteString(field.Name[1 : nameLen-1])
   481  					sb.WriteString("'")
   482  					continue
   483  				}
   484  				if field.Name[0] == '\'' && field.Name[nameLen-1] == '\'' && nameLen >= 3 {
   485  					//field.Name = "'" + strings.Replace(field.Name[1:nameLen-1], "'", "''", -1) + "'"
   486  					sb.WriteString("'")
   487  					sb.WriteString(strings.Replace(field.Name[1:nameLen-1], "'", "''", -1))
   488  					sb.WriteString("'")
   489  					continue
   490  				}
   491  				sb.WriteString(field.Name)
   492  			}
   493  			sb.WriteString(")")
   494  		}
   495  	} else {
   496  		sb.WriteString("`()VALUES()")
   497  	}
   498  
   499  	// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
   500  	q := sb.String()
   501  	queryStrPool.Put(sb)
   502  	a.pushStatement(name, "bulk-insert", q)
   503  	return q, nil
   504  }
   505  
   506  func (a *MysqlAdapter) buildColumns(cols string) string {
   507  	if cols == "" {
   508  		return ""
   509  	}
   510  
   511  	// Escape the column names, just in case we've used a reserved keyword
   512  	var cb strings.Builder
   513  	pcols := processColumns(cols)
   514  	var n int
   515  	for i, col := range pcols {
   516  		if i != 0 {
   517  			n += 1
   518  		}
   519  		if col.Type == TokenFunc {
   520  			n += len(col.Left)
   521  		} else {
   522  			n += len(col.Left) + 2
   523  		}
   524  	}
   525  	cb.Grow(n)
   526  
   527  	for i, col := range pcols {
   528  		if col.Type == TokenFunc {
   529  			if i != 0 {
   530  				cb.WriteString(",")
   531  			}
   532  			//q += col.Left + ","
   533  			cb.WriteString(col.Left)
   534  		} else {
   535  			//q += "`" + col.Left + "`,"
   536  			if i != 0 {
   537  				cb.WriteString(",`")
   538  			} else {
   539  				cb.WriteString("`")
   540  			}
   541  			cb.WriteString(col.Left)
   542  			cb.WriteString("`")
   543  		}
   544  	}
   545  
   546  	return cb.String()
   547  }
   548  
   549  // ! DEPRECATED
   550  func (a *MysqlAdapter) SimpleReplace(name, table, cols, fields string) (string, error) {
   551  	if table == "" {
   552  		return "", errors.New("You need a name for this table")
   553  	}
   554  	if len(cols) == 0 {
   555  		return "", errors.New("No columns found for SimpleInsert")
   556  	}
   557  	if len(fields) == 0 {
   558  		return "", errors.New("No input data found for SimpleInsert")
   559  	}
   560  
   561  	q := "REPLACE INTO `" + table + "`(" + a.buildColumns(cols) + ") VALUES ("
   562  	for _, field := range processFields(fields) {
   563  		q += field.Name + ","
   564  	}
   565  	q = q[0:len(q)-1] + ")"
   566  
   567  	// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
   568  	a.pushStatement(name, "replace", q)
   569  	return q, nil
   570  }
   571  
   572  func (a *MysqlAdapter) SimpleUpsert(name, table, columns, fields, where string) (string, error) {
   573  	if table == "" {
   574  		return "", errors.New("You need a name for this table")
   575  	}
   576  	if len(columns) == 0 {
   577  		return "", errors.New("No columns found for SimpleInsert")
   578  	}
   579  	if len(fields) == 0 {
   580  		return "", errors.New("No input data found for SimpleInsert")
   581  	}
   582  	if where == "" {
   583  		return "", errors.New("You need a where for this upsert")
   584  	}
   585  
   586  	q := "INSERT INTO `" + table + "`("
   587  	parsedFields := processFields(fields)
   588  
   589  	var insertColumns, insertValues string
   590  	setBit := ") ON DUPLICATE KEY UPDATE "
   591  
   592  	for columnID, col := range processColumns(columns) {
   593  		field := parsedFields[columnID]
   594  		if col.Type == TokenFunc {
   595  			insertColumns += col.Left + ","
   596  			insertValues += field.Name + ","
   597  			setBit += col.Left + " = " + field.Name + " AND "
   598  		} else {
   599  			insertColumns += "`" + col.Left + "`,"
   600  			insertValues += field.Name + ","
   601  			setBit += "`" + col.Left + "` = " + field.Name + " AND "
   602  		}
   603  	}
   604  	insertColumns = insertColumns[0 : len(insertColumns)-1]
   605  	insertValues = insertValues[0 : len(insertValues)-1]
   606  	insertColumns += ") VALUES (" + insertValues
   607  	setBit = setBit[0 : len(setBit)-5]
   608  
   609  	q += insertColumns + setBit
   610  
   611  	// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
   612  	a.pushStatement(name, "upsert", q)
   613  	return q, nil
   614  }
   615  
   616  const sulen1 = len("UPDATE `` SET ")
   617  
   618  func (a *MysqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error) {
   619  	if up.table == "" {
   620  		return "", errors.New("You need a name for this table")
   621  	}
   622  	if up.set == "" {
   623  		return "", errors.New("You need to set data in this update statement")
   624  	}
   625  
   626  	var sb *strings.Builder
   627  	ii := queryStrPool.Get()
   628  	if ii == nil {
   629  		sb = &strings.Builder{}
   630  	} else {
   631  		sb = ii.(*strings.Builder)
   632  		sb.Reset()
   633  	}
   634  	sb.Grow(sulen1 + len(up.table))
   635  	sb.WriteString("UPDATE `")
   636  	sb.WriteString(up.table)
   637  	sb.WriteString("` SET ")
   638  
   639  	set := processSet(up.set)
   640  	sb.Grow(len(set) * 6)
   641  	for i, item := range set {
   642  		if i != 0 {
   643  			sb.WriteString(",`")
   644  		} else {
   645  			sb.WriteString("`")
   646  		}
   647  		sb.WriteString(item.Column)
   648  		sb.WriteString("`=")
   649  		for _, token := range item.Expr {
   650  			switch token.Type {
   651  			case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr:
   652  				sb.WriteString(" ")
   653  				sb.WriteString(token.Contents)
   654  			case TokenColumn:
   655  				sb.WriteString(" `")
   656  				sb.WriteString(token.Contents)
   657  				sb.WriteString("`")
   658  			case TokenString:
   659  				sb.WriteString(" '")
   660  				sb.WriteString(token.Contents)
   661  				sb.WriteString("'")
   662  			}
   663  		}
   664  	}
   665  
   666  	e := a.buildFlexiWhereSb(sb, up.where, up.dateCutoff)
   667  	if e != nil {
   668  		return sb.String(), e
   669  	}
   670  
   671  	// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
   672  	q := sb.String()
   673  	queryStrPool.Put(sb)
   674  	a.pushStatement(up.name, "update", q)
   675  	return q, nil
   676  }
   677  
   678  const sdlen1 = len("DELETE FROM `` WHERE")
   679  
   680  func (a *MysqlAdapter) SimpleDelete(name, table, where string) (string, error) {
   681  	if table == "" {
   682  		return "", errors.New("You need a name for this table")
   683  	}
   684  	if where == "" {
   685  		return "", errors.New("You need to specify what data you want to delete")
   686  	}
   687  	var sb *strings.Builder
   688  	ii := queryStrPool.Get()
   689  	if ii == nil {
   690  		sb = &strings.Builder{}
   691  	} else {
   692  		sb = ii.(*strings.Builder)
   693  		sb.Reset()
   694  	}
   695  	sb.Grow(sdlen1 + len(table))
   696  	sb.WriteString("DELETE FROM `")
   697  	sb.WriteString(table)
   698  	sb.WriteString("` WHERE")
   699  
   700  	// Add support for BETWEEN x.x
   701  	for i, loc := range processWhere(where) {
   702  		if i != 0 {
   703  			sb.WriteString(" AND")
   704  		}
   705  		for _, token := range loc.Expr {
   706  			switch token.Type {
   707  			case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot:
   708  				sb.WriteRune(' ')
   709  				sb.WriteString(token.Contents)
   710  			case TokenColumn:
   711  				sb.WriteString(" `")
   712  				sb.WriteString(token.Contents)
   713  				sb.WriteRune('`')
   714  			case TokenString:
   715  				sb.WriteString(" '")
   716  				sb.WriteString(token.Contents)
   717  				sb.WriteRune('\'')
   718  			default:
   719  				panic("This token doesn't exist o_o")
   720  			}
   721  		}
   722  	}
   723  
   724  	q := strings.TrimSpace(sb.String())
   725  	queryStrPool.Put(sb)
   726  	// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
   727  	a.pushStatement(name, "delete", q)
   728  	return q, nil
   729  }
   730  
   731  const cdlen1 = len("DELETE FROM ``")
   732  
   733  func (a *MysqlAdapter) ComplexDelete(b *deletePrebuilder) (string, error) {
   734  	if b.table == "" {
   735  		return "", errors.New("You need a name for this table")
   736  	}
   737  	if b.where == "" && b.dateCutoff == nil {
   738  		return "", errors.New("You need to specify what data you want to delete")
   739  	}
   740  	var sb *strings.Builder
   741  	ii := queryStrPool.Get()
   742  	if ii == nil {
   743  		sb = &strings.Builder{}
   744  	} else {
   745  		sb = ii.(*strings.Builder)
   746  		sb.Reset()
   747  	}
   748  	sb.Grow(cdlen1 + len(b.table))
   749  	sb.WriteString("DELETE FROM `")
   750  	sb.WriteString(b.table)
   751  	sb.WriteRune('`')
   752  
   753  	e := a.buildFlexiWhereSb(sb, b.where, b.dateCutoff)
   754  	if e != nil {
   755  		return sb.String(), e
   756  	}
   757  	q := sb.String()
   758  	queryStrPool.Put(sb)
   759  
   760  	// TODO: Shunt the table name logic and associated stmt list up to the a higher layer to reduce the amount of unnecessary overhead in the builder / accumulator
   761  	a.pushStatement(b.name, "delete", q)
   762  	return q, nil
   763  }
   764  
   765  // We don't want to accidentally wipe tables, so we'll have a separate method for purging tables instead
   766  func (a *MysqlAdapter) Purge(name, table string) (string, error) {
   767  	if table == "" {
   768  		return "", errors.New("You need a name for this table")
   769  	}
   770  	q := "DELETE FROM `" + table + "`"
   771  	a.pushStatement(name, "purge", q)
   772  	return q, nil
   773  }
   774  
   775  func (a *MysqlAdapter) buildWhere(where string, sb *strings.Builder) error {
   776  	if len(where) == 0 {
   777  		return nil
   778  	}
   779  	spl := processWhere(where)
   780  	sb.Grow(len(spl) * 8)
   781  	for i, loc := range spl {
   782  		if i != 0 {
   783  			sb.WriteString(" AND ")
   784  		} else {
   785  			sb.WriteString(" WHERE ")
   786  		}
   787  		for _, token := range loc.Expr {
   788  			switch token.Type {
   789  			case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot, TokenLike:
   790  				sb.WriteString(token.Contents)
   791  				sb.WriteRune(' ')
   792  			case TokenColumn:
   793  				sb.WriteRune('`')
   794  				sb.WriteString(token.Contents)
   795  				sb.WriteRune('`')
   796  			case TokenString:
   797  				sb.WriteRune('\'')
   798  				sb.WriteString(token.Contents)
   799  				sb.WriteRune('\'')
   800  			default:
   801  				return errors.New("This token doesn't exist o_o")
   802  			}
   803  		}
   804  	}
   805  	return nil
   806  }
   807  
   808  // The new version of buildWhere() currently only used in ComplexSelect for complex OO builder queries
   809  const FlexiHint1 = len(` <UTC_TIMESTAMP()-interval ?  `)
   810  
   811  func (a *MysqlAdapter) buildFlexiWhere(where string, dateCutoff *dateCutoff) (q string, err error) {
   812  	if len(where) == 0 && dateCutoff == nil {
   813  		return "", nil
   814  	}
   815  
   816  	var sb strings.Builder
   817  	sb.WriteString(" WHERE")
   818  	if dateCutoff != nil {
   819  		sb.Grow(6 + FlexiHint1)
   820  		sb.WriteRune(' ')
   821  		sb.WriteString(dateCutoff.Column)
   822  		switch dateCutoff.Type {
   823  		case 0:
   824  			sb.WriteString(" BETWEEN (UTC_TIMESTAMP()-interval ")
   825  			sb.WriteString(strconv.Itoa(dateCutoff.Quantity))
   826  			sb.WriteString(" ")
   827  			sb.WriteString(dateCutoff.Unit)
   828  			sb.WriteString(") AND UTC_TIMESTAMP()")
   829  		case 11:
   830  			sb.WriteString("<UTC_TIMESTAMP()-interval ? ")
   831  			sb.WriteString(dateCutoff.Unit)
   832  		default:
   833  			sb.WriteString("<UTC_TIMESTAMP()-interval ")
   834  			sb.WriteString(strconv.Itoa(dateCutoff.Quantity))
   835  			sb.WriteRune(' ')
   836  			sb.WriteString(dateCutoff.Unit)
   837  		}
   838  	}
   839  	if dateCutoff != nil && len(where) != 0 {
   840  		sb.WriteString(" AND")
   841  	}
   842  
   843  	if len(where) != 0 {
   844  		wh := processWhere(where)
   845  		sb.Grow((len(wh) * 8) - 5)
   846  		for i, loc := range wh {
   847  			if i != 0 {
   848  				sb.WriteString(" AND ")
   849  			}
   850  			for _, token := range loc.Expr {
   851  				switch token.Type {
   852  				case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot, TokenLike:
   853  					sb.WriteString(" ")
   854  					sb.WriteString(token.Contents)
   855  				case TokenColumn:
   856  					sb.WriteString(" `")
   857  					sb.WriteString(token.Contents)
   858  					sb.WriteString("`")
   859  				case TokenString:
   860  					sb.WriteString(" '")
   861  					sb.WriteString(token.Contents)
   862  					sb.WriteString("'")
   863  				default:
   864  					return sb.String(), errors.New("This token doesn't exist o_o")
   865  				}
   866  			}
   867  		}
   868  	}
   869  	return sb.String(), nil
   870  }
   871  
   872  func (a *MysqlAdapter) buildFlexiWhereSb(sb *strings.Builder, where string, dateCutoff *dateCutoff) (err error) {
   873  	if len(where) == 0 && dateCutoff == nil {
   874  		return nil
   875  	}
   876  
   877  	sb.WriteString(" WHERE")
   878  	if dateCutoff != nil {
   879  		sb.Grow(6 + FlexiHint1)
   880  		sb.WriteRune(' ')
   881  		sb.WriteString(dateCutoff.Column)
   882  		switch dateCutoff.Type {
   883  		case 0:
   884  			sb.WriteString(" BETWEEN (UTC_TIMESTAMP()-interval ")
   885  			sb.WriteString(strconv.Itoa(dateCutoff.Quantity))
   886  			sb.WriteString(" ")
   887  			sb.WriteString(dateCutoff.Unit)
   888  			sb.WriteString(") AND UTC_TIMESTAMP()")
   889  		case 11:
   890  			sb.WriteString("<UTC_TIMESTAMP()-interval ? ")
   891  			sb.WriteString(dateCutoff.Unit)
   892  		default:
   893  			sb.WriteString("<UTC_TIMESTAMP()-interval ")
   894  			sb.WriteString(strconv.Itoa(dateCutoff.Quantity))
   895  			sb.WriteRune(' ')
   896  			sb.WriteString(dateCutoff.Unit)
   897  		}
   898  	}
   899  	if dateCutoff != nil && len(where) != 0 {
   900  		sb.WriteString(" AND")
   901  	}
   902  
   903  	if len(where) != 0 {
   904  		wh := processWhere(where)
   905  		sb.Grow((len(wh) * 8) - 5)
   906  		for i, loc := range wh {
   907  			if i != 0 {
   908  				sb.WriteString(" AND ")
   909  			}
   910  			for _, token := range loc.Expr {
   911  				switch token.Type {
   912  				case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot, TokenLike:
   913  					sb.WriteString(" ")
   914  					sb.WriteString(token.Contents)
   915  				case TokenColumn:
   916  					sb.WriteString(" `")
   917  					sb.WriteString(token.Contents)
   918  					sb.WriteString("`")
   919  				case TokenString:
   920  					sb.WriteString(" '")
   921  					sb.WriteString(token.Contents)
   922  					sb.WriteString("'")
   923  				default:
   924  					return errors.New("This token doesn't exist o_o")
   925  				}
   926  			}
   927  		}
   928  	}
   929  	return nil
   930  }
   931  
   932  func (a *MysqlAdapter) buildOrderby(orderby string) (q string) {
   933  	if len(orderby) != 0 {
   934  		var sb strings.Builder
   935  		ord := processOrderby(orderby)
   936  		sb.Grow(10 + (len(ord) * 8) - 1)
   937  		sb.WriteString(" ORDER BY ")
   938  		for i, col := range ord {
   939  			// TODO: We might want to escape this column
   940  			if i != 0 {
   941  				sb.WriteString(",`")
   942  			} else {
   943  				sb.WriteString("`")
   944  			}
   945  			sb.WriteString(strings.Replace(col.Column, ".", "`.`", -1))
   946  			sb.WriteString("` ")
   947  			sb.WriteString(strings.ToUpper(col.Order))
   948  		}
   949  		q = sb.String()
   950  	}
   951  	return q
   952  }
   953  
   954  func (a *MysqlAdapter) buildOrderbySb(sb *strings.Builder, orderby string) {
   955  	if len(orderby) != 0 {
   956  		ord := processOrderby(orderby)
   957  		sb.Grow(10 + (len(ord) * 8) - 1)
   958  		sb.WriteString(" ORDER BY ")
   959  		for i, col := range ord {
   960  			// TODO: We might want to escape this column
   961  			if i != 0 {
   962  				sb.WriteString(",`")
   963  			} else {
   964  				sb.WriteString("`")
   965  			}
   966  			sb.WriteString(strings.Replace(col.Column, ".", "`.`", -1))
   967  			sb.WriteString("` ")
   968  			sb.WriteString(strings.ToUpper(col.Order))
   969  		}
   970  	}
   971  }
   972  
   973  func (a *MysqlAdapter) SimpleSelect(name, table, cols, where, orderby, limit string) (string, error) {
   974  	if table == "" {
   975  		return "", errors.New("You need a name for this table")
   976  	}
   977  	if len(cols) == 0 {
   978  		return "", errors.New("No columns found for SimpleSelect")
   979  	}
   980  	var sb *strings.Builder
   981  	ii := queryStrPool.Get()
   982  	if ii == nil {
   983  		sb = &strings.Builder{}
   984  	} else {
   985  		sb = ii.(*strings.Builder)
   986  		sb.Reset()
   987  	}
   988  	sb.WriteString("SELECT ")
   989  
   990  	// Slice up the user friendly strings into something easier to process
   991  	for i, col := range strings.Split(strings.TrimSpace(cols), ",") {
   992  		if i != 0 {
   993  			sb.WriteString("`,`")
   994  		} else {
   995  			sb.WriteRune('`')
   996  		}
   997  		sb.WriteString(strings.TrimSpace(col))
   998  	}
   999  
  1000  	sb.WriteString("`FROM`")
  1001  	sb.WriteString(table)
  1002  	sb.WriteRune('`')
  1003  	err := a.buildWhere(where, sb)
  1004  	if err != nil {
  1005  		return "", err
  1006  	}
  1007  	a.buildOrderbySb(sb, orderby)
  1008  	a.buildLimitSb(sb, limit)
  1009  
  1010  	q := strings.TrimSpace(sb.String())
  1011  	queryStrPool.Put(sb)
  1012  	a.pushStatement(name, "select", q)
  1013  	return q, nil
  1014  }
  1015  
  1016  func (a *MysqlAdapter) ComplexSelect(preBuilder *selectPrebuilder) (out string, e error) {
  1017  	var sb *strings.Builder
  1018  	ii := queryStrPool.Get()
  1019  	if ii == nil {
  1020  		sb = &strings.Builder{}
  1021  	} else {
  1022  		sb = ii.(*strings.Builder)
  1023  		sb.Reset()
  1024  	}
  1025  	e = a.complexSelect(preBuilder, sb)
  1026  	out = sb.String()
  1027  	queryStrPool.Put(sb)
  1028  	a.pushStatement(preBuilder.name, "select", out)
  1029  	return out, e
  1030  }
  1031  
  1032  const cslen1 = len("SELECT  FROM ``")
  1033  const cslen2 = len("WHERE``IN(")
  1034  
  1035  func (a *MysqlAdapter) complexSelect(preBuilder *selectPrebuilder, sb *strings.Builder) error {
  1036  	if preBuilder.table == "" {
  1037  		return errors.New("You need a name for this table")
  1038  	}
  1039  	if len(preBuilder.columns) == 0 {
  1040  		return errors.New("No columns found for ComplexSelect")
  1041  	}
  1042  
  1043  	cols := a.buildJoinColumns(preBuilder.columns)
  1044  	sb.Grow(cslen1 + len(cols) + len(preBuilder.table))
  1045  	sb.WriteString("SELECT ")
  1046  	sb.WriteString(cols)
  1047  	sb.WriteString(" FROM `")
  1048  	sb.WriteString(preBuilder.table)
  1049  	sb.WriteRune('`')
  1050  
  1051  	// TODO: Let callers have a Where() and a InQ()
  1052  	if preBuilder.inChain != nil {
  1053  		sb.Grow(cslen2 + len(preBuilder.inColumn))
  1054  		sb.WriteString("WHERE`")
  1055  		sb.WriteString(preBuilder.inColumn)
  1056  		sb.WriteString("`IN(")
  1057  		e := a.complexSelect(preBuilder.inChain, sb)
  1058  		if e != nil {
  1059  			return e
  1060  		}
  1061  		sb.WriteRune(')')
  1062  	} else {
  1063  		e := a.buildFlexiWhereSb(sb, preBuilder.where, preBuilder.dateCutoff)
  1064  		if e != nil {
  1065  			return e
  1066  		}
  1067  	}
  1068  
  1069  	a.buildOrderbySb(sb, preBuilder.orderby)
  1070  	a.buildLimitSb(sb, preBuilder.limit)
  1071  	return nil
  1072  }
  1073  
  1074  func (a *MysqlAdapter) SimpleLeftJoin(name, table1, table2, columns, joiners, where, orderby, limit string) (string, error) {
  1075  	if table1 == "" {
  1076  		return "", errors.New("You need a name for the left table")
  1077  	}
  1078  	if table2 == "" {
  1079  		return "", errors.New("You need a name for the right table")
  1080  	}
  1081  	if len(columns) == 0 {
  1082  		return "", errors.New("No columns found for SimpleLeftJoin")
  1083  	}
  1084  	if len(joiners) == 0 {
  1085  		return "", errors.New("No joiners found for SimpleLeftJoin")
  1086  	}
  1087  
  1088  	whereStr, err := a.buildJoinWhere(where)
  1089  	if err != nil {
  1090  		return "", err
  1091  	}
  1092  
  1093  	thalf1 := strings.Split(strings.Replace(table1, " as ", " AS ", -1), " AS ")
  1094  	var as1 string
  1095  	if len(thalf1) == 2 {
  1096  		as1 = " AS `" + thalf1[1] + "`"
  1097  	}
  1098  	thalf2 := strings.Split(strings.Replace(table2, " as ", " AS ", -1), " AS ")
  1099  	var as2 string
  1100  	if len(thalf2) == 2 {
  1101  		as2 = " AS `" + thalf2[1] + "`"
  1102  	}
  1103  
  1104  	q := "SELECT" + a.buildJoinColumns(columns) + " FROM `" + thalf1[0] + "`" + as1 + " LEFT JOIN `" + thalf2[0] + "`" + as2 + " ON " + a.buildJoiners(joiners) + whereStr + a.buildOrderby(orderby) + a.buildLimit(limit)
  1105  
  1106  	q = strings.TrimSpace(q)
  1107  	a.pushStatement(name, "select", q)
  1108  	return q, nil
  1109  }
  1110  
  1111  func (a *MysqlAdapter) SimpleInnerJoin(name, table1, table2, columns, joiners, where, orderby, limit string) (string, error) {
  1112  	if table1 == "" {
  1113  		return "", errors.New("You need a name for the left table")
  1114  	}
  1115  	if table2 == "" {
  1116  		return "", errors.New("You need a name for the right table")
  1117  	}
  1118  	if len(columns) == 0 {
  1119  		return "", errors.New("No columns found for SimpleInnerJoin")
  1120  	}
  1121  	if len(joiners) == 0 {
  1122  		return "", errors.New("No joiners found for SimpleInnerJoin")
  1123  	}
  1124  
  1125  	whereStr, err := a.buildJoinWhere(where)
  1126  	if err != nil {
  1127  		return "", err
  1128  	}
  1129  
  1130  	thalf1 := strings.Split(strings.Replace(table1, " as ", " AS ", -1), " AS ")
  1131  	var as1 string
  1132  	if len(thalf1) == 2 {
  1133  		as1 = " AS `" + thalf1[1] + "`"
  1134  	}
  1135  	thalf2 := strings.Split(strings.Replace(table2, " as ", " AS ", -1), " AS ")
  1136  	var as2 string
  1137  	if len(thalf2) == 2 {
  1138  		as2 = " AS `" + thalf2[1] + "`"
  1139  	}
  1140  
  1141  	q := "SELECT " + a.buildJoinColumns(columns) + " FROM `" + thalf1[0] + "`" + as1 + " INNER JOIN `" + thalf2[0] + "`" + as2 + " ON " + a.buildJoiners(joiners) + whereStr + a.buildOrderby(orderby) + a.buildLimit(limit)
  1142  
  1143  	q = strings.TrimSpace(q)
  1144  	a.pushStatement(name, "select", q)
  1145  	return q, nil
  1146  }
  1147  
  1148  func (a *MysqlAdapter) SimpleUpdateSelect(up *updatePrebuilder) (string, error) {
  1149  	sel := up.whereSubQuery
  1150  	sb := &strings.Builder{}
  1151  	err := a.buildWhere(sel.where, sb)
  1152  	if err != nil {
  1153  		return "", err
  1154  	}
  1155  
  1156  	var setter string
  1157  	for _, item := range processSet(up.set) {
  1158  		setter += "`" + item.Column + "`="
  1159  		for _, token := range item.Expr {
  1160  			switch token.Type {
  1161  			case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr:
  1162  				setter += token.Contents
  1163  			case TokenColumn:
  1164  				setter += "`" + token.Contents + "`"
  1165  			case TokenString:
  1166  				setter += "'" + token.Contents + "'"
  1167  			}
  1168  		}
  1169  		setter += ","
  1170  	}
  1171  	setter = setter[0 : len(setter)-1]
  1172  
  1173  	q := "UPDATE `" + up.table + "` SET " + setter + " WHERE (SELECT" + a.buildJoinColumns(sel.columns) + " FROM `" + sel.table + "`" + sb.String() + a.buildOrderby(sel.orderby) + a.buildLimit(sel.limit) + ")"
  1174  	q = strings.TrimSpace(q)
  1175  	a.pushStatement(up.name, "update", q)
  1176  	return q, nil
  1177  }
  1178  
  1179  func (a *MysqlAdapter) SimpleInsertSelect(name string, ins DBInsert, sel DBSelect) (string, error) {
  1180  	sb := &strings.Builder{}
  1181  	e := a.buildWhere(sel.Where, sb)
  1182  	if e != nil {
  1183  		return "", e
  1184  	}
  1185  
  1186  	q := "INSERT INTO `" + ins.Table + "`(" + a.buildColumns(ins.Columns) + ") SELECT" + a.buildJoinColumns(sel.Columns) + " FROM `" + sel.Table + "`" + sb.String() + a.buildOrderby(sel.Orderby) + a.buildLimit(sel.Limit)
  1187  	q = strings.TrimSpace(q)
  1188  	a.pushStatement(name, "insert", q)
  1189  	return q, nil
  1190  }
  1191  
  1192  func (a *MysqlAdapter) SimpleInsertLeftJoin(name string, ins DBInsert, sel DBJoin) (string, error) {
  1193  	whereStr, e := a.buildJoinWhere(sel.Where)
  1194  	if e != nil {
  1195  		return "", e
  1196  	}
  1197  
  1198  	q := "INSERT INTO `" + ins.Table + "`(" + a.buildColumns(ins.Columns) + ") SELECT" + a.buildJoinColumns(sel.Columns) + " FROM `" + sel.Table1 + "` LEFT JOIN `" + sel.Table2 + "` ON " + a.buildJoiners(sel.Joiners) + whereStr + a.buildOrderby(sel.Orderby) + a.buildLimit(sel.Limit)
  1199  	q = strings.TrimSpace(q)
  1200  	a.pushStatement(name, "insert", q)
  1201  	return q, nil
  1202  }
  1203  
  1204  // TODO: Make this more consistent with the other build* methods?
  1205  func (a *MysqlAdapter) buildJoiners(joiners string) (q string) {
  1206  	var qb strings.Builder
  1207  	for i, j := range processJoiner(joiners) {
  1208  		//q += "`" + j.LeftTable + "`.`" + j.LeftColumn + "` " + j.Operator + " `" + j.RightTable + "`.`" + j.RightColumn + "` AND "
  1209  		if i != 0 {
  1210  			qb.WriteString("AND`")
  1211  		} else {
  1212  			qb.WriteString("`")
  1213  		}
  1214  		qb.WriteString(j.LeftTable)
  1215  		qb.WriteString("`.`")
  1216  		qb.WriteString(j.LeftColumn)
  1217  		qb.WriteString("` ")
  1218  		qb.WriteString(j.Operator)
  1219  		qb.WriteString(" `")
  1220  		qb.WriteString(j.RightTable)
  1221  		qb.WriteString("`.`")
  1222  		qb.WriteString(j.RightColumn)
  1223  		qb.WriteString("`")
  1224  	}
  1225  	return qb.String()
  1226  }
  1227  
  1228  // Add support for BETWEEN x.x
  1229  func (a *MysqlAdapter) buildJoinWhere(where string) (q string, e error) {
  1230  	if len(where) != 0 {
  1231  		var qsb strings.Builder
  1232  		ws := processWhere(where)
  1233  		qsb.Grow(6 + (len(ws) * 5))
  1234  		qsb.WriteString(" WHERE")
  1235  		for i, loc := range ws {
  1236  			if i != 0 {
  1237  				qsb.WriteString(" AND")
  1238  			}
  1239  			for _, token := range loc.Expr {
  1240  				switch token.Type {
  1241  				case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot, TokenLike:
  1242  					qsb.WriteRune(' ')
  1243  					qsb.WriteString(token.Contents)
  1244  				case TokenColumn:
  1245  					qsb.WriteString(" `")
  1246  					halves := strings.Split(token.Contents, ".")
  1247  					if len(halves) == 2 {
  1248  						qsb.WriteString(halves[0])
  1249  						qsb.WriteString("`.`")
  1250  						qsb.WriteString(halves[1])
  1251  					} else {
  1252  						qsb.WriteString(token.Contents)
  1253  					}
  1254  					qsb.WriteRune('`')
  1255  				case TokenString:
  1256  					qsb.WriteString(" '")
  1257  					qsb.WriteString(token.Contents)
  1258  					qsb.WriteRune('\'')
  1259  				default:
  1260  					return qsb.String(), errors.New("This token doesn't exist o_o")
  1261  				}
  1262  			}
  1263  		}
  1264  		return qsb.String(), nil
  1265  	}
  1266  	return q, nil
  1267  }
  1268  
  1269  func (a *MysqlAdapter) buildLimit(limit string) (q string) {
  1270  	if limit != "" {
  1271  		q = " LIMIT " + limit
  1272  	}
  1273  	return q
  1274  }
  1275  
  1276  func (a *MysqlAdapter) buildLimitSb(sb *strings.Builder, limit string) {
  1277  	if limit != "" {
  1278  		sb.WriteString(" LIMIT ")
  1279  		sb.WriteString(limit)
  1280  	}
  1281  }
  1282  
  1283  func (a *MysqlAdapter) buildJoinColumns(cols string) (q string) {
  1284  	for _, col := range processColumns(cols) {
  1285  		// TODO: Move the stirng and number logic to processColumns?
  1286  		// TODO: Error if [0] doesn't exist
  1287  		firstChar := col.Left[0]
  1288  		if firstChar == '\'' {
  1289  			col.Type = TokenString
  1290  		} else {
  1291  			_, e := strconv.Atoi(string(firstChar))
  1292  			if e == nil {
  1293  				col.Type = TokenNumber
  1294  			}
  1295  		}
  1296  
  1297  		// Escape the column names, just in case we've used a reserved keyword
  1298  		source := col.Left
  1299  		if col.Table != "" {
  1300  			source = "`" + col.Table + "`.`" + source + "`"
  1301  		} else if col.Type != TokenScope && col.Type != TokenFunc && col.Type != TokenNumber && col.Type != TokenSub && col.Type != TokenString {
  1302  			source = "`" + source + "`"
  1303  		}
  1304  
  1305  		var alias string
  1306  		if col.Alias != "" {
  1307  			alias = " AS `" + col.Alias + "`"
  1308  		}
  1309  		q += " " + source + alias + ","
  1310  	}
  1311  	return q[0 : len(q)-1]
  1312  }
  1313  
  1314  func (a *MysqlAdapter) SimpleInsertInnerJoin(name string, ins DBInsert, sel DBJoin) (string, error) {
  1315  	whereStr, err := a.buildJoinWhere(sel.Where)
  1316  	if err != nil {
  1317  		return "", err
  1318  	}
  1319  
  1320  	q := "INSERT INTO `" + ins.Table + "`(" + a.buildColumns(ins.Columns) + ") SELECT" + a.buildJoinColumns(sel.Columns) + " FROM `" + sel.Table1 + "` INNER JOIN `" + sel.Table2 + "` ON " + a.buildJoiners(sel.Joiners) + whereStr + a.buildOrderby(sel.Orderby) + a.buildLimit(sel.Limit)
  1321  	q = strings.TrimSpace(q)
  1322  	a.pushStatement(name, "insert", q)
  1323  	return q, nil
  1324  }
  1325  
  1326  const sclen1 = len("SELECT COUNT(*) FROM``")
  1327  
  1328  func (a *MysqlAdapter) SimpleCount(name, table, where, limit string) (q string, e error) {
  1329  	if table == "" {
  1330  		return "", errors.New("You need a name for this table")
  1331  	}
  1332  	var sb *strings.Builder
  1333  	ii := queryStrPool.Get()
  1334  	if ii == nil {
  1335  		sb = &strings.Builder{}
  1336  	} else {
  1337  		sb = ii.(*strings.Builder)
  1338  		sb.Reset()
  1339  	}
  1340  	sb.Grow(sclen1 + len(table))
  1341  	sb.WriteString("SELECT COUNT(*) FROM`")
  1342  	sb.WriteString(table)
  1343  	sb.WriteRune('`')
  1344  	if e = a.buildWhere(where, sb); e != nil {
  1345  		return "", e
  1346  	}
  1347  	a.buildLimitSb(sb, limit)
  1348  
  1349  	q = strings.TrimSpace(sb.String())
  1350  	queryStrPool.Put(sb)
  1351  	a.pushStatement(name, "select", q)
  1352  	return q, nil
  1353  }
  1354  
  1355  func (a *MysqlAdapter) Builder() *prebuilder {
  1356  	return &prebuilder{a}
  1357  }
  1358  
  1359  func (a *MysqlAdapter) Write() error {
  1360  	var stmts, body string
  1361  	for _, name := range a.BufferOrder {
  1362  		if name[0] == '_' {
  1363  			continue
  1364  		}
  1365  		stmt := a.Buffer[name]
  1366  		// ? - Table creation might be a little complex for Go to do outside a SQL file :(
  1367  		if stmt.Type == "upsert" {
  1368  			stmts += "\t" + name + " *qgen.MySQLUpsertCallback\n"
  1369  			body += `	
  1370  	dl("Preparing ` + name + ` statement.")
  1371  	stmts.` + name + `, e = qgen.PrepareMySQLUpsertCallback(db,"` + stmt.Contents + `")
  1372  	if e != nil {
  1373  		l("Error in ` + name + ` statement.")
  1374  		return e
  1375  	}
  1376  	`
  1377  		} else if stmt.Type != "create-table" {
  1378  			stmts += "\t" + name + " *sql.Stmt\n"
  1379  			body += `	
  1380  	dl("Preparing ` + name + ` statement.")
  1381  	stmts.` + name + `, e = db.Prepare("` + stmt.Contents + `")
  1382  	if e != nil {
  1383  		l("Error in ` + name + ` statement.")
  1384  		return e
  1385  	}
  1386  	`
  1387  		}
  1388  	}
  1389  
  1390  	// TODO: Move these custom queries out of this file
  1391  	out := `// +build !pgsql,!mssql
  1392  
  1393  /* This file was generated by Gosora's Query Generator. Please try to avoid modifying this file, as it might change at any time. */
  1394  
  1395  package main
  1396  
  1397  import(
  1398  	"log"
  1399  	"database/sql"
  1400  	c "github.com/Azareal/Gosora/common"
  1401  	//"github.com/Azareal/Gosora/query_gen"
  1402  )
  1403  
  1404  // nolint
  1405  type Stmts struct {
  1406  ` + stmts + `
  1407  	getActivityFeedByWatcher *sql.Stmt
  1408  	//getActivityFeedByWatcherAfter *sql.Stmt
  1409  	getActivityCountByWatcher *sql.Stmt
  1410  
  1411  	Mocks bool
  1412  }
  1413  
  1414  // nolint
  1415  func _gen_mysql() (e error) {
  1416  	dl := c.DebugLog
  1417  	dl("Building the generated statements")
  1418  	l := log.Print
  1419  	_ = l
  1420  ` + body + `
  1421  	return nil
  1422  }
  1423  `
  1424  	return writeFile("./gen_mysql.go", out)
  1425  }
  1426  
  1427  // Internal methods, not exposed in the interface
  1428  func (a *MysqlAdapter) pushStatement(name, stype, q string) {
  1429  	if name == "" {
  1430  		return
  1431  	}
  1432  	a.Buffer[name] = DBStmt{q, stype}
  1433  	a.BufferOrder = append(a.BufferOrder, name)
  1434  }
  1435  
  1436  func (a *MysqlAdapter) stringyType(ct string) bool {
  1437  	ct = strings.ToLower(ct)
  1438  	return ct == "varchar" || ct == "tinytext" || ct == "text" || ct == "mediumtext" || ct == "longtext" || ct == "char" || ct == "datetime" || ct == "timestamp" || ct == "time" || ct == "date"
  1439  }