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 }