github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/query_gen/mssql.go (about) 1 /* WIP Under Really Heavy Construction */ 2 package qgen 3 4 import ( 5 "database/sql" 6 "errors" 7 "log" 8 "strconv" 9 "strings" 10 ) 11 12 func init() { 13 Registry = append(Registry, 14 &MssqlAdapter{Name: "mssql", Buffer: make(map[string]DBStmt)}, 15 ) 16 } 17 18 type MssqlAdapter struct { 19 Name string // ? - Do we really need this? Can't we hard-code this? 20 Buffer map[string]DBStmt 21 BufferOrder []string // Map iteration order is random, so we need this to track the order, so we don't get huge diffs every commit 22 keys map[string]string 23 } 24 25 // GetName gives you the name of the database adapter. In this case, it's Mssql 26 func (a *MssqlAdapter) GetName() string { 27 return a.Name 28 } 29 30 func (a *MssqlAdapter) GetStmt(name string) DBStmt { 31 return a.Buffer[name] 32 } 33 34 func (a *MssqlAdapter) GetStmts() map[string]DBStmt { 35 return a.Buffer 36 } 37 38 // TODO: Implement this 39 func (a *MssqlAdapter) BuildConn(config map[string]string) (*sql.DB, error) { 40 return nil, nil 41 } 42 43 func (a *MssqlAdapter) DbVersion() string { 44 return "SELECT CONCAT(SERVERPROPERTY('productversion'), SERVERPROPERTY ('productlevel'), SERVERPROPERTY ('edition'))" 45 } 46 47 func (a *MssqlAdapter) DropTable(name, table string) (string, error) { 48 if table == "" { 49 return "", errors.New("You need a name for this table") 50 } 51 q := "DROP TABLE IF EXISTS [" + table + "];" 52 a.pushStatement(name, "drop-table", q) 53 return q, nil 54 } 55 56 // TODO: Add support for foreign keys? 57 // TODO: Convert any remaining stringy types to nvarchar 58 // We may need to change the CreateTable API to better suit Mssql and the other database drivers which are coming up 59 func (a *MssqlAdapter) CreateTable(name, table, charset, collation string, columns []DBTableColumn, keys []DBTableKey) (string, error) { 60 if table == "" { 61 return "", errors.New("You need a name for this table") 62 } 63 if len(columns) == 0 { 64 return "", errors.New("You can't have a table with no columns") 65 } 66 67 q := "CREATE TABLE [" + table + "] (" 68 for _, column := range columns { 69 column, size, end := a.parseColumn(column) 70 q += "\n\t[" + column.Name + "] " + column.Type + size + end + "," 71 } 72 73 if len(keys) > 0 { 74 for _, key := range keys { 75 q += "\n\t" + key.Type 76 if key.Type != "unique" { 77 q += " key" 78 } 79 q += "(" 80 for _, column := range strings.Split(key.Columns, ",") { 81 q += "[" + column + "]," 82 } 83 q = q[0:len(q)-1] + ")," 84 } 85 } 86 87 q = q[0:len(q)-1] + "\n);" 88 a.pushStatement(name, "create-table", q) 89 return q, nil 90 } 91 92 func (a *MssqlAdapter) parseColumn(column DBTableColumn) (col DBTableColumn, size string, end string) { 93 var max, createdAt bool 94 switch column.Type { 95 case "createdAt": 96 column.Type = "datetime" 97 createdAt = true 98 case "varchar": 99 column.Type = "nvarchar" 100 case "text": 101 column.Type = "nvarchar" 102 max = true 103 case "json": 104 column.Type = "nvarchar" 105 max = true 106 case "boolean": 107 column.Type = "bit" 108 } 109 if column.Size > 0 { 110 size = " (" + strconv.Itoa(column.Size) + ")" 111 } 112 if max { 113 size = " (MAX)" 114 } 115 116 if column.Default != "" { 117 end = " DEFAULT " 118 if createdAt { 119 end += "GETUTCDATE()" // TODO: Use GETUTCDATE() in updates instead of the neutral format 120 } else if a.stringyType(column.Type) && column.Default != "''" { 121 end += "'" + column.Default + "'" 122 } else { 123 end += column.Default 124 } 125 } 126 if !column.Null { 127 end += " not null" 128 } 129 130 // ! Not exactly the meaning of auto increment... 131 if column.AutoIncrement { 132 end += " IDENTITY" 133 } 134 return column, size, end 135 } 136 137 // TODO: Test this, not sure if some things work 138 // TODO: Add support for keys 139 func (a *MssqlAdapter) AddColumn(name, table string, column DBTableColumn, key *DBTableKey) (string, error) { 140 if table == "" { 141 return "", errors.New("You need a name for this table") 142 } 143 144 column, size, end := a.parseColumn(column) 145 q := "ALTER TABLE [" + table + "] ADD [" + column.Name + "] " + column.Type + size + end + ";" 146 a.pushStatement(name, "add-column", q) 147 return q, nil 148 } 149 150 // TODO: Implement this 151 func (a *MssqlAdapter) DropColumn(name, table, colName string) (string, error) { 152 return "", errors.New("not implemented") 153 } 154 155 // TODO: Implement this 156 func (a *MssqlAdapter) RenameColumn(name, table, oldName, newName string) (string, error) { 157 return "", errors.New("not implemented") 158 } 159 160 // TODO: Implement this 161 func (a *MssqlAdapter) ChangeColumn(name, table, colName string, col DBTableColumn) (string, error) { 162 return "", errors.New("not implemented") 163 } 164 165 // TODO: Implement this 166 func (a *MssqlAdapter) SetDefaultColumn(name, table, colName, colType, defaultStr string) (string, error) { 167 if colType == "text" { 168 return "", errors.New("text fields cannot have default values") 169 } 170 return "", errors.New("not implemented") 171 } 172 173 // TODO: Implement this 174 // TODO: Test to make sure everything works here 175 func (a *MssqlAdapter) AddIndex(name, table, iname, colname string) (string, error) { 176 if table == "" { 177 return "", errors.New("You need a name for this table") 178 } 179 if iname == "" { 180 return "", errors.New("You need a name for the index") 181 } 182 if colname == "" { 183 return "", errors.New("You need a name for the column") 184 } 185 return "", errors.New("not implemented") 186 } 187 188 // TODO: Implement this 189 // TODO: Test to make sure everything works here 190 func (a *MssqlAdapter) AddKey(name, table, column string, key DBTableKey) (string, error) { 191 if table == "" { 192 return "", errors.New("You need a name for this table") 193 } 194 if column == "" { 195 return "", errors.New("You need a name for the column") 196 } 197 return "", errors.New("not implemented") 198 } 199 200 // TODO: Implement this 201 // TODO: Test to make sure everything works here 202 func (a *MssqlAdapter) RemoveIndex(name, table, iname string) (string, error) { 203 if table == "" { 204 return "", errors.New("You need a name for this table") 205 } 206 if iname == "" { 207 return "", errors.New("You need a name for the index") 208 } 209 return "", errors.New("not implemented") 210 } 211 212 // TODO: Implement this 213 // TODO: Test to make sure everything works here 214 func (a *MssqlAdapter) AddForeignKey(name, table, column, ftable, fcolumn string, cascade bool) (out string, e error) { 215 c := func(str string, val bool) { 216 if e != nil || !val { 217 return 218 } 219 e = errors.New("You need a " + str + " for this table") 220 } 221 c("name", table == "") 222 c("column", column == "") 223 c("ftable", ftable == "") 224 c("fcolumn", fcolumn == "") 225 if e != nil { 226 return "", e 227 } 228 return "", errors.New("not implemented") 229 } 230 231 func (a *MssqlAdapter) SimpleInsert(name, table, cols, fields string) (string, error) { 232 q, err := a.simpleBulkInsert(name, table, cols, []string{fields}) 233 a.pushStatement(name, "insert", q) 234 return q, err 235 } 236 237 func (a *MssqlAdapter) SimpleBulkInsert(name, table, cols string, fieldSet []string) (string, error) { 238 q, err := a.simpleBulkInsert(name, table, cols, fieldSet) 239 a.pushStatement(name, "bulk-insert", q) 240 return q, err 241 } 242 243 func (a *MssqlAdapter) simpleBulkInsert(name, table, cols string, fieldSet []string) (string, error) { 244 if table == "" { 245 return "", errors.New("You need a name for this table") 246 } 247 248 q := "INSERT INTO [" + table + "] (" 249 if cols == "" { 250 q += ") VALUES ()" 251 a.pushStatement(name, "insert", q) 252 return q, nil 253 } 254 255 // Escape the column names, just in case we've used a reserved keyword 256 for _, col := range processColumns(cols) { 257 if col.Type == TokenFunc { 258 q += col.Left + "," 259 } else { 260 q += "[" + col.Left + "]," 261 } 262 } 263 q = q[0 : len(q)-1] 264 265 q += ") VALUES (" 266 for oi, fields := range fieldSet { 267 if oi != 0 { 268 q += ",(" 269 } 270 for _, field := range processFields(fields) { 271 field.Name = strings.Replace(field.Name, "UTC_TIMESTAMP()", "GETUTCDATE()", -1) 272 //log.Print("field.Name ", field.Name) 273 nameLen := len(field.Name) 274 if field.Name[0] == '"' && field.Name[nameLen-1] == '"' && nameLen >= 3 { 275 field.Name = "'" + field.Name[1:nameLen-1] + "'" 276 } 277 if field.Name[0] == '\'' && field.Name[nameLen-1] == '\'' && nameLen >= 3 { 278 field.Name = "'" + strings.Replace(field.Name[1:nameLen-1], "'", "''", -1) + "'" 279 } 280 q += field.Name + "," 281 } 282 q = q[0:len(q)-1] + ")" 283 } 284 return q, nil 285 } 286 287 // ! DEPRECATED 288 func (a *MssqlAdapter) SimpleReplace(name, table, columns, fields string) (string, error) { 289 log.Print("In SimpleReplace") 290 key, ok := a.keys[table] 291 if !ok { 292 return "", errors.New("Unable to elide key from table '" + table + "', please use SimpleUpsert (coming soon!) instead") 293 } 294 log.Print("After the key check") 295 296 // Escape the column names, just in case we've used a reserved keyword 297 var keyPosition int 298 for _, column := range processColumns(columns) { 299 if column.Left == key { 300 continue 301 } 302 keyPosition++ 303 } 304 305 var keyValue string 306 for fieldID, field := range processFields(fields) { 307 field.Name = strings.Replace(field.Name, "UTC_TIMESTAMP()", "GETUTCDATE()", -1) 308 nameLen := len(field.Name) 309 if field.Name[0] == '"' && field.Name[nameLen-1] == '"' && nameLen >= 3 { 310 field.Name = "'" + field.Name[1:nameLen-1] + "'" 311 } 312 if field.Name[0] == '\'' && field.Name[nameLen-1] == '\'' && nameLen >= 3 { 313 field.Name = "'" + strings.Replace(field.Name[1:nameLen-1], "'", "''", -1) + "'" 314 } 315 if keyPosition == fieldID { 316 keyValue = field.Name 317 continue 318 } 319 } 320 return a.SimpleUpsert(name, table, columns, fields, "key = "+keyValue) 321 } 322 323 func (a *MssqlAdapter) SimpleUpsert(name, table, columns, fields, where string) (string, error) { 324 if table == "" { 325 return "", errors.New("You need a name for this table") 326 } 327 if len(columns) == 0 { 328 return "", errors.New("No columns found for SimpleInsert") 329 } 330 if len(fields) == 0 { 331 return "", errors.New("No input data found for SimpleInsert") 332 } 333 334 var fieldCount int 335 var fieldOutput string 336 q := "MERGE [" + table + "] WITH(HOLDLOCK) as t1 USING (VALUES(" 337 parsedFields := processFields(fields) 338 for _, field := range parsedFields { 339 fieldCount++ 340 field.Name = strings.Replace(field.Name, "UTC_TIMESTAMP()", "GETUTCDATE()", -1) 341 //log.Print("field.Name ", field.Name) 342 nameLen := len(field.Name) 343 if field.Name[0] == '"' && field.Name[nameLen-1] == '"' && nameLen >= 3 { 344 field.Name = "'" + field.Name[1:nameLen-1] + "'" 345 } 346 if field.Name[0] == '\'' && field.Name[nameLen-1] == '\'' && nameLen >= 3 { 347 field.Name = "'" + strings.Replace(field.Name[1:nameLen-1], "'", "''", -1) + "'" 348 } 349 fieldOutput += field.Name + "," 350 } 351 fieldOutput = fieldOutput[0 : len(fieldOutput)-1] 352 q += fieldOutput + ")) AS updates (" 353 354 // nolint The linter wants this to be less readable 355 for fieldID, _ := range parsedFields { 356 q += "f" + strconv.Itoa(fieldID) + "," 357 } 358 q = q[0:len(q)-1] + ") ON " 359 360 //querystr += "t1.[" + key + "] = " 361 // Add support for BETWEEN x.x 362 for _, loc := range processWhere(where) { 363 for _, token := range loc.Expr { 364 switch token.Type { 365 case TokenSub: 366 q += " ?" 367 case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike: 368 // TODO: Split the function case off to speed things up 369 if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { 370 token.Contents = "GETUTCDATE()" 371 } 372 q += " " + token.Contents 373 case TokenColumn: 374 q += " [" + token.Contents + "]" 375 case TokenString: 376 q += " '" + token.Contents + "'" 377 default: 378 panic("This token doesn't exist o_o") 379 } 380 } 381 } 382 383 matched := " WHEN MATCHED THEN UPDATE SET " 384 notMatched := "WHEN NOT MATCHED THEN INSERT(" 385 var fieldList string 386 387 // Escape the column names, just in case we've used a reserved keyword 388 for columnID, col := range processColumns(columns) { 389 fieldList += "f" + strconv.Itoa(columnID) + "," 390 if col.Type == TokenFunc { 391 matched += col.Left + " = f" + strconv.Itoa(columnID) + "," 392 notMatched += col.Left + "," 393 } else { 394 matched += "[" + col.Left + "] = f" + strconv.Itoa(columnID) + "," 395 notMatched += "[" + col.Left + "]," 396 } 397 } 398 399 matched = matched[0 : len(matched)-1] 400 notMatched = notMatched[0 : len(notMatched)-1] 401 fieldList = fieldList[0 : len(fieldList)-1] 402 403 notMatched += ") VALUES (" + fieldList + ");" 404 q += matched + " " + notMatched 405 406 // TODO: Run this on debug mode? 407 if name[0] == '_' { 408 log.Print(name+" query: ", q) 409 } 410 a.pushStatement(name, "upsert", q) 411 return q, nil 412 } 413 414 func (a *MssqlAdapter) SimpleUpdate(up *updatePrebuilder) (string, error) { 415 if up.table == "" { 416 return "", errors.New("You need a name for this table") 417 } 418 if up.set == "" { 419 return "", errors.New("You need to set data in this update statement") 420 } 421 422 q := "UPDATE [" + up.table + "] SET " 423 for _, item := range processSet(up.set) { 424 q += "[" + item.Column + "]=" 425 for _, token := range item.Expr { 426 switch token.Type { 427 case TokenSub: 428 q += " ?" 429 case TokenFunc, TokenOp, TokenNumber, TokenOr: 430 // TODO: Split the function case off to speed things up 431 if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { 432 token.Contents = "GETUTCDATE()" 433 } 434 q += " " + token.Contents 435 case TokenColumn: 436 q += " [" + token.Contents + "]" 437 case TokenString: 438 q += " '" + token.Contents + "'" 439 default: 440 panic("This token doesn't exist o_o") 441 } 442 } 443 q += "," 444 } 445 q = q[0 : len(q)-1] 446 447 // Add support for BETWEEN x.x 448 if len(up.where) != 0 { 449 q += " WHERE" 450 for _, loc := range processWhere(up.where) { 451 for _, token := range loc.Expr { 452 switch token.Type { 453 case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot, TokenLike: 454 // TODO: Split the function case off to speed things up 455 if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { 456 token.Contents = "GETUTCDATE()" 457 } 458 q += " " + token.Contents 459 case TokenColumn: 460 q += " [" + token.Contents + "]" 461 case TokenString: 462 q += " '" + token.Contents + "'" 463 default: 464 panic("This token doesn't exist o_o") 465 } 466 } 467 q += " AND" 468 } 469 q = q[0 : len(q)-4] 470 } 471 472 a.pushStatement(up.name, "update", q) 473 return q, nil 474 } 475 476 func (a *MssqlAdapter) SimpleUpdateSelect(b *updatePrebuilder) (string, error) { 477 return "", errors.New("not implemented") 478 } 479 480 func (a *MssqlAdapter) SimpleDelete(name string, table string, where string) (string, error) { 481 if table == "" { 482 return "", errors.New("You need a name for this table") 483 } 484 if where == "" { 485 return "", errors.New("You need to specify what data you want to delete") 486 } 487 q := "DELETE FROM [" + table + "] WHERE" 488 489 // Add support for BETWEEN x.x 490 for _, loc := range processWhere(where) { 491 for _, token := range loc.Expr { 492 switch token.Type { 493 case TokenSub: 494 q += " ?" 495 case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike: 496 // TODO: Split the function case off to speed things up 497 if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { 498 token.Contents = "GETUTCDATE()" 499 } 500 q += " " + token.Contents 501 case TokenColumn: 502 q += " [" + token.Contents + "]" 503 case TokenString: 504 q += " '" + token.Contents + "'" 505 default: 506 panic("This token doesn't exist o_o") 507 } 508 } 509 q += " AND" 510 } 511 512 q = strings.TrimSpace(q[0 : len(q)-4]) 513 a.pushStatement(name, "delete", q) 514 return q, nil 515 } 516 517 func (a *MssqlAdapter) ComplexDelete(b *deletePrebuilder) (string, error) { 518 return "", errors.New("not implemented") 519 } 520 521 // We don't want to accidentally wipe tables, so we'll have a separate method for purging tables instead 522 func (a *MssqlAdapter) Purge(name string, table string) (string, error) { 523 if table == "" { 524 return "", errors.New("You need a name for this table") 525 } 526 q := "DELETE FROM [" + table + "]" 527 a.pushStatement(name, "purge", q) 528 return q, nil 529 } 530 531 func (a *MssqlAdapter) SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error) { 532 if table == "" { 533 return "", errors.New("You need a name for this table") 534 } 535 if len(columns) == 0 { 536 return "", errors.New("No columns found for SimpleSelect") 537 } 538 // TODO: Add this to the MySQL adapter in order to make this problem more discoverable? 539 if len(orderby) == 0 && limit != "" { 540 return "", errors.New("Orderby needs to be set to use limit on Mssql") 541 } 542 subCount := 0 543 q := "" 544 545 // Escape the column names, just in case we've used a reserved keyword 546 colslice := strings.Split(strings.TrimSpace(columns), ",") 547 for _, column := range colslice { 548 q += "[" + strings.TrimSpace(column) + "]," 549 } 550 q = q[0:len(q)-1] + " FROM [" + table + "]" 551 552 // Add support for BETWEEN x.x 553 if len(where) != 0 { 554 q += " WHERE" 555 for _, loc := range processWhere(where) { 556 for _, token := range loc.Expr { 557 switch token.Type { 558 case TokenSub: 559 subCount++ 560 q += " ?" + strconv.Itoa(subCount) 561 case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike: 562 // TODO: Split the function case off to speed things up 563 // MSSQL seems to convert the formats? so we'll compare it with a regular date. Do this with the other methods too? 564 if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { 565 token.Contents = "GETDATE()" 566 } 567 q += " " + token.Contents 568 case TokenColumn: 569 q += " [" + token.Contents + "]" 570 case TokenString: 571 q += " '" + token.Contents + "'" 572 default: 573 panic("This token doesn't exist o_o") 574 } 575 } 576 q += " AND" 577 } 578 q = q[0 : len(q)-4] 579 } 580 581 // TODO: MSSQL requires ORDER BY for LIMIT 582 if len(orderby) != 0 { 583 q += " ORDER BY " 584 for _, column := range processOrderby(orderby) { 585 // TODO: We might want to escape this column 586 q += column.Column + " " + strings.ToUpper(column.Order) + "," 587 } 588 q = q[0 : len(q)-1] 589 } 590 591 if limit != "" { 592 limiter := processLimit(limit) 593 log.Printf("limiter: %+v\n", limiter) 594 if limiter.Offset != "" { 595 if limiter.Offset == "?" { 596 subCount++ 597 q += " OFFSET ?" + strconv.Itoa(subCount) + " ROWS" 598 } else { 599 q += " OFFSET " + limiter.Offset + " ROWS" 600 } 601 } 602 603 // ! Does this work without an offset? 604 if limiter.MaxCount != "" { 605 if limiter.MaxCount == "?" { 606 subCount++ 607 limiter.MaxCount = "?" + strconv.Itoa(subCount) 608 } 609 q += " FETCH NEXT " + limiter.MaxCount + " ROWS ONLY " 610 } 611 } 612 613 q = strings.TrimSpace("SELECT " + q) 614 // TODO: Run this on debug mode? 615 if name[0] == '_' && limit == "" { 616 log.Print(name+" query: ", q) 617 } 618 a.pushStatement(name, "select", q) 619 return q, nil 620 } 621 622 // TODO: ComplexSelect 623 func (a *MssqlAdapter) ComplexSelect(preBuilder *selectPrebuilder) (string, error) { 624 return "", nil 625 } 626 627 func (a *MssqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) { 628 if table1 == "" { 629 return "", errors.New("You need a name for the left table") 630 } 631 if table2 == "" { 632 return "", errors.New("You need a name for the right table") 633 } 634 if len(columns) == 0 { 635 return "", errors.New("No columns found for SimpleLeftJoin") 636 } 637 if len(joiners) == 0 { 638 return "", errors.New("No joiners found for SimpleLeftJoin") 639 } 640 // TODO: Add this to the MySQL adapter in order to make this problem more discoverable? 641 if len(orderby) == 0 && limit != "" { 642 return "", errors.New("Orderby needs to be set to use limit on Mssql") 643 } 644 subCount := 0 645 q := "" 646 647 for _, col := range processColumns(columns) { 648 var source, alias string 649 // Escape the column names, just in case we've used a reserved keyword 650 if col.Table != "" { 651 source = "[" + col.Table + "].[" + col.Left + "]" 652 } else if col.Type == TokenFunc { 653 source = col.Left 654 } else { 655 source = "[" + col.Left + "]" 656 } 657 658 if col.Alias != "" { 659 alias = " AS '" + col.Alias + "'" 660 } 661 q += source + alias + "," 662 } 663 // Remove the trailing comma 664 q = q[0 : len(q)-1] 665 666 q += " FROM [" + table1 + "] LEFT JOIN [" + table2 + "] ON " 667 for _, j := range processJoiner(joiners) { 668 q += "[" + j.LeftTable + "].[" + j.LeftColumn + "]" + j.Operator + "[" + j.RightTable + "].[" + j.RightColumn + "] AND " 669 } 670 // Remove the trailing AND 671 q = q[0 : len(q)-4] 672 673 // Add support for BETWEEN x.x 674 if len(where) != 0 { 675 q += " WHERE" 676 for _, loc := range processWhere(where) { 677 for _, token := range loc.Expr { 678 switch token.Type { 679 case TokenSub: 680 subCount++ 681 q += " ?" + strconv.Itoa(subCount) 682 case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike: 683 // TODO: Split the function case off to speed things up 684 if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { 685 token.Contents = "GETUTCDATE()" 686 } 687 q += " " + token.Contents 688 case TokenColumn: 689 halves := strings.Split(token.Contents, ".") 690 if len(halves) == 2 { 691 q += " [" + halves[0] + "].[" + halves[1] + "]" 692 } else { 693 q += " [" + token.Contents + "]" 694 } 695 case TokenString: 696 q += " '" + token.Contents + "'" 697 default: 698 panic("This token doesn't exist o_o") 699 } 700 } 701 q += " AND" 702 } 703 q = q[0 : len(q)-4] 704 } 705 706 // TODO: MSSQL requires ORDER BY for LIMIT 707 if len(orderby) != 0 { 708 q += " ORDER BY " 709 for _, column := range processOrderby(orderby) { 710 log.Print("column: ", column) 711 // TODO: We might want to escape this column 712 q += column.Column + " " + strings.ToUpper(column.Order) + "," 713 } 714 q = q[0 : len(q)-1] 715 } else if limit != "" { 716 key, ok := a.keys[table1] 717 if ok { 718 q += " ORDER BY [" + table1 + "].[" + key + "]" 719 } 720 } 721 722 if limit != "" { 723 limiter := processLimit(limit) 724 if limiter.Offset != "" { 725 if limiter.Offset == "?" { 726 subCount++ 727 q += " OFFSET ?" + strconv.Itoa(subCount) + " ROWS" 728 } else { 729 q += " OFFSET " + limiter.Offset + " ROWS" 730 } 731 } 732 733 // ! Does this work without an offset? 734 if limiter.MaxCount != "" { 735 if limiter.MaxCount == "?" { 736 subCount++ 737 limiter.MaxCount = "?" + strconv.Itoa(subCount) 738 } 739 q += " FETCH NEXT " + limiter.MaxCount + " ROWS ONLY " 740 } 741 } 742 743 q = strings.TrimSpace("SELECT " + q) 744 // TODO: Run this on debug mode? 745 if name[0] == '_' && limit == "" { 746 log.Print(name+" query: ", q) 747 } 748 a.pushStatement(name, "select", q) 749 return q, nil 750 } 751 752 func (a *MssqlAdapter) SimpleInnerJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) { 753 if table1 == "" { 754 return "", errors.New("You need a name for the left table") 755 } 756 if table2 == "" { 757 return "", errors.New("You need a name for the right table") 758 } 759 if len(columns) == 0 { 760 return "", errors.New("No columns found for SimpleInnerJoin") 761 } 762 if len(joiners) == 0 { 763 return "", errors.New("No joiners found for SimpleInnerJoin") 764 } 765 // TODO: Add this to the MySQL adapter in order to make this problem more discoverable? 766 if len(orderby) == 0 && limit != "" { 767 return "", errors.New("Orderby needs to be set to use limit on Mssql") 768 } 769 subCount := 0 770 q := "" 771 772 for _, col := range processColumns(columns) { 773 var source, alias string 774 // Escape the column names, just in case we've used a reserved keyword 775 if col.Table != "" { 776 source = "[" + col.Table + "].[" + col.Left + "]" 777 } else if col.Type == TokenFunc { 778 source = col.Left 779 } else { 780 source = "[" + col.Left + "]" 781 } 782 783 if col.Alias != "" { 784 alias = " AS '" + col.Alias + "'" 785 } 786 q += source + alias + "," 787 } 788 // Remove the trailing comma 789 q = q[0 : len(q)-1] 790 791 q += " FROM [" + table1 + "] INNER JOIN [" + table2 + "] ON " 792 for _, j := range processJoiner(joiners) { 793 q += "[" + j.LeftTable + "].[" + j.LeftColumn + "]" + j.Operator + "[" + j.RightTable + "].[" + j.RightColumn + "] AND " 794 } 795 // Remove the trailing AND 796 q = q[0 : len(q)-4] 797 798 // Add support for BETWEEN x.x 799 if len(where) != 0 { 800 q += " WHERE" 801 for _, loc := range processWhere(where) { 802 for _, token := range loc.Expr { 803 switch token.Type { 804 case TokenSub: 805 subCount++ 806 q += " ?" + strconv.Itoa(subCount) 807 case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike: 808 // TODO: Split the function case off to speed things up 809 if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { 810 token.Contents = "GETUTCDATE()" 811 } 812 q += " " + token.Contents 813 case TokenColumn: 814 halves := strings.Split(token.Contents, ".") 815 if len(halves) == 2 { 816 q += " [" + halves[0] + "].[" + halves[1] + "]" 817 } else { 818 q += " [" + token.Contents + "]" 819 } 820 case TokenString: 821 q += " '" + token.Contents + "'" 822 default: 823 panic("This token doesn't exist o_o") 824 } 825 } 826 q += " AND" 827 } 828 q = q[0 : len(q)-4] 829 } 830 831 // TODO: MSSQL requires ORDER BY for LIMIT 832 if len(orderby) != 0 { 833 q += " ORDER BY " 834 for _, column := range processOrderby(orderby) { 835 log.Print("column: ", column) 836 // TODO: We might want to escape this column 837 q += column.Column + " " + strings.ToUpper(column.Order) + "," 838 } 839 q = q[0 : len(q)-1] 840 } else if limit != "" { 841 key, ok := a.keys[table1] 842 if ok { 843 log.Print("key: ", key) 844 q += " ORDER BY [" + table1 + "].[" + key + "]" 845 } 846 } 847 848 if limit != "" { 849 limiter := processLimit(limit) 850 if limiter.Offset != "" { 851 if limiter.Offset == "?" { 852 subCount++ 853 q += " OFFSET ?" + strconv.Itoa(subCount) + " ROWS" 854 } else { 855 q += " OFFSET " + limiter.Offset + " ROWS" 856 } 857 } 858 859 // ! Does this work without an offset? 860 if limiter.MaxCount != "" { 861 if limiter.MaxCount == "?" { 862 subCount++ 863 limiter.MaxCount = "?" + strconv.Itoa(subCount) 864 } 865 q += " FETCH NEXT " + limiter.MaxCount + " ROWS ONLY " 866 } 867 } 868 869 q = strings.TrimSpace("SELECT " + q) 870 // TODO: Run this on debug mode? 871 if name[0] == '_' && limit == "" { 872 log.Print(name+" query: ", q) 873 } 874 a.pushStatement(name, "select", q) 875 return q, nil 876 } 877 878 func (a *MssqlAdapter) SimpleInsertSelect(name string, ins DBInsert, sel DBSelect) (string, error) { 879 // TODO: More errors. 880 // TODO: Add this to the MySQL adapter in order to make this problem more discoverable? 881 if len(sel.Orderby) == 0 && sel.Limit != "" { 882 return "", errors.New("Orderby needs to be set to use limit on Mssql") 883 } 884 885 /* Insert */ 886 q := "INSERT INTO [" + ins.Table + "] (" 887 888 // Escape the column names, just in case we've used a reserved keyword 889 for _, col := range processColumns(ins.Columns) { 890 if col.Type == TokenFunc { 891 q += col.Left + "," 892 } else { 893 q += "[" + col.Left + "]," 894 } 895 } 896 q = q[0:len(q)-1] + ") SELECT " 897 898 /* Select */ 899 subCount := 0 900 901 for _, col := range processColumns(sel.Columns) { 902 var source, alias string 903 // Escape the column names, just in case we've used a reserved keyword 904 if col.Type == TokenFunc || col.Type == TokenSub { 905 source = col.Left 906 } else { 907 source = "[" + col.Left + "]" 908 } 909 if col.Alias != "" { 910 alias = " AS [" + col.Alias + "]" 911 } 912 q += " " + source + alias + "," 913 } 914 q = q[0:len(q)-1] + " FROM [" + sel.Table + "] " 915 916 // Add support for BETWEEN x.x 917 if len(sel.Where) != 0 { 918 q += " WHERE" 919 for _, loc := range processWhere(sel.Where) { 920 for _, token := range loc.Expr { 921 switch token.Type { 922 case TokenSub: 923 subCount++ 924 q += " ?" + strconv.Itoa(subCount) 925 case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike: 926 // TODO: Split the function case off to speed things up 927 if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { 928 token.Contents = "GETUTCDATE()" 929 } 930 q += " " + token.Contents 931 case TokenColumn: 932 q += " [" + token.Contents + "]" 933 case TokenString: 934 q += " '" + token.Contents + "'" 935 default: 936 panic("This token doesn't exist o_o") 937 } 938 } 939 q += " AND" 940 } 941 q = q[0 : len(q)-4] 942 } 943 944 // TODO: MSSQL requires ORDER BY for LIMIT 945 if len(sel.Orderby) != 0 { 946 q += " ORDER BY " 947 for _, column := range processOrderby(sel.Orderby) { 948 // TODO: We might want to escape this column 949 q += column.Column + " " + strings.ToUpper(column.Order) + "," 950 } 951 q = q[0 : len(q)-1] 952 } else if sel.Limit != "" { 953 key, ok := a.keys[sel.Table] 954 if ok { 955 q += " ORDER BY [" + sel.Table + "].[" + key + "]" 956 } 957 } 958 959 if sel.Limit != "" { 960 limiter := processLimit(sel.Limit) 961 if limiter.Offset != "" { 962 if limiter.Offset == "?" { 963 subCount++ 964 q += " OFFSET ?" + strconv.Itoa(subCount) + " ROWS" 965 } else { 966 q += " OFFSET " + limiter.Offset + " ROWS" 967 } 968 } 969 970 // ! Does this work without an offset? 971 if limiter.MaxCount != "" { 972 if limiter.MaxCount == "?" { 973 subCount++ 974 limiter.MaxCount = "?" + strconv.Itoa(subCount) 975 } 976 q += " FETCH NEXT " + limiter.MaxCount + " ROWS ONLY " 977 } 978 } 979 980 q = strings.TrimSpace(q) 981 // TODO: Run this on debug mode? 982 if name[0] == '_' && sel.Limit == "" { 983 log.Print(name+" query: ", q) 984 } 985 a.pushStatement(name, "insert", q) 986 return q, nil 987 } 988 989 func (a *MssqlAdapter) simpleJoin(name string, ins DBInsert, sel DBJoin, joinType string) (string, error) { 990 // TODO: More errors. 991 // TODO: Add this to the MySQL adapter in order to make this problem more discoverable? 992 if len(sel.Orderby) == 0 && sel.Limit != "" { 993 return "", errors.New("Orderby needs to be set to use limit on Mssql") 994 } 995 996 /* Insert */ 997 q := "INSERT INTO [" + ins.Table + "] (" 998 999 // Escape the column names, just in case we've used a reserved keyword 1000 for _, col := range processColumns(ins.Columns) { 1001 if col.Type == TokenFunc { 1002 q += col.Left + "," 1003 } else { 1004 q += "[" + col.Left + "]," 1005 } 1006 } 1007 q = q[0:len(q)-1] + ") SELECT " 1008 1009 /* Select */ 1010 subCount := 0 1011 1012 for _, col := range processColumns(sel.Columns) { 1013 var source, alias string 1014 // Escape the column names, just in case we've used a reserved keyword 1015 if col.Table != "" { 1016 source = "[" + col.Table + "].[" + col.Left + "]" 1017 } else if col.Type == TokenFunc { 1018 source = col.Left 1019 } else { 1020 source = "[" + col.Left + "]" 1021 } 1022 if col.Alias != "" { 1023 alias = " AS '" + col.Alias + "'" 1024 } 1025 q += source + alias + "," 1026 } 1027 q = q[0 : len(q)-1] 1028 1029 q += " FROM [" + sel.Table1 + "] " + joinType + " JOIN [" + sel.Table2 + "] ON " 1030 for _, j := range processJoiner(sel.Joiners) { 1031 q += "[" + j.LeftTable + "].[" + j.LeftColumn + "] " + j.Operator + " [" + j.RightTable + "].[" + j.RightColumn + "] AND " 1032 } 1033 q = q[0 : len(q)-4] 1034 1035 // Add support for BETWEEN x.x 1036 if len(sel.Where) != 0 { 1037 q += " WHERE" 1038 for _, loc := range processWhere(sel.Where) { 1039 for _, token := range loc.Expr { 1040 switch token.Type { 1041 case TokenSub: 1042 subCount++ 1043 q += " ?" + strconv.Itoa(subCount) 1044 case TokenFunc, TokenOp, TokenNumber, TokenOr, TokenNot, TokenLike: 1045 // TODO: Split the function case off to speed things up 1046 if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { 1047 token.Contents = "GETUTCDATE()" 1048 } 1049 q += " " + token.Contents 1050 case TokenColumn: 1051 halves := strings.Split(token.Contents, ".") 1052 if len(halves) == 2 { 1053 q += " [" + halves[0] + "].[" + halves[1] + "]" 1054 } else { 1055 q += " [" + token.Contents + "]" 1056 } 1057 case TokenString: 1058 q += " '" + token.Contents + "'" 1059 default: 1060 panic("This token doesn't exist o_o") 1061 } 1062 } 1063 q += " AND" 1064 } 1065 q = q[0 : len(q)-4] 1066 } 1067 1068 // TODO: MSSQL requires ORDER BY for LIMIT 1069 if len(sel.Orderby) != 0 { 1070 q += " ORDER BY " 1071 for _, column := range processOrderby(sel.Orderby) { 1072 log.Print("column: ", column) 1073 // TODO: We might want to escape this column 1074 q += column.Column + " " + strings.ToUpper(column.Order) + "," 1075 } 1076 q = q[0 : len(q)-1] 1077 } else if sel.Limit != "" { 1078 key, ok := a.keys[sel.Table1] 1079 if ok { 1080 q += " ORDER BY [" + sel.Table1 + "].[" + key + "]" 1081 } 1082 } 1083 1084 if sel.Limit != "" { 1085 limiter := processLimit(sel.Limit) 1086 if limiter.Offset != "" { 1087 if limiter.Offset == "?" { 1088 subCount++ 1089 q += " OFFSET ?" + strconv.Itoa(subCount) + " ROWS" 1090 } else { 1091 q += " OFFSET " + limiter.Offset + " ROWS" 1092 } 1093 } 1094 1095 // ! Does this work without an offset? 1096 if limiter.MaxCount != "" { 1097 if limiter.MaxCount == "?" { 1098 subCount++ 1099 limiter.MaxCount = "?" + strconv.Itoa(subCount) 1100 } 1101 q += " FETCH NEXT " + limiter.MaxCount + " ROWS ONLY " 1102 } 1103 } 1104 1105 q = strings.TrimSpace(q) 1106 // TODO: Run this on debug mode? 1107 if name[0] == '_' && sel.Limit == "" { 1108 log.Print(name+" query: ", q) 1109 } 1110 a.pushStatement(name, "insert", q) 1111 return q, nil 1112 } 1113 1114 func (a *MssqlAdapter) SimpleInsertLeftJoin(name string, ins DBInsert, sel DBJoin) (string, error) { 1115 return a.simpleJoin(name, ins, sel, "LEFT") 1116 } 1117 1118 func (a *MssqlAdapter) SimpleInsertInnerJoin(name string, ins DBInsert, sel DBJoin) (string, error) { 1119 return a.simpleJoin(name, ins, sel, "INNER") 1120 } 1121 1122 func (a *MssqlAdapter) SimpleCount(name, table, where, limit string) (string, error) { 1123 if table == "" { 1124 return "", errors.New("You need a name for this table") 1125 } 1126 q := "SELECT COUNT(*) FROM [" + table + "]" 1127 1128 // TODO: Add support for BETWEEN x.x 1129 if len(where) != 0 { 1130 q += " WHERE" 1131 for _, loc := range processWhere(where) { 1132 for _, token := range loc.Expr { 1133 switch token.Type { 1134 case TokenFunc, TokenOp, TokenNumber, TokenSub, TokenOr, TokenNot, TokenLike: 1135 if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" { 1136 token.Contents = "GETUTCDATE()" 1137 } 1138 q += " " + token.Contents 1139 case TokenColumn: 1140 q += " [" + token.Contents + "]" 1141 case TokenString: 1142 q += " '" + token.Contents + "'" 1143 default: 1144 panic("This token doesn't exist o_o") 1145 } 1146 } 1147 q += " AND" 1148 } 1149 q = q[0 : len(q)-4] 1150 } 1151 if limit != "" { 1152 q += " LIMIT " + limit 1153 } 1154 1155 q = strings.TrimSpace(q) 1156 a.pushStatement(name, "select", q) 1157 return q, nil 1158 } 1159 1160 func (a *MssqlAdapter) Builder() *prebuilder { 1161 return &prebuilder{a} 1162 } 1163 1164 func (a *MssqlAdapter) Write() error { 1165 var stmts, body string 1166 for _, name := range a.BufferOrder { 1167 if name == "" { 1168 continue 1169 } 1170 stmt := a.Buffer[name] 1171 // TODO: Add support for create-table? Table creation might be a little complex for Go to do outside a SQL file :( 1172 if stmt.Type != "create-table" { 1173 stmts += "\t" + name + " *sql.Stmt\n" 1174 body += ` 1175 common.DebugLog("Preparing ` + name + ` statement.") 1176 stmts.` + name + `, err = db.Prepare("` + stmt.Contents + `") 1177 if err != nil { 1178 log.Print("Error in ` + name + ` statement.") 1179 log.Print("Bad Query: ","` + stmt.Contents + `") 1180 return err 1181 } 1182 ` 1183 } 1184 } 1185 1186 // TODO: Move these custom queries out of this file 1187 out := `// +build mssql 1188 1189 // This file was generated by Gosora's Query Generator. Please try to avoid modifying this file, as it might change at any time. 1190 package main 1191 1192 import "log" 1193 import "database/sql" 1194 import "github.com/Azareal/Gosora/common" 1195 1196 // nolint 1197 type Stmts struct { 1198 ` + stmts + ` 1199 getActivityFeedByWatcher *sql.Stmt 1200 getActivityCountByWatcher *sql.Stmt 1201 1202 Mocks bool 1203 } 1204 1205 // nolint 1206 func _gen_mssql() (err error) { 1207 common.DebugLog("Building the generated statements") 1208 ` + body + ` 1209 return nil 1210 } 1211 ` 1212 return writeFile("./gen_mssql.go", out) 1213 } 1214 1215 // Internal methods, not exposed in the interface 1216 func (a *MssqlAdapter) pushStatement(name, stype, q string) { 1217 if name == "" { 1218 return 1219 } 1220 a.Buffer[name] = DBStmt{q, stype} 1221 a.BufferOrder = append(a.BufferOrder, name) 1222 } 1223 1224 func (a *MssqlAdapter) stringyType(ct string) bool { 1225 ct = strings.ToLower(ct) 1226 return ct == "char" || ct == "varchar" || ct == "datetime" || ct == "text" || ct == "nvarchar" 1227 } 1228 1229 type SetPrimaryKeys interface { 1230 SetPrimaryKeys(keys map[string]string) 1231 } 1232 1233 func (a *MssqlAdapter) SetPrimaryKeys(keys map[string]string) { 1234 a.keys = keys 1235 }