mirror of https://github.com/VinGarcia/ksql.git
Update README by moving some specialized sections to the Wiki
parent
be9f686a9a
commit
cf93f7441a
417
README.md
417
README.md
|
@ -7,13 +7,13 @@
|
||||||
KissSQL or the "Keep it Simple" SQL package was created to offer an
|
KissSQL or the "Keep it Simple" SQL package was created to offer an
|
||||||
actually simple and satisfactory tool for interacting with SQL Databases.
|
actually simple and satisfactory tool for interacting with SQL Databases.
|
||||||
|
|
||||||
The core idea on `ksql` is to offer an easy to use interface,
|
The core idea on KSQL is to offer an easy to use interface,
|
||||||
the actual comunication with the database is decoupled so we can use
|
the actual comunication with the database is decoupled so we can use
|
||||||
`ksql` on top of `pgx`, `database/sql` and possibly other tools.
|
KSQL on top of `pgx`, `database/sql` and possibly other tools.
|
||||||
You can even create you own backend adapter for `ksql` which is
|
You can even create you own backend adapter for KSQL which is
|
||||||
useful in some situations.
|
useful in some situations.
|
||||||
|
|
||||||
## Using `ksql`
|
## Using KSQL
|
||||||
|
|
||||||
This is a TLDR version of the more complete examples below.
|
This is a TLDR version of the more complete examples below.
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ func main() {
|
||||||
|
|
||||||
fmt.Println("number of users by type:", count)
|
fmt.Println("number of users by type:", count)
|
||||||
|
|
||||||
// For loading entities from the database `ksql` can build
|
// For loading entities from the database KSQL can build
|
||||||
// the SELECT part of the query for you if you omit it like this:
|
// the SELECT part of the query for you if you omit it like this:
|
||||||
var users []User
|
var users []User
|
||||||
err = db.Query(ctx, &users, "FROM users WHERE type = $1", "admin")
|
err = db.Query(ctx, &users, "FROM users WHERE type = $1", "admin")
|
||||||
|
@ -81,97 +81,15 @@ but work on different databases, they are:
|
||||||
- `ksqlserver.New(ctx, os.Getenv("POSTGRES_URL"), ksql.Config{})` for SQLServer, it works on top of `database/sql`
|
- `ksqlserver.New(ctx, os.Getenv("POSTGRES_URL"), ksql.Config{})` for SQLServer, it works on top of `database/sql`
|
||||||
- `ksqlite3.New(ctx, os.Getenv("POSTGRES_URL"), ksql.Config{})` for SQLite3, it works on top of `database/sql`
|
- `ksqlite3.New(ctx, os.Getenv("POSTGRES_URL"), ksql.Config{})` for SQLite3, it works on top of `database/sql`
|
||||||
|
|
||||||
## Why `ksql`?
|
|
||||||
|
|
||||||
> Note: If you want numbers see our [Benchmark section](https://github.com/vingarcia/ksql#benchmark-comparison) below
|
|
||||||
|
|
||||||
ksql is meant to improve on the existing ecosystem by providing
|
|
||||||
a well-designed database package that has:
|
|
||||||
|
|
||||||
1. A small number of easy-to-use helper functions for common use cases
|
|
||||||
2. Support for more complicated use-cases by allowing
|
|
||||||
the user to write SQL directly
|
|
||||||
|
|
||||||
This strategy allows the API to be
|
|
||||||
|
|
||||||
- Very simple with a small number of functions
|
|
||||||
- Harness all the power of SQL, by just allowing the user to type SQL
|
|
||||||
- Less opportunities for making mistakes, which makes code reviews easier
|
|
||||||
- A succinct and idiomatic Go idiom reducing the cognitive complexity of your code
|
|
||||||
- Easy ways of mocking your database when you need to
|
|
||||||
- Support for all common relational database: `mysql`, `sqlite`, `sqlserver` and `postgres`
|
|
||||||
|
|
||||||
Some special use-cases also have some special support:
|
|
||||||
|
|
||||||
- The `QueryChunks()` method helps you in the few situations when you might
|
|
||||||
need to load in a single query more data than would fit in memory.
|
|
||||||
- For saving you time when you are selecting all fields from a struct you
|
|
||||||
can omit the `SELECT ...` part of the query which causes ksql to write
|
|
||||||
this part for you saving a lot of work when working with big structs/tables.
|
|
||||||
- The Nested Structs feature will help you reuse existing structs/models when working with JOINs.
|
|
||||||
|
|
||||||
**Supported Drivers:**
|
|
||||||
|
|
||||||
ksql is well decoupled from its backend implementation which makes
|
|
||||||
it easy to change the actual technology used, currently we already
|
|
||||||
support the following options:
|
|
||||||
|
|
||||||
- Using the `database/sql` as the backend we support the following drivers:
|
|
||||||
- `"postgres"`
|
|
||||||
- `"sqlite3"`
|
|
||||||
- `"mysql"`
|
|
||||||
- `"sqlserver"`
|
|
||||||
- We also support `pgx` (actually `pgxpool`) as the backend which
|
|
||||||
is a lot faster for Postgres databases.
|
|
||||||
|
|
||||||
If you need a new `database/sql` driver or backend adapter included
|
|
||||||
please open an issue or make your own implementation
|
|
||||||
and submit it as a Pull Request.
|
|
||||||
|
|
||||||
## Comparing `ksql` with other tools
|
|
||||||
|
|
||||||
`ksql` was created because of a few insatisfactions
|
|
||||||
with the existing packages for interacting with
|
|
||||||
relational databases in Go. To mention a few:
|
|
||||||
|
|
||||||
**Low Level Tools:**
|
|
||||||
|
|
||||||
Tools like `database/sql`, `sqlx` and even `pgx` will usually
|
|
||||||
require you to check errors several times for the same query and
|
|
||||||
also when iterating over several rows you end up with a `for rows.Next() {}`
|
|
||||||
loop which is often more cognitive complex than desirable.
|
|
||||||
|
|
||||||
**High Level Tools such as ORMs:**
|
|
||||||
|
|
||||||
More high level tools such as `gorm` and `bun` will often force you
|
|
||||||
and your team to interact with a complicated DSL which requires
|
|
||||||
time to learn it and then ending up still being a little bit harder
|
|
||||||
to read than a regular SQL query would be.
|
|
||||||
|
|
||||||
**Code Generation tools:**
|
|
||||||
|
|
||||||
Tools like `sqlc` and `sqlboiler` that rely on code generation
|
|
||||||
are good options if performance is your main goal, but they also
|
|
||||||
have some issues that might bother you:
|
|
||||||
|
|
||||||
- There is some learning curve that goes beyond just reading a GoDoc as with most packages.
|
|
||||||
- You will often need to copy to and from custom generated structs instead of using your own.
|
|
||||||
- Sometimes the generated function will not be as flexible as you'd prefer forcing you to make
|
|
||||||
some tricks with SQL (e.g. that happens with `sqlc` for partial updates for example).
|
|
||||||
- And it does add an extra step on your building process.
|
|
||||||
|
|
||||||
And finally you might just prefer to avoid codegen when possible,
|
|
||||||
in which case ksql is also for you.
|
|
||||||
|
|
||||||
## Kiss Interface
|
## Kiss Interface
|
||||||
|
|
||||||
The current interface is as follows and we plan on keeping
|
The current interface is as follows and we plan on keeping
|
||||||
it with as little functions as possible, so don't expect many additions:
|
it with as little functions as possible, so don't expect many additions:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Provider describes the ksql public behavior
|
// Provider describes the KSQL public behavior
|
||||||
//
|
//
|
||||||
// The Insert, Patch, Delete and QueryOne functions return ksql.ErrRecordNotFound
|
// The Insert, Patch, Delete and QueryOne functions return `ksql.ErrRecordNotFound`
|
||||||
// if no record was found or no rows were changed during the operation.
|
// if no record was found or no rows were changed during the operation.
|
||||||
type Provider interface {
|
type Provider interface {
|
||||||
Insert(ctx context.Context, table Table, record interface{}) error
|
Insert(ctx context.Context, table Table, record interface{}) error
|
||||||
|
@ -187,9 +105,25 @@ type Provider interface {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage examples
|
## Using KSQL
|
||||||
|
|
||||||
This example is also available [here](./examples/crud/crud.go)
|
In the example below we'll cover all the most common use-cases such as:
|
||||||
|
|
||||||
|
1. Inserting records
|
||||||
|
2. Updating records
|
||||||
|
3. Deleting records
|
||||||
|
4. Querying one or many records
|
||||||
|
5. Making transactions
|
||||||
|
|
||||||
|
More advanced use cases are illustrated on their own pages on [our Wiki](https://github.com/VinGarcia/ksql/wiki):
|
||||||
|
|
||||||
|
- [Querying in Chunks for Big Queries](https://github.com/VinGarcia/ksql/wiki/Querying-in-Chunks-for-Big-Queries)
|
||||||
|
- [Avoiding Code Duplication with the Select Builder](https://github.com/VinGarcia/ksql/wiki/Avoiding-Code-Duplication-with-the-Select-Builder)
|
||||||
|
- [Reusing Existing Structs on Queries with JOINs](https://github.com/VinGarcia/ksql/wiki/Reusing-Existing-Structs-on-Queries-with-JOINs)
|
||||||
|
- [Testing Tools and `ksql.Mock`](https://github.com/VinGarcia/ksql/wiki/Testing-Tools-and-ksql.Mock)
|
||||||
|
|
||||||
|
For the more common use-cases please read the example below,
|
||||||
|
which is also available [here](./examples/crud/crud.go)
|
||||||
if you want to compile it yourself.
|
if you want to compile it yourself.
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
|
@ -228,7 +162,7 @@ type Address struct {
|
||||||
City string `json:"city"`
|
City string `json:"city"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UsersTable informs ksql the name of the table and that it can
|
// UsersTable informs KSQL the name of the table and that it can
|
||||||
// use the default value for the primary key column name: "id"
|
// use the default value for the primary key column name: "id"
|
||||||
var UsersTable = ksql.NewTable("users")
|
var UsersTable = ksql.NewTable("users")
|
||||||
|
|
||||||
|
@ -297,7 +231,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieving Cristina, note that if you omit the SELECT part of the query
|
// Retrieving Cristina, note that if you omit the SELECT part of the query
|
||||||
// ksql will build it for you (efficiently) based on the fields from the struct:
|
// KSQL will build it for you (efficiently) based on the fields from the struct:
|
||||||
var cris User
|
var cris User
|
||||||
err = db.QueryOne(ctx, &cris, "FROM users WHERE name = ? ORDER BY id", "Cristina")
|
err = db.QueryOne(ctx, &cris, "FROM users WHERE name = ? ORDER BY id", "Cristina")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -323,7 +257,7 @@ func main() {
|
||||||
// Partial update technique 2:
|
// Partial update technique 2:
|
||||||
err = db.Patch(ctx, UsersTable, PartialUpdateUser{
|
err = db.Patch(ctx, UsersTable, PartialUpdateUser{
|
||||||
ID: cris.ID,
|
ID: cris.ID,
|
||||||
Age: nullable.Int(28),
|
Age: nullable.Int(28), // (just a pointer to an int, if null it won't be updated)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
|
@ -374,305 +308,18 @@ func main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Query Chunks Feature
|
|
||||||
|
|
||||||
It's very unsual for us to need to load a number of records from the
|
|
||||||
database that might be too big for fitting in memory, e.g. load all the
|
|
||||||
users and send them somewhere. But it might happen.
|
|
||||||
|
|
||||||
For these cases it's best to load chunks of data at a time so
|
|
||||||
that we can work on a substantial amount of data at a time and never
|
|
||||||
overload our memory capacity. For this use case we have a specific
|
|
||||||
function called `QueryChunks`:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
err = db.QueryChunks(ctx, ksql.ChunkParser{
|
|
||||||
Query: "SELECT * FROM users WHERE type = ?",
|
|
||||||
Params: []interface{}{usersType},
|
|
||||||
ChunkSize: 100,
|
|
||||||
ForEachChunk: func(users []User) error {
|
|
||||||
err := sendUsersSomewhere(users)
|
|
||||||
if err != nil {
|
|
||||||
// This will abort the QueryChunks loop and return this error
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
It's signature is more complicated than the other two Query\* methods,
|
|
||||||
thus, it is adivisible to always prefer using the other two when possible
|
|
||||||
reserving this one for the rare use-case where you are actually
|
|
||||||
loading big sections of the database into memory.
|
|
||||||
|
|
||||||
## Select Generator Feature
|
|
||||||
|
|
||||||
There are good reasons not to use `SELECT *` queries the most important
|
|
||||||
of them is that you might end up loading more information than you are actually
|
|
||||||
going to use putting more pressure in your database for no good reason.
|
|
||||||
|
|
||||||
To prevent that `ksql` has a feature specifically for building the `SELECT`
|
|
||||||
part of the query using the tags from the input struct.
|
|
||||||
Using it is very simple and it works with all the 3 Query\* functions:
|
|
||||||
|
|
||||||
Querying a single user:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
var user User
|
|
||||||
err = db.QueryOne(ctx, &user, "FROM users WHERE id = ?", userID)
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Querying a page of users:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
var users []User
|
|
||||||
err = db.Query(ctx, &users, "FROM users WHERE type = ? ORDER BY id LIMIT ? OFFSET ?", "Cristina", limit, offset)
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Querying all the users, or any potentially big number of users, from the database (not usual, but supported):
|
|
||||||
|
|
||||||
```golang
|
|
||||||
err = db.QueryChunks(ctx, ksql.ChunkParser{
|
|
||||||
Query: "FROM users WHERE type = ?",
|
|
||||||
Params: []interface{}{usersType},
|
|
||||||
ChunkSize: 100,
|
|
||||||
ForEachChunk: func(users []User) error {
|
|
||||||
err := sendUsersSomewhere(users)
|
|
||||||
if err != nil {
|
|
||||||
// This will abort the QueryChunks loop and return this error
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The implementation of this feature is actually simple internally.
|
|
||||||
First we check if the query is starting with the word `FROM`,
|
|
||||||
if it is then we just get the `ksql` tags from the struct and
|
|
||||||
then use it for building the `SELECT` statement.
|
|
||||||
|
|
||||||
The `SELECT` statement is then cached so we don't have to build it again
|
|
||||||
the next time in order to keep the library efficient even when
|
|
||||||
using this feature.
|
|
||||||
|
|
||||||
## Select Generation with Joins
|
|
||||||
|
|
||||||
So there is one use-case that was not covered by `ksql` so far:
|
|
||||||
|
|
||||||
What if you want to JOIN multiple tables for which you already have
|
|
||||||
structs defined? Would you need to create a new struct to represent
|
|
||||||
the joined columns of the two tables? no, we actually have this covered as well.
|
|
||||||
|
|
||||||
`ksql` has a special feature for allowing the reuse of existing
|
|
||||||
structs by using composition in an anonymous struct, and then
|
|
||||||
generating the `SELECT` part of the query accordingly:
|
|
||||||
|
|
||||||
Querying a single joined row:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
var row struct{
|
|
||||||
User User `tablename:"u"` // (here the tablename must match the aliased tablename in the query)
|
|
||||||
Post Post `tablename:"posts"` // (if no alias is used you should use the actual name of the table)
|
|
||||||
}
|
|
||||||
err = db.QueryOne(ctx, &row, "FROM users as u JOIN posts ON u.id = posts.user_id WHERE u.id = ?", userID)
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Querying a page of joined rows:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
var rows []struct{
|
|
||||||
User User `tablename:"u"`
|
|
||||||
Post Post `tablename:"p"`
|
|
||||||
}
|
|
||||||
err = db.Query(ctx, &rows,
|
|
||||||
"FROM users as u JOIN posts as p ON u.id = p.user_id WHERE name = ? LIMIT ? OFFSET ?",
|
|
||||||
"Cristina", limit, offset,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Querying all the users, or any potentially big number of users, from the database (not usual, but supported):
|
|
||||||
|
|
||||||
```golang
|
|
||||||
err = db.QueryChunks(ctx, ksql.ChunkParser{
|
|
||||||
Query: "FROM users as u JOIN posts as p ON u.id = p.user_id WHERE type = ?",
|
|
||||||
Params: []interface{}{usersType},
|
|
||||||
ChunkSize: 100,
|
|
||||||
ForEachChunk: func(rows []struct{
|
|
||||||
User User `tablename:"u"`
|
|
||||||
Post Post `tablename:"p"`
|
|
||||||
}) error {
|
|
||||||
err := sendRowsSomewhere(rows)
|
|
||||||
if err != nil {
|
|
||||||
// This will abort the QueryChunks loop and return this error
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
As advanced as this feature might seem we don't do any parsing of the query,
|
|
||||||
and all the work is done only once and then cached.
|
|
||||||
|
|
||||||
What actually happens is that we use the "tablename" tag to build the `SELECT`
|
|
||||||
part of the query like this:
|
|
||||||
|
|
||||||
- `SELECT u.id, u.name, u.age, p.id, p.title `
|
|
||||||
|
|
||||||
This is then cached, and when we need it again we concatenate it with the rest
|
|
||||||
of the query.
|
|
||||||
|
|
||||||
This feature has two important limitations:
|
|
||||||
|
|
||||||
1. It is not possible to use `tablename` tags together with normal `ksql` tags.
|
|
||||||
Doing so will cause the `tablename` tags to be ignored in favor of the `ksql` ones.
|
|
||||||
2. It is not possible to use it without omitting the `SELECT` part of the query.
|
|
||||||
While in normal queries we match the selected field with the attribute by name,
|
|
||||||
in queries joining multiple tables we can't use this strategy because
|
|
||||||
different tables might have columns with the same name, and we don't
|
|
||||||
really have access to the full name of these columns making, for example,
|
|
||||||
it impossible to differentiate between `u.id` and `p.id` except by the
|
|
||||||
order in which these fields were passed. Thus, it is necessary that
|
|
||||||
the library itself writes the `SELECT` part of the query when using
|
|
||||||
this technique so that we can control the order or the selected fields.
|
|
||||||
|
|
||||||
Ok, but what if I don't want to use this feature?
|
|
||||||
|
|
||||||
You are not forced to, and there are a few use-cases where you would prefer not to, e.g.:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
var rows []struct{
|
|
||||||
UserName string `ksql:"name"`
|
|
||||||
PostTitle string `ksql:"title"`
|
|
||||||
}
|
|
||||||
err := db.Query(ctx, &rows, "SELECT u.name, p.title FROM users u JOIN posts p ON u.id = p.user_id LIMIT 10")
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In the example above, since we are only interested in a couple of columns it
|
|
||||||
is far simpler and more efficient for the database to only select the columns
|
|
||||||
that we actually care about, so it's better not to use composite structs.
|
|
||||||
|
|
||||||
## Testing Examples & ksql.Mock
|
|
||||||
|
|
||||||
`ksql.Mock` is a simple mock that is available out of the box for the `ksql.Provider` interface.
|
|
||||||
|
|
||||||
For each of the methods available on the interface this Mock has a function attribute with the same
|
|
||||||
function signature and with the same name but with an `Fn` in the end of the name.
|
|
||||||
|
|
||||||
For instantiating this mock and at the same time mocking one of the functions all you need to do is
|
|
||||||
this:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
var capturedRecord interface{}
|
|
||||||
var capturedQuery string
|
|
||||||
var capturedParams []interface{}
|
|
||||||
mockDB := ksql.Mock{
|
|
||||||
QueryOneFn: func(ctx context.Context, record interface{}, query string, params ...interface{}) error {
|
|
||||||
capturedRecord = record
|
|
||||||
capturedQuery = query
|
|
||||||
capturedParams = params
|
|
||||||
|
|
||||||
// For simulating an error you would do this:
|
|
||||||
return fmt.Errorf("some fake error")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var user User
|
|
||||||
err := GetUser(db, &user, otherArgs)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
assert.Equal(t, user, capturedRecord)
|
|
||||||
assert.Equal(t, `SELECT * FROM user WHERE other_args=$1`, capturedQuery)
|
|
||||||
assert.Equal(t, []interface{}{otherArgs}, capturedParams)
|
|
||||||
```
|
|
||||||
|
|
||||||
For different types of functions you might need do some tricks with structs to make it easier
|
|
||||||
to write the tests such as:
|
|
||||||
|
|
||||||
Converting a struct to something that is more easy to assert like a `map[string]interface{}`
|
|
||||||
or filling a struct with fake data from the database, for these situations the library provides
|
|
||||||
the following functions:
|
|
||||||
|
|
||||||
- `ksqltest.FillStructWith(struct interface{}, dbRow map[string]interface{}) error`
|
|
||||||
- `ksqltest.FillSliceWith(structSlice interface{}, dbRows []map[string]interface{}) error`
|
|
||||||
- `ksqltest.StructToMap(struct interface{}) (map[string]interface{}, error)`
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
createdAt := time.Now().Add(10*time.Hour)
|
|
||||||
mockDB := ksql.Mock{
|
|
||||||
QueryOneFn: func(ctx context.Context, record interface{}, query string, params ...interface{}) error {
|
|
||||||
// For simulating a succesful scenario you can just fillup the struct:
|
|
||||||
return ksqltest.FillStructWith(record, map[string]interface{}{
|
|
||||||
"id": 42,
|
|
||||||
"name": "fake-name",
|
|
||||||
"age": 32,
|
|
||||||
"created_at": createdAt,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var user User
|
|
||||||
err := GetUser(db, &user, otherArgs)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, user, User{
|
|
||||||
ID: 42,
|
|
||||||
Name: "fake-name",
|
|
||||||
Age: 32,
|
|
||||||
CreatedAt: createdAt,
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
And for running tests on the `QueryChunks` function which is a particularly complex function
|
|
||||||
we also have this test helper:
|
|
||||||
|
|
||||||
- `ksqltest.CallFunctionWithRows(fn interface{}, rows []map[string]interface{}) (map[string]interface{}, error)`
|
|
||||||
|
|
||||||
If you want to see examples (we have examples for all the public functions) just
|
|
||||||
read the example tests available on our [example service](./examples/example_service/).
|
|
||||||
|
|
||||||
Please note that that in the example service above we have two sets
|
|
||||||
of tests in two different files, exemplifying how to use `gomock` and
|
|
||||||
then how to use `ksql.Mock{}`.
|
|
||||||
|
|
||||||
## Benchmark Comparison
|
## Benchmark Comparison
|
||||||
|
|
||||||
The results of the benchmark are good:
|
The results of the benchmark are good:
|
||||||
they show that ksql is in practical terms,
|
they show that KSQL is in practical terms,
|
||||||
as fast as sqlx which was our goal from the start.
|
as fast as sqlx which was our goal from the start.
|
||||||
|
|
||||||
To understand the benchmark below you must know
|
To understand the benchmark below you must know
|
||||||
that all tests are performed using Postgres 12.1 and
|
that all tests are performed using Postgres 12.1 and
|
||||||
that we are comparing the following tools:
|
that we are comparing the following tools:
|
||||||
|
|
||||||
- ksql using the adapter that wraps `database/sql`
|
- KSQL using the adapter that wraps `database/sql`
|
||||||
- ksql using the adapter that wraps `pgx`
|
- KSQL using the adapter that wraps `pgx`
|
||||||
- `database/sql`
|
- `database/sql`
|
||||||
- `sqlx`
|
- `sqlx`
|
||||||
- `pgx` (with `pgxpool`)
|
- `pgx` (with `pgxpool`)
|
||||||
|
@ -732,7 +379,7 @@ Benchmark executed at: 2022-05-31
|
||||||
Benchmark executed on commit: ed0327babe06a657b2348d2e9d5e5ea824a71fc0
|
Benchmark executed on commit: ed0327babe06a657b2348d2e9d5e5ea824a71fc0
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running the ksql tests (for contributors)
|
## Running the KSQL tests (for contributors)
|
||||||
|
|
||||||
The tests use `docker-test` for setting up all the supported databases,
|
The tests use `docker-test` for setting up all the supported databases,
|
||||||
which means that:
|
which means that:
|
||||||
|
|
|
@ -128,7 +128,7 @@ func main() {
|
||||||
// Partial update technique 2:
|
// Partial update technique 2:
|
||||||
err = db.Patch(ctx, UsersTable, PartialUpdateUser{
|
err = db.Patch(ctx, UsersTable, PartialUpdateUser{
|
||||||
ID: cris.ID,
|
ID: cris.ID,
|
||||||
Age: nullable.Int(28),
|
Age: nullable.Int(28), // (just a pointer to an int, if null it won't be updated)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
|
|
16
ksql.go
16
ksql.go
|
@ -25,9 +25,9 @@ func initializeQueryCache() map[string]map[reflect.Type]string {
|
||||||
return cache
|
return cache
|
||||||
}
|
}
|
||||||
|
|
||||||
// DB represents the ksql client responsible for
|
// DB represents the KSQL client responsible for
|
||||||
// interfacing with the "database/sql" package implementing
|
// interfacing with the "database/sql" package implementing
|
||||||
// the KissSQL interface `ksql.Provider`.
|
// the KSQL interface `ksql.Provider`.
|
||||||
type DB struct {
|
type DB struct {
|
||||||
driver string
|
driver string
|
||||||
dialect Dialect
|
dialect Dialect
|
||||||
|
@ -36,9 +36,9 @@ type DB struct {
|
||||||
|
|
||||||
// DBAdapter is minimalistic interface to decouple our implementation
|
// DBAdapter is minimalistic interface to decouple our implementation
|
||||||
// from database/sql, i.e. if any struct implements the functions below
|
// from database/sql, i.e. if any struct implements the functions below
|
||||||
// with the exact same semantic as the sql package it will work with ksql.
|
// with the exact same semantic as the sql package it will work with KSQL.
|
||||||
//
|
//
|
||||||
// To create a new client using this adapter use ksql.NewWithAdapter()
|
// To create a new client using this adapter use `ksql.NewWithAdapter()`
|
||||||
type DBAdapter interface {
|
type DBAdapter interface {
|
||||||
ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
|
ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
|
||||||
QueryContext(ctx context.Context, query string, args ...interface{}) (Rows, error)
|
QueryContext(ctx context.Context, query string, args ...interface{}) (Rows, error)
|
||||||
|
@ -74,7 +74,7 @@ type Tx interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config describes the optional arguments accepted
|
// Config describes the optional arguments accepted
|
||||||
// by the ksql.New() function.
|
// by the `ksql.New()` function.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// MaxOpenCons defaults to 1 if not set
|
// MaxOpenCons defaults to 1 if not set
|
||||||
MaxOpenConns int
|
MaxOpenConns int
|
||||||
|
@ -528,13 +528,13 @@ func assertStructPtr(t reflect.Type) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes one record from the database using the ID or IDs
|
// Delete deletes one record from the database using the ID or IDs
|
||||||
// defined on the ksql.Table passed as second argument.
|
// defined on the `ksql.Table` passed as second argument.
|
||||||
//
|
//
|
||||||
// For tables with a single ID column you can pass the record
|
// For tables with a single ID column you can pass the record
|
||||||
// to be deleted as a struct, as a map or just pass the ID itself.
|
// to be deleted as a struct, as a map or just pass the ID itself.
|
||||||
//
|
//
|
||||||
// For tables with composite keys you must pass the record
|
// For tables with composite keys you must pass the record
|
||||||
// as a struct or a map so that ksql can read all the composite keys
|
// as a struct or a map so that KSQL can read all the composite keys
|
||||||
// from it.
|
// from it.
|
||||||
//
|
//
|
||||||
// The examples below should work for both types of tables:
|
// The examples below should work for both types of tables:
|
||||||
|
@ -944,7 +944,7 @@ func scanRowsFromType(
|
||||||
if info.IsNestedStruct {
|
if info.IsNestedStruct {
|
||||||
// This version is positional meaning that it expect the arguments
|
// This version is positional meaning that it expect the arguments
|
||||||
// to follow an specific order. It's ok because we don't allow the
|
// to follow an specific order. It's ok because we don't allow the
|
||||||
// user to type the "SELECT" part of the query for nested ksqltest.
|
// user to type the "SELECT" part of the query for nested structs.
|
||||||
scanArgs, err = getScanArgsForNestedStructs(dialect, rows, t, v, info)
|
scanArgs, err = getScanArgsForNestedStructs(dialect, rows, t, v, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
Loading…
Reference in New Issue