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  }