forked from go-ozzo/ozzo-dbx
-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
316 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,9 @@ ozzo-dbx is a Go package that enhances the standard `database/sql` package by pr | |
as well as DB-agnostic query building capabilities. It has the following features: | ||
|
||
* Populating data into structs and NullString maps | ||
* DB-agnostic query building, including SELECT queries, data manipulation queries, and schema manipulation queries | ||
* Named parameter binding | ||
* DB-agnostic query building methods, including SELECT queries, data manipulation queries, and schema manipulation queries | ||
* Powerful query condition building | ||
* Open architecture allowing addition of new database support or customization of existing support | ||
* Logging executed SQL statements | ||
* Supporting major relational databases | ||
|
@@ -160,13 +162,14 @@ which will execute the query and populate the result into the specified variable | |
For example, | ||
|
||
```go | ||
q := db.NewQuery("SELECT id, name FROM users LIMIT 10") | ||
type User struct { | ||
ID int | ||
Name string | ||
} | ||
|
||
var ( | ||
users []struct { | ||
ID int | ||
Name string | ||
} | ||
users []User | ||
user User | ||
|
||
row dbx.NullStringMap | ||
|
||
|
@@ -176,11 +179,19 @@ var ( | |
err error | ||
) | ||
|
||
// populate all rows into users struct slice | ||
q := db.NewQuery("SELECT id, name FROM users LIMIT 10") | ||
|
||
// populate all rows into a User slice | ||
err = q.All(&users) | ||
fmt.Println(users[0].ID, users[0].Name) | ||
|
||
// populate the first row into a User struct | ||
err = q.One(&user) | ||
fmt.Println(user.ID, user.Name) | ||
|
||
// populate the first row into a NullString map | ||
err = q.One(&row) | ||
fmt.Println(row["id"], row["name"]) | ||
|
||
// populate the first row into id and name | ||
err = q.Row(&id, &name) | ||
|
@@ -192,46 +203,233 @@ for rows.Next() { | |
} | ||
``` | ||
|
||
When populating a struct, the data of a resulting column will be populated to an exported struct field if the name | ||
of the field maps to that of the column according to the field mapping function specified by `Query.FieldMapper`. | ||
For example, | ||
|
||
If a resulting column does not have a corresponding struct field, it will be skipped without error. The default | ||
field mapping function separates words in a field name by underscores and turns them into lower case. For example, | ||
a field name `FirstName` will be mapped to the column name `first_name`, and `MyID` to `my_id`. | ||
When populating a struct, the following rules are used to determine which columns should go into which struct fields: | ||
|
||
If a field has a `db` tag, the tag name will be used as the corresponding column name. | ||
* Only exported struct fields can be populated. | ||
* A field receives data if its name is mapped to a column according to the field mapping function `Query.FieldMapper`. | ||
The default field mapping function separates words in a field name by underscores and turns them into lower case. | ||
For example, a field name `FirstName` will be mapped to the column name `first_name`, and `MyID` to `my_id`. | ||
* If a field has a `db` tag, the tag value will be used as the corresponding column name. If the `db` tag is a dash `-`, | ||
it means the field should NOT be populated. | ||
* For anonymous fields that are of struct type, they will be expanded and their component fields will be populated | ||
according to the rules described above. | ||
* For named fields that are of struct type, they will also be expanded. But their component fields will be prefixed | ||
with the struct names when being populated. | ||
|
||
Note that only exported fields can be populated with data. Unexported fields are ignored. Anonymous struct fields | ||
will be expanded and populated according to the above rules. Named struct fields will also be expanded, but their | ||
sub-fields will be prefixed with the struct names. In the following example, the `User` type can be used to populate | ||
data from a column named `prof.age`. If the `Profile` field is anonymous, it would be able to receive data from | ||
from a column named `age` without the prefix `prof.`. | ||
The following example shows how fields are populated according to the rules above: | ||
|
||
```go | ||
type Profile struct { | ||
Age int | ||
type User struct { | ||
id int | ||
Type int `db:"-"` | ||
MyName string `db:"name"` | ||
Prof Profile | ||
} | ||
|
||
type User struct { | ||
Prof Profile | ||
type Profile struct { | ||
Age int | ||
} | ||
``` | ||
|
||
* `User.id`: not populated because the `db` tag is `-`; | ||
* `User.Type`: not populated because the field is not exported; | ||
* `User.MyName`: to be populated from the `name` column, according to the `db` tag; | ||
* `Profile.Age`: to be populated from the `prof.age` column, since `Prof` is a named field of struct type | ||
and its fields will be prefixed with `prof.`. | ||
|
||
Note that if a column in the result does not have a corresponding struct field, it will be ignored. Similarly, | ||
if a struct field does not have a corresponding column in the result, it will not be populated. | ||
|
||
## Binding Parameters | ||
|
||
## Building SELECT Queries | ||
A SQL statement is usually parameterized with dynamic values. For example, you may want to select the user record | ||
according to the user ID received from the client. Parameter binding should be used in this case, and it is almost | ||
always preferred for security reason. Unlike `database/sql` which does anonymous parameter binding, `ozzo-dbx` uses | ||
named parameter binding. For example, | ||
|
||
```go | ||
q := db.NewQuery("SELECT id, name FROM users WHERE id={:id}") | ||
q.Bind(dbx.Params{"id": 100}) | ||
q.One(&user) | ||
``` | ||
|
||
The above example will select the user record whose `id` is 100. The method `Query.Bind()` binds a set | ||
of named parameters to a SQL statement which contains parameter placeholders in the format of `{:ParamName}`. | ||
|
||
If a SQL statement needs to be executed multiple times with different parameter values, it should be prepared | ||
to improve the performance. For example, | ||
|
||
```go | ||
q := db.NewQuery("SELECT id, name FROM users WHERE id={:id}") | ||
q.Prepare() | ||
|
||
q.Bind(dbx.Params{"id": 100}) | ||
q.One(&user) | ||
|
||
q.Bind(dbx.Params{"id": 200}) | ||
q.One(&user) | ||
|
||
// ... | ||
``` | ||
|
||
Note that anonymous parameter binding is not supported as it will mess up with named parameters. | ||
|
||
## Building Queries | ||
|
||
Instead of writing plain SQLs, `ozzo-dbx` allows you to build SQLs programmatically, which often leads to cleaner, | ||
more secure, and DB-agnostic code. You can build three types of queries: the SELECT queries, the data manipulation | ||
queries, and the schema manipulation queries. | ||
|
||
### Building SELECT Queries | ||
|
||
Building a SELECT query starts by calling `DB.Select()`. You can build different clauses of a SELECT query using | ||
the corresponding query building methods. For example, | ||
|
||
```go | ||
db, _ := dbx.Open("mysql", "user:pass@/example") | ||
db.Select("id", "name"). | ||
From("users"). | ||
Where(dbx.HashExp{"id": 100}). | ||
One(&user) | ||
``` | ||
|
||
The above code will generate and execute the following SQL statement: | ||
|
||
```sql | ||
SELECT `id`, `name` | ||
FROM `users` | ||
WHERE `id`={:p0} | ||
``` | ||
|
||
## Building Data Manipulation Queries | ||
Notice how the table and column names are properly quoted according to the currently using database type. | ||
And parameter binding is used to populate the value of `p0` in the `WHERE` clause. | ||
|
||
`dbx-ozzo` supports very flexible and powerful query condition building which can be used to build SQL clauses | ||
such as `WHERE`, `HAVING`, etc. For example, | ||
|
||
```go | ||
// id=100 | ||
dbx.NewExp("id={:id}", Params{"id": 100}) | ||
|
||
// id=100 AND status=1 | ||
dbx.HashExp{"id": 100, "status": 1} | ||
|
||
// status=1 OR age>30 | ||
dbx.Or(dbx.HashExp{"status": 1}, dbx.NewExp("age>30")) | ||
|
||
// name LIKE '%admin%' AND name LIKE '%example%' | ||
dbx.Like("name", "admin", "example") | ||
``` | ||
|
||
### Building Data Manipulation Queries | ||
|
||
Data manipulation queries are those changing the data in the database, such as INSERT, UPDATE, DELETE statements. | ||
Such queries can be built by calling the corresponding methods of `DB`. For example, | ||
|
||
```go | ||
db, _ := dbx.Open("mysql", "user:pass@/example") | ||
|
||
## Building Schema Manipulation Queries | ||
// INSERT INTO `users` (`name`, `email`) VALUES ({:p0}, {:p1}) | ||
db.Insert("users", dbx.Params{ | ||
"name": "James", | ||
"email": "[email protected]", | ||
}).Execute() | ||
|
||
// UPDATE `users` SET `status`={:p0} WHERE `id`={:p1} | ||
db.Update("users", dbx.Params{"status": 1}, dbx.HashExp{"id": 100}).Execute() | ||
|
||
// DELETE FROM `users` WHERE `status`={:p0} | ||
db.Delete("users", dbx.HashExp{"status": 2}).Execute() | ||
``` | ||
|
||
When building data manipulation queries, remember to call `Execute()` at the end to execute the queries. | ||
|
||
### Building Schema Manipulation Queries | ||
|
||
Schema manipulation queries are those changing the database schema, such as creating a new table, adding a new column. | ||
These queries can be built by calling the corresponding methods of `DB`. For example, | ||
|
||
```go | ||
db, _ := dbx.Open("mysql", "user:pass@/example") | ||
|
||
// CREATE TABLE `users` (`id` int primary key, `name` varchar(255)) | ||
q := db.CreateTable("users", map[string]string{ | ||
"id": "int primary key", | ||
"name": "varchar(255)", | ||
}) | ||
q.Execute() | ||
``` | ||
|
||
## Using Transactions | ||
|
||
## Handling Errors | ||
You can use all aforementioned query execution and building methods with transaction. For example, | ||
|
||
```go | ||
db, _ := dbx.Open("mysql", "user:pass@/example") | ||
|
||
tx, _ := db.Begin() | ||
|
||
_, err1 := tx.Insert("users", dbx.Params{ | ||
"name": "user1", | ||
}).Execute() | ||
_, err2 := tx.Insert("users", dbx.Params{ | ||
"name": "user2", | ||
}).Execute() | ||
|
||
if err1 == nil && err2 == nil { | ||
tx.Commit() | ||
} else { | ||
tx.Rollback() | ||
} | ||
``` | ||
|
||
## Logging Executed SQL Statements | ||
|
||
When `DB.LogFunc` is configured with a compatible log function, all SQL statements being executed will be logged. | ||
The following example shows how to configure the logger using the standard `log` package: | ||
|
||
```go | ||
import ( | ||
"fmt" | ||
"log" | ||
"github.com/go-ozzo/ozzo-dbx" | ||
) | ||
|
||
func main() { | ||
db, _ := dbx.Open("mysql", "user:pass@/example") | ||
db.LogFunc = log.Printf | ||
|
||
// ... | ||
) | ||
``` | ||
And the following example shows how to use the `ozzo-log` package which allows logging message severities and categories: | ||
```go | ||
import ( | ||
"fmt" | ||
"github.com/go-ozzo/ozzo-dbx" | ||
"github.com/go-ozzo/ozzo-log" | ||
) | ||
|
||
func main() { | ||
logger := log.NewLogger() | ||
logger.Targets = []log.Target{log.NewConsoleTarget()} | ||
logger.Open() | ||
|
||
db, _ := dbx.Open("mysql", "user:pass@/example") | ||
db.LogFunc = logger.Info | ||
|
||
// ... | ||
) | ||
``` | ||
## Supporting New Databases | ||
While `ozzo-dbx` provides out-of-box support for most major relational databases, its open architecture | ||
allows you to add support for new databases. The effort of adding support for a new database involves: | ||
* Create a struct that implements the `QueryBuilder` interface. You may use `BaseQueryBuilder` directly or extend it | ||
via composition. | ||
* Create a struct that implements the `Builder` interface. You may extend `BaseBuilder` via composition. | ||
* Write an `init()` function to register the new builder in `dbx.BuilderFuncMap`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.