gitlab.com/beacon-software/gadget@v0.0.0-20181217202115-54565ea1ed5e/database/qb/querybuilder.go (about) 1 package qb 2 3 import ( 4 "fmt" 5 "strings" 6 7 "gitlab.com/beacon-software/gadget/errors" 8 ) 9 10 // ValidationFromNotSetError is set on the query when From has not been called on this query. 11 type ValidationFromNotSetError struct{ trace []string } 12 13 func (err *ValidationFromNotSetError) Error() string { 14 return "validation: from table must be set" 15 } 16 17 // Trace returns the stack trace for the error 18 func (err *ValidationFromNotSetError) Trace() []string { 19 return err.trace 20 } 21 22 // NewValidationFromNotSetError instantiates a ValidationFromNotSetError with a stack trace 23 func NewValidationFromNotSetError() errors.TracerError { 24 return &ValidationFromNotSetError{trace: errors.GetStackTrace()} 25 } 26 27 // MissingTablesError is returned when column's are being used from a table that is not part of the query. 28 type MissingTablesError struct { 29 Tables []string 30 trace []string 31 } 32 33 func (err *MissingTablesError) Error() string { 34 return fmt.Sprintf("validation: the folllowing tables are required but were not included in a join or from: %s", 35 err.Tables) 36 } 37 38 // Trace returns the stack trace for the error 39 func (err *MissingTablesError) Trace() []string { 40 return err.trace 41 } 42 43 // NewMissingTablesError is returned when column's are being used from a table that is not part of the query. 44 func NewMissingTablesError(tables []string) errors.TracerError { 45 return &MissingTablesError{ 46 Tables: tables, 47 trace: errors.GetStackTrace(), 48 } 49 } 50 51 // Comparison of two fields 52 type Comparison string 53 54 // JoinType inner or outer 55 type JoinType string 56 57 // JoinDirection left or right 58 type JoinDirection string 59 60 // OrderDirection for use in an order by Ascending or Descending 61 type OrderDirection string 62 63 const ( 64 // Equal Comparison Operator 65 Equal Comparison = "=" 66 // NotEqual Comparison Operator 67 NotEqual Comparison = "!=" 68 // LessThan Comparison Operator 69 LessThan Comparison = "<" 70 // LessThanEqual Comparison Operator 71 LessThanEqual Comparison = "<=" 72 // GreaterThan Comparison Operator 73 GreaterThan Comparison = ">" 74 // GreaterThanEqual Comparison Operator 75 GreaterThanEqual Comparison = ">=" 76 // NullSafeEqual Comparison Operator 77 NullSafeEqual Comparison = "<=>" 78 // Is Comparison Operator 79 Is Comparison = "IS" 80 // IsNot Comparison Operator 81 IsNot Comparison = "IS NOT" 82 // In Comparison Operator 83 In Comparison = "IN" 84 // Inner JoinType 85 Inner JoinType = "INNER" 86 // Outer JoinType 87 Outer JoinType = "OUTER" 88 // Cross JoinType 89 Cross JoinType = "CROSS" 90 // Left JoinDirection 91 Left JoinDirection = "LEFT" 92 // Right JoinDirection 93 Right JoinDirection = "RIGHT" 94 // Ascending OrderDirection 95 Ascending OrderDirection = "ASC" 96 // Descending OrderDirection 97 Descending OrderDirection = "DESC" 98 // And expression conjunction 99 And = "AND" 100 // Or expression conjunction 101 Or = "OR" 102 // XOr expression conjunction 103 XOr = "XOR" 104 // NoLimit is the value that represents not applying a limit on the query 105 NoLimit = 0 106 ) 107 108 // Table represents a db table 109 type Table interface { 110 // GetName returns the name of the database table 111 GetName() string 112 // GetAlias returns the alias of the database table to be used in the query 113 GetAlias() string 114 // PrimaryKey returns the primary key TableField 115 PrimaryKey() TableField 116 // AllColumns returns the AllColumns TableField for this Table 117 AllColumns() TableField 118 // ReadColumns returns the default set of columns for a read operation 119 ReadColumns() []TableField 120 // WriteColumns returns the default set of columns for a write operation 121 WriteColumns() []TableField 122 // SortBy returns the name of the default sort by field 123 SortBy() (TableField, OrderDirection) 124 } 125 126 // TableField represents a single column on a table. 127 type TableField struct { 128 // Name of the column in the database table 129 Name string 130 // Table that the column is on 131 Table string 132 } 133 134 // GetName that can be used to reference this expression 135 func (tf TableField) GetName() string { 136 return tf.Name 137 } 138 139 // GetTables that are used in this expression 140 func (tf TableField) GetTables() []string { 141 return []string{tf.Table} 142 } 143 144 // SQL that represents this table field 145 func (tf TableField) SQL() string { 146 if "*" == tf.Name { 147 return fmt.Sprintf("`%s`.%s", tf.Table, tf.Name) 148 } 149 return fmt.Sprintf("`%s`.`%s`", tf.Table, tf.Name) 150 } 151 152 // Equal returns a condition expression for this table field Equal to the passed obj. 153 func (tf TableField) Equal(obj interface{}) *ConditionExpression { 154 return FieldComparison(tf, Equal, obj) 155 } 156 157 // NotEqual returns a condition expression for this table field NotEqual to the passed obj. 158 func (tf TableField) NotEqual(obj interface{}) *ConditionExpression { 159 return FieldComparison(tf, NotEqual, obj) 160 } 161 162 // LessThan returns a condition expression for this table field LessThan to the passed obj. 163 func (tf TableField) LessThan(obj interface{}) *ConditionExpression { 164 return FieldComparison(tf, LessThan, obj) 165 } 166 167 // LessThanEqual returns a condition expression for this table field LessThanEqual to the passed obj. 168 func (tf TableField) LessThanEqual(obj interface{}) *ConditionExpression { 169 return FieldComparison(tf, LessThanEqual, obj) 170 } 171 172 // GreaterThan returns a condition expression for this table field GreaterThan to the passed obj. 173 func (tf TableField) GreaterThan(obj interface{}) *ConditionExpression { 174 return FieldComparison(tf, GreaterThan, obj) 175 } 176 177 // GreaterThanEqual returns a condition expression for this table field GreaterThanEqual to the passed obj. 178 func (tf TableField) GreaterThanEqual(obj interface{}) *ConditionExpression { 179 return FieldComparison(tf, GreaterThanEqual, obj) 180 } 181 182 // NullSafeEqual returns a condition expression for this table field NullSafeEqual to the passed obj. 183 func (tf TableField) NullSafeEqual(obj interface{}) *ConditionExpression { 184 return FieldComparison(tf, NullSafeEqual, obj) 185 } 186 187 // In returns a condition expression for this table field in to the passed objs. 188 func (tf TableField) In(objs ...interface{}) *ConditionExpression { 189 return FieldIn(tf, objs...) 190 } 191 192 // IsNull returns a condition expression for this table field when it is NULL 193 func (tf TableField) IsNull() *ConditionExpression { 194 return FieldComparison(tf, Is, SQLNull) 195 } 196 197 // IsNotNull returns a condition expression for this table field where it is not NULL 198 func (tf TableField) IsNotNull() *ConditionExpression { 199 return FieldComparison(tf, IsNot, SQLNull) 200 } 201 202 type orderByExpression struct { 203 field TableField 204 direction OrderDirection 205 } 206 207 type orderBy struct { 208 expressions []orderByExpression 209 } 210 211 func (ob *orderBy) addExpression(field TableField, direction OrderDirection) *orderBy { 212 exp := orderByExpression{field: field, direction: direction} 213 if nil == ob.expressions { 214 ob.expressions = []orderByExpression{exp} 215 } else { 216 ob.expressions = append(ob.expressions, exp) 217 } 218 return ob 219 } 220 221 func (ob *orderBy) getTables() []string { 222 tables := make([]string, len(ob.expressions)) 223 for i, exp := range ob.expressions { 224 tables[i] = exp.field.Table 225 } 226 return tables 227 } 228 229 func (ob *orderBy) sql() (string, bool) { 230 if nil == ob.expressions || len(ob.expressions) == 0 { 231 return "", false 232 } 233 orderByLines := []string{} 234 for _, orderBy := range ob.expressions { 235 orderByLines = append(orderByLines, fmt.Sprintf("`%s` %s", orderBy.field.Name, orderBy.direction)) 236 } 237 return "ORDER BY " + strings.Join(orderByLines, ", "), true 238 } 239 240 type whereCondition struct { 241 expression *ConditionExpression 242 } 243 244 func (wc *whereCondition) setExpression(left TableField, comparator Comparison, right interface{}) *whereCondition { 245 wc.expression = FieldComparison(left, comparator, right) 246 return wc 247 } 248 249 func (wc *whereCondition) tables() []string { 250 tables := []string{} 251 if nil != wc.expression { 252 for _, table := range wc.expression.Tables() { 253 tables = append(tables, table) 254 } 255 } 256 return tables 257 } 258 259 func (wc *whereCondition) sql() (string, []interface{}, bool) { 260 var sql string 261 var values []interface{} 262 ok := false 263 if nil != wc.expression { 264 sql, values = wc.expression.SQL() 265 ok = true 266 } 267 return sql, values, ok 268 } 269 270 // Join on the tables inside the query. 271 type Join struct { 272 direction JoinDirection 273 joinType JoinType 274 table Table 275 condition *ConditionExpression 276 err error 277 } 278 279 // JoinError signifying a problem with the created join. 280 type JoinError struct { 281 conditionTables []string 282 joinTable string 283 trace []string 284 } 285 286 // NewJoin of the specified type and direction. 287 func NewJoin(joinType JoinType, joinDirection JoinDirection, table Table) *Join { 288 return &Join{joinType: joinType, direction: joinDirection, table: table, 289 err: errors.New("no condition specified for join")} 290 } 291 292 func (err *JoinError) Error() string { 293 return fmt.Sprintf("join field to field condition (tables: %s) does not include table being joined '%s'", 294 err.conditionTables, err.joinTable) 295 } 296 297 // On specifies the the conditions of a join based upon two fields or a field and a discrete value 298 func (join *Join) On(left TableField, comparison Comparison, right interface{}) *ConditionExpression { 299 join.err = nil 300 rt, ok := right.(TableField) 301 if ok && left.Table != join.table.GetName() && rt.Table != join.table.GetName() { 302 join.err = &JoinError{conditionTables: []string{left.Table, rt.Table}, joinTable: join.table.GetName()} 303 } else if !ok && left.Table != join.table.GetName() { 304 join.err = &JoinError{conditionTables: []string{left.Table}, joinTable: join.table.GetName()} 305 } 306 join.condition = FieldComparison(left, comparison, right) 307 return join.condition 308 } 309 310 // SQL that represents this join. 311 func (join *Join) SQL() (string, []interface{}) { 312 if nil != join.err { 313 return "", []interface{}{} 314 } 315 var lines []string 316 if join.joinType == Inner || join.joinType == Cross { 317 lines = []string{fmt.Sprintf("%s JOIN `%s` AS `%s` ON", join.joinType, 318 join.table.GetName(), join.table.GetAlias())} 319 } else { 320 lines = []string{fmt.Sprintf("%s %s JOIN `%s` AS `%s` ON", join.direction, join.joinType, join.table.GetName(), 321 join.table.GetAlias())} 322 } 323 expressionSQL, values := join.condition.SQL() 324 325 lines = append(lines, expressionSQL) 326 return strings.Join(lines, " "), values 327 } 328 329 // Select creates a new select query based on the passed expressions for the select clause. 330 func Select(selectExpressions ...SelectExpression) *SelectQuery { 331 query := &SelectQuery{ 332 selectExps: selectExpressions, 333 orderBy: &orderBy{}, 334 groupBy: []SelectExpression{}, 335 where: &whereCondition{}, 336 Seperator: " ", 337 } 338 for _, exp := range selectExpressions { 339 for _, table := range exp.GetTables() { 340 query.GetAlias(table) 341 } 342 } 343 return query 344 } 345 346 // SelectDistinct creates a new select query based on the passed expressions for the select clause with a distinct 347 // modifier. 348 func SelectDistinct(selectExpressions ...SelectExpression) *SelectQuery { 349 query := Select(selectExpressions...) 350 query.distinct = true 351 return query 352 } 353 354 // Insert columns into a table 355 func Insert(columns ...TableField) *InsertQuery { 356 return &InsertQuery{ 357 columns: columns, 358 values: [][]interface{}{}, 359 onDuplicate: []TableField{}, 360 onDuplicateValues: []interface{}{}, 361 } 362 } 363 364 // Update returns a query that can be used for updating rows in the passed table. 365 func Update(table Table) *UpdateQuery { 366 return &UpdateQuery{ 367 tableReference: table, 368 assignments: []comparisonExpression{}, 369 orderBy: &orderBy{}, 370 where: &whereCondition{}, 371 } 372 } 373 374 // Delete from from the specified tables that match the criteria specified in where. 375 func Delete(rowsIn ...Table) *DeleteQuery { 376 return &DeleteQuery{ 377 tables: rowsIn, 378 joins: []*Join{}, 379 where: &whereCondition{}, 380 } 381 }