github.com/Accefy/pop@v0.0.0-20230428174248-e9f677eab5b9/query.go (about)

     1  package pop
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/gobuffalo/pop/v6/logging"
     8  )
     9  
    10  type operation string
    11  
    12  const (
    13  	Select operation = "SELECT"
    14  	Delete operation = "DELETE"
    15  )
    16  
    17  // Query is the main value that is used to build up a query
    18  // to be executed against the `Connection`.
    19  type Query struct {
    20  	RawSQL                  *clause
    21  	limitResults            int
    22  	addColumns              []string
    23  	eagerMode               EagerMode
    24  	eager                   bool
    25  	eagerFields             []string
    26  	whereClauses            clauses
    27  	orderClauses            clauses
    28  	fromClauses             fromClauses
    29  	belongsToThroughClauses belongsToThroughClauses
    30  	joinClauses             joinClauses
    31  	groupClauses            groupClauses
    32  	havingClauses           havingClauses
    33  	Paginator               *Paginator
    34  	Connection              *Connection
    35  	Operation               operation
    36  }
    37  
    38  // Clone will fill targetQ query with the connection used in q, if
    39  // targetQ is not empty, Clone will override all the fields.
    40  func (q *Query) Clone(targetQ *Query) {
    41  	rawSQL := *q.RawSQL
    42  	targetQ.RawSQL = &rawSQL
    43  
    44  	targetQ.limitResults = q.limitResults
    45  	targetQ.whereClauses = q.whereClauses
    46  	targetQ.orderClauses = q.orderClauses
    47  	targetQ.fromClauses = q.fromClauses
    48  	targetQ.belongsToThroughClauses = q.belongsToThroughClauses
    49  	targetQ.joinClauses = q.joinClauses
    50  	targetQ.groupClauses = q.groupClauses
    51  	targetQ.havingClauses = q.havingClauses
    52  	targetQ.addColumns = q.addColumns
    53  	targetQ.Operation = q.Operation
    54  
    55  	if q.Paginator != nil {
    56  		paginator := *q.Paginator
    57  		targetQ.Paginator = &paginator
    58  	}
    59  
    60  	if q.Connection != nil {
    61  		connection := *q.Connection
    62  		targetQ.Connection = &connection
    63  	}
    64  }
    65  
    66  // RawQuery will override the query building feature of Pop and will use
    67  // whatever query you want to execute against the `Connection`. You can continue
    68  // to use the `?` argument syntax.
    69  //
    70  //	c.RawQuery("select * from foo where id = ?", 1)
    71  func (c *Connection) RawQuery(stmt string, args ...interface{}) *Query {
    72  	return Q(c).RawQuery(stmt, args...)
    73  }
    74  
    75  // RawQuery will override the query building feature of Pop and will use
    76  // whatever query you want to execute against the `Connection`. You can continue
    77  // to use the `?` argument syntax.
    78  //
    79  //	q.RawQuery("select * from foo where id = ?", 1)
    80  func (q *Query) RawQuery(stmt string, args ...interface{}) *Query {
    81  	q.RawSQL = &clause{stmt, args}
    82  	return q
    83  }
    84  
    85  // Eager will enable associations loading of the model.
    86  // by defaults loads all the associations on the model,
    87  // but can take a variadic list of associations to load.
    88  //
    89  // 	c.Eager().Find(model, 1) // will load all associations for model.
    90  // 	c.Eager("Books").Find(model, 1) // will load only Book association for model.
    91  //
    92  // Eager also enable nested models creation:
    93  //
    94  //	model := Parent{Child: Child{}, Parent: &Parent{}}
    95  //	c.Eager().Create(&model) // will create all associations for model.
    96  //	c.Eager("Child").Create(&model) // will only create the Child association for model.
    97  func (c *Connection) Eager(fields ...string) *Connection {
    98  	con := c.copy()
    99  	con.eager = true
   100  	con.eagerFields = append(c.eagerFields, fields...)
   101  	return con
   102  }
   103  
   104  // Eager will enable load associations of the model.
   105  // by defaults loads all the associations on the model,
   106  // but can take a variadic list of associations to load.
   107  //
   108  // 	q.Eager().Find(model, 1) // will load all associations for model.
   109  // 	q.Eager("Books").Find(model, 1) // will load only Book association for model.
   110  func (q *Query) Eager(fields ...string) *Query {
   111  	q.eager = true
   112  	q.eagerFields = append(q.eagerFields, fields...)
   113  	return q
   114  }
   115  
   116  // disableEager disables eager mode for current query and Connection.
   117  func (q *Query) disableEager() {
   118  	q.Connection.eager, q.eager = false, false
   119  	q.Connection.eagerFields, q.eagerFields = []string{}, []string{}
   120  }
   121  
   122  // Where will append a where clause to the query. You may use `?` in place of
   123  // arguments.
   124  //
   125  // 	c.Where("id = ?", 1)
   126  // 	q.Where("id in (?)", 1, 2, 3)
   127  func (c *Connection) Where(stmt string, args ...interface{}) *Query {
   128  	q := Q(c)
   129  	return q.Where(stmt, args...)
   130  }
   131  
   132  // Where will append a where clause to the query. You may use `?` in place of
   133  // arguments.
   134  //
   135  // 	q.Where("id = ?", 1)
   136  // 	q.Where("id in (?)", 1, 2, 3)
   137  func (q *Query) Where(stmt string, args ...interface{}) *Query {
   138  	if q.RawSQL.Fragment != "" {
   139  		log(logging.Warn, "Query is setup to use raw SQL")
   140  		return q
   141  	}
   142  	if inRegex.MatchString(stmt) {
   143  		var inq []string
   144  		for i := 0; i < len(args); i++ {
   145  			inq = append(inq, "?")
   146  		}
   147  		qs := fmt.Sprintf("(%s)", strings.Join(inq, ","))
   148  		stmt = inRegex.ReplaceAllString(stmt, " IN "+qs)
   149  	}
   150  	q.whereClauses = append(q.whereClauses, clause{stmt, args})
   151  	return q
   152  }
   153  
   154  // Order will append an order clause to the query.
   155  //
   156  // 	c.Order("name desc")
   157  func (c *Connection) Order(stmt string, args ...interface{}) *Query {
   158  	return Q(c).Order(stmt, args...)
   159  }
   160  
   161  // Order will append an order clause to the query.
   162  //
   163  // 	q.Order("name desc")
   164  func (q *Query) Order(stmt string, args ...interface{}) *Query {
   165  	if q.RawSQL.Fragment != "" {
   166  		log(logging.Warn, "Query is setup to use raw SQL")
   167  		return q
   168  	}
   169  	q.orderClauses = append(q.orderClauses, clause{stmt, args})
   170  	return q
   171  }
   172  
   173  // Limit will create a query and add a limit clause to it.
   174  //
   175  // 	c.Limit(10)
   176  func (c *Connection) Limit(limit int) *Query {
   177  	return Q(c).Limit(limit)
   178  }
   179  
   180  // Limit will add a limit clause to the query.
   181  //
   182  // 	q.Limit(10)
   183  func (q *Query) Limit(limit int) *Query {
   184  	q.limitResults = limit
   185  	return q
   186  }
   187  
   188  // Preload activates preload eager Mode automatically.
   189  func (c *Connection) EagerPreload(fields ...string) *Query {
   190  	return Q(c).EagerPreload(fields...)
   191  }
   192  
   193  // Preload activates preload eager Mode automatically.
   194  func (q *Query) EagerPreload(fields ...string) *Query {
   195  	q.Eager(fields...)
   196  	q.eagerMode = EagerPreload
   197  	return q
   198  }
   199  
   200  // Q will create a new "empty" query from the current connection.
   201  func Q(c *Connection) *Query {
   202  	return &Query{
   203  		RawSQL:      &clause{},
   204  		Connection:  c,
   205  		eager:       c.eager,
   206  		eagerFields: c.eagerFields,
   207  		eagerMode:   eagerModeNil,
   208  		Operation:   Select,
   209  	}
   210  }
   211  
   212  // ToSQL will generate SQL and the appropriate arguments for that SQL
   213  // from the `Model` passed in.
   214  func (q Query) ToSQL(model *Model, addColumns ...string) (string, []interface{}) {
   215  	sb := q.toSQLBuilder(model, addColumns...)
   216  	// nil model is allowed only when if RawSQL is provided.
   217  	if model == nil && (q.RawSQL == nil || q.RawSQL.Fragment == "") {
   218  		return "", nil
   219  	}
   220  	return sb.String(), sb.Args()
   221  }
   222  
   223  // ToSQLBuilder returns a new `SQLBuilder` that can be used to generate SQL,
   224  // get arguments, and more.
   225  func (q Query) toSQLBuilder(model *Model, addColumns ...string) *sqlBuilder {
   226  	if len(q.addColumns) != 0 {
   227  		addColumns = q.addColumns
   228  	}
   229  	return newSQLBuilder(q, model, addColumns...)
   230  }