├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── config.go ├── model_interface.go ├── model_interface_test.go ├── query.go ├── query_execute.go ├── query_execute_test.go ├── query_sql.go ├── query_sql_and_execute.go ├── query_sql_and_execute_test.go ├── query_sql_delete.go ├── query_sql_delete_test.go ├── query_sql_insert.go ├── query_sql_insert_test.go ├── query_sql_select.go ├── query_sql_select_test.go ├── query_sql_test.go ├── query_sql_update.go ├── query_sql_update_test.go ├── query_sql_where.go ├── query_sql_where_test.go ├── sql_builder_interface.go ├── sql_placeholder_formats.go ├── squirrel_part.go ├── squirrel_part_test.go ├── squirrel_statement_builder.go ├── utils.go └── utils_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | # Glide 27 | glide.lock 28 | 29 | # VS Code 30 | .vscode -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.2 5 | - 1.3 6 | - 1.4 7 | - 1.5 8 | - 1.6 9 | - 1.7 10 | - 1.8 11 | - master 12 | 13 | # Instruct go get to also install test dependencies (namely go-sqlmock). 14 | install: 15 | - go get -t 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Abraham Botros 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LORE 2 | 3 | [![GoDoc](https://godoc.org/github.com/abrahambotros/lore?status.svg)](https://godoc.org/github.com/abrahambotros/lore) 4 | [![Build Status](https://travis-ci.org/abrahambotros/lore.svg)](https://travis-ci.org/abrahambotros/lore) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/abrahambotros/lore)](https://goreportcard.com/report/github.com/abrahambotros/lore) 6 | [![gocover.io](https://gocover.io/_badge/github.com/abrahambotros/lore)](https://gocover.io/github.com/abrahambotros/lore) 7 | 8 | Light Object-Relational Environment (LORE) provides a simple and lightweight pseudo-ORM/pseudo-struct-mapping environment for Go. 9 | 10 | ## Motivation 11 | With LORE, you weave your own lore and control your own magic (... bear with me, I'll explain). LORE provides a thin veil that abstracts away some of the inconveniences of object-relational mapping in general, but intentionally and explicitly avoids doing any hand-wavy magic tricks that place someone else's mystery black box between you and your data. 12 | 13 | To do so, LORE acts as a simple layer gluing together some wonderful and powerful libraries, providing a minimal abstraction above them for convenience: 14 | 15 | * Squirrel (https://github.com/Masterminds/squirrel) for SQL generation. 16 | * In particular, the entrypoints for building queries via LORE (Query.BuildSqlSelect/Insert/Update/Delete and related Query methods) directly return squirrel SQL builders, so you get all of the power of Squirrel with some added convenience (tablename handling, common related queries, etc.) via LORE. 17 | * SQLX (http://jmoiron.github.io/sqlx/) for running DB SQL queries and parsing results. 18 | 19 | Aside from this, you're in charge of your own schema, migration, etc. - no full ORM and cruft to get in the way of your lore! 20 | 21 | Lastly, while I'm sure it could still be improved, I've tried (and will continue to try) to ensure documentation provides full transparency of what is going on, so that your lore is entirely in your control. 22 | 23 | ## Guide and examples 24 | Below is a run-through of getting set up and constructing some simple examples. Check GoDoc/source documentation for more information on any item, and look through the test source files for more thorough examples of usage. I also recommend using string constants everywhere for your db field names (string literals everywhere are literally the devil and should be banned from your lore IMO - pun intended), but am just using literals here ONLY for simplicity and succinctness. 25 | 26 | ### Config 27 | When initializing your app, you should inform LORE of your desired config before constructing any LORE queries. To do so, you can use the `SetConfig` function and pass in a `*lore.Config`. Currently, this only determines the SQL placeholder format used in all Squirrel queries. 28 | 29 | ```go 30 | lore.SetConfig(&lore.Config{ 31 | SQLPlaceholderFormat: lore.SQLPlaceholderFormatDollar, // Or: SQLPlaceholderFormatQuestion 32 | }) 33 | 34 | // Or you can use the default config, which currently sets to the dollar-sign/$# placeholder format, 35 | // but it's probably best to call SetConfig explicitly as above. 36 | lore.SetConfigDefault() 37 | ``` 38 | 39 | ### Model interface 40 | To use LORE with your model, your model should implement `lore.ModelInterface`; see an example of 41 | this below. Note that you can use "db:" tags for sqlx directly; LORE currently doesn't do any 42 | specific handling of tags, as it delegates this all to sqlx. 43 | 44 | Please check out the GoDoc for [ModelInterface](https://godoc.org/github.com/abrahambotros/lore#ModelInterface) for much more detail for each method you need to implement, in particular `DbFieldMap` (which shouldn't include auto-managed keys, such as serial keys). 45 | 46 | ```go 47 | type Legend struct { 48 | Id int `db:"id"` // Serial/auto-incrementing primary key 49 | Name string `db:"name"` 50 | Culture string `db:"culture"` 51 | } 52 | 53 | var _ lore.ModelInterface = (*Legend)(nil) 54 | 55 | func (*Legend) DbTableName() string { 56 | return "legends" 57 | } 58 | 59 | func (l *Legend) DbFieldMap() map[string]interface{} { 60 | // Note that Id is purposefully left out, since this is a serial/auto-incrementing field! 61 | return map[string]interface{}{ 62 | "name": l.Name, 63 | "culture": l.Culture, 64 | } 65 | } 66 | 67 | func (*Legend) DbPrimaryFieldKey() string { 68 | return "id" 69 | } 70 | 71 | func (l *Legend) DbPrimaryFieldValue() interface{} { 72 | return l.Id 73 | } 74 | ``` 75 | 76 | ### Build SQL query 77 | Now we can use LORE to build a SQL query for your model. This involves 3 steps: 78 | 79 | 1. Create new `*lore.Query` instance, passing in your model (implementing `ModelInterface`). 80 | 2. Build SQL query via the `lore.Query.BuildSql*` methods, which wrap Squirrel builders. 81 | 3. Pass the completed SQL builder you made in step (2) back to the Query from (1) via `lore.Query.SetSqlBuilder`. Note that this can be combined with step (2) in simple cases as in the example below. 82 | 83 | Let's try building the following query on the `Legend` model we defined above: 84 | 85 | `SELECT * FROM legends WHERE name = 'Mars' AND culture = 'Roman' LIMIT 1;` 86 | 87 | ```go 88 | /* 89 | 1. Create new *lore.Query with our Legend model. 90 | */ 91 | q := lore.NewQuery(&Legend{}) 92 | 93 | /* 94 | 2. Build the SQL query; here we use the BuildSqlSelectStar entrypoint and finish building via 95 | Squirrel, then set it back into the Query via SetSqlBuilder in the same command. This tells LORE 96 | that this is the SQl that will be run when the Query is executed later. 97 | 98 | Note that BuildSqlSelectStar is an example of just one convenience wrapper that LORE provides; 99 | alternatively, you can just build the SQL via Squirrel, or use BuildSqlSelect and pass in your 100 | own column names, etc. 101 | */ 102 | q.SetSqlBuilder( 103 | q.BuildSqlSelectStar(). // This returns a Squirrel builder directly now, so the rest of this chain here is purely Squirrel. 104 | Where(squirrel.Eq{ 105 | "name": "Mars", 106 | "culture": "Roman", 107 | }). 108 | Limit(1), 109 | ) 110 | 111 | // If you want, you can use ToSql to get the Squirrel ToSql representation of the Query at any time. 112 | qSql, qArgs, err := q.ToSql() 113 | ``` 114 | 115 | ### Execute SQL query on your DB 116 | Now that we've built a `*lore.Query` and attached our SQL builder to it, we can execute this as a query against our DB. LORE currently provides 3 methods for doing so: 117 | 118 | 1. `*lore.Query.Execute` - wraps sqlx.DB.Exec 119 | 2. `*lore.Query.ExecuteThenParseSingle` - wraps sqlx.DB.Get 120 | 3. `*lore.Query.ExecuteThenParseList` - wraps sqlx.DB.List 121 | 122 | All of these methods attempt to return the number of rows affected by the query, along with of course an error if one was encountered. See GoDoc/source for more details, especially regarding numRowsAffected (see comments for the `Execute` functions in particular). 123 | 124 | ```go 125 | /* 126 | Execute and parse to list. Note that we pass in a POINTER to a list of structs we want to 127 | scan the DB rows back into - when passing into the Execute* Query methods, LORE assumes you're 128 | passing in a pointer (either to a list or a single struct), and will return an error if it 129 | detects otherwise. 130 | */ 131 | db := getDb() // ... Your own lore should conjure up a *sqlx.DB instance here. 132 | discoveredLegend := &Legend{} 133 | // See notes for numRowsAffected in Query.Execute documentation. 134 | numRowsAffected, err := q.ExecuteThenParseSingle(db, discoveredLegend) 135 | if err != nil { 136 | // Handle errors here. 137 | } 138 | // Row matching the SQL query is written into discoveredLegend. Do whatever with it now. 139 | discoveredLegend.GetCorrespondingCelestialBody() 140 | discoveredLegend.TellUsYourTale() 141 | 142 | ... 143 | 144 | /* 145 | Also try the other query wrappers listed below for creating your SQL... (See GoDoc/source for more 146 | details and possibly more functions) 147 | */ 148 | q.SetSqlBuilder( 149 | q.BuildSqlSelect(columns ...string) 150 | q.BuildSqlSelectStar() 151 | 152 | q.BuildSqlInsert() 153 | q.BuildSqlInsertColumnsAndValues(columns []string, values []interface{}) 154 | q.BuildSqlInsertModel() 155 | 156 | q.BuildSqlUpdate() 157 | q.BuildSqlUpdateModelByPrimaryKey() 158 | 159 | q.BuildSqlDelete() 160 | q.BuildSqlDeleteModelByPrimaryKey() 161 | ) 162 | ``` 163 | 164 | ### Convenience wrappers 165 | The functions below handle both building a SQL statement and executing it on the DB you supply, all in one easy call. For many typical use cases, you might find you can just call these and not worry about the intermediate steps (note that you don't even need to explicitly create a Query, though you still need to supply a config on app init!). See the GoDoc/source for more details of any particular function. 166 | 167 | ```go 168 | lore.SelectModelByPrimaryKey 169 | lore.SelectModelsWhere 170 | lore.InsertNewModel 171 | lore.UpdateModelByPrimaryKey 172 | lore.UpdateSetMapWhere 173 | lore.DeleteModelByPrimaryKey 174 | lore.DeleteModelsWhere 175 | ``` 176 | 177 | ## DANGER: WIP 178 | LORE is a major WIP. Contributions are welcome, but use in production is cautioned against at the moment unless you know full well what you're doing! 179 | 180 | ## TODO 181 | * Augment convenience SQL-and-execute functions to call only Execute with no parse result if resultPtr is nil. 182 | * Augment convenience SQL-and-execute functions with ORDER BY support. 183 | * Allow using sqlx QueryRow/QueryRowX for large/unrestricted-length queries instead of just Get/Select. 184 | * Better tests, especially for Execute\* methods and SQL-and-execute functions. 185 | * Consider better way to relate updates to SQL-builders to the parent query without having to call `SetSqlBuilder` every time. 186 | * Dedicated examples in GoDoc. 187 | 188 | ## Final notes 189 | Thanks for looking, and please feel free to message me if you're having any problems with LORE! I'm also always open to suggestions on ways to improve LORE, whether minor changes/fixes or even large rewrites - always happy to learn of better ways to do things! 190 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | /* 4 | _config holds the current Config instance. Note that this should NEVER be accessed directly, and 5 | should instead be retrieved via GetConfig to provide null safety. 6 | */ 7 | var _config *Config 8 | 9 | /* 10 | Config provides a struct for configuring all LORE queries. 11 | */ 12 | type Config struct { 13 | /* 14 | DB driver's placeholder format for injecting query parameters via SQL. 15 | */ 16 | SQLPlaceholderFormat SQLPlaceholderFormat 17 | } 18 | 19 | /* 20 | GetConfig returns the current config object. If no config already exists, a default is given. 21 | */ 22 | func GetConfig() *Config { 23 | if _config == nil { 24 | _config = GetConfigDefault() 25 | } 26 | return _config 27 | } 28 | 29 | /* 30 | GetConfigDefault returns the default config object. 31 | */ 32 | func GetConfigDefault() *Config { 33 | return &Config{ 34 | SQLPlaceholderFormat: SQLPlaceholderFormatDollar, 35 | } 36 | } 37 | 38 | /* 39 | SetConfig sets the current config for all future LORE queries. 40 | */ 41 | func SetConfig(c *Config) { 42 | _config = c 43 | } 44 | 45 | /* 46 | SetConfigDefault sets a default config for all future LORE queries. 47 | */ 48 | func SetConfigDefault() { 49 | SetConfig(GetConfigDefault()) 50 | } 51 | -------------------------------------------------------------------------------- /model_interface.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | /* 4 | ModelInterface provides a generic interface for enabling external models to interface with internal 5 | machinery here. 6 | */ 7 | type ModelInterface interface { 8 | /* 9 | DbTableName provides the name of the corresponding table in the database for this model. 10 | */ 11 | DbTableName() string 12 | /* 13 | DbFieldMap builds a map for this model instance from field/column name to this instance's 14 | current value for that field/column. Note that this should NOT include auto-managed db 15 | fields, such as SERIAL/AUTO-INCREMENTING KEYS, unless you want to try to specifically 16 | override these each time (typically not the case). 17 | 18 | This is used to provide Insert and Update functionality convenience by providing all of the 19 | columns and values to write into the table row. 20 | */ 21 | DbFieldMap() map[string]interface{} 22 | /* 23 | DbPrimaryFieldKey returns a string indicating the column name of the primary field of the 24 | model. This is used for Update and Delete queries for the WHERE condition. 25 | 26 | If the implementing model does not have such a field, an empty string can be returned, which 27 | will result in an error being thrown if any methods are called that require a non-empty 28 | field (such as an UPDATE that should have a non-empty WHERE condition for updating by 29 | primary key). If multiple columns define the primary field, you will have to implement the 30 | condition yourself instead. 31 | */ 32 | DbPrimaryFieldKey() string 33 | /* 34 | DbPrimaryFieldValue returns the current value of the primary field of the model. This is 35 | used for Update and Delete queries for the WHERE condition in conjunction with 36 | DbPrimaryFieldKey. 37 | 38 | If the implementing model does not have such a primary field, nil can be returned. Note that 39 | an error will be thrown if the DbPrimaryFieldKey method returns an empty string (see 40 | DbPrimaryFieldKey), but an error is not necessarily thrown if DbPrimaryFieldKey is 41 | non-empty and DbPrimaryFieldValue is nil (i.e., nil is a valid value to set for a primary 42 | field value if you really want). 43 | */ 44 | DbPrimaryFieldValue() interface{} 45 | } 46 | -------------------------------------------------------------------------------- /model_interface_test.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import "testing" 4 | 5 | const ( 6 | _TEST_DB_TABLENAME string = "tests" 7 | _TEST_DB_FIELDNAME_ID string = "id" 8 | _TEST_DB_FIELDNAME_FIELD string = "field" 9 | _TEST_MODEL_ID int = 1 10 | _TEST_MODEL_FIELD int64 = 2 11 | ) 12 | 13 | type testModel struct { 14 | Id int `db:"id"` 15 | Field int64 `db:"field"` 16 | } 17 | 18 | type testModelInvalid struct { 19 | Id int `db:"id"` 20 | Field int64 `db:"field"` 21 | } 22 | 23 | /* 24 | Enforce interface. 25 | */ 26 | var _ ModelInterface = (*testModel)(nil) 27 | var _ ModelInterface = (*testModelInvalid)(nil) 28 | 29 | /* 30 | DbTableName implementation for testModel ModelInterface. 31 | */ 32 | func (*testModel) DbTableName() string { 33 | return _TEST_DB_TABLENAME 34 | } 35 | 36 | /* 37 | DbTableName implementation for testModelInvalid ModelInterface. 38 | */ 39 | func (*testModelInvalid) DbTableName() string { 40 | return "" 41 | } 42 | 43 | /* 44 | DbFieldMap implementation for testModel ModelInterface. 45 | */ 46 | func (tm *testModel) DbFieldMap() map[string]interface{} { 47 | return map[string]interface{}{ 48 | _TEST_DB_FIELDNAME_FIELD: tm.Field, 49 | } 50 | } 51 | 52 | /* 53 | DbFieldMap implementation for testModelInvalid ModelInterface. 54 | */ 55 | func (tm *testModelInvalid) DbFieldMap() map[string]interface{} { 56 | return nil 57 | } 58 | 59 | /* 60 | DbPrimaryFieldKey implementation for testModel ModelInterface. 61 | */ 62 | func (*testModel) DbPrimaryFieldKey() string { 63 | return _TEST_DB_FIELDNAME_ID 64 | } 65 | 66 | /* 67 | DbPrimaryFieldKey implementation for testModelInvalid ModelInterface. 68 | */ 69 | func (*testModelInvalid) DbPrimaryFieldKey() string { 70 | return "" 71 | } 72 | 73 | /* 74 | DbPrimaryFieldValue implementation for testModel ModelInterface. 75 | */ 76 | func (tm *testModel) DbPrimaryFieldValue() interface{} { 77 | return _TEST_MODEL_ID 78 | } 79 | 80 | /* 81 | DbPrimaryFieldValue implementation for testModelInvalid ModelInterface. 82 | */ 83 | func (tm *testModelInvalid) DbPrimaryFieldValue() interface{} { 84 | return nil 85 | } 86 | 87 | /* 88 | createTestModelInstance creates a new testModel instance with valid values. 89 | */ 90 | func createTestModelInstance() *testModel { 91 | return &testModel{ 92 | Id: _TEST_MODEL_ID, 93 | Field: _TEST_MODEL_FIELD, 94 | } 95 | } 96 | 97 | /* 98 | createTestModelInvalidInstance creates a new testModelInvalid instance. This should NOT block 99 | compilation, but SHOULD allow us to check invalid conditions at test time. 100 | */ 101 | func createTestModelInvalidInstance() *testModelInvalid { 102 | return &testModelInvalid{ 103 | Id: 0, 104 | Field: -1, 105 | } 106 | } 107 | 108 | /* 109 | TestModelInterfaceInstance tests the ModelInterface interface by creating a new model instance. This 110 | is a trivial test. 111 | */ 112 | func TestModelInterfaceInstance(t *testing.T) { 113 | tm := createTestModelInstance() 114 | var mi ModelInterface 115 | mi = tm 116 | 117 | // Test table name. 118 | dbTableName := mi.DbTableName() 119 | if dbTableName != _TEST_DB_TABLENAME { 120 | t.Errorf("Invalid DbTableName: %s, expected %s", dbTableName, _TEST_DB_TABLENAME) 121 | return 122 | } 123 | 124 | // Test primary key/value. 125 | primaryFieldKey := mi.DbPrimaryFieldKey() 126 | primaryFieldValue := mi.DbPrimaryFieldValue() 127 | if primaryFieldKey != _TEST_DB_FIELDNAME_ID || primaryFieldValue != _TEST_MODEL_ID { 128 | t.Errorf("Invalid primary key/value combination: (%s, %+v), expected (%s, %+v)", primaryFieldKey, primaryFieldValue, _TEST_DB_FIELDNAME_ID, _TEST_MODEL_ID) 129 | return 130 | } 131 | 132 | // Test field map. 133 | dbFieldMap := mi.DbFieldMap() 134 | if len(dbFieldMap) != 1 || dbFieldMap[_TEST_DB_FIELDNAME_FIELD] != tm.Field { 135 | t.Errorf("Invalid DbFieldMap: %+v", dbFieldMap) 136 | return 137 | } 138 | } 139 | 140 | /* 141 | TestInvalidModelInterfaceInstance tests the ModelInterface interface against an invalid model 142 | instance. 143 | */ 144 | func TestInvalidModelInterfaceInstance(t *testing.T) { 145 | tm := createTestModelInvalidInstance() 146 | var mi ModelInterface 147 | mi = tm 148 | if tm == nil || mi == nil { 149 | t.Errorf("Expected non-nil ModelInterface instance, even if invalid") 150 | return 151 | } 152 | } 153 | 154 | /* 155 | newTestModelEmpty returns a pointer to a new, empty instance of testModel. 156 | */ 157 | func newTestModelEmpty() *testModel { 158 | return &testModel{} 159 | } 160 | 161 | /* 162 | newTestModelEmptyList returns a pointer to a new, empty list for testModels. 163 | */ 164 | func newTestModelEmptyList() *[]testModel { 165 | return &[]testModel{} 166 | } 167 | -------------------------------------------------------------------------------- /query.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | /* 4 | Query provides a generic, chainable query instance for callers to configure a specific query. 5 | */ 6 | type Query struct { 7 | modelInterface ModelInterface 8 | sqlBuilder SqlBuilderInterface 9 | } 10 | 11 | /* 12 | NewQuery instantiates a new Query instance based on the given ModelInterface. 13 | */ 14 | func NewQuery(modelInterface ModelInterface) *Query { 15 | return &Query{ 16 | modelInterface: modelInterface, 17 | sqlBuilder: nil, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /query_execute.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/jmoiron/sqlx" 9 | ) 10 | 11 | type executeMode int8 12 | 13 | const ( 14 | _EXECUTE_MODE_NO_PARSE executeMode = 0 15 | _EXECUTE_MODE_PARSE_SINGLE executeMode = 1 16 | _EXECUTE_MODE_PARSE_LIST executeMode = 2 17 | ) 18 | 19 | /* 20 | Execute wraps sqlx.DB.Exec, and does not parse the result into any struct. However, it still returns 21 | the basic info of numRowsAffected IN SOME CASES (see below). 22 | 23 | NOTE: As mentioned at http://jmoiron.github.io/sqlx/#exec access to numRowsAffected (via 24 | sqlx.Result.RowsAffected) is db-driver-dependent. 25 | */ 26 | func (q *Query) Execute(db *sqlx.DB) (numRowsAffected uint64, err error) { 27 | return q.execute(db, nil, _EXECUTE_MODE_NO_PARSE) 28 | } 29 | 30 | /* 31 | ExecuteThenParseSingle wraps sqlx.DB.Get to execute the query and then parse the result into the 32 | given single struct in resultSinglePtr. Scanning the db results into the result interface is done 33 | according to typical sqlx scanning behavior for sqlx.DB.Get. 34 | 35 | When sql.ErrNoRows is encountered by the underlying sqlx.DB.Get query (any time no matching row is 36 | found for this query), this function returns with found equal to false (naturally), but DOES NOT 37 | RETURN ANY ERROR (even though the sql package did). 38 | 39 | Note that resultSinglePtr should be a POINTER TO A SINGLE STRUCT that you want to scan the results 40 | into. An error will be returned if resultSinglePtr is not detected as a pointer to a single struct. 41 | */ 42 | func (q *Query) ExecuteThenParseSingle(db *sqlx.DB, resultSinglePtr interface{}) (found bool, err error) { 43 | numRowsAffected, err := q.execute(db, resultSinglePtr, _EXECUTE_MODE_PARSE_SINGLE) 44 | return (numRowsAffected > 0), err 45 | } 46 | 47 | /* 48 | ExecuteThenParseList wraps sqlx.DB.Select to execute the query and then parse the result into the 49 | given list of structs in resultSinglePtr. Scanning the db results into the result interface is done 50 | according to typical sqlx scanning behavior for sqlx.DB.Select. 51 | 52 | Note that resultListPtr should be a POINTER TO A LIST OF STRUCTS that you want to scan the results 53 | into. An error will be returned if resultListPtr is not detected as a pointer to a list. 54 | 55 | This function returns numRowsAffected equal to the length derived from the result slice after the 56 | query has been run. 57 | 58 | NOTE: Any callers of this function should ensure the underlying query is appropriately bounded, as 59 | sqlx.DB.Select will load the entire result set into memory at once; see 60 | http://jmoiron.github.io/sqlx/#getAndSelect for more information. 61 | */ 62 | func (q *Query) ExecuteThenParseList(db *sqlx.DB, resultListPtr interface{}) (numRowsAffected uint64, err error) { 63 | return q.execute(db, resultListPtr, _EXECUTE_MODE_PARSE_LIST) 64 | } 65 | 66 | /* 67 | execute provides the underlying execute functionality for executing SQL on a sqlx.DB. When mode is 68 | NO_PARSE, just calls db.Exec; when PARSE_SINGLE, calls db.Get; when PARSE_LIST, calls db.Select. 69 | 70 | Note that in the PARSE_SINGLE case, when sql.ErrNoRows is encountered by the underlying sqlx.DB.Get 71 | query (any time no matching row is found for this query), this function returns 0 rows affected 72 | (naturally), but DOES NOT RETURN ANY ERROR. 73 | */ 74 | func (q *Query) execute(db *sqlx.DB, resultPtr interface{}, mode executeMode) (numRowsAffected uint64, err error) { 75 | // Handle invalid input. 76 | if db == nil { 77 | return 0, errors.New("db cannot be nil") 78 | } 79 | switch mode { 80 | case _EXECUTE_MODE_NO_PARSE: 81 | break 82 | case _EXECUTE_MODE_PARSE_SINGLE: 83 | if !isPointer(resultPtr) || isPointerToSlice(resultPtr) { 84 | return 0, fmt.Errorf("Result pointer cannot point to slice when in ParseSingle mode. ResultPtr: %+v", resultPtr) 85 | } 86 | case _EXECUTE_MODE_PARSE_LIST: 87 | if !isPointer(resultPtr) || !isPointerToSlice(resultPtr) { 88 | return 0, fmt.Errorf("Result pointer must point to slice when in ParseList mode. ResultPtr: %+v", resultPtr) 89 | } 90 | default: 91 | return 0, fmt.Errorf("Invalid execute mode: %d", mode) 92 | } 93 | 94 | // Get SQL. 95 | qSql, qSqlArgs, err := q.ToSql() 96 | if err != nil { 97 | return 0, fmt.Errorf("Error building Query SQL: %s", err.Error()) 98 | } 99 | 100 | // Execute SQL with args on db. 101 | var rawExecResult sql.Result 102 | switch mode { 103 | case _EXECUTE_MODE_NO_PARSE: 104 | rawExecResult, err = db.Exec(qSql, qSqlArgs...) 105 | case _EXECUTE_MODE_PARSE_SINGLE: 106 | err = db.Get(resultPtr, qSql, qSqlArgs...) 107 | case _EXECUTE_MODE_PARSE_LIST: 108 | err = db.Select(resultPtr, qSql, qSqlArgs...) 109 | } 110 | 111 | // Handle any specific soft errors. 112 | if err == sql.ErrNoRows { 113 | // If specifically encountered this error, then set the number of rows affected to 0, and 114 | // return with no error immediately. 115 | return 0, nil 116 | } 117 | 118 | // Handle hard errors. 119 | if err != nil { 120 | return 0, fmt.Errorf("Error running Query: %s. SQL:%s, SQL args:%+v", err.Error(), qSql, qSqlArgs) 121 | } 122 | 123 | // Do any additional per-mode handling of result/return values. 124 | switch mode { 125 | case _EXECUTE_MODE_NO_PARSE: 126 | // If no-parse mode, then attempt using the raw sql.Result to determine number of rows 127 | // affected 128 | numRowsAffectedInt, err := rawExecResult.RowsAffected() 129 | if err != nil { 130 | return 0, err 131 | } 132 | numRowsAffected = uint64(numRowsAffectedInt) 133 | case _EXECUTE_MODE_PARSE_SINGLE: 134 | // If single mode, then assuming sql.ErrNoRows was caught above, we must have retrieved a 135 | // single result. 136 | numRowsAffected = 1 137 | case _EXECUTE_MODE_PARSE_LIST: 138 | // Otherwise, if in multi mode, then explicitly count number of items returned via 139 | // reflection, assuming the input was indeed a pointer to a slice. 140 | numRowsAffected, err = getPointerSliceLength(resultPtr) 141 | if err != nil { 142 | return 0, err 143 | } 144 | } 145 | 146 | // Return with success. 147 | return numRowsAffected, nil 148 | } 149 | -------------------------------------------------------------------------------- /query_execute_test.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | sqlmock "gopkg.in/DATA-DOG/go-sqlmock.v1" 8 | 9 | "github.com/jmoiron/sqlx" 10 | ) 11 | 12 | /* 13 | getTestSqlxDb returns a sqlx.DB for testing, along with a mock handle. 14 | */ 15 | func getTestSqlxDb(t *testing.T) (*sqlx.DB, sqlmock.Sqlmock) { 16 | mockDb, mock, err := sqlmock.New() 17 | if err != nil { 18 | t.Fatalf("Error getting test mock sqlx db: %s", err.Error()) 19 | } 20 | sqlxDb := sqlx.NewDb(mockDb, "sqlmock") 21 | return sqlxDb, mock 22 | } 23 | 24 | /* 25 | TestExecuteUnexported tests the (unexported) Query execute method. 26 | */ 27 | func TestExecuteUnexported(t *testing.T) { 28 | db, _ := getTestSqlxDb(t) 29 | 30 | // Build test query. 31 | q := NewQuery(newTestModelEmpty()) 32 | q.SetSqlBuilder( 33 | q.BuildSqlSelectStar(), 34 | ) 35 | 36 | // Test nil db resilience. 37 | numRowsAffected, err := q.execute(nil, nil, _EXECUTE_MODE_NO_PARSE) 38 | if numRowsAffected != 0 || err == nil { 39 | t.Errorf("Expected 0 rows affected and non-empty err since nil db") 40 | return 41 | } 42 | 43 | // Test invalid mode resilience. 44 | numRowsAffected, err = q.execute(db, nil, 42) 45 | if numRowsAffected != 0 || err == nil { 46 | t.Errorf("Expected 0 rows affected and non-empty err since invalid mode") 47 | return 48 | } 49 | 50 | // Build test/mock db. 51 | db, dbMock := getTestSqlxDb(t) 52 | 53 | // Test single-row query. 54 | rows := sqlmock.NewRows([]string{_TEST_DB_FIELDNAME_ID, _TEST_DB_FIELDNAME_FIELD}). 55 | AddRow(_TEST_MODEL_ID, _TEST_MODEL_FIELD) 56 | dbMock.ExpectQuery(fmt.Sprintf("^SELECT \\* FROM %s", _TEST_DB_TABLENAME)). 57 | WillReturnRows(rows) 58 | numRowsAffected, err = q.execute(db, newTestModelEmpty(), _EXECUTE_MODE_PARSE_SINGLE) 59 | if err != nil { 60 | t.Error(err) 61 | return 62 | } 63 | if numRowsAffected != 1 { 64 | t.Errorf("Expect numRowsAffected equal to 1, but got: %d", numRowsAffected) 65 | return 66 | } 67 | 68 | // Add another row and test multi-row query. 69 | rows = sqlmock.NewRows([]string{_TEST_DB_FIELDNAME_ID, _TEST_DB_FIELDNAME_FIELD}). 70 | AddRow(_TEST_MODEL_ID, _TEST_MODEL_FIELD). 71 | AddRow(_TEST_MODEL_ID+1, _TEST_MODEL_FIELD+1) 72 | dbMock.ExpectQuery(fmt.Sprintf("^SELECT \\* FROM %s", _TEST_DB_TABLENAME)). 73 | WillReturnRows(rows) 74 | numRowsAffected, err = q.execute(db, newTestModelEmptyList(), _EXECUTE_MODE_PARSE_LIST) 75 | if err != nil { 76 | t.Error(err) 77 | return 78 | } 79 | if numRowsAffected != 2 { 80 | t.Errorf("Expect numRowsAffected equal to 2, but got: %d", numRowsAffected) 81 | return 82 | } 83 | } 84 | 85 | /* 86 | TestExecute tests the Query Execute method using a mock db. 87 | */ 88 | func TestExecute(t *testing.T) { 89 | // Build test query. 90 | q := NewQuery(newTestModelEmpty()) 91 | q.SetSqlBuilder( 92 | q.BuildSqlSelectStar(), 93 | ) 94 | 95 | // Build test/mock db. 96 | db, dbMock := getTestSqlxDb(t) 97 | dbMock.ExpectExec(fmt.Sprintf("^SELECT \\* FROM %s", _TEST_DB_TABLENAME)).WillReturnResult(sqlmock.NewResult(1, 1)) 98 | numRowsAffected, err := q.Execute(db) 99 | if err != nil { 100 | t.Errorf("Error in Execute: %s", err.Error()) 101 | return 102 | } 103 | if numRowsAffected != 1 { 104 | t.Errorf("Unexpected numRowsAffected (%d) != 1", numRowsAffected) 105 | return 106 | } 107 | err = dbMock.ExpectationsWereMet() 108 | if err != nil { 109 | t.Error(err) 110 | return 111 | } 112 | } 113 | 114 | /* 115 | TestExecuteThenParseSingle tests the Query ExecuteThenParseSingle method using a mock db. 116 | 117 | TODO: Better testing of return result. 118 | TODO: Test invalid model interface. 119 | */ 120 | func TestExecuteThenParseSingle(t *testing.T) { 121 | // Build test query. 122 | q := NewQuery(newTestModelEmpty()) 123 | q.SetSqlBuilder( 124 | q.BuildSqlSelectStar().Limit(1).Suffix(RETURNING_STAR), 125 | ) 126 | 127 | // Build test/mock db. 128 | db, dbMock := getTestSqlxDb(t) 129 | dbMock.ExpectQuery(fmt.Sprintf("^SELECT \\* FROM %s LIMIT 1 RETURNING \\*", _TEST_DB_TABLENAME)). 130 | WillReturnRows(sqlmock.NewRows([]string{})) 131 | found, err := q.ExecuteThenParseSingle(db, newTestModelEmpty()) 132 | if err != nil { 133 | t.Errorf("Error in ExecuteThenParseList: %s", err.Error()) 134 | return 135 | } 136 | if found { 137 | t.Errorf("Unexpected found = %t", found) 138 | return 139 | } 140 | 141 | // Should get error if pass in empty list to ExecuteThenParseSingle. 142 | _, err = q.ExecuteThenParseSingle(db, newTestModelEmptyList()) 143 | if err == nil { 144 | t.Error("Expected error from passing in list instead of single to ExecuteThenParseSingle, but got no such error") 145 | return 146 | } 147 | } 148 | 149 | /* 150 | TestExecuteThenParseList tests the Query ExecuteThenParseList method using a mock db. 151 | 152 | TODO: Better testing of return result. 153 | */ 154 | func TestExecuteThenParseList(t *testing.T) { 155 | // Build test query. 156 | q := NewQuery(newTestModelEmpty()) 157 | q.SetSqlBuilder( 158 | q.BuildSqlSelectStar().Suffix(RETURNING_STAR), 159 | ) 160 | 161 | // Build test/mock db. 162 | db, dbMock := getTestSqlxDb(t) 163 | dbMock.ExpectQuery(fmt.Sprintf("^SELECT \\* FROM %s RETURNING \\*", _TEST_DB_TABLENAME)). 164 | WillReturnRows(sqlmock.NewRows([]string{})) 165 | numRowsAffected, err := q.ExecuteThenParseList(db, newTestModelEmptyList()) 166 | if err != nil { 167 | t.Errorf("Error in ExecuteThenParseList: %s", err.Error()) 168 | return 169 | } 170 | if numRowsAffected != 0 { 171 | t.Errorf("Unexpected numRowsAffected (%d) != 0", numRowsAffected) 172 | return 173 | } 174 | 175 | // Should get error if pass in empty-but-not-list to ExecuteThenParseList. 176 | _, err = q.ExecuteThenParseList(db, newTestModelEmpty()) 177 | if err == nil { 178 | t.Error("Expected error from passing in single instead of list to ExecuteThenParseList, but got no such error") 179 | return 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /query_sql.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import "errors" 4 | 5 | /* 6 | SetSqlBuilder sets the Query instance's internal sqlBuilder to the given SqlBuilderInterface 7 | instance. 8 | */ 9 | func (q *Query) SetSqlBuilder(sqlBuilder SqlBuilderInterface) { 10 | q.sqlBuilder = sqlBuilder 11 | } 12 | 13 | /* 14 | ToSql wraps the ToSql method of the internal sqlBuilder, returning the SQL-with-args form of the 15 | query according to the internal sqlBuilder. 16 | 17 | This requires that SetSqlBuilder has already been called, typically with a sqlBuilder/squirrel 18 | builder. If not, an error is returned. 19 | */ 20 | func (q *Query) ToSql() (sql string, args []interface{}, err error) { 21 | // If internal sqlBuilder is nil, then return error. 22 | if q.sqlBuilder == nil { 23 | return "", nil, errors.New("Query has not been specialized via Select/Insert/Update/Delete/etc.") 24 | } 25 | 26 | // Otherwise, wrap/return internal sqlBuilder's ToSql. 27 | return q.sqlBuilder.ToSql() 28 | } 29 | -------------------------------------------------------------------------------- /query_sql_and_execute.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/Masterminds/squirrel" 7 | "github.com/jmoiron/sqlx" 8 | ) 9 | 10 | /* 11 | SelectModelByPrimaryKey builds and executes a SQL statement on the db similar to the following SQL, 12 | scanning the result into resultSinglePtr. This is essentially a convenience wrapper around 13 | BuildSqlSelectStar and ExecuteThenParseSingle. 14 | 15 | `SELECT * FROM WHERE = LIMIT 1;` 16 | */ 17 | func SelectModelByPrimaryKey(mi ModelInterface, db *sqlx.DB, resultSinglePtr interface{}) (found bool, err error) { 18 | // Ensure non-empty primary field key. 19 | if mi.DbPrimaryFieldKey() == "" { 20 | return false, errors.New(_ERR_EMPTY_PRIMARY_KEY) 21 | } 22 | 23 | // Build query. 24 | q := NewQuery(mi) 25 | q.SetSqlBuilder( 26 | q.BuildSqlSelectStar(). 27 | Where(squirrel.Eq{ 28 | mi.DbPrimaryFieldKey(): mi.DbPrimaryFieldValue(), 29 | }). 30 | Limit(1), 31 | ) 32 | 33 | // Execute ParseSingle query to return single, if found. 34 | return q.ExecuteThenParseSingle(db, resultSinglePtr) 35 | } 36 | 37 | /* 38 | SelectModelWhere builds and executes a SQL statement on the db similar to the following SQL, 39 | scanning the result into resultSinglePtr. This is essentially a convenience wrapper around 40 | BuildSqlSelectStar and ExecuteThenParseSingle, applying the WHERE clause accordingly. 41 | 42 | Note that where is a pointer; if no where is desired, pass nil instead. When desired, this is 43 | typically something like a squirrel.Eq instance, etc. For your convenience, you can use lore.Where 44 | to build a single Where-ish object around a squirrel.Eq/squirrel.Gt/etc. 45 | 46 | `SELECT * FROM
WHERE LIMIT 1;` 47 | */ 48 | func SelectModelWhere(mi ModelInterface, db *sqlx.DB, where *sqlPart, resultSinglePtr interface{}) (found bool, err error) { 49 | // Build query. 50 | q := NewQuery(mi) 51 | qSqlBuilder := q.BuildSqlSelectStar() 52 | // Add where if non-nil. 53 | if where != nil { 54 | qSqlBuilder = qSqlBuilder.Where(*where) 55 | } 56 | q.SetSqlBuilder(qSqlBuilder) 57 | 58 | // Execute ParseSingle query. 59 | return q.ExecuteThenParseSingle(db, resultSinglePtr) 60 | } 61 | 62 | /* 63 | SelectModelsWhere is analogous to SelectModelWhere, but wraps ExecuteThenParseList instead of 64 | ExecuteThenParseSingle, and allows appying a LIMIT clause too. 65 | 66 | Note that limit is a pointer here - if nil is supplied, no limit is set on the SQL statement; 67 | otherwise, the underlying limit uint64 is applied. 68 | 69 | `SELECT * FROM
WHERE LIMIT ;` 70 | */ 71 | func SelectModelsWhere(mi ModelInterface, db *sqlx.DB, where *sqlPart, limit *uint64, resultListPtr interface{}) (numRowsAffected uint64, err error) { 72 | // Build query. 73 | q := NewQuery(mi) 74 | qSqlBuilder := q.BuildSqlSelectStar() 75 | // Add where if non-nil. 76 | if where != nil { 77 | qSqlBuilder = qSqlBuilder.Where(*where) 78 | } 79 | // Add limit if non-nil. 80 | if limit != nil { 81 | qSqlBuilder = qSqlBuilder.Limit(*limit) 82 | } 83 | q.SetSqlBuilder(qSqlBuilder) 84 | 85 | // Execute ParseList query. 86 | return q.ExecuteThenParseList(db, resultListPtr) 87 | } 88 | 89 | /* 90 | InsertNewModel builds and executes a SQL statement on the db similar to the following SQL, scanning 91 | the result into resultSinglePtr. This is essentially a convenience wrapper around 92 | BuildSqlInsertModel and ExecuteThenParseSingle. 93 | 94 | `INSERT INTO
() VALUES () RETURNING * ;` 95 | */ 96 | func InsertNewModel(mi ModelInterface, db *sqlx.DB, resultSinglePtr interface{}) error { 97 | // Build query. 98 | q := NewQuery(mi) 99 | qSqlBuilder, err := q.BuildSqlInsertModel() 100 | if err != nil { 101 | return err 102 | } 103 | q.SetSqlBuilder( 104 | qSqlBuilder.Suffix(RETURNING_STAR), 105 | ) 106 | 107 | // Execute ParseSingle query. 108 | _, err = q.ExecuteThenParseSingle(db, resultSinglePtr) 109 | return err 110 | } 111 | 112 | /* 113 | UpdateModelByPrimaryKey builds and executes a SQL statement on the db similar to the following SQL, 114 | scanning the result into resultSinglePtr. This is essentially a convenience wrapper around 115 | BuildSqlUpdateModelByPrimaryKey and ExecuteThenParseSingle. 116 | 117 | `UPDATE
SET 118 | WHERE = RETURNING * ;` 119 | */ 120 | func UpdateModelByPrimaryKey(mi ModelInterface, db *sqlx.DB, resultSinglePtr interface{}) (found bool, err error) { 121 | // Build query. 122 | q := NewQuery(mi) 123 | qSqlBuilder, err := q.BuildSqlUpdateModelByPrimaryKey() 124 | if err != nil { 125 | return false, err 126 | } 127 | q.SetSqlBuilder( 128 | qSqlBuilder.Suffix(RETURNING_STAR), 129 | ) 130 | 131 | // Execute ParseSingle query. 132 | return q.ExecuteThenParseSingle(db, resultSinglePtr) 133 | } 134 | 135 | /* 136 | UpdateSetMapWhere builds and executes a SQL statement on the db similar to the following SQL, 137 | scanning the result into resultListPtr. This is essentially a convenience wrapper around 138 | BuildSqlUpdateSetMap and ExecuteThenParseList, applying the WHERE clause accordingly. 139 | 140 | Note that where is a pointer; if no where is desired, pass nil instead. When desired, this is 141 | typically something like a squirrel.Eq instance, etc. For your convenience, you can use lore.Where 142 | to build a single Where-ish object around a squirrel.Eq/squirrel.Gt/etc. 143 | 144 | `UPDATE
SET WHERE RETURNING * ;` 145 | */ 146 | func UpdateSetMapWhere(mi ModelInterface, db *sqlx.DB, m map[string]interface{}, where *sqlPart, resultListPtr interface{}) (numRowsAffected uint64, err error) { 147 | // Build query. 148 | q := NewQuery(mi) 149 | qSqlBuilder := q.BuildSqlUpdateSetMap(m) 150 | // Add where if non-nil. 151 | if where != nil { 152 | qSqlBuilder = qSqlBuilder.Where(*where) 153 | } 154 | q.SetSqlBuilder( 155 | qSqlBuilder.Suffix(RETURNING_STAR), 156 | ) 157 | 158 | // Execute ParseList query. 159 | return q.ExecuteThenParseList(db, resultListPtr) 160 | } 161 | 162 | /* 163 | DeleteModelByPrimaryKey builds and executes a SQL statement on the db similar to the following SQL, 164 | scanning the result into resultSinglePtr. This is essentially a convenience wrapper around 165 | BuildSqlDeleteModelByPrimaryKey and ExecuteThenParseSingle. 166 | 167 | `DELETE FROM
WHERE = RETURNING * ;` 168 | */ 169 | func DeleteModelByPrimaryKey(mi ModelInterface, db *sqlx.DB, resultSinglePtr interface{}) (found bool, err error) { 170 | // Build query. 171 | q := NewQuery(mi) 172 | qSqlBuilder, err := q.BuildSqlDeleteModelByPrimaryKey() 173 | if err != nil { 174 | return false, err 175 | } 176 | q.SetSqlBuilder( 177 | qSqlBuilder.Suffix(RETURNING_STAR), 178 | ) 179 | 180 | // Execute ParseSingle query. 181 | return q.ExecuteThenParseSingle(db, resultSinglePtr) 182 | } 183 | 184 | /* 185 | DeleteModelsWhere builds and executes a SQL statement on the db similar to the following SQL, 186 | scanning the result into resultListPtr. This is essentially a convenience wrapper around 187 | BuildSqlDelete and ExecuteThenParseList, applying the WHERE clause accordingly. 188 | 189 | Note that where is a pointer; if no where is desired, pass nil instead. When desired, this is 190 | typically something like a squirrel.Eq instance, etc. For your convenience, you can use lore.Where 191 | to build a single Where-ish object around a squirrel.Eq/squirrel.Gt/etc. 192 | 193 | `DELETE FROM
WHERE RETURNING * ;` 194 | */ 195 | func DeleteModelsWhere(mi ModelInterface, db *sqlx.DB, where *sqlPart, resultListPtr interface{}) (numRowsAffected uint64, err error) { 196 | // Build query. 197 | q := NewQuery(mi) 198 | qSqlBuilder := q.BuildSqlDelete() 199 | // Add where if non-nil. 200 | if where != nil { 201 | qSqlBuilder = qSqlBuilder.Where(*where) 202 | } 203 | q.SetSqlBuilder( 204 | qSqlBuilder.Suffix(RETURNING_STAR), 205 | ) 206 | 207 | // Execute ParseList query. 208 | return q.ExecuteThenParseList(db, resultListPtr) 209 | } 210 | -------------------------------------------------------------------------------- /query_sql_and_execute_test.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/Masterminds/squirrel" 8 | ) 9 | 10 | /* 11 | TestSelectModelByPrimaryKey tests the SelectModelByPrimaryKey function. 12 | */ 13 | func TestSelectModelByPrimaryKey(t *testing.T) { 14 | // Test invalid ModelInterface instance. 15 | tmi := createTestModelInvalidInstance() 16 | 17 | // Test empty primary key resilience. 18 | pk := tmi.DbPrimaryFieldKey() 19 | if pk != "" { 20 | t.Error("Expected empty primary field key for invalid instance") 21 | return 22 | } 23 | found, err := SelectModelByPrimaryKey(tmi, nil, nil) 24 | if found != false || err == nil { 25 | t.Error("Expected not found and non-nil err for invalid instance with empty primary key") 26 | return 27 | } 28 | 29 | tm := createTestModelInstance() 30 | db, dbMock := getTestSqlxDb(t) 31 | dbMock.ExpectQuery(fmt.Sprintf( 32 | "^SELECT \\* FROM %s WHERE %s =", _TEST_DB_TABLENAME, _TEST_DB_FIELDNAME_ID, 33 | )).WithArgs(_TEST_MODEL_ID) 34 | SelectModelByPrimaryKey(tm, db, newTestModelEmpty()) 35 | err = dbMock.ExpectationsWereMet() 36 | if err != nil { 37 | t.Error(err) 38 | return 39 | } 40 | } 41 | 42 | /* 43 | TestSelectModelWhere tests the SelectModelWhere function. 44 | */ 45 | func TestSelectModelWhere(t *testing.T) { 46 | tm := createTestModelInstance() 47 | db, dbMock := getTestSqlxDb(t) 48 | dbMock.ExpectQuery(fmt.Sprintf( 49 | "^SELECT \\* FROM %s WHERE %s", _TEST_DB_TABLENAME, _TEST_DB_FIELDNAME_FIELD, 50 | )).WithArgs(_TEST_MODEL_FIELD) 51 | SelectModelWhere(tm, db, newSqlPart(squirrel.Eq{ 52 | _TEST_DB_FIELDNAME_FIELD: _TEST_MODEL_FIELD, 53 | }), newTestModelEmpty()) 54 | err := dbMock.ExpectationsWereMet() 55 | if err != nil { 56 | t.Error(err) 57 | return 58 | } 59 | } 60 | 61 | /* 62 | TestSelectModelsWhere tests the SelectModelsWhere function. 63 | */ 64 | func TestSelectModelsWhere(t *testing.T) { 65 | tm := createTestModelInstance() 66 | db, dbMock := getTestSqlxDb(t) 67 | dbMock.ExpectQuery(fmt.Sprintf( 68 | "^SELECT \\* FROM %s WHERE %s.*LIMIT 3", _TEST_DB_TABLENAME, _TEST_DB_FIELDNAME_FIELD, 69 | )).WithArgs(_TEST_MODEL_FIELD) 70 | var limit uint64 71 | limit = 3 72 | SelectModelsWhere(tm, db, newSqlPart(squirrel.Eq{ 73 | _TEST_DB_FIELDNAME_FIELD: _TEST_MODEL_FIELD, 74 | }), &limit, newTestModelEmptyList()) 75 | err := dbMock.ExpectationsWereMet() 76 | if err != nil { 77 | t.Error(err) 78 | return 79 | } 80 | } 81 | 82 | /* 83 | TestInsertNewModel tests the InsertNewModel function. 84 | 85 | TODO: Find way to test args in a way that is robust to random changes in argument order injected via 86 | Squirrel. 87 | */ 88 | func TestInsertNewModel(t *testing.T) { 89 | tm := createTestModelInstance() 90 | db, dbMock := getTestSqlxDb(t) 91 | dbMock.ExpectQuery(fmt.Sprintf( 92 | "^INSERT INTO %s.*RETURNING \\*", _TEST_DB_TABLENAME, 93 | )) 94 | InsertNewModel(tm, db, newTestModelEmpty()) 95 | err := dbMock.ExpectationsWereMet() 96 | if err != nil { 97 | t.Error(err) 98 | return 99 | } 100 | } 101 | 102 | /* 103 | TestUpdateModelByPrimaryKey tests the UpdateModelByPrimaryKey function. 104 | 105 | TODO: Find way to test args in a way that is robust to random changes in argument order injected via 106 | Squirrel. 107 | */ 108 | func TestUpdateModelByPrimaryKey(t *testing.T) { 109 | tm := createTestModelInstance() 110 | db, dbMock := getTestSqlxDb(t) 111 | dbMock.ExpectQuery(fmt.Sprintf( 112 | "^UPDATE %s SET .* WHERE %s.*RETURNING \\*", _TEST_DB_TABLENAME, _TEST_DB_FIELDNAME_ID, 113 | )) 114 | UpdateModelByPrimaryKey(tm, db, newTestModelEmpty()) 115 | err := dbMock.ExpectationsWereMet() 116 | if err != nil { 117 | t.Error(err) 118 | return 119 | } 120 | } 121 | 122 | /* 123 | TestUpdateSetMapWhere tests the UpdateSetMapWhere function. 124 | */ 125 | func TestUpdateSetMapWhere(t *testing.T) { 126 | tm := createTestModelInstance() 127 | db, dbMock := getTestSqlxDb(t) 128 | dbMock.ExpectQuery(fmt.Sprintf( 129 | "^UPDATE %s SET %s =.* WHERE %s =.*RETURNING \\*", 130 | _TEST_DB_TABLENAME, 131 | _TEST_DB_FIELDNAME_FIELD, 132 | _TEST_DB_FIELDNAME_ID, 133 | )).WithArgs(_TEST_MODEL_FIELD+1, _TEST_MODEL_ID) 134 | UpdateSetMapWhere(tm, db, map[string]interface{}{ 135 | _TEST_DB_FIELDNAME_FIELD: _TEST_MODEL_FIELD + 1, 136 | }, newSqlPart(squirrel.Eq{ 137 | _TEST_DB_FIELDNAME_ID: _TEST_MODEL_ID, 138 | }), newTestModelEmptyList()) 139 | err := dbMock.ExpectationsWereMet() 140 | if err != nil { 141 | t.Error(err) 142 | return 143 | } 144 | } 145 | 146 | /* 147 | TestDeleteModelByPrimaryKey tests the DeleteModelByPrimaryKey function. 148 | */ 149 | func TestDeleteModelByPrimaryKey(t *testing.T) { 150 | tm := createTestModelInstance() 151 | db, dbMock := getTestSqlxDb(t) 152 | dbMock.ExpectQuery(fmt.Sprintf( 153 | "^DELETE FROM %s WHERE %s =.*RETURNING \\*", _TEST_DB_TABLENAME, _TEST_DB_FIELDNAME_ID, 154 | )).WithArgs(_TEST_MODEL_ID) 155 | DeleteModelByPrimaryKey(tm, db, newTestModelEmpty()) 156 | err := dbMock.ExpectationsWereMet() 157 | if err != nil { 158 | t.Error(err) 159 | return 160 | } 161 | } 162 | 163 | /* 164 | TestDeleteModelsWhere tests the DeleteModelsWhere function. 165 | 166 | TODO: Find way to test multiple WHERE args in a way that is robust to random changes in argument 167 | order injected via Squirrel. 168 | */ 169 | func TestDeleteModelsWhere(t *testing.T) { 170 | tm := createTestModelInstance() 171 | db, dbMock := getTestSqlxDb(t) 172 | dbMock.ExpectQuery(fmt.Sprintf( 173 | "^DELETE FROM %s WHERE %s = .* RETURNING \\*", 174 | _TEST_DB_TABLENAME, 175 | _TEST_DB_FIELDNAME_FIELD, 176 | )).WithArgs(_TEST_MODEL_FIELD) 177 | DeleteModelsWhere(tm, db, newSqlPart(squirrel.Eq{ 178 | _TEST_DB_FIELDNAME_FIELD: _TEST_MODEL_FIELD, 179 | }), newTestModelEmptyList()) 180 | err := dbMock.ExpectationsWereMet() 181 | if err != nil { 182 | t.Error(err) 183 | return 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /query_sql_delete.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/Masterminds/squirrel" 7 | ) 8 | 9 | /* 10 | BuildSqlDelete provides the entrypoint for specializing a generic Query as a DELETE query on the 11 | table for the given ModelInterface. This directly returns a new squirrel.DeleteBuilder that can be 12 | placed back into the Query instance via SetSqlBuilder; the underlying SQL has the form: 13 | `DELETE FROM `. 14 | */ 15 | func (q *Query) BuildSqlDelete() squirrel.DeleteBuilder { 16 | return newSquirrelStatementBuilder(). 17 | Delete(q.modelInterface.DbTableName()) 18 | } 19 | 20 | /* 21 | BuildSqlDeleteModelByPrimaryKey wraps BuildSqlDelete to perform the delete on the table row with the 22 | matching primary key/value of this Query's ModelInterface's model instance. Alias for 23 | `query.BuildSqlDelete().Where()`. 24 | */ 25 | func (q *Query) BuildSqlDeleteModelByPrimaryKey() (squirrel.DeleteBuilder, error) { 26 | mi := q.modelInterface 27 | 28 | // Return error if primary key is empty. 29 | pk := mi.DbPrimaryFieldKey() 30 | if pk == "" { 31 | return q.BuildSqlDelete(), errors.New(_ERR_EMPTY_PRIMARY_KEY) 32 | } 33 | 34 | return q.BuildSqlDelete(). 35 | Where(squirrel.Eq{ 36 | pk: mi.DbPrimaryFieldValue(), 37 | }), nil 38 | } 39 | -------------------------------------------------------------------------------- /query_sql_delete_test.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/Masterminds/squirrel" 8 | ) 9 | 10 | func TestBuildSqlDelete(t *testing.T) { 11 | SetConfigDefault() 12 | tm := createTestModelInstance() 13 | tmTestValueId := 1 14 | 15 | // Build query and SQL. 16 | q := NewQuery(tm) 17 | q.SetSqlBuilder( 18 | q.BuildSqlDelete(). 19 | Where(squirrel.Eq{ 20 | _TEST_DB_FIELDNAME_ID: tmTestValueId, 21 | }). 22 | Suffix(RETURNING_STAR), 23 | ) 24 | 25 | // Delegate to SQL test helper. 26 | expectedSql := fmt.Sprintf( 27 | "DELETE FROM %s WHERE %s = $1 RETURNING *", 28 | _TEST_DB_TABLENAME, 29 | _TEST_DB_FIELDNAME_ID, 30 | ) 31 | expectedArgs := []interface{}{tmTestValueId} 32 | err := testBuildSqlHelper(q, expectedSql, expectedArgs) 33 | if err != nil { 34 | t.Error(err) 35 | return 36 | } 37 | } 38 | 39 | func TestBuildSqlDeleteModelByPrimaryKey(t *testing.T) { 40 | SetConfigDefault() 41 | tm := createTestModelInstance() 42 | tmTestValueId := 1 43 | tm.Id = tmTestValueId 44 | 45 | // Build query and SQL. 46 | q := NewQuery(tm) 47 | qSqlBuilder, err := q.BuildSqlDeleteModelByPrimaryKey() 48 | if err != nil { 49 | t.Error(err) 50 | return 51 | } 52 | q.SetSqlBuilder( 53 | qSqlBuilder.Suffix(RETURNING_STAR), 54 | ) 55 | 56 | // Delegate to SQL test helper. 57 | expectedSql := fmt.Sprintf( 58 | "DELETE FROM %s WHERE %s = $1 RETURNING *", 59 | _TEST_DB_TABLENAME, 60 | _TEST_DB_FIELDNAME_ID, 61 | ) 62 | expectedArgs := []interface{}{tmTestValueId} 63 | err = testBuildSqlHelper(q, expectedSql, expectedArgs) 64 | if err != nil { 65 | t.Error(err) 66 | return 67 | } 68 | } 69 | 70 | func TestBuildSqlDeleteModelByPrimaryKeyInvalid(t *testing.T) { 71 | SetConfigDefault() 72 | tm := createTestModelInvalidInstance() 73 | q := NewQuery(tm) 74 | 75 | // Ensure that primary key is empty for this invalid instance. 76 | if tm.DbPrimaryFieldKey() != "" { 77 | t.Error("Expected empty primary field key for testing") 78 | return 79 | } 80 | 81 | // Expect error since empty primary key. 82 | _, err := q.BuildSqlDeleteModelByPrimaryKey() 83 | if err == nil { 84 | t.Error("Expected non-nil err since invalid empty primary key", err) 85 | return 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /query_sql_insert.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import "github.com/Masterminds/squirrel" 4 | 5 | /* 6 | BuildSqlInsert provides the entrypoint for specializing a generic Query as an INSERT query on the 7 | table for the given ModelInterface. This directly returns a new squirrel.InsertBuilder that can be 8 | placed back into the Query instance via SetSqlBuilder; the underlying SQL has the form: 9 | "INSERT INTO ". 10 | */ 11 | func (q *Query) BuildSqlInsert() squirrel.InsertBuilder { 12 | return newSquirrelStatementBuilder(). 13 | Insert(q.modelInterface.DbTableName()) 14 | } 15 | 16 | /* 17 | BuildSqlInsertColumnsAndValues wraps BuildSqlInsert with the given columns and values. Alias for 18 | query.BuildSqlInsert().Columns().Values(). 19 | */ 20 | func (q *Query) BuildSqlInsertColumnsAndValues(columns []string, values []interface{}) squirrel.InsertBuilder { 21 | return q.BuildSqlInsert().Columns(columns...).Values(values...) 22 | } 23 | 24 | /* 25 | BuildSqlInsertModel wraps BuildSqlInsert with the given ModelInterface's DbFieldMap as the INSERT 26 | query's columns and values. Uses the ModelInterface the Query was originally created with. Alias for 27 | query.BuildSqlInsertColumnsAndValues(, ). 28 | */ 29 | func (q *Query) BuildSqlInsertModel() (squirrel.InsertBuilder, error) { 30 | // Parse columns and values from model's DbFieldMap. 31 | columns, values, err := getMapKeysVals(q.modelInterface.DbFieldMap()) 32 | if err != nil { 33 | return q.BuildSqlInsert(), err 34 | } 35 | 36 | // Delegate to BuildSqlInsertColumnsAndValues. 37 | return q.BuildSqlInsertColumnsAndValues(columns, values), nil 38 | } 39 | -------------------------------------------------------------------------------- /query_sql_insert_test.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestBuildSqlInsertColumnsAndValues(t *testing.T) { 9 | SetConfigDefault() 10 | tm := createTestModelInstance() 11 | tmTestValueField := tm.Field*2 + 1 12 | 13 | // Build query and SQL. 14 | q := NewQuery(tm) 15 | q.SetSqlBuilder( 16 | q.BuildSqlInsertColumnsAndValues( 17 | []string{_TEST_DB_FIELDNAME_FIELD}, 18 | []interface{}{tmTestValueField}, 19 | ).Suffix(RETURNING_STAR), 20 | ) 21 | 22 | // Delegate to SQL test helper. 23 | expectedSql := fmt.Sprintf( 24 | "INSERT INTO %s (%s) VALUES ($1) RETURNING *", 25 | _TEST_DB_TABLENAME, 26 | _TEST_DB_FIELDNAME_FIELD, 27 | ) 28 | expectedArgs := []interface{}{tmTestValueField} 29 | err := testBuildSqlHelper(q, expectedSql, expectedArgs) 30 | if err != nil { 31 | t.Error(err) 32 | return 33 | } 34 | } 35 | 36 | func TestBuildSqlInsertModel(t *testing.T) { 37 | SetConfigDefault() 38 | tm := createTestModelInstance() 39 | tmTestValueField := tm.Field*2 + 1 40 | tm.Field = tmTestValueField 41 | 42 | // Build query and SQL. 43 | q := NewQuery(tm) 44 | qSqlBuilder, err := q.BuildSqlInsertModel() 45 | if err != nil { 46 | t.Error(err) 47 | return 48 | } 49 | q.SetSqlBuilder( 50 | qSqlBuilder.Suffix(RETURNING_STAR), 51 | ) 52 | 53 | // Delegate to SQL test helper. 54 | err = testBuildSqlHelper( 55 | q, 56 | fmt.Sprintf( 57 | "INSERT INTO tests (%s) VALUES ($1) RETURNING *", 58 | _TEST_DB_FIELDNAME_FIELD, 59 | ), 60 | []interface{}{tm.Field}, 61 | ) 62 | if err != nil { 63 | t.Error(err) 64 | return 65 | } 66 | } 67 | 68 | func TestBuildSqlInsertModelInvalid(t *testing.T) { 69 | SetConfigDefault() 70 | tm := createTestModelInvalidInstance() 71 | 72 | // Ensure that nil DbFieldMap for this invalid instance. 73 | if tm.DbFieldMap() != nil { 74 | t.Error("Expected nil db field map for testing") 75 | return 76 | } 77 | 78 | // Expect error since nil db field map. 79 | q := NewQuery(tm) 80 | _, err := q.BuildSqlInsertModel() 81 | if err == nil { 82 | t.Error("Expected non-nil err since nil DbFieldMap", err) 83 | return 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /query_sql_select.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import "github.com/Masterminds/squirrel" 4 | 5 | /* 6 | BuildSqlSelect provides the entrypoint for specializing a generic Query as a SELECT query on the table 7 | for the given ModelInterface. This directly returns a new squirrel.SelectBuilder that can be placed 8 | back into the Query instance via SetSqlBuilder; the underlying SQL has the form: 9 | "SELECT FROM ". 10 | */ 11 | func (q *Query) BuildSqlSelect(columns ...string) squirrel.SelectBuilder { 12 | return newSquirrelStatementBuilder(). 13 | Select(columns...). 14 | From(q.modelInterface.DbTableName()) 15 | } 16 | 17 | /* 18 | BuildSqlSelectStar wraps BuildSqlSelect as a SELECT * query. Alias for query.BuildSqlSelect("*"). 19 | */ 20 | func (q *Query) BuildSqlSelectStar() squirrel.SelectBuilder { 21 | return q.BuildSqlSelect(STAR) 22 | } 23 | -------------------------------------------------------------------------------- /query_sql_select_test.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/Masterminds/squirrel" 8 | ) 9 | 10 | func TestBuildSqlSelect(t *testing.T) { 11 | SetConfigDefault() 12 | tm := createTestModelInstance() 13 | testValueWhereField := 2 14 | limit := uint64(10) 15 | 16 | // Build query and SQL. 17 | q := NewQuery(tm) 18 | q.SetSqlBuilder( 19 | q.BuildSqlSelect(_TEST_DB_FIELDNAME_ID). 20 | Where(squirrel.Eq{ 21 | _TEST_DB_FIELDNAME_FIELD: testValueWhereField, 22 | }). 23 | Limit(limit), 24 | ) 25 | 26 | // Delegate to SQL test helper. 27 | err := testBuildSqlHelper( 28 | q, 29 | fmt.Sprintf( 30 | "SELECT %s FROM %s WHERE %s = $1 LIMIT %d", 31 | _TEST_DB_FIELDNAME_ID, 32 | _TEST_DB_TABLENAME, 33 | _TEST_DB_FIELDNAME_FIELD, 34 | limit, 35 | ), 36 | []interface{}{testValueWhereField}, 37 | ) 38 | if err != nil { 39 | t.Error(err) 40 | return 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /query_sql_test.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | /* 9 | TestToSqlInvalid tests invalid inputs to the ToSql method. 10 | */ 11 | func TestToSqlInvalid(t *testing.T) { 12 | // Build test query. 13 | q := NewQuery(newTestModelEmpty()) 14 | q.SetSqlBuilder( 15 | q.BuildSqlSelectStar(), 16 | ) 17 | 18 | // Test nil sqlBuilder resilience. 19 | q.sqlBuilder = nil 20 | qSql, qSqlArgs, err := q.ToSql() 21 | if qSql != "" || qSqlArgs != nil || err == nil { 22 | t.Error("Expect empty qSql, nil qSqlArgs, and non-nil err since q.sqlBuilder is nil") 23 | return 24 | } 25 | } 26 | 27 | /* 28 | testBuildSqlHelper builds the SQL from the Query and compares it against the expected SQL and args. 29 | Returns an error on any errors or deviations from what was expected. 30 | 31 | Note that this only can handle a minimal number of args, since Squirrel does not always order args 32 | the same way - thus, trying to test for many args would raise too many permutations of arguments to 33 | easily and quickly test without shifting too focus to accounting for these permutations. 34 | */ 35 | func testBuildSqlHelper(q *Query, expectedSql string, expectedArgs []interface{}) error { 36 | // Test ToSql. 37 | qSql, qSqlArgs, err := q.ToSql() 38 | if err != nil { 39 | return fmt.Errorf("Error building query to SQL: %s", err.Error()) 40 | } 41 | 42 | // Test if generated SQL is as expected. 43 | if qSql != expectedSql { 44 | return fmt.Errorf("Incorrect SQL output: %s. Expected: %s", qSql, expectedSql) 45 | } 46 | 47 | // Test if args are exactly as expected. 48 | numQSqlArgs := len(qSqlArgs) 49 | numExpectedArgs := len(expectedArgs) 50 | if numQSqlArgs != numExpectedArgs { 51 | return fmt.Errorf("Incorrect number of SQL args: %d. Expected: %d", numQSqlArgs, numExpectedArgs) 52 | } 53 | for i := 0; i < numQSqlArgs; i++ { 54 | if qSqlArgs[i] != expectedArgs[i] { 55 | return fmt.Errorf("Incorrect SQL arg at index %d: %+v, expected: %+v", i, qSqlArgs[i], expectedArgs[i]) 56 | } 57 | } 58 | 59 | // Return with no error. 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /query_sql_update.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/Masterminds/squirrel" 7 | ) 8 | 9 | /* 10 | BuildSqlUpdate provides the entrypoint for specializing a generic Query as an UPDATE query on the 11 | table for the given ModelInterface. This directly returns a new squirrel.UpdateBuilder that can be 12 | placed back into the Query instance via SetSqlBuilder; the underlying SQL has the form: 13 | `UPDATE `. 14 | */ 15 | func (q *Query) BuildSqlUpdate() squirrel.UpdateBuilder { 16 | return newSquirrelStatementBuilder(). 17 | Update(q.modelInterface.DbTableName()) 18 | } 19 | 20 | /* 21 | BuildSqlUpdateSetMap wraps BuildSqlUpdate with the columns and values in the given map. Alias for 22 | `query.BuildSqlUpdate().SetMap()`. 23 | */ 24 | func (q *Query) BuildSqlUpdateSetMap(m map[string]interface{}) squirrel.UpdateBuilder { 25 | return q.BuildSqlUpdate().SetMap(m) 26 | } 27 | 28 | /* 29 | BuildSqlUpdateModelByPrimaryKey wraps BuildSqlUpdate to perform the update with the given columns 30 | and values defined by the Query's ModelInterface's DbFieldMap on the table row with the matching 31 | primary key/value for this ModelInterface's model instance. Alias for 32 | `query.BuildSqlUpdate().Where().Set()`. 33 | */ 34 | func (q *Query) BuildSqlUpdateModelByPrimaryKey() (squirrel.UpdateBuilder, error) { 35 | mi := q.modelInterface 36 | 37 | // Return error if primary key is empty. 38 | pk := mi.DbPrimaryFieldKey() 39 | if pk == "" { 40 | return q.BuildSqlUpdate(), errors.New(_ERR_EMPTY_PRIMARY_KEY) 41 | } 42 | 43 | return q.BuildSqlUpdate(). 44 | Where(squirrel.Eq{ 45 | pk: mi.DbPrimaryFieldValue(), 46 | }). 47 | SetMap(mi.DbFieldMap()), nil 48 | } 49 | -------------------------------------------------------------------------------- /query_sql_update_test.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/Masterminds/squirrel" 8 | ) 9 | 10 | func TestBuildSqlUpdate(t *testing.T) { 11 | SetConfigDefault() 12 | tm := createTestModelInstance() 13 | tmTestValueId := 1 14 | tmTestValueField := tm.Field*2 + 1 15 | 16 | // Build query and SQL. 17 | q := NewQuery(tm) 18 | q.SetSqlBuilder( 19 | q.BuildSqlUpdate(). 20 | Set(_TEST_DB_FIELDNAME_FIELD, tmTestValueField). 21 | Where(squirrel.Eq{ 22 | _TEST_DB_FIELDNAME_ID: tmTestValueId, 23 | }). 24 | Suffix(RETURNING_STAR), 25 | ) 26 | 27 | // Delegate to SQL test helper. 28 | err := testBuildSqlHelper( 29 | q, 30 | fmt.Sprintf( 31 | "UPDATE %s SET %s = $1 WHERE %s = $2 RETURNING *", 32 | _TEST_DB_TABLENAME, 33 | _TEST_DB_FIELDNAME_FIELD, 34 | _TEST_DB_FIELDNAME_ID, 35 | ), 36 | []interface{}{tmTestValueField, tmTestValueId}, 37 | ) 38 | if err != nil { 39 | t.Error(err) 40 | return 41 | } 42 | } 43 | 44 | func TestBuildSqlUpdateModelByPrimaryKey(t *testing.T) { 45 | SetConfigDefault() 46 | tm := createTestModelInstance() 47 | tmTestValueId := 1 48 | tmTestValueField := tm.Field*2 + 1 49 | tm.Id = tmTestValueId 50 | tm.Field = tmTestValueField 51 | 52 | // Build query and SQL. 53 | q := NewQuery(tm) 54 | qSqlBuilder, err := q.BuildSqlUpdateModelByPrimaryKey() 55 | if err != nil { 56 | t.Error(err) 57 | return 58 | } 59 | q.SetSqlBuilder( 60 | qSqlBuilder.Suffix(RETURNING_STAR), 61 | ) 62 | 63 | // Delegate to SQL test helper. 64 | err = testBuildSqlHelper( 65 | q, 66 | fmt.Sprintf( 67 | "UPDATE %s SET %s = $1 WHERE %s = $2 RETURNING *", 68 | _TEST_DB_TABLENAME, 69 | _TEST_DB_FIELDNAME_FIELD, 70 | _TEST_DB_FIELDNAME_ID, 71 | ), 72 | []interface{}{tm.Field, tm.Id}, 73 | ) 74 | if err != nil { 75 | t.Error(err) 76 | return 77 | } 78 | } 79 | 80 | func TestBuildSqlUpdateModelByPrimaryKeyInvalid(t *testing.T) { 81 | SetConfigDefault() 82 | tm := createTestModelInvalidInstance() 83 | 84 | // Ensure that primary key is empty for this invalid instance. 85 | if tm.DbPrimaryFieldKey() != "" { 86 | t.Error("Expected empty primary field key for testing") 87 | return 88 | } 89 | 90 | // Expect error since empty primary key. 91 | q := NewQuery(tm) 92 | _, err := q.BuildSqlUpdateModelByPrimaryKey() 93 | if err == nil { 94 | t.Error("Expected non-nil err since invalid empty primary key", err) 95 | return 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /query_sql_where.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | /* 4 | Where returns a new part as a Sqlizer interface, allowing this to be called in typical WHERE 5 | clauses via Squirrel. 6 | */ 7 | func Where(pred interface{}, args ...interface{}) *sqlPart { 8 | return newSqlPart(pred, args...) 9 | } 10 | -------------------------------------------------------------------------------- /query_sql_where_test.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Masterminds/squirrel" 7 | ) 8 | 9 | /* 10 | TestWhere tests the Where function. 11 | 12 | TODO: Add tests for args params. 13 | */ 14 | func TestWhere(t *testing.T) { 15 | where := Where(map[string]interface{}{ 16 | _TEST_DB_FIELDNAME_FIELD: _TEST_MODEL_FIELD, 17 | }) 18 | pred, ok := where.pred.(map[string]interface{}) 19 | if !ok { 20 | t.Error("Unexpected invalid type assertion") 21 | return 22 | } 23 | if pred[_TEST_DB_FIELDNAME_FIELD] != _TEST_MODEL_FIELD { 24 | t.Errorf( 25 | "Expected where squirrel.Eq field mapping of %s to %d, but got map: %+v", 26 | _TEST_DB_FIELDNAME_FIELD, 27 | _TEST_MODEL_FIELD, 28 | where.pred, 29 | ) 30 | return 31 | } 32 | 33 | // TODO: Add additional tests here for squirrel interfaces. 34 | where = Where(squirrel.Eq{ 35 | _TEST_DB_FIELDNAME_FIELD: _TEST_MODEL_FIELD, 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /sql_builder_interface.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | /* 4 | SqlBuilderInterface provides a generic interface for SQL-izing. This mirrors the squirrel.Sqlizer 5 | interface. 6 | */ 7 | type SqlBuilderInterface interface { 8 | ToSql() (sql string, args []interface{}, err error) 9 | } 10 | -------------------------------------------------------------------------------- /sql_placeholder_formats.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import "github.com/Masterminds/squirrel" 4 | 5 | /* 6 | SQLPlaceholderFormat aliases squirrel.PlaceholderFormat. 7 | */ 8 | type SQLPlaceholderFormat squirrel.PlaceholderFormat 9 | 10 | /* 11 | SQLPlaceholderFormatDollar aliases squirrel.Dollar. 12 | */ 13 | var SQLPlaceholderFormatDollar = squirrel.Dollar 14 | 15 | /* 16 | SQLPlaceholderFormatQuestion aliases squirrel.Question. 17 | */ 18 | var SQLPlaceholderFormatQuestion = squirrel.Question 19 | -------------------------------------------------------------------------------- /squirrel_part.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Masterminds/squirrel" 7 | ) 8 | 9 | /* 10 | sqlPart is directly imported from Squirrel.part. 11 | */ 12 | type sqlPart struct { 13 | pred interface{} 14 | args []interface{} 15 | } 16 | 17 | /* 18 | newSqlPart is directly imported from Squirrel.newPart. 19 | */ 20 | func newSqlPart(pred interface{}, args ...interface{}) *sqlPart { 21 | return &sqlPart{pred, args} 22 | } 23 | 24 | /* 25 | ToSql is directly imported from Squirrel.part.ToSql. 26 | */ 27 | func (p sqlPart) ToSql() (sql string, args []interface{}, err error) { 28 | switch pred := p.pred.(type) { 29 | case nil: 30 | // no-op 31 | case squirrel.Sqlizer: 32 | sql, args, err = pred.ToSql() 33 | case string: 34 | sql = pred 35 | args = p.args 36 | default: 37 | err = fmt.Errorf("expected string or Sqlizer, not %T", pred) 38 | } 39 | return 40 | } 41 | -------------------------------------------------------------------------------- /squirrel_part_test.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/Masterminds/squirrel" 8 | ) 9 | 10 | func TestNewSqlPart(t *testing.T) { 11 | newSqlPart(squirrel.Eq{ 12 | _TEST_DB_FIELDNAME_FIELD: _TEST_MODEL_FIELD, 13 | }) 14 | newSqlPart(map[string]interface{}{ 15 | _TEST_DB_FIELDNAME_FIELD: _TEST_MODEL_FIELD, 16 | }) 17 | } 18 | 19 | func TestSqlPartToSql(t *testing.T) { 20 | // Test invalid input resilience. 21 | sp := newSqlPart(10) 22 | sql, args, err := sp.ToSql() 23 | if err == nil { 24 | t.Error("Expect non-nil err since invalid pred") 25 | return 26 | } 27 | 28 | // Test squirrel interface. 29 | sp = newSqlPart(squirrel.Eq{ 30 | _TEST_DB_FIELDNAME_FIELD: _TEST_MODEL_FIELD, 31 | }) 32 | sql, args, err = sp.ToSql() 33 | if sql != fmt.Sprintf("%s = ?", _TEST_DB_FIELDNAME_FIELD) { 34 | t.Errorf("Invalid sql generated: %s", sql) 35 | return 36 | } 37 | arg0, ok := args[0].(int64) 38 | if !ok { 39 | t.Error("Unexpected error casting to int64") 40 | return 41 | } 42 | if arg0 != _TEST_MODEL_FIELD { 43 | t.Errorf("Expected %d, got %d", _TEST_MODEL_FIELD, arg0) 44 | return 45 | } 46 | 47 | // Test string. 48 | sp = newSqlPart( 49 | fmt.Sprintf("%s = ?", _TEST_DB_FIELDNAME_FIELD), 50 | _TEST_MODEL_FIELD, 51 | ) 52 | sql, args, err = sp.ToSql() 53 | if err != nil { 54 | t.Error(err) 55 | return 56 | } 57 | if sql != fmt.Sprintf("%s = ?", _TEST_DB_FIELDNAME_FIELD) { 58 | t.Errorf("Invalid sql generated: %s", sql) 59 | return 60 | } 61 | arg0, ok = args[0].(int64) 62 | if !ok { 63 | t.Error("Unexpected error casting to int64") 64 | return 65 | } 66 | if arg0 != _TEST_MODEL_FIELD { 67 | t.Errorf("Expected %d, got %d", _TEST_MODEL_FIELD, arg0) 68 | return 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /squirrel_statement_builder.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import "github.com/Masterminds/squirrel" 4 | 5 | /* 6 | SquirrelStatementBuilder provides a generic interface for squirrel statement builders. Wraps 7 | SqlBuilderInterface. 8 | */ 9 | type SquirrelStatementBuilder SqlBuilderInterface 10 | 11 | /* 12 | newSquirrelStatementBuilder returns a new squirrel.StatementBuilder(Type) instance, with the current 13 | config applied. 14 | */ 15 | func newSquirrelStatementBuilder() squirrel.StatementBuilderType { 16 | c := GetConfig() 17 | return squirrel.StatementBuilder. 18 | PlaceholderFormat(c.SQLPlaceholderFormat) 19 | } 20 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | const ( 10 | // STAR provides a hard-coded constant string representing the common star/* character in SQL. 11 | STAR string = "*" 12 | // RETURNING_STAR provides a hard-coded constant string representing the common "RETURNING *" 13 | // clause in SQL. 14 | RETURNING_STAR string = "RETURNING *" 15 | // _ERR_EMPTY_PRIMARY_KEY defines a common error message used by this package to indicate an 16 | // empty primary key is found on a ModelInterface instance when a non-empty one is required. 17 | _ERR_EMPTY_PRIMARY_KEY string = "Empty primary key encountered; cannot perform functions requiring primary keys" 18 | ) 19 | 20 | /* 21 | isPointer returns true if the given interface is a pointer; false otherwise. 22 | */ 23 | func isPointer(i interface{}) bool { 24 | return reflect.ValueOf(i).Kind() == reflect.Ptr 25 | } 26 | 27 | /* 28 | isPointerToSlice returns true if the given interface is a pointer to a slice; false otherwise. 29 | */ 30 | func isPointerToSlice(i interface{}) bool { 31 | return isPointer(i) && reflect.TypeOf(reflect.ValueOf(i).Elem().Interface()).Kind() == reflect.Slice 32 | } 33 | 34 | /* 35 | getPointerSliceLength returns the length of the slice pointed to by the given pointer. If the 36 | underlying object is not a slice, an error is returned. 37 | */ 38 | func getPointerSliceLength(i interface{}) (uint64, error) { 39 | if isPointerToSlice(i) { 40 | return uint64(reflect.ValueOf(i).Elem().Len()), nil 41 | } 42 | 43 | // If reach here, then i was not a pointer to a slice; return error. 44 | return 0, fmt.Errorf("Input was not a valid pointer to slice: %+v", i) 45 | } 46 | 47 | /* 48 | getMapKeysVals takes in a map and returns two correspondingly-ordered slices - one containing the 49 | string keys of the map, and the other containing the corresponding values. 50 | */ 51 | func getMapKeysVals(m map[string]interface{}) (keys []string, vals []interface{}, err error) { 52 | // If invalid map, return error now. 53 | if m == nil { 54 | return nil, nil, errors.New("Input map cannot be nil") 55 | } 56 | 57 | // Create keys and vals slices with appropriate length/capacity. 58 | n := len(m) 59 | keys = make([]string, n) 60 | vals = make([]interface{}, n) 61 | 62 | // Iterate through map keys/vals and place into slices. 63 | i := 0 64 | for key, val := range m { 65 | keys[i] = key 66 | vals[i] = val 67 | i++ 68 | } 69 | 70 | // Return final keys/vals with success. 71 | return keys, vals, nil 72 | } 73 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package lore 2 | 3 | import "testing" 4 | 5 | func TestGetPointerSliceLength(t *testing.T) { 6 | // Test invalid resilience. 7 | n := 1 8 | invalids := []interface{}{ 9 | 1, 10 | &n, 11 | "invalid", 12 | } 13 | for _, invalid := range invalids { 14 | _, err := getPointerSliceLength(invalid) 15 | if err == nil { 16 | t.Error("Expect non-nil err since not valid pointer to slice") 17 | return 18 | } 19 | } 20 | 21 | // Test valid lengths. 22 | var tests = []struct { 23 | slice []interface{} 24 | length uint64 25 | }{ 26 | { 27 | slice: []interface{}{}, 28 | length: 0, 29 | }, { 30 | slice: []interface{}{1}, 31 | length: 1, 32 | }, { 33 | slice: []interface{}{1, "test", struct{}{}}, 34 | length: 3, 35 | }, 36 | } 37 | for _, test := range tests { 38 | l, err := getPointerSliceLength(&test.slice) 39 | if err != nil { 40 | t.Error(err) 41 | return 42 | } 43 | if l != test.length { 44 | t.Errorf("Expect length %d, but got %d", test.length, l) 45 | return 46 | } 47 | } 48 | } 49 | --------------------------------------------------------------------------------