mirror of
1
Fork 0

Merge remote-tracking branch 'upstream/main' into update/sqlite-library

Signed-off-by: kim (grufwub) <grufwub@gmail.com>
This commit is contained in:
kim (grufwub) 2021-09-08 21:13:54 +01:00
commit 6a2d0d9508
33 changed files with 444 additions and 323 deletions

2
go.mod
View File

@ -47,7 +47,7 @@ require (
github.com/superseriousbusiness/oauth2/v4 v4.3.0-SSB github.com/superseriousbusiness/oauth2/v4 v4.3.0-SSB
github.com/tdewolff/minify/v2 v2.9.21 github.com/tdewolff/minify/v2 v2.9.21
github.com/tidwall/buntdb v1.2.4 // indirect github.com/tidwall/buntdb v1.2.4 // indirect
github.com/uptrace/bun v0.4.3 github.com/uptrace/bun v1.0.4
github.com/uptrace/bun/dialect/pgdialect v0.4.3 github.com/uptrace/bun/dialect/pgdialect v0.4.3
github.com/uptrace/bun/dialect/sqlitedialect v0.4.3 github.com/uptrace/bun/dialect/sqlitedialect v0.4.3
github.com/urfave/cli/v2 v2.3.0 github.com/urfave/cli/v2 v2.3.0

3
go.sum
View File

@ -454,8 +454,9 @@ github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
github.com/uptrace/bun v0.4.3 h1:x6bjDqwjxwM/9Q1eauhkznuvTrz/rLiCK2p4tT63sAE=
github.com/uptrace/bun v0.4.3/go.mod h1:aL6D9vPw8DXaTQTwGrEPtUderBYXx7ShUmPfnxnqscw= github.com/uptrace/bun v0.4.3/go.mod h1:aL6D9vPw8DXaTQTwGrEPtUderBYXx7ShUmPfnxnqscw=
github.com/uptrace/bun v1.0.4 h1:XKkddp+F5rbjyZCfEXPHc9ZEG3RE8VktO4HCcg5nzCQ=
github.com/uptrace/bun v1.0.4/go.mod h1:aL6D9vPw8DXaTQTwGrEPtUderBYXx7ShUmPfnxnqscw=
github.com/uptrace/bun/dialect/pgdialect v0.4.3 h1:lM2IUKpU99110chKkupw3oTfXiOKpB0hTJIe6frqQDo= github.com/uptrace/bun/dialect/pgdialect v0.4.3 h1:lM2IUKpU99110chKkupw3oTfXiOKpB0hTJIe6frqQDo=
github.com/uptrace/bun/dialect/pgdialect v0.4.3/go.mod h1:BaNvWejl32oKUhwpFkw/eNcWldzIlVY4nfw/sNul0s8= github.com/uptrace/bun/dialect/pgdialect v0.4.3/go.mod h1:BaNvWejl32oKUhwpFkw/eNcWldzIlVY4nfw/sNul0s8=
github.com/uptrace/bun/dialect/sqlitedialect v0.4.3 h1:h+vqLGCeY22PFrbCOpQqK5+/p1qWCXYIhIUm/D5Vw08= github.com/uptrace/bun/dialect/sqlitedialect v0.4.3 h1:h+vqLGCeY22PFrbCOpQqK5+/p1qWCXYIhIUm/D5Vw08=

View File

@ -1,5 +1,30 @@
# Changelog # Changelog
## v1.0.4 - Sep 06 2021
- Added support for MariaDB.
- Restored default `SET` for `ON CONFLICT DO UPDATE` queries.
## v1.0.3 - Sep 06 2021
- Fixed bulk soft deletes.
- pgdialect: fixed scanning into an array pointer.
## v1.0.2 - Sep 04 2021
- Changed to completely ignore fields marked with `bun:"-"`. If you want to be able to scan into
such columns, use `bun:",scanonly"`.
- pgdriver: fixed SASL authentication handling.
## v1.0.1 - Sep 02 2021
- pgdriver: added erroneous zero writes retry.
- Improved column handling in Relation callback.
## v1.0.0 - Sep 01 2021
- First stable release.
## v0.4.1 - Aug 18 2021 ## v0.4.1 - Aug 18 2021
- Fixed migrate package to properly rollback migrations. - Fixed migrate package to properly rollback migrations.

View File

@ -1,4 +1,13 @@
# Releasing ## Running tests
To run tests, you need Docker which starts PostgreSQL and MySQL servers:
```shell
cd internal/dbtest
./test.sh
```
## Releasing
1. Run `release.sh` script which updates versions in go.mod files and pushes a new branch to GitHub: 1. Run `release.sh` script which updates versions in go.mod files and pushes a new branch to GitHub:

View File

@ -4,17 +4,21 @@
</a> </a>
</p> </p>
# Simple and performant SQL database client # Simple and performant client for PostgreSQL, MySQL, and SQLite
[![build workflow](https://github.com/uptrace/bun/actions/workflows/build.yml/badge.svg)](https://github.com/uptrace/bun/actions) [![build workflow](https://github.com/uptrace/bun/actions/workflows/build.yml/badge.svg)](https://github.com/uptrace/bun/actions)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/uptrace/bun)](https://pkg.go.dev/github.com/uptrace/bun) [![PkgGoDev](https://pkg.go.dev/badge/github.com/uptrace/bun)](https://pkg.go.dev/github.com/uptrace/bun)
[![Documentation](https://img.shields.io/badge/bun-documentation-informational)](https://bun.uptrace.dev/) [![Documentation](https://img.shields.io/badge/bun-documentation-informational)](https://bun.uptrace.dev/)
[![Chat](https://discordapp.com/api/guilds/752070105847955518/widget.png)](https://discord.gg/rWtp5Aj) [![Chat](https://discordapp.com/api/guilds/752070105847955518/widget.png)](https://discord.gg/rWtp5Aj)
**Status**: API freeze (stable release). Note that all sub-packages (mainly extra/\* packages) are
not part of the API freeze and are developed independently. You can think of them as 3-rd party
packages that share one repo with the core.
Main features are: Main features are:
- Works with [PostgreSQL](https://bun.uptrace.dev/guide/drivers.html#postgresql), - Works with [PostgreSQL](https://bun.uptrace.dev/guide/drivers.html#postgresql),
[MySQL](https://bun.uptrace.dev/guide/drivers.html#mysql), [MySQL](https://bun.uptrace.dev/guide/drivers.html#mysql) (including MariaDB),
[SQLite](https://bun.uptrace.dev/guide/drivers.html#sqlite). [SQLite](https://bun.uptrace.dev/guide/drivers.html#sqlite).
- [Selecting](/example/basic/) into a map, struct, slice of maps/structs/vars. - [Selecting](/example/basic/) into a map, struct, slice of maps/structs/vars.
- [Bulk inserts](https://bun.uptrace.dev/guide/queries.html#insert). - [Bulk inserts](https://bun.uptrace.dev/guide/queries.html#insert).
@ -96,7 +100,7 @@ You also need to install a database/sql driver and the corresponding Bun
## Quickstart ## Quickstart
First you need to create a `sql.DB`. Here we are using the First you need to create a `sql.DB`. Here we are using the
[sqliteshim](https://pkg.go.dev/github.com/uptrace/bun/driver/sqliteshim) driver which choses [sqliteshim](https://pkg.go.dev/github.com/uptrace/bun/driver/sqliteshim) driver which chooses
between [modernc.org/sqlite](https://modernc.org/sqlite/) and between [modernc.org/sqlite](https://modernc.org/sqlite/) and
[mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) depending on your platform. [mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) depending on your platform.
@ -109,7 +113,8 @@ if err != nil {
} }
``` ```
And then create a `bun.DB` on top of it using the corresponding SQLite dialect: And then create a `bun.DB` on top of it using the corresponding SQLite
[dialect](https://bun.uptrace.dev/guide/drivers.html) that comes with Bun:
```go ```go
import ( import (

13
vendor/github.com/uptrace/bun/bun.go generated vendored
View File

@ -5,19 +5,17 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"github.com/uptrace/bun/internal"
"github.com/uptrace/bun/schema" "github.com/uptrace/bun/schema"
) )
type ( type (
Safe = schema.Safe Safe = schema.Safe
Ident = schema.Ident Ident = schema.Ident
)
type NullTime = schema.NullTime NullTime = schema.NullTime
BaseModel = schema.BaseModel
type BaseModel = schema.BaseModel
type (
BeforeScanHook = schema.BeforeScanHook BeforeScanHook = schema.BeforeScanHook
AfterScanHook = schema.AfterScanHook AfterScanHook = schema.AfterScanHook
) )
@ -70,6 +68,11 @@ type AfterDropTableHook interface {
AfterDropTable(ctx context.Context, query *DropTableQuery) error AfterDropTable(ctx context.Context, query *DropTableQuery) error
} }
// SetLogger overwriters default Bun logger.
func SetLogger(logger internal.Logging) {
internal.Logger = logger
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
type InValues struct { type InValues struct {

24
vendor/github.com/uptrace/bun/db.go generated vendored
View File

@ -3,7 +3,6 @@ package bun
import ( import (
"context" "context"
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"reflect" "reflect"
"strings" "strings"
@ -473,30 +472,9 @@ func (tx Tx) NewDropColumn() *DropColumnQuery {
return NewDropColumnQuery(tx.db).Conn(tx) return NewDropColumnQuery(tx.db).Conn(tx)
} }
//------------------------------------------------------------------------------0 //------------------------------------------------------------------------------
func (db *DB) makeQueryBytes() []byte { func (db *DB) makeQueryBytes() []byte {
// TODO: make this configurable? // TODO: make this configurable?
return make([]byte, 0, 4096) return make([]byte, 0, 4096)
} }
//------------------------------------------------------------------------------
type result struct {
r sql.Result
n int
}
func (r result) RowsAffected() (int64, error) {
if r.r != nil {
return r.r.RowsAffected()
}
return int64(r.n), nil
}
func (r result) LastInsertId() (int64, error) {
if r.r != nil {
return r.r.LastInsertId()
}
return 0, errors.New("LastInsertId is not available")
}

View File

@ -8,10 +8,8 @@ func (n Name) String() string {
return "pg" return "pg"
case SQLite: case SQLite:
return "sqlite" return "sqlite"
case MySQL5: case MySQL:
return "mysql5" return "mysql"
case MySQL8:
return "mysql8"
default: default:
return "invalid" return "invalid"
} }
@ -21,6 +19,5 @@ const (
Invalid Name = iota Invalid Name = iota
PG PG
SQLite SQLite
MySQL5 MySQL
MySQL8
) )

View File

@ -4,10 +4,9 @@ import "github.com/uptrace/bun/internal"
type Feature = internal.Flag type Feature = internal.Flag
const DefaultFeatures = Returning | TableCascade
const ( const (
Returning Feature = 1 << iota CTE Feature = 1 << iota
Returning
DefaultPlaceholder DefaultPlaceholder
DoubleColonCast DoubleColonCast
ValuesRow ValuesRow

View File

@ -8,18 +8,18 @@ import (
"github.com/uptrace/bun/schema" "github.com/uptrace/bun/schema"
) )
type join struct { type relationJoin struct {
Parent *join Parent *relationJoin
BaseModel tableModel BaseModel tableModel
JoinModel tableModel JoinModel tableModel
Relation *schema.Relation Relation *schema.Relation
ApplyQueryFunc func(*SelectQuery) *SelectQuery apply func(*SelectQuery) *SelectQuery
columns []schema.QueryWithArgs columns []schema.QueryWithArgs
} }
func (j *join) applyQuery(q *SelectQuery) { func (j *relationJoin) applyTo(q *SelectQuery) {
if j.ApplyQueryFunc == nil { if j.apply == nil {
return return
} }
@ -30,24 +30,20 @@ func (j *join) applyQuery(q *SelectQuery) {
table, q.table = q.table, j.JoinModel.Table() table, q.table = q.table, j.JoinModel.Table()
columns, q.columns = q.columns, nil columns, q.columns = q.columns, nil
q = j.ApplyQueryFunc(q) q = j.apply(q)
// Restore state. // Restore state.
q.table = table q.table = table
j.columns, q.columns = q.columns, columns j.columns, q.columns = q.columns, columns
} }
func (j *join) Select(ctx context.Context, q *SelectQuery) error { func (j *relationJoin) Select(ctx context.Context, q *SelectQuery) error {
switch j.Relation.Type { switch j.Relation.Type {
case schema.HasManyRelation:
return j.selectMany(ctx, q)
case schema.ManyToManyRelation:
return j.selectM2M(ctx, q)
} }
panic("not reached") panic("not reached")
} }
func (j *join) selectMany(ctx context.Context, q *SelectQuery) error { func (j *relationJoin) selectMany(ctx context.Context, q *SelectQuery) error {
q = j.manyQuery(q) q = j.manyQuery(q)
if q == nil { if q == nil {
return nil return nil
@ -55,7 +51,7 @@ func (j *join) selectMany(ctx context.Context, q *SelectQuery) error {
return q.Scan(ctx) return q.Scan(ctx)
} }
func (j *join) manyQuery(q *SelectQuery) *SelectQuery { func (j *relationJoin) manyQuery(q *SelectQuery) *SelectQuery {
hasManyModel := newHasManyModel(j) hasManyModel := newHasManyModel(j)
if hasManyModel == nil { if hasManyModel == nil {
return nil return nil
@ -86,13 +82,13 @@ func (j *join) manyQuery(q *SelectQuery) *SelectQuery {
q = q.Where("? = ?", j.Relation.PolymorphicField.SQLName, j.Relation.PolymorphicValue) q = q.Where("? = ?", j.Relation.PolymorphicField.SQLName, j.Relation.PolymorphicValue)
} }
j.applyQuery(q) j.applyTo(q)
q = q.Apply(j.hasManyColumns) q = q.Apply(j.hasManyColumns)
return q return q
} }
func (j *join) hasManyColumns(q *SelectQuery) *SelectQuery { func (j *relationJoin) hasManyColumns(q *SelectQuery) *SelectQuery {
if j.Relation.M2MTable != nil { if j.Relation.M2MTable != nil {
q = q.ColumnExpr(string(j.Relation.M2MTable.SQLAlias) + ".*") q = q.ColumnExpr(string(j.Relation.M2MTable.SQLAlias) + ".*")
} }
@ -122,7 +118,7 @@ func (j *join) hasManyColumns(q *SelectQuery) *SelectQuery {
return q return q
} }
func (j *join) selectM2M(ctx context.Context, q *SelectQuery) error { func (j *relationJoin) selectM2M(ctx context.Context, q *SelectQuery) error {
q = j.m2mQuery(q) q = j.m2mQuery(q)
if q == nil { if q == nil {
return nil return nil
@ -130,7 +126,7 @@ func (j *join) selectM2M(ctx context.Context, q *SelectQuery) error {
return q.Scan(ctx) return q.Scan(ctx)
} }
func (j *join) m2mQuery(q *SelectQuery) *SelectQuery { func (j *relationJoin) m2mQuery(q *SelectQuery) *SelectQuery {
fmter := q.db.fmter fmter := q.db.fmter
m2mModel := newM2MModel(j) m2mModel := newM2MModel(j)
@ -170,13 +166,13 @@ func (j *join) m2mQuery(q *SelectQuery) *SelectQuery {
j.Relation.M2MTable.SQLAlias, m2mJoinField.SQLName) j.Relation.M2MTable.SQLAlias, m2mJoinField.SQLName)
} }
j.applyQuery(q) j.applyTo(q)
q = q.Apply(j.hasManyColumns) q = q.Apply(j.hasManyColumns)
return q return q
} }
func (j *join) hasParent() bool { func (j *relationJoin) hasParent() bool {
if j.Parent != nil { if j.Parent != nil {
switch j.Parent.Relation.Type { switch j.Parent.Relation.Type {
case schema.HasOneRelation, schema.BelongsToRelation: case schema.HasOneRelation, schema.BelongsToRelation:
@ -186,7 +182,7 @@ func (j *join) hasParent() bool {
return false return false
} }
func (j *join) appendAlias(fmter schema.Formatter, b []byte) []byte { func (j *relationJoin) appendAlias(fmter schema.Formatter, b []byte) []byte {
quote := fmter.IdentQuote() quote := fmter.IdentQuote()
b = append(b, quote) b = append(b, quote)
@ -195,7 +191,7 @@ func (j *join) appendAlias(fmter schema.Formatter, b []byte) []byte {
return b return b
} }
func (j *join) appendAliasColumn(fmter schema.Formatter, b []byte, column string) []byte { func (j *relationJoin) appendAliasColumn(fmter schema.Formatter, b []byte, column string) []byte {
quote := fmter.IdentQuote() quote := fmter.IdentQuote()
b = append(b, quote) b = append(b, quote)
@ -206,7 +202,7 @@ func (j *join) appendAliasColumn(fmter schema.Formatter, b []byte, column string
return b return b
} }
func (j *join) appendBaseAlias(fmter schema.Formatter, b []byte) []byte { func (j *relationJoin) appendBaseAlias(fmter schema.Formatter, b []byte) []byte {
quote := fmter.IdentQuote() quote := fmter.IdentQuote()
if j.hasParent() { if j.hasParent() {
@ -218,7 +214,7 @@ func (j *join) appendBaseAlias(fmter schema.Formatter, b []byte) []byte {
return append(b, j.BaseModel.Table().SQLAlias...) return append(b, j.BaseModel.Table().SQLAlias...)
} }
func (j *join) appendSoftDelete(b []byte, flags internal.Flag) []byte { func (j *relationJoin) appendSoftDelete(b []byte, flags internal.Flag) []byte {
b = append(b, '.') b = append(b, '.')
b = append(b, j.JoinModel.Table().SoftDeleteField.SQLName...) b = append(b, j.JoinModel.Table().SoftDeleteField.SQLName...)
if flags.Has(deletedFlag) { if flags.Has(deletedFlag) {
@ -229,7 +225,7 @@ func (j *join) appendSoftDelete(b []byte, flags internal.Flag) []byte {
return b return b
} }
func appendAlias(b []byte, j *join) []byte { func appendAlias(b []byte, j *relationJoin) []byte {
if j.hasParent() { if j.hasParent() {
b = appendAlias(b, j.Parent) b = appendAlias(b, j.Parent)
b = append(b, "__"...) b = append(b, "__"...)
@ -238,7 +234,7 @@ func appendAlias(b []byte, j *join) []byte {
return b return b
} }
func (j *join) appendHasOneJoin( func (j *relationJoin) appendHasOneJoin(
fmter schema.Formatter, b []byte, q *SelectQuery, fmter schema.Formatter, b []byte, q *SelectQuery,
) (_ []byte, err error) { ) (_ []byte, err error) {
isSoftDelete := j.JoinModel.Table().SoftDeleteField != nil && !q.flags.Has(allWithDeletedFlag) isSoftDelete := j.JoinModel.Table().SoftDeleteField != nil && !q.flags.Has(allWithDeletedFlag)

View File

@ -115,6 +115,7 @@ func (m *Migrator) Reset(ctx context.Context) error {
return m.Init(ctx) return m.Init(ctx)
} }
// Migrate runs unapplied migrations. If a migration fails, migrate immediately exits.
func (m *Migrator) Migrate(ctx context.Context, opts ...MigrationOption) (*MigrationGroup, error) { func (m *Migrator) Migrate(ctx context.Context, opts ...MigrationOption) (*MigrationGroup, error) {
cfg := newMigrationConfig(opts) cfg := newMigrationConfig(opts)
@ -146,7 +147,7 @@ func (m *Migrator) Migrate(ctx context.Context, opts ...MigrationOption) (*Migra
if !cfg.nop && migration.Up != nil { if !cfg.nop && migration.Up != nil {
if err := migration.Up(ctx, m.db); err != nil { if err := migration.Up(ctx, m.db); err != nil {
return nil, err return group, err
} }
} }

View File

@ -38,16 +38,16 @@ type tableModel interface {
Table() *schema.Table Table() *schema.Table
Relation() *schema.Relation Relation() *schema.Relation
Join(string, func(*SelectQuery) *SelectQuery) *join Join(string) *relationJoin
GetJoin(string) *join GetJoin(string) *relationJoin
GetJoins() []join GetJoins() []relationJoin
AddJoin(join) *join AddJoin(relationJoin) *relationJoin
Root() reflect.Value Root() reflect.Value
ParentIndex() []int ParentIndex() []int
Mount(reflect.Value) Mount(reflect.Value)
updateSoftDeleteField() error updateSoftDeleteField(time.Time) error
} }
func newModel(db *DB, dest []interface{}) (model, error) { func newModel(db *DB, dest []interface{}) (model, error) {

View File

@ -21,7 +21,7 @@ type hasManyModel struct {
var _ tableModel = (*hasManyModel)(nil) var _ tableModel = (*hasManyModel)(nil)
func newHasManyModel(j *join) *hasManyModel { func newHasManyModel(j *relationJoin) *hasManyModel {
baseTable := j.BaseModel.Table() baseTable := j.BaseModel.Table()
joinModel := j.JoinModel.(*sliceTableModel) joinModel := j.JoinModel.(*sliceTableModel)
baseValues := baseValues(joinModel, j.Relation.BaseFields) baseValues := baseValues(joinModel, j.Relation.BaseFields)

View File

@ -21,7 +21,7 @@ type m2mModel struct {
var _ tableModel = (*m2mModel)(nil) var _ tableModel = (*m2mModel)(nil)
func newM2MModel(j *join) *m2mModel { func newM2MModel(j *relationJoin) *m2mModel {
baseTable := j.BaseModel.Table() baseTable := j.BaseModel.Table()
joinModel := j.JoinModel.(*sliceTableModel) joinModel := j.JoinModel.(*sliceTableModel)
baseValues := baseValues(joinModel, baseTable.PKs) baseValues := baseValues(joinModel, baseTable.PKs)

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"reflect" "reflect"
"time"
"github.com/uptrace/bun/schema" "github.com/uptrace/bun/schema"
) )
@ -45,8 +46,8 @@ func (m *sliceTableModel) init(sliceType reflect.Type) {
} }
} }
func (m *sliceTableModel) Join(name string, apply func(*SelectQuery) *SelectQuery) *join { func (m *sliceTableModel) Join(name string) *relationJoin {
return m.join(m.slice, name, apply) return m.join(m.slice, name)
} }
func (m *sliceTableModel) Bind(bind reflect.Value) { func (m *sliceTableModel) Bind(bind reflect.Value) {
@ -100,12 +101,12 @@ var (
_ schema.AfterScanHook = (*sliceTableModel)(nil) _ schema.AfterScanHook = (*sliceTableModel)(nil)
) )
func (m *sliceTableModel) updateSoftDeleteField() error { func (m *sliceTableModel) updateSoftDeleteField(tm time.Time) error {
sliceLen := m.slice.Len() sliceLen := m.slice.Len()
for i := 0; i < sliceLen; i++ { for i := 0; i < sliceLen; i++ {
strct := indirect(m.slice.Index(i)) strct := indirect(m.slice.Index(i))
fv := m.table.SoftDeleteField.Value(strct) fv := m.table.SoftDeleteField.Value(strct)
if err := m.table.UpdateSoftDeleteField(fv); err != nil { if err := m.table.UpdateSoftDeleteField(fv, tm); err != nil {
return err return err
} }
} }

View File

@ -6,8 +6,8 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"strings" "strings"
"time"
"github.com/uptrace/bun/dialect"
"github.com/uptrace/bun/schema" "github.com/uptrace/bun/schema"
) )
@ -16,7 +16,7 @@ type structTableModel struct {
table *schema.Table table *schema.Table
rel *schema.Relation rel *schema.Relation
joins []join joins []relationJoin
dest interface{} dest interface{}
root reflect.Value root reflect.Value
@ -151,7 +151,7 @@ func (m *structTableModel) AfterScan(ctx context.Context) error {
return firstErr return firstErr
} }
func (m *structTableModel) GetJoin(name string) *join { func (m *structTableModel) GetJoin(name string) *relationJoin {
for i := range m.joins { for i := range m.joins {
j := &m.joins[i] j := &m.joins[i]
if j.Relation.Field.Name == name || j.Relation.Field.GoName == name { if j.Relation.Field.Name == name || j.Relation.Field.GoName == name {
@ -161,30 +161,28 @@ func (m *structTableModel) GetJoin(name string) *join {
return nil return nil
} }
func (m *structTableModel) GetJoins() []join { func (m *structTableModel) GetJoins() []relationJoin {
return m.joins return m.joins
} }
func (m *structTableModel) AddJoin(j join) *join { func (m *structTableModel) AddJoin(j relationJoin) *relationJoin {
m.joins = append(m.joins, j) m.joins = append(m.joins, j)
return &m.joins[len(m.joins)-1] return &m.joins[len(m.joins)-1]
} }
func (m *structTableModel) Join(name string, apply func(*SelectQuery) *SelectQuery) *join { func (m *structTableModel) Join(name string) *relationJoin {
return m.join(m.strct, name, apply) return m.join(m.strct, name)
} }
func (m *structTableModel) join( func (m *structTableModel) join(bind reflect.Value, name string) *relationJoin {
bind reflect.Value, name string, apply func(*SelectQuery) *SelectQuery,
) *join {
path := strings.Split(name, ".") path := strings.Split(name, ".")
index := make([]int, 0, len(path)) index := make([]int, 0, len(path))
currJoin := join{ currJoin := relationJoin{
BaseModel: m, BaseModel: m,
JoinModel: m, JoinModel: m,
} }
var lastJoin *join var lastJoin *relationJoin
for _, name := range path { for _, name := range path {
relation, ok := currJoin.JoinModel.Table().Relations[name] relation, ok := currJoin.JoinModel.Table().Relations[name]
@ -214,20 +212,12 @@ func (m *structTableModel) join(
} }
} }
// No joins with such name.
if lastJoin == nil {
return nil
}
if apply != nil {
lastJoin.ApplyQueryFunc = apply
}
return lastJoin return lastJoin
} }
func (m *structTableModel) updateSoftDeleteField() error { func (m *structTableModel) updateSoftDeleteField(tm time.Time) error {
fv := m.table.SoftDeleteField.Value(m.strct) fv := m.table.SoftDeleteField.Value(m.strct)
return m.table.UpdateSoftDeleteField(fv) return m.table.UpdateSoftDeleteField(fv, tm)
} }
func (m *structTableModel) ScanRows(ctx context.Context, rows *sql.Rows) (int, error) { func (m *structTableModel) ScanRows(ctx context.Context, rows *sql.Rows) (int, error) {
@ -235,20 +225,24 @@ func (m *structTableModel) ScanRows(ctx context.Context, rows *sql.Rows) (int, e
return 0, rows.Err() return 0, rows.Err()
} }
var n int
if err := m.ScanRow(ctx, rows); err != nil { if err := m.ScanRow(ctx, rows); err != nil {
return 0, err return 0, err
} }
n++
// For inserts, SQLite3 can return a row like it was inserted sucessfully and then // And discard the rest. This is especially important for SQLite3, which can return
// an actual error for the next row. See issues/100. // a row like it was inserted sucessfully and then return an actual error for the next row.
if m.db.dialect.Name() == dialect.SQLite { // See issues/100.
_ = rows.Next() for rows.Next() {
n++
}
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
return 0, err return 0, err
} }
}
return 1, nil return n, nil
} }
func (m *structTableModel) ScanRow(ctx context.Context, rows *sql.Rows) error { func (m *structTableModel) ScanRow(ctx context.Context, rows *sql.Rows) error {
@ -305,6 +299,9 @@ func (m *structTableModel) scanColumn(column string, src interface{}) (bool, err
} }
if field, ok := m.table.FieldMap[column]; ok { if field, ok := m.table.FieldMap[column]; ok {
if src == nil && m.isNil() {
return true, nil
}
return true, field.ScanValue(m.strct, src) return true, field.ScanValue(m.strct, src)
} }
@ -312,6 +309,7 @@ func (m *structTableModel) scanColumn(column string, src interface{}) (bool, err
if join := m.GetJoin(joinName); join != nil { if join := m.GetJoin(joinName); join != nil {
return true, join.JoinModel.ScanColumn(column, src) return true, join.JoinModel.ScanColumn(column, src)
} }
if m.table.ModelName == joinName { if m.table.ModelName == joinName {
return true, m.ScanColumn(column, src) return true, m.ScanColumn(column, src)
} }
@ -320,6 +318,10 @@ func (m *structTableModel) scanColumn(column string, src interface{}) (bool, err
return false, nil return false, nil
} }
func (m *structTableModel) isNil() bool {
return m.strct.Kind() == reflect.Ptr && m.strct.IsNil()
}
func (m *structTableModel) AppendNamedArg( func (m *structTableModel) AppendNamedArg(
fmter schema.Formatter, b []byte, name string, fmter schema.Formatter, b []byte, name string,
) ([]byte, bool) { ) ([]byte, bool) {

View File

@ -3,6 +3,7 @@ package bun
import ( import (
"context" "context"
"database/sql" "database/sql"
"database/sql/driver"
"errors" "errors"
"fmt" "fmt"
@ -262,7 +263,10 @@ func (q *baseQuery) _excludeColumn(column string) bool {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
func (q *baseQuery) modelHasTableName() bool { func (q *baseQuery) modelHasTableName() bool {
return !q.modelTable.IsZero() || q.table != nil if !q.modelTable.IsZero() {
return q.modelTable.Query != ""
}
return q.table != nil
} }
func (q *baseQuery) hasTables() bool { func (q *baseQuery) hasTables() bool {
@ -387,18 +391,10 @@ func (q *baseQuery) appendColumns(fmter schema.Formatter, b []byte) (_ []byte, e
} }
func (q *baseQuery) getFields() ([]*schema.Field, error) { func (q *baseQuery) getFields() ([]*schema.Field, error) {
table := q.tableModel.Table()
if len(q.columns) == 0 { if len(q.columns) == 0 {
return table.Fields, nil return q.table.Fields, nil
} }
return q._getFields(false)
fields, err := q._getFields(false)
if err != nil {
return nil, err
}
return fields, nil
} }
func (q *baseQuery) getDataFields() ([]*schema.Field, error) { func (q *baseQuery) getDataFields() ([]*schema.Field, error) {
@ -435,28 +431,28 @@ func (q *baseQuery) scan(
query string, query string,
model model, model model,
hasDest bool, hasDest bool,
) (res result, _ error) { ) (sql.Result, error) {
ctx, event := q.db.beforeQuery(ctx, queryApp, query, nil) ctx, event := q.db.beforeQuery(ctx, queryApp, query, nil)
rows, err := q.conn.QueryContext(ctx, query) rows, err := q.conn.QueryContext(ctx, query)
if err != nil { if err != nil {
q.db.afterQuery(ctx, event, nil, err) q.db.afterQuery(ctx, event, nil, err)
return res, err return nil, err
} }
defer rows.Close() defer rows.Close()
n, err := model.ScanRows(ctx, rows) numRow, err := model.ScanRows(ctx, rows)
if err != nil { if err != nil {
q.db.afterQuery(ctx, event, nil, err) q.db.afterQuery(ctx, event, nil, err)
return res, err return nil, err
} }
res.n = n if numRow == 0 && hasDest && isSingleRowModel(model) {
if n == 0 && hasDest && isSingleRowModel(model) {
err = sql.ErrNoRows err = sql.ErrNoRows
} }
q.db.afterQuery(ctx, event, nil, err) res := driver.RowsAffected(numRow)
q.db.afterQuery(ctx, event, res, err)
return res, err return res, err
} }
@ -465,18 +461,16 @@ func (q *baseQuery) exec(
ctx context.Context, ctx context.Context,
queryApp schema.QueryAppender, queryApp schema.QueryAppender,
query string, query string,
) (res result, _ error) { ) (sql.Result, error) {
ctx, event := q.db.beforeQuery(ctx, queryApp, query, nil) ctx, event := q.db.beforeQuery(ctx, queryApp, query, nil)
r, err := q.conn.ExecContext(ctx, query) res, err := q.conn.ExecContext(ctx, query)
if err != nil { if err != nil {
q.db.afterQuery(ctx, event, nil, err) q.db.afterQuery(ctx, event, nil, err)
return res, err return res, err
} }
res.r = r q.db.afterQuery(ctx, event, res, err)
q.db.afterQuery(ctx, event, nil, err)
return res, nil return res, nil
} }
@ -556,10 +550,12 @@ func (q *whereBaseQuery) addWhereGroup(sep string, where []schema.QueryWithSep)
return return
} }
where[0].Sep = "" q.addWhere(schema.SafeQueryWithSep("", nil, sep))
q.addWhere(schema.SafeQueryWithSep("", nil, "("))
q.addWhere(schema.SafeQueryWithSep("", nil, sep+"(")) where[0].Sep = ""
q.where = append(q.where, where...) q.where = append(q.where, where...)
q.addWhere(schema.SafeQueryWithSep("", nil, ")")) q.addWhere(schema.SafeQueryWithSep("", nil, ")"))
} }
@ -623,11 +619,11 @@ func appendWhere(
fmter schema.Formatter, b []byte, where []schema.QueryWithSep, fmter schema.Formatter, b []byte, where []schema.QueryWithSep,
) (_ []byte, err error) { ) (_ []byte, err error) {
for i, where := range where { for i, where := range where {
if i > 0 || where.Sep == "(" { if i > 0 {
b = append(b, where.Sep...) b = append(b, where.Sep...)
} }
if where.Query == "" && where.Args == nil { if where.Query == "" {
continue continue
} }

View File

@ -3,6 +3,7 @@ package bun
import ( import (
"context" "context"
"database/sql" "database/sql"
"time"
"github.com/uptrace/bun/dialect/feature" "github.com/uptrace/bun/dialect/feature"
"github.com/uptrace/bun/internal" "github.com/uptrace/bun/internal"
@ -135,15 +136,18 @@ func (q *DeleteQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, e
fmter = formatterWithModel(fmter, q) fmter = formatterWithModel(fmter, q)
if q.isSoftDelete() { if q.isSoftDelete() {
if err := q.tableModel.updateSoftDeleteField(); err != nil { now := time.Now()
if err := q.tableModel.updateSoftDeleteField(now); err != nil {
return nil, err return nil, err
} }
upd := UpdateQuery{ upd := &UpdateQuery{
whereBaseQuery: q.whereBaseQuery, whereBaseQuery: q.whereBaseQuery,
returningQuery: q.returningQuery, returningQuery: q.returningQuery,
} }
upd.Column(q.table.SoftDeleteField.Name) upd.Set(q.softDeleteSet(fmter, now))
return upd.AppendQuery(fmter, b) return upd.AppendQuery(fmter, b)
} }
@ -193,6 +197,18 @@ func (q *DeleteQuery) isSoftDelete() bool {
return q.tableModel != nil && q.table.SoftDeleteField != nil && !q.flags.Has(forceDeleteFlag) return q.tableModel != nil && q.table.SoftDeleteField != nil && !q.flags.Has(forceDeleteFlag)
} }
func (q *DeleteQuery) softDeleteSet(fmter schema.Formatter, tm time.Time) string {
b := make([]byte, 0, 32)
if fmter.HasFeature(feature.UpdateMultiTable) {
b = append(b, q.table.SQLAlias...)
b = append(b, '.')
}
b = append(b, q.table.SoftDeleteField.SQLName...)
b = append(b, " = "...)
b = q.db.Dialect().Append(fmter, b, tm)
return internal.String(b)
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
func (q *DeleteQuery) Exec(ctx context.Context, dest ...interface{}) (sql.Result, error) { func (q *DeleteQuery) Exec(ctx context.Context, dest ...interface{}) (sql.Result, error) {

View File

@ -5,6 +5,7 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"reflect" "reflect"
"strings"
"github.com/uptrace/bun/dialect/feature" "github.com/uptrace/bun/dialect/feature"
"github.com/uptrace/bun/internal" "github.com/uptrace/bun/internal"
@ -16,7 +17,7 @@ type InsertQuery struct {
returningQuery returningQuery
customValueQuery customValueQuery
onConflict schema.QueryWithArgs on schema.QueryWithArgs
setQuery setQuery
ignore bool ignore bool
@ -88,13 +89,13 @@ func (q *InsertQuery) ExcludeColumn(columns ...string) *InsertQuery {
return q return q
} }
// Value overwrites model value for the column in INSERT and UPDATE queries. // Value overwrites model value for the column.
func (q *InsertQuery) Value(column string, value string, args ...interface{}) *InsertQuery { func (q *InsertQuery) Value(column string, expr string, args ...interface{}) *InsertQuery {
if q.table == nil { if q.table == nil {
q.err = errNilModel q.err = errNilModel
return q return q
} }
q.addValue(q.table, column, value, args) q.addValue(q.table, column, expr, args)
return q return q
} }
@ -162,7 +163,7 @@ func (q *InsertQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, e
} }
b = append(b, "INTO "...) b = append(b, "INTO "...)
if q.db.features.Has(feature.InsertTableAlias) && !q.onConflict.IsZero() { if q.db.features.Has(feature.InsertTableAlias) && !q.on.IsZero() {
b, err = q.appendFirstTableWithAlias(fmter, b) b, err = q.appendFirstTableWithAlias(fmter, b)
} else { } else {
b, err = q.appendFirstTable(fmter, b) b, err = q.appendFirstTable(fmter, b)
@ -382,7 +383,7 @@ func (q *InsertQuery) appendFields(
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
func (q *InsertQuery) On(s string, args ...interface{}) *InsertQuery { func (q *InsertQuery) On(s string, args ...interface{}) *InsertQuery {
q.onConflict = schema.SafeQuery(s, args) q.on = schema.SafeQuery(s, args)
return q return q
} }
@ -392,12 +393,12 @@ func (q *InsertQuery) Set(query string, args ...interface{}) *InsertQuery {
} }
func (q *InsertQuery) appendOn(fmter schema.Formatter, b []byte) (_ []byte, err error) { func (q *InsertQuery) appendOn(fmter schema.Formatter, b []byte) (_ []byte, err error) {
if q.onConflict.IsZero() { if q.on.IsZero() {
return b, nil return b, nil
} }
b = append(b, " ON "...) b = append(b, " ON "...)
b, err = q.onConflict.AppendQuery(fmter, b) b, err = q.on.AppendQuery(fmter, b)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -413,7 +414,7 @@ func (q *InsertQuery) appendOn(fmter schema.Formatter, b []byte) (_ []byte, err
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else if len(q.columns) > 0 { } else if q.onConflictDoUpdate() {
fields, err := q.getDataFields() fields, err := q.getDataFields()
if err != nil { if err != nil {
return nil, err return nil, err
@ -434,6 +435,10 @@ func (q *InsertQuery) appendOn(fmter schema.Formatter, b []byte) (_ []byte, err
return b, nil return b, nil
} }
func (q *InsertQuery) onConflictDoUpdate() bool {
return strings.HasSuffix(strings.ToUpper(q.on.Query), " DO UPDATE")
}
func (q *InsertQuery) appendSetExcluded(b []byte, fields []*schema.Field) []byte { func (q *InsertQuery) appendSetExcluded(b []byte, fields []*schema.Field) []byte {
b = append(b, " SET "...) b = append(b, " SET "...)
for i, f := range fields { for i, f := range fields {

View File

@ -286,41 +286,38 @@ func (q *SelectQuery) joinOn(cond string, args []interface{}, sep string) *Selec
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Relation adds a relation to the query. Relation name can be: // Relation adds a relation to the query.
// - RelationName to select all columns,
// - RelationName.column_name,
// - RelationName._ to join relation without selecting relation columns.
func (q *SelectQuery) Relation(name string, apply ...func(*SelectQuery) *SelectQuery) *SelectQuery { func (q *SelectQuery) Relation(name string, apply ...func(*SelectQuery) *SelectQuery) *SelectQuery {
if len(apply) > 1 {
panic("only one apply function is supported")
}
if q.tableModel == nil { if q.tableModel == nil {
q.setErr(errNilModel) q.setErr(errNilModel)
return q return q
} }
var fn func(*SelectQuery) *SelectQuery join := q.tableModel.Join(name)
if len(apply) == 1 {
fn = apply[0]
} else if len(apply) > 1 {
panic("only one apply function is supported")
}
join := q.tableModel.Join(name, fn)
if join == nil { if join == nil {
q.setErr(fmt.Errorf("%s does not have relation=%q", q.table, name)) q.setErr(fmt.Errorf("%s does not have relation=%q", q.table, name))
return q return q
} }
if len(apply) == 1 {
join.apply = apply[0]
}
return q return q
} }
func (q *SelectQuery) forEachHasOneJoin(fn func(*join) error) error { func (q *SelectQuery) forEachHasOneJoin(fn func(*relationJoin) error) error {
if q.tableModel == nil { if q.tableModel == nil {
return nil return nil
} }
return q._forEachHasOneJoin(fn, q.tableModel.GetJoins()) return q._forEachHasOneJoin(fn, q.tableModel.GetJoins())
} }
func (q *SelectQuery) _forEachHasOneJoin(fn func(*join) error, joins []join) error { func (q *SelectQuery) _forEachHasOneJoin(fn func(*relationJoin) error, joins []relationJoin) error {
for i := range joins { for i := range joins {
j := &joins[i] j := &joins[i]
switch j.Relation.Type { switch j.Relation.Type {
@ -336,16 +333,23 @@ func (q *SelectQuery) _forEachHasOneJoin(fn func(*join) error, joins []join) err
return nil return nil
} }
func (q *SelectQuery) selectJoins(ctx context.Context, joins []join) error { func (q *SelectQuery) selectJoins(ctx context.Context, joins []relationJoin) error {
var err error
for i := range joins { for i := range joins {
j := &joins[i] j := &joins[i]
var err error
switch j.Relation.Type { switch j.Relation.Type {
case schema.HasOneRelation, schema.BelongsToRelation: case schema.HasOneRelation, schema.BelongsToRelation:
err = q.selectJoins(ctx, j.JoinModel.GetJoins()) err = q.selectJoins(ctx, j.JoinModel.GetJoins())
case schema.HasManyRelation:
err = j.selectMany(ctx, q.db.NewSelect())
case schema.ManyToManyRelation:
err = j.selectM2M(ctx, q.db.NewSelect())
default: default:
err = j.Select(ctx, q.db.NewSelect()) panic("not reached")
} }
if err != nil { if err != nil {
return err return err
} }
@ -415,7 +419,7 @@ func (q *SelectQuery) appendQuery(
} }
} }
if err := q.forEachHasOneJoin(func(j *join) error { if err := q.forEachHasOneJoin(func(j *relationJoin) error {
b = append(b, ' ') b = append(b, ' ')
b, err = j.appendHasOneJoin(fmter, b, q) b, err = j.appendHasOneJoin(fmter, b, q)
return err return err
@ -545,13 +549,13 @@ func (q *SelectQuery) appendColumns(fmter schema.Formatter, b []byte) (_ []byte,
b = append(b, '*') b = append(b, '*')
} }
if err := q.forEachHasOneJoin(func(j *join) error { if err := q.forEachHasOneJoin(func(join *relationJoin) error {
if len(b) != start { if len(b) != start {
b = append(b, ", "...) b = append(b, ", "...)
start = len(b) start = len(b)
} }
b, err = q.appendHasOneColumns(fmter, b, j) b, err = q.appendHasOneColumns(fmter, b, join)
if err != nil { if err != nil {
return err return err
} }
@ -567,18 +571,19 @@ func (q *SelectQuery) appendColumns(fmter schema.Formatter, b []byte) (_ []byte,
} }
func (q *SelectQuery) appendHasOneColumns( func (q *SelectQuery) appendHasOneColumns(
fmter schema.Formatter, b []byte, join *join, fmter schema.Formatter, b []byte, join *relationJoin,
) (_ []byte, err error) { ) (_ []byte, err error) {
join.applyQuery(q) join.applyTo(q)
if join.columns != nil { if join.columns != nil {
table := join.JoinModel.Table()
for i, col := range join.columns { for i, col := range join.columns {
if i > 0 { if i > 0 {
b = append(b, ", "...) b = append(b, ", "...)
} }
if col.Args == nil { if col.Args == nil {
if field, ok := q.table.FieldMap[col.Query]; ok { if field, ok := table.FieldMap[col.Query]; ok {
b = join.appendAlias(fmter, b) b = join.appendAlias(fmter, b)
b = append(b, '.') b = append(b, '.')
b = append(b, field.SQLName...) b = append(b, field.SQLName...)
@ -691,7 +696,7 @@ func (q *SelectQuery) Scan(ctx context.Context, dest ...interface{}) error {
return err return err
} }
if res.n > 0 { if n, _ := res.RowsAffected(); n > 0 {
if tableModel, ok := model.(tableModel); ok { if tableModel, ok := model.(tableModel); ok {
if err := q.selectJoins(ctx, tableModel.GetJoins()); err != nil { if err := q.selectJoins(ctx, tableModel.GetJoins()); err != nil {
return err return err

View File

@ -90,13 +90,13 @@ func (q *UpdateQuery) Set(query string, args ...interface{}) *UpdateQuery {
return q return q
} }
// Value overwrites model value for the column in INSERT and UPDATE queries. // Value overwrites model value for the column.
func (q *UpdateQuery) Value(column string, value string, args ...interface{}) *UpdateQuery { func (q *UpdateQuery) Value(column string, expr string, args ...interface{}) *UpdateQuery {
if q.table == nil { if q.table == nil {
q.err = errNilModel q.err = errNilModel
return q return q
} }
q.addValue(q.table, column, value, args) q.addValue(q.table, column, expr, args)
return q return q
} }
@ -321,20 +321,36 @@ func (q *UpdateQuery) Bulk() *UpdateQuery {
return q return q
} }
return q.With("_data", q.db.NewValues(model)). set, err := q.updateSliceSet(q.db.fmter, model)
if err != nil {
q.setErr(err)
return q
}
values := q.db.NewValues(model)
values.customValueQuery = q.customValueQuery
return q.With("_data", values).
Model(model). Model(model).
TableExpr("_data"). TableExpr("_data").
Set(q.updateSliceSet(model)). Set(set).
Where(q.updateSliceWhere(model)) Where(q.updateSliceWhere(model))
} }
func (q *UpdateQuery) updateSliceSet(model *sliceTableModel) string { func (q *UpdateQuery) updateSliceSet(
fmter schema.Formatter, model *sliceTableModel,
) (string, error) {
fields, err := q.getDataFields()
if err != nil {
return "", err
}
var b []byte var b []byte
for i, field := range model.table.DataFields { for i, field := range fields {
if i > 0 { if i > 0 {
b = append(b, ", "...) b = append(b, ", "...)
} }
if q.db.fmter.HasFeature(feature.UpdateMultiTable) { if fmter.HasFeature(feature.UpdateMultiTable) {
b = append(b, model.table.SQLAlias...) b = append(b, model.table.SQLAlias...)
b = append(b, '.') b = append(b, '.')
} }
@ -342,7 +358,7 @@ func (q *UpdateQuery) updateSliceSet(model *sliceTableModel) string {
b = append(b, " = _data."...) b = append(b, " = _data."...)
b = append(b, field.SQLName...) b = append(b, field.SQLName...)
} }
return internal.String(b) return internal.String(b), nil
} }
func (db *UpdateQuery) updateSliceWhere(model *sliceTableModel) string { func (db *UpdateQuery) updateSliceWhere(model *sliceTableModel) string {

View File

@ -34,6 +34,16 @@ func (q *ValuesQuery) Conn(db IConn) *ValuesQuery {
return q return q
} }
// Value overwrites model value for the column.
func (q *ValuesQuery) Value(column string, expr string, args ...interface{}) *ValuesQuery {
if q.table == nil {
q.err = errNilModel
return q
}
q.addValue(q.table, column, expr, args)
return q
}
func (q *ValuesQuery) WithOrder() *ValuesQuery { func (q *ValuesQuery) WithOrder() *ValuesQuery {
q.withOrder = true q.withOrder = true
return q return q

View File

@ -2,7 +2,6 @@ package schema
import ( import (
"database/sql/driver" "database/sql/driver"
"encoding/json"
"fmt" "fmt"
"net" "net"
"reflect" "reflect"
@ -14,16 +13,6 @@ import (
"github.com/uptrace/bun/internal" "github.com/uptrace/bun/internal"
) )
var (
timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
ipType = reflect.TypeOf((*net.IP)(nil)).Elem()
ipNetType = reflect.TypeOf((*net.IPNet)(nil)).Elem()
jsonRawMessageType = reflect.TypeOf((*json.RawMessage)(nil)).Elem()
driverValuerType = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
queryAppenderType = reflect.TypeOf((*QueryAppender)(nil)).Elem()
)
type ( type (
AppenderFunc func(fmter Formatter, b []byte, v reflect.Value) []byte AppenderFunc func(fmter Formatter, b []byte, v reflect.Value) []byte
CustomAppender func(typ reflect.Type) AppenderFunc CustomAppender func(typ reflect.Type) AppenderFunc
@ -60,6 +49,8 @@ var appenders = []AppenderFunc{
func Appender(typ reflect.Type, custom CustomAppender) AppenderFunc { func Appender(typ reflect.Type, custom CustomAppender) AppenderFunc {
switch typ { switch typ {
case bytesType:
return appendBytesValue
case timeType: case timeType:
return appendTimeValue return appendTimeValue
case ipType: case ipType:
@ -93,7 +84,9 @@ func Appender(typ reflect.Type, custom CustomAppender) AppenderFunc {
case reflect.Interface: case reflect.Interface:
return ifaceAppenderFunc(typ, custom) return ifaceAppenderFunc(typ, custom)
case reflect.Ptr: case reflect.Ptr:
return ptrAppenderFunc(typ, custom) if fn := Appender(typ.Elem(), custom); fn != nil {
return PtrAppender(fn)
}
case reflect.Slice: case reflect.Slice:
if typ.Elem().Kind() == reflect.Uint8 { if typ.Elem().Kind() == reflect.Uint8 {
return appendBytesValue return appendBytesValue
@ -123,13 +116,12 @@ func ifaceAppenderFunc(typ reflect.Type, custom func(reflect.Type) AppenderFunc)
} }
} }
func ptrAppenderFunc(typ reflect.Type, custom func(reflect.Type) AppenderFunc) AppenderFunc { func PtrAppender(fn AppenderFunc) AppenderFunc {
appender := Appender(typ.Elem(), custom)
return func(fmter Formatter, b []byte, v reflect.Value) []byte { return func(fmter Formatter, b []byte, v reflect.Value) []byte {
if v.IsNil() { if v.IsNil() {
return dialect.AppendNull(b) return dialect.AppendNull(b)
} }
return appender(fmter, b, v.Elem()) return fn(fmter, b, v.Elem())
} }
} }

View File

@ -89,10 +89,10 @@ func (f Formatter) AppendQuery(dst []byte, query string, args ...interface{}) []
func (f Formatter) append(dst []byte, p *parser.Parser, args []interface{}) []byte { func (f Formatter) append(dst []byte, p *parser.Parser, args []interface{}) []byte {
var namedArgs NamedArgAppender var namedArgs NamedArgAppender
if len(args) == 1 { if len(args) == 1 {
var ok bool if v, ok := args[0].(NamedArgAppender); ok {
namedArgs, ok = args[0].(NamedArgAppender) namedArgs = v
if !ok { } else if v, ok := newStructArgs(f, args[0]); ok {
namedArgs, _ = newStructArgs(f, args[0]) namedArgs = v
} }
} }

View File

@ -1,6 +1,23 @@
package schema package schema
import "reflect" import (
"database/sql/driver"
"encoding/json"
"net"
"reflect"
"time"
)
var (
bytesType = reflect.TypeOf((*[]byte)(nil)).Elem()
timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
ipType = reflect.TypeOf((*net.IP)(nil)).Elem()
ipNetType = reflect.TypeOf((*net.IPNet)(nil)).Elem()
jsonRawMessageType = reflect.TypeOf((*json.RawMessage)(nil)).Elem()
driverValuerType = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
queryAppenderType = reflect.TypeOf((*QueryAppender)(nil)).Elem()
)
func indirectType(t reflect.Type) reflect.Type { func indirectType(t reflect.Type) reflect.Type {
if t.Kind() == reflect.Ptr { if t.Kind() == reflect.Ptr {

View File

@ -7,10 +7,12 @@ import (
"net" "net"
"reflect" "reflect"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/vmihailenco/msgpack/v5" "github.com/vmihailenco/msgpack/v5"
"github.com/uptrace/bun/dialect/sqltype"
"github.com/uptrace/bun/extra/bunjson" "github.com/uptrace/bun/extra/bunjson"
"github.com/uptrace/bun/internal" "github.com/uptrace/bun/internal"
) )
@ -19,7 +21,10 @@ var scannerType = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
type ScannerFunc func(dest reflect.Value, src interface{}) error type ScannerFunc func(dest reflect.Value, src interface{}) error
var scanners = []ScannerFunc{ var scanners []ScannerFunc
func init() {
scanners = []ScannerFunc{
reflect.Bool: scanBool, reflect.Bool: scanBool,
reflect.Int: scanInt64, reflect.Int: scanInt64,
reflect.Int8: scanInt64, reflect.Int8: scanInt64,
@ -37,8 +42,7 @@ var scanners = []ScannerFunc{
reflect.Complex64: nil, reflect.Complex64: nil,
reflect.Complex128: nil, reflect.Complex128: nil,
reflect.Array: nil, reflect.Array: nil,
reflect.Chan: nil, reflect.Interface: scanInterface,
reflect.Func: nil,
reflect.Map: scanJSON, reflect.Map: scanJSON,
reflect.Ptr: nil, reflect.Ptr: nil,
reflect.Slice: scanJSON, reflect.Slice: scanJSON,
@ -46,6 +50,7 @@ var scanners = []ScannerFunc{
reflect.Struct: scanJSON, reflect.Struct: scanJSON,
reflect.UnsafePointer: nil, reflect.UnsafePointer: nil,
} }
}
func FieldScanner(dialect Dialect, field *Field) ScannerFunc { func FieldScanner(dialect Dialect, field *Field) ScannerFunc {
if field.Tag.HasOption("msgpack") { if field.Tag.HasOption("msgpack") {
@ -54,6 +59,12 @@ func FieldScanner(dialect Dialect, field *Field) ScannerFunc {
if field.Tag.HasOption("json_use_number") { if field.Tag.HasOption("json_use_number") {
return scanJSONUseNumber return scanJSONUseNumber
} }
if field.StructField.Type.Kind() == reflect.Interface {
switch strings.ToUpper(field.UserSQLType) {
case sqltype.JSON, sqltype.JSONB:
return scanJSONIntoInterface
}
}
return dialect.Scanner(field.StructField.Type) return dialect.Scanner(field.StructField.Type)
} }
@ -62,7 +73,7 @@ func Scanner(typ reflect.Type) ScannerFunc {
if kind == reflect.Ptr { if kind == reflect.Ptr {
if fn := Scanner(typ.Elem()); fn != nil { if fn := Scanner(typ.Elem()); fn != nil {
return ptrScanner(fn) return PtrScanner(fn)
} }
} }
@ -84,6 +95,8 @@ func Scanner(typ reflect.Type) ScannerFunc {
return scanIP return scanIP
case ipNetType: case ipNetType:
return scanIPNet return scanIPNet
case bytesType:
return scanBytes
case jsonRawMessageType: case jsonRawMessageType:
return scanJSONRawMessage return scanJSONRawMessage
} }
@ -196,6 +209,21 @@ func scanString(dest reflect.Value, src interface{}) error {
return fmt.Errorf("bun: can't scan %#v into %s", src, dest.Type()) return fmt.Errorf("bun: can't scan %#v into %s", src, dest.Type())
} }
func scanBytes(dest reflect.Value, src interface{}) error {
switch src := src.(type) {
case nil:
dest.SetBytes(nil)
return nil
case string:
dest.SetBytes([]byte(src))
return nil
case []byte:
dest.SetBytes(src)
return nil
}
return fmt.Errorf("bun: can't scan %#v into %s", src, dest.Type())
}
func scanTime(dest reflect.Value, src interface{}) error { func scanTime(dest reflect.Value, src interface{}) error {
switch src := src.(type) { switch src := src.(type) {
case nil: case nil:
@ -352,7 +380,7 @@ func toBytes(src interface{}) ([]byte, error) {
} }
} }
func ptrScanner(fn ScannerFunc) ScannerFunc { func PtrScanner(fn ScannerFunc) ScannerFunc {
return func(dest reflect.Value, src interface{}) error { return func(dest reflect.Value, src interface{}) error {
if src == nil { if src == nil {
if !dest.CanAddr() { if !dest.CanAddr() {
@ -383,6 +411,43 @@ func scanNull(dest reflect.Value) error {
return nil return nil
} }
func scanJSONIntoInterface(dest reflect.Value, src interface{}) error {
if dest.IsNil() {
if src == nil {
return nil
}
b, err := toBytes(src)
if err != nil {
return err
}
return bunjson.Unmarshal(b, dest.Addr().Interface())
}
dest = dest.Elem()
if fn := Scanner(dest.Type()); fn != nil {
return fn(dest, src)
}
return fmt.Errorf("bun: can't scan %#v into %s", src, dest.Type())
}
func scanInterface(dest reflect.Value, src interface{}) error {
if dest.IsNil() {
if src == nil {
return nil
}
dest.Set(reflect.ValueOf(src))
return nil
}
dest = dest.Elem()
if fn := Scanner(dest.Type()); fn != nil {
return fn(dest, src)
}
return fmt.Errorf("bun: can't scan %#v into %s", src, dest.Type())
}
func nilable(kind reflect.Kind) bool { func nilable(kind reflect.Kind) bool {
switch kind { switch kind {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:

View File

@ -40,7 +40,7 @@ type QueryWithArgs struct {
var _ QueryAppender = QueryWithArgs{} var _ QueryAppender = QueryWithArgs{}
func SafeQuery(query string, args []interface{}) QueryWithArgs { func SafeQuery(query string, args []interface{}) QueryWithArgs {
if query != "" && args == nil { if args == nil {
args = make([]interface{}, 0) args = make([]interface{}, 0)
} }
return QueryWithArgs{Query: query, Args: args} return QueryWithArgs{Query: query, Args: args}

View File

@ -40,15 +40,12 @@ var sqlTypes = []string{
reflect.Complex64: "", reflect.Complex64: "",
reflect.Complex128: "", reflect.Complex128: "",
reflect.Array: "", reflect.Array: "",
reflect.Chan: "",
reflect.Func: "",
reflect.Interface: "", reflect.Interface: "",
reflect.Map: sqltype.VarChar, reflect.Map: sqltype.VarChar,
reflect.Ptr: "", reflect.Ptr: "",
reflect.Slice: sqltype.VarChar, reflect.Slice: sqltype.VarChar,
reflect.String: sqltype.VarChar, reflect.String: sqltype.VarChar,
reflect.Struct: sqltype.VarChar, reflect.Struct: sqltype.VarChar,
reflect.UnsafePointer: "",
} }
func DiscoverSQLType(typ reflect.Type) string { func DiscoverSQLType(typ reflect.Type) string {

View File

@ -60,10 +60,9 @@ type Table struct {
Unique map[string][]*Field Unique map[string][]*Field
SoftDeleteField *Field SoftDeleteField *Field
UpdateSoftDeleteField func(fv reflect.Value) error UpdateSoftDeleteField func(fv reflect.Value, tm time.Time) error
allFields []*Field // read only allFields []*Field // read only
skippedFields []*Field
flags internal.Flag flags internal.Flag
} }
@ -104,9 +103,7 @@ func (t *Table) init1() {
} }
func (t *Table) init2() { func (t *Table) init2() {
t.initInlines()
t.initRelations() t.initRelations()
t.skippedFields = nil
} }
func (t *Table) setName(name string) { func (t *Table) setName(name string) {
@ -207,15 +204,20 @@ func (t *Table) initFields() {
func (t *Table) addFields(typ reflect.Type, baseIndex []int) { func (t *Table) addFields(typ reflect.Type, baseIndex []int) {
for i := 0; i < typ.NumField(); i++ { for i := 0; i < typ.NumField(); i++ {
f := typ.Field(i) f := typ.Field(i)
unexported := f.PkgPath != ""
// Make a copy so slice is not shared between fields. if unexported && !f.Anonymous { // unexported
continue
}
if f.Tag.Get("bun") == "-" {
continue
}
// Make a copy so the slice is not shared between fields.
index := make([]int, len(baseIndex)) index := make([]int, len(baseIndex))
copy(index, baseIndex) copy(index, baseIndex)
if f.Anonymous { if f.Anonymous {
if f.Tag.Get("bun") == "-" {
continue
}
if f.Name == "BaseModel" && f.Type == baseModelType { if f.Name == "BaseModel" && f.Type == baseModelType {
if len(index) == 0 { if len(index) == 0 {
t.processBaseModelField(f) t.processBaseModelField(f)
@ -243,8 +245,7 @@ func (t *Table) addFields(typ reflect.Type, baseIndex []int) {
continue continue
} }
field := t.newField(f, index) if field := t.newField(f, index); field != nil {
if field != nil {
t.addField(field) t.addField(field)
} }
} }
@ -284,11 +285,10 @@ func (t *Table) processBaseModelField(f reflect.StructField) {
func (t *Table) newField(f reflect.StructField, index []int) *Field { func (t *Table) newField(f reflect.StructField, index []int) *Field {
tag := tagparser.Parse(f.Tag.Get("bun")) tag := tagparser.Parse(f.Tag.Get("bun"))
if f.PkgPath != "" {
return nil
}
sqlName := internal.Underscore(f.Name) sqlName := internal.Underscore(f.Name)
if tag.Name != "" {
sqlName = tag.Name
}
if tag.Name != sqlName && isKnownFieldOption(tag.Name) { if tag.Name != sqlName && isKnownFieldOption(tag.Name) {
internal.Warn.Printf( internal.Warn.Printf(
@ -303,11 +303,6 @@ func (t *Table) newField(f reflect.StructField, index []int) *Field {
} }
} }
skip := tag.Name == "-"
if !skip && tag.Name != "" {
sqlName = tag.Name
}
index = append(index, f.Index...) index = append(index, f.Index...)
if field := t.fieldWithLock(sqlName); field != nil { if field := t.fieldWithLock(sqlName); field != nil {
if indexEqual(field.Index, index) { if indexEqual(field.Index, index) {
@ -371,9 +366,11 @@ func (t *Table) newField(f reflect.StructField, index []int) *Field {
} }
t.allFields = append(t.allFields, field) t.allFields = append(t.allFields, field)
if skip { if tag.HasOption("scanonly") {
t.skippedFields = append(t.skippedFields, field)
t.FieldMap[field.Name] = field t.FieldMap[field.Name] = field
if field.IndirectType.Kind() == reflect.Struct {
t.inlineFields(field, nil)
}
return nil return nil
} }
@ -386,14 +383,6 @@ func (t *Table) newField(f reflect.StructField, index []int) *Field {
return field return field
} }
func (t *Table) initInlines() {
for _, f := range t.skippedFields {
if f.IndirectType.Kind() == reflect.Struct {
t.inlineFields(f, nil)
}
}
}
//--------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------
func (t *Table) initRelations() { func (t *Table) initRelations() {
@ -745,17 +734,15 @@ func (t *Table) m2mRelation(field *Field) *Relation {
return rel return rel
} }
func (t *Table) inlineFields(field *Field, path map[reflect.Type]struct{}) { func (t *Table) inlineFields(field *Field, seen map[reflect.Type]struct{}) {
if path == nil { if seen == nil {
path = map[reflect.Type]struct{}{ seen = map[reflect.Type]struct{}{t.Type: {}}
t.Type: {},
}
} }
if _, ok := path[field.IndirectType]; ok { if _, ok := seen[field.IndirectType]; ok {
return return
} }
path[field.IndirectType] = struct{}{} seen[field.IndirectType] = struct{}{}
joinTable := t.dialect.Tables().Ref(field.IndirectType) joinTable := t.dialect.Tables().Ref(field.IndirectType)
for _, f := range joinTable.allFields { for _, f := range joinTable.allFields {
@ -775,8 +762,8 @@ func (t *Table) inlineFields(field *Field, path map[reflect.Type]struct{}) {
continue continue
} }
if _, ok := path[f.IndirectType]; !ok { if _, ok := seen[f.IndirectType]; !ok {
t.inlineFields(f, path) t.inlineFields(f, seen)
} }
} }
} }
@ -784,9 +771,6 @@ func (t *Table) inlineFields(field *Field, path map[reflect.Type]struct{}) {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
func (t *Table) Dialect() Dialect { return t.dialect } func (t *Table) Dialect() Dialect { return t.dialect }
//------------------------------------------------------------------------------
func (t *Table) HasBeforeScanHook() bool { return t.flags.Has(beforeScanHookFlag) } func (t *Table) HasBeforeScanHook() bool { return t.flags.Has(beforeScanHookFlag) }
func (t *Table) HasAfterScanHook() bool { return t.flags.Has(afterScanHookFlag) } func (t *Table) HasAfterScanHook() bool { return t.flags.Has(afterScanHookFlag) }
@ -845,6 +829,7 @@ func isKnownFieldOption(name string) bool {
"default", "default",
"unique", "unique",
"soft_delete", "soft_delete",
"scanonly",
"pk", "pk",
"autoincrement", "autoincrement",
@ -883,35 +868,35 @@ func parseRelationJoin(join string) ([]string, []string) {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
func softDeleteFieldUpdater(field *Field) func(fv reflect.Value) error { func softDeleteFieldUpdater(field *Field) func(fv reflect.Value, tm time.Time) error {
typ := field.StructField.Type typ := field.StructField.Type
switch typ { switch typ {
case timeType: case timeType:
return func(fv reflect.Value) error { return func(fv reflect.Value, tm time.Time) error {
ptr := fv.Addr().Interface().(*time.Time) ptr := fv.Addr().Interface().(*time.Time)
*ptr = time.Now() *ptr = tm
return nil return nil
} }
case nullTimeType: case nullTimeType:
return func(fv reflect.Value) error { return func(fv reflect.Value, tm time.Time) error {
ptr := fv.Addr().Interface().(*sql.NullTime) ptr := fv.Addr().Interface().(*sql.NullTime)
*ptr = sql.NullTime{Time: time.Now()} *ptr = sql.NullTime{Time: tm}
return nil return nil
} }
case nullIntType: case nullIntType:
return func(fv reflect.Value) error { return func(fv reflect.Value, tm time.Time) error {
ptr := fv.Addr().Interface().(*sql.NullInt64) ptr := fv.Addr().Interface().(*sql.NullInt64)
*ptr = sql.NullInt64{Int64: time.Now().UnixNano()} *ptr = sql.NullInt64{Int64: tm.UnixNano()}
return nil return nil
} }
} }
switch field.IndirectType.Kind() { switch field.IndirectType.Kind() {
case reflect.Int64: case reflect.Int64:
return func(fv reflect.Value) error { return func(fv reflect.Value, tm time.Time) error {
ptr := fv.Addr().Interface().(*int64) ptr := fv.Addr().Interface().(*int64)
*ptr = time.Now().UnixNano() *ptr = tm.UnixNano()
return nil return nil
} }
case reflect.Ptr: case reflect.Ptr:
@ -922,17 +907,16 @@ func softDeleteFieldUpdater(field *Field) func(fv reflect.Value) error {
switch typ { //nolint:gocritic switch typ { //nolint:gocritic
case timeType: case timeType:
return func(fv reflect.Value) error { return func(fv reflect.Value, tm time.Time) error {
now := time.Now() fv.Set(reflect.ValueOf(&tm))
fv.Set(reflect.ValueOf(&now))
return nil return nil
} }
} }
switch typ.Kind() { //nolint:gocritic switch typ.Kind() { //nolint:gocritic
case reflect.Int64: case reflect.Int64:
return func(fv reflect.Value) error { return func(fv reflect.Value, tm time.Time) error {
utime := time.Now().UnixNano() utime := tm.UnixNano()
fv.Set(reflect.ValueOf(&utime)) fv.Set(reflect.ValueOf(&utime))
return nil return nil
} }
@ -941,8 +925,8 @@ func softDeleteFieldUpdater(field *Field) func(fv reflect.Value) error {
return softDeleteFieldUpdaterFallback(field) return softDeleteFieldUpdaterFallback(field)
} }
func softDeleteFieldUpdaterFallback(field *Field) func(fv reflect.Value) error { func softDeleteFieldUpdaterFallback(field *Field) func(fv reflect.Value, tm time.Time) error {
return func(fv reflect.Value) error { return func(fv reflect.Value, tm time.Time) error {
return field.ScanWithCheck(fv, time.Now()) return field.ScanWithCheck(fv, tm)
} }
} }

View File

@ -67,6 +67,7 @@ func (t *Tables) Ref(typ reflect.Type) *Table {
} }
func (t *Tables) table(typ reflect.Type, allowInProgress bool) *Table { func (t *Tables) table(typ reflect.Type, allowInProgress bool) *Table {
typ = indirectType(typ)
if typ.Kind() != reflect.Struct { if typ.Kind() != reflect.Struct {
panic(fmt.Errorf("got %s, wanted %s", typ.Kind(), reflect.Struct)) panic(fmt.Errorf("got %s, wanted %s", typ.Kind(), reflect.Struct))
} }

View File

@ -2,5 +2,5 @@ package bun
// Version is the current release version. // Version is the current release version.
func Version() string { func Version() string {
return "0.4.3" return "1.0.4"
} }

2
vendor/modules.txt vendored
View File

@ -394,7 +394,7 @@ github.com/tdewolff/parse/v2/strconv
github.com/tmthrgd/go-hex github.com/tmthrgd/go-hex
# github.com/ugorji/go/codec v1.2.6 # github.com/ugorji/go/codec v1.2.6
github.com/ugorji/go/codec github.com/ugorji/go/codec
# github.com/uptrace/bun v0.4.3 # github.com/uptrace/bun v1.0.4
## explicit ## explicit
github.com/uptrace/bun github.com/uptrace/bun
github.com/uptrace/bun/dialect github.com/uptrace/bun/dialect