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  }