@@ -19,13 +19,27 @@ Major additional concepts are:
1919* Marshal rows into structs (with embedded struct support), maps, and slices
2020* Named parameter support including prepared statements
2121* ` Get ` and ` Select ` to go quickly from query to struct/slice
22+ * Bulk insert SQL builder
23+ * Generic functions ` One ` and ` List `
24+ * Range function ` AllRows ` , or for prepared statements use ` All `
2225
2326In addition to the [ godoc API documentation] ( http://godoc.org/github.com/jmoiron/sqlx ) ,
2427there is also some [ user documentation] ( http://jmoiron.github.io/sqlx/ ) that
2528explains how to use ` database/sql ` along with sqlx.
2629
2730## Recent Changes
2831
32+ * Set Go version to 1.23.
33+
34+ * Added ` AllRows ` package function and ` All ` on statement types to
35+ range over rows and automatically close the result set.
36+
37+ * Added functions ` One ` and ` List ` for querying with
38+ generics. Modifications to types Stmt and NamedStmt to support
39+ generics may cause some small compatibility changes that should be
40+ quick to resolve with a search and replace. Both are now type
41+ aliases to attempt to minimize the impact of this change.
42+
2943* fix for sql in-list parsing that will properly ignore question marks
3044 in comments and strings. This frequently caused a confusing error
3145 ("number of bindVars exceeds arguments").
@@ -73,170 +87,64 @@ new changes. Compatibility beyond that is not guaranteed.
7387Versioning is done with Go modules. Breaking changes (eg. removing deprecated API)
7488will get major version number bumps.
7589
76- ## install
90+ ## Install
7791
7892 go get github.com/vinovest/sqlx
7993
80- ## issues
81-
82- Row headers can be ambiguous (` SELECT 1 AS a, 2 AS a ` ), and the result of
83- ` Columns() ` does not fully qualify column names in queries like:
84-
85- ``` sql
86- SELECT a .id , a .name , b .id , b .name FROM foos AS a JOIN foos AS b ON a .parent = b .id ;
87- ```
88-
89- making a struct or map destination ambiguous. Use ` AS ` in your queries
90- to give columns distinct names, ` rows.Scan ` to scan them manually, or
91- ` SliceScan ` to get a slice of results.
94+ ## Usage
9295
93- ## usage
96+ There are several standalone examples in the examples directory that demonstrate various features of sqlx.
9497
95- Below is an example which shows some common use cases for sqlx. Check
96- [ sqlx_test.go] ( https://github.com/vinovest/sqlx/blob/master/sqlx_test.go ) for more
97- usage.
98+ * [ Generics] ( ./examples/generics/main.go ) - query with generics
99+ * [ Bulk inserts] ( ./examples/bulk/main.go ) - efficient bulk inserts
100+ * [ Named queries] ( ./examples/named/main.go ) - named parameter queries
101+ * [ SQL in-lists] ( ./examples/inlist/main.go ) - using ` In ` to generate SQL ` IN ` clauses
102+ * [ Prepared statements] ( ./examples/preparedstatements/main.go ) - prepared statements offer better performance
98103
104+ The simplified form of querying using sqlx looks like the below. Given a struct ` Person ` , querying all of the rows
105+ in the ` person ` table is as simple as:
99106
100107``` go
101108package main
102109
103110import (
104- " database/sql"
105111 " fmt"
106- " log"
107112
108113 _ " github.com/lib/pq"
109114 " github.com/vinovest/sqlx"
110115)
111116
112- var schema = `
113- CREATE TABLE person (
114- first_name text,
115- last_name text,
116- email text
117- );
118-
119- CREATE TABLE place (
120- country text,
121- city text NULL,
122- telcode integer
123- )`
124-
125117type Person struct {
126118 FirstName string ` db:"first_name"`
127119 LastName string ` db:"last_name"`
128120 Email string
129121}
130122
131- type Place struct {
132- Country string
133- City sql.NullString
134- TelCode int
135- }
136-
137123func main () {
138- // this Pings the database trying to connect
139- // use sqlx.Open() for sql.Open() semantics
140- db , err := sqlx.Connect (" postgres" , " user=foo dbname=bar sslmode=disable" )
141- if err != nil {
142- log.Fatalln (err)
143- }
124+ db , _ := sqlx.Connect (" postgres" , " user=foo dbname=bar sslmode=disable" )
125+ people , _ := sqlx.List [Person](db, " SELECT * FROM person" )
126+ fmt.Printf (" People %#v \n " , people)
144127
145- // exec the schema or fail; multi-statement Exec behavior varies between
146- // database drivers; pq will exec them all, sqlite3 won't, ymmv
147- db.MustExec (schema)
148-
149- tx := db.MustBegin ()
150- tx.MustExec (" INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)" , " Jason" , " Moiron" , " jmoiron@jmoiron.net" )
151- tx.MustExec (" INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)" , " John" , " Doe" , " johndoeDNE@gmail.net" )
152- tx.MustExec (" INSERT INTO place (country, city, telcode) VALUES ($1, $2, $3)" , " United States" , " New York" , " 1" )
153- tx.MustExec (" INSERT INTO place (country, telcode) VALUES ($1, $2)" , " Hong Kong" , " 852" )
154- tx.MustExec (" INSERT INTO place (country, telcode) VALUES ($1, $2)" , " Singapore" , " 65" )
155- // Named queries can use structs, so if you have an existing struct (i.e. person := &Person{}) that you have populated, you can pass it in as &person
156- tx.NamedExec (" INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)" , &Person{" Jane" , " Citizen" , " jane.citzen@example.com" })
157- tx.Commit ()
158-
159- // Query the database, storing results in a []Person (wrapped in []interface{})
160- people := []Person{}
161- db.Select (&people, " SELECT * FROM person ORDER BY first_name ASC" )
162- jason , john := people[0 ], people[1 ]
163-
164- fmt.Printf (" %#v \n %#v " , jason, john)
165- // Person{FirstName:"Jason", LastName:"Moiron", Email:"jmoiron@jmoiron.net"}
166- // Person{FirstName:"John", LastName:"Doe", Email:"johndoeDNE@gmail.net"}
167-
168- // You can also get a single result, a la QueryRow
169- jason = Person{}
170- err = db.Get (&jason, " SELECT * FROM person WHERE first_name=$1" , " Jason" )
171- fmt.Printf (" %#v \n " , jason)
172- // Person{FirstName:"Jason", LastName:"Moiron", Email:"jmoiron@jmoiron.net"}
173-
174- // if you have null fields and use SELECT *, you must use sql.Null* in your struct
175- places := []Place{}
176- err = db.Select (&places, " SELECT * FROM place ORDER BY telcode ASC" )
177- if err != nil {
178- fmt.Println (err)
179- return
180- }
181- usa , singsing , honkers := places[0 ], places[1 ], places[2 ]
182-
183- fmt.Printf (" %#v \n %#v \n %#v \n " , usa, singsing, honkers)
184- // Place{Country:"United States", City:sql.NullString{String:"New York", Valid:true}, TelCode:1}
185- // Place{Country:"Singapore", City:sql.NullString{String:"", Valid:false}, TelCode:65}
186- // Place{Country:"Hong Kong", City:sql.NullString{String:"", Valid:false}, TelCode:852}
187-
188- // Loop through rows using only one struct
189- place := Place{}
190- rows , err := db.Queryx (" SELECT * FROM place" )
191- for rows.Next () {
192- err := rows.StructScan (&place)
128+ // alternatively, range over rows
129+ rows , _ := db.Queryx (" select * from person" )
130+ for person , err := range sqlx.AllRows [Person](rows) {
193131 if err != nil {
194- log.Fatalln (err)
195- }
196- fmt.Printf (" %#v \n " , place)
197- }
198- // Place{Country:"United States", City:sql.NullString{String:"New York", Valid:true}, TelCode:1}
199- // Place{Country:"Hong Kong", City:sql.NullString{String:"", Valid:false}, TelCode:852}
200- // Place{Country:"Singapore", City:sql.NullString{String:"", Valid:false}, TelCode:65}
201-
202- // Named queries, using `:name` as the bindvar. Automatic bindvar support
203- // which takes into account the dbtype based on the driverName on sqlx.Open/Connect
204- _, err = db.NamedExec (` INSERT INTO person (first_name,last_name,email) VALUES (:first,:last,:email)` ,
205- map [string ]interface {}{
206- " first" : " Bin" ,
207- " last" : " Smuth" ,
208- " email" : " bensmith@allblacks.nz" ,
209- })
210-
211- // Selects Mr. Smith from the database
212- rows, err = db.NamedQuery (` SELECT * FROM person WHERE first_name=:fn` , map [string ]interface {}{" fn" : " Bin" })
213-
214- // Named queries can also use structs. Their bind names follow the same rules
215- // as the name -> db mapping, so struct fields are lowercased and the `db` tag
216- // is taken into consideration.
217- rows, err = db.NamedQuery (` SELECT * FROM person WHERE first_name=:first_name` , jason)
218-
219-
220- // batch insert
221-
222- // batch insert with structs
223- personStructs := []Person{
224- {FirstName: " Ardie" , LastName: " Savea" , Email: " asavea@ab.co.nz" },
225- {FirstName: " Sonny Bill" , LastName: " Williams" , Email: " sbw@ab.co.nz" },
226- {FirstName: " Ngani" , LastName: " Laumape" , Email: " nlaumape@ab.co.nz" },
132+ panic (err)
133+ }
134+ fmt.Printf (" %#v \n " , person)
227135 }
136+ }
137+ ```
228138
229- _, err = db.NamedExec (` INSERT INTO person (first_name, last_name, email)
230- VALUES (:first_name, :last_name, :email)` , personStructs)
139+ ## Issues
231140
232- // batch insert with maps
233- personMaps := []map [string ]interface {}{
234- {" first_name" : " Ardie" , " last_name" : " Savea" , " email" : " asavea@ab.co.nz" },
235- {" first_name" : " Sonny Bill" , " last_name" : " Williams" , " email" : " sbw@ab.co.nz" },
236- {" first_name" : " Ngani" , " last_name" : " Laumape" , " email" : " nlaumape@ab.co.nz" },
237- }
141+ Row headers can be ambiguous (` SELECT 1 AS a, 2 AS a ` ), and the result of
142+ ` Columns() ` does not fully qualify column names in queries like:
238143
239- _, err = db.NamedExec (` INSERT INTO person (first_name, last_name, email)
240- VALUES (:first_name, :last_name, :email)` , personMaps)
241- }
144+ ``` sql
145+ SELECT a .id , a .name , b .id , b .name FROM foos AS a JOIN foos AS b ON a .parent = b .id ;
242146```
147+
148+ making a struct or map destination ambiguous. Use ` AS ` in your queries
149+ to give columns distinct names, ` rows.Scan ` to scan them manually, or
150+ ` SliceScan ` to get a slice of results.
0 commit comments