[chore] Update bun / sqlite versions; update gtsmodels (#754)

* upstep bun and sqlite versions

* allow specific columns to be updated in the db

* only update necessary columns for user

* bit tidier

* only update necessary fields of media_attachment

* only update relevant instance fields

* update tests

* update only specific account columns

* use bool pointers on gtsmodels
includes attachment, status, account, user

* update columns more selectively

* test all default fields on new account insert

* updating remaining bools on gtsmodels

* initialize pointer fields when extracting AP emoji

* copy bools properly

* add copyBoolPtr convenience function + test it

* initialize false bool ptrs a bit more neatly
This commit is contained in:
tobi
2022-08-15 12:35:05 +02:00
committed by GitHub
parent 52fe681ba2
commit ac6ed3d939
376 changed files with 337942 additions and 298092 deletions

View File

@ -1,18 +1,87 @@
# [](https://github.com/uptrace/bun/compare/v1.1.2...v) (2022-03-29)
## [1.1.7](https://github.com/uptrace/bun/compare/v1.1.6...v1.1.7) (2022-07-29)
### Bug Fixes
* fix panic message when has-many encounter an error ([cfd2747](https://github.com/uptrace/bun/commit/cfd27475fac89a1c8cf798bfa64898bd77bbba79))
* **migrate:** change rollback to match migrate behavior ([df5af9c](https://github.com/uptrace/bun/commit/df5af9c9cbdf54ce243e037bbb2c7b154f8422b3))
* change ScanAndCount without a limit to select all rows ([de5c570](https://github.com/uptrace/bun/commit/de5c5704166563aea41a82f7863f2db88ff108e2))
## [1.1.6](https://github.com/uptrace/bun/compare/v1.1.5...v1.1.6) (2022-07-10)
### Bug Fixes
* bunotel add set attributes to query metrics ([dae82cc](https://github.com/uptrace/bun/commit/dae82cc0e3af49be1e474027b55c34364676985d))
* **db.ScanRows:** ensure rows.Close is called ([9ffbc6a](https://github.com/uptrace/bun/commit/9ffbc6a46e24b908742b6973f33ef8e5b17cc12b))
* merge apply ([3081849](https://github.com/uptrace/bun/commit/30818499eacddd3b1a3e749091ba6a1468125641))
* **migrate:** close conn/tx on error ([7b168ea](https://github.com/uptrace/bun/commit/7b168eabfe0f844bcbf8dc89629d04c385b9f58c))
* **migrate:** type Migration should be used as a value rather than a pointer ([fb43935](https://github.com/uptrace/bun/commit/fb4393582b49fe528800a66aac5fb1c9a6033048))
* **migrate:** type MigrationGroup should be used as a value rather than a pointer ([649da1b](https://github.com/uptrace/bun/commit/649da1b3c158060add9b61b32c289260daafa65a))
* mssql cursor pagination ([#589](https://github.com/uptrace/bun/issues/589)) ([b34ec97](https://github.com/uptrace/bun/commit/b34ec97ddda95629f73762721d60fd3e00e7e99f))
### Features
* added QueryBuilder interface for SelectQuery, UpdateQuery, DeleteQuery ([#499](https://github.com/uptrace/bun/issues/499)) ([59fef48](https://github.com/uptrace/bun/commit/59fef48f6b3ec7f32bdda779b6693c333ff1dfdb))
* "skipupdate" model field tag ([#565](https://github.com/uptrace/bun/issues/565)) ([9288294](https://github.com/uptrace/bun/commit/928829482c718a0c215aa4f4adfa6f3fb3ed4302))
* add pgdriver write error to log ([5ddda3d](https://github.com/uptrace/bun/commit/5ddda3de31cd08ceee4bdea64ceae8d15eace07b))
* add query string representation ([520da7e](https://github.com/uptrace/bun/commit/520da7e1d6dbf7b06846f6b39a7f99e8753c1466))
* add relation condition with tag ([fe5bbf6](https://github.com/uptrace/bun/commit/fe5bbf64f33d25b310e5510ece7d705b9eb3bfea))
* add support for ON UPDATE and ON DELETE rules on belongs-to relationships from struct tags ([#533](https://github.com/uptrace/bun/issues/533)) ([a327b2a](https://github.com/uptrace/bun/commit/a327b2ae216abb55a705626296c0cdbf8d648697))
* add tx methods to IDB ([#587](https://github.com/uptrace/bun/issues/587)) ([feab313](https://github.com/uptrace/bun/commit/feab313c0358200b6e270ac70f4551b011ab5276))
* added raw query calls ([#596](https://github.com/uptrace/bun/issues/596)) ([127644d](https://github.com/uptrace/bun/commit/127644d2eea443736fbd6bed3417595d439e4639))
* **bunotel:** add option to enable formatting of queries ([#547](https://github.com/uptrace/bun/issues/547)) ([b9c768c](https://github.com/uptrace/bun/commit/b9c768cec3b5dea36c3c9c344d1e76e0ffad1369))
* **config.go:** add sslrootcert support to DSN parameters ([3bd5d69](https://github.com/uptrace/bun/commit/3bd5d692d7df4f30d07b835d6a46fc7af382489a))
* create an extra module for newrelic ([#599](https://github.com/uptrace/bun/issues/599)) ([6c676ce](https://github.com/uptrace/bun/commit/6c676ce13f05fe763471fbec2d5a2db48bc88650))
* **migrate:** add WithMarkAppliedOnSuccess ([31b2cc4](https://github.com/uptrace/bun/commit/31b2cc4f5ccd794a436d081073d4974835d3780d))
* **pgdialect:** add hstore support ([66b44f7](https://github.com/uptrace/bun/commit/66b44f7c0edc205927fb8be96aaf263b31828fa1))
* **pgdialect:** add identity support ([646251e](https://github.com/uptrace/bun/commit/646251ec02a1e2ec717e907e6f128d8b51f17c6d))
* **pgdriver:** expose pgdriver.ParseTime ([405a7d7](https://github.com/uptrace/bun/commit/405a7d78d8f60cf27e8f175deaf95db5877d84be))
## [1.1.5](https://github.com/uptrace/bun/compare/v1.1.4...v1.1.5) (2022-05-12)
### Bug Fixes
* **driver/sqliteshim:** make it work with recent version of modernc sqlite ([2360584](https://github.com/uptrace/bun/commit/23605846c20684e39bf1eaac50a2147a1b68a729))
## [1.1.4](https://github.com/uptrace/bun/compare/v1.1.3...v1.1.4) (2022-04-20)
### Bug Fixes
* automatically set nullzero when there is default:value option ([72c44ae](https://github.com/uptrace/bun/commit/72c44aebbeec3a83ed97ea25a3262174d744df65))
* fix ForceDelete on live/undeleted rows ([1a33250](https://github.com/uptrace/bun/commit/1a33250f27f00e752a735ce10311ac95dcb0c968))
* fix OmitZero and value overriding ([087ea07](https://github.com/uptrace/bun/commit/087ea0730551f1e841bacb6ad2fa3afd512a1df8))
* rename Query to QueryBuilder ([98d111b](https://github.com/uptrace/bun/commit/98d111b7cc00fa61b6b2cec147f43285f4baadb4))
### Features
* add ApplyQueryBuilder ([582eca0](https://github.com/uptrace/bun/commit/582eca09cf2b59e67c2e4a2ad24f1a74cb53addd))
* **config.go:** add connect_timeout to DSN parsable params ([998b04d](https://github.com/uptrace/bun/commit/998b04d51a9a4f182ac3458f90db8dbf9185c4ba)), closes [#505](https://github.com/uptrace/bun/issues/505)
# [1.1.3](https://github.com/uptrace/bun/compare/v1.1.2...v) (2022-03-29)
### Bug Fixes
- fix panic message when has-many encounter an error
([cfd2747](https://github.com/uptrace/bun/commit/cfd27475fac89a1c8cf798bfa64898bd77bbba79))
- **migrate:** change rollback to match migrate behavior
([df5af9c](https://github.com/uptrace/bun/commit/df5af9c9cbdf54ce243e037bbb2c7b154f8422b3))
### Features
- added QueryBuilder interface for SelectQuery, UpdateQuery, DeleteQuery
([#499](https://github.com/uptrace/bun/issues/499))
([59fef48](https://github.com/uptrace/bun/commit/59fef48f6b3ec7f32bdda779b6693c333ff1dfdb))
# [1.1.2](https://github.com/uptrace/bun/compare/v1.1.2...v) (2022-03-22)
### Bug Fixes

View File

@ -1,13 +1,14 @@
# SQL-first Go ORM for PostgreSQL, MySQL, MSSQL, and SQLite
# SQL-first Golang ORM for PostgreSQL, MySQL, MSSQL, and SQLite
[![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)
[![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)
Bun is brought to you by :star: [**uptrace/uptrace**](https://github.com/uptrace/uptrace). Uptrace
is an open source and blazingly fast **distributed tracing** backend powered by OpenTelemetry and
ClickHouse. Give it a star as well!
> Bun is brought to you by :star: [**uptrace/uptrace**](https://github.com/uptrace/uptrace). Uptrace
> is an open source and blazingly fast
> [distributed tracing tool](https://get.uptrace.dev/compare/distributed-tracing-tools.html) powered
> by OpenTelemetry and ClickHouse. Give it a star as well!
## Features
@ -26,7 +27,7 @@ ClickHouse. Give it a star as well!
Resources:
- [**Get started**](https://bun.uptrace.dev/guide/getting-started.html)
- [**Get started**](https://bun.uptrace.dev/guide/golang-orm.html)
- [Examples](https://github.com/uptrace/bun/tree/master/example)
- [Discussions](https://github.com/uptrace/bun/discussions)
- [Chat](https://discord.gg/rWtp5Aj)
@ -253,7 +254,7 @@ topRegions := db.NewSelect().
TableExpr("regional_sales").
Where("total_sales > (SELECT SUM(total_sales) / 10 FROM regional_sales)")
var items map[string]interface{}
var items []map[string]interface{}
err := db.NewSelect().
With("regional_sales", regionalSales).
With("top_regions", topRegions).
@ -301,9 +302,15 @@ if err := db.NewSelect().Model(user1).Where("id = ?", 1).Scan(ctx); err != nil {
}
```
See [**Getting started**](https://bun.uptrace.dev/guide/getting-started.html) guide and check
See [**Getting started**](https://bun.uptrace.dev/guide/golang-orm.html) guide and check
[examples](example).
## See also
- [Golang HTTP router](https://github.com/uptrace/bunrouter)
- [Golang ClickHouse ORM](https://github.com/uptrace/go-clickhouse)
- [Golang msgpack](https://github.com/vmihailenco/msgpack)
## Contributors
Thanks to all the people who already contributed!

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

@ -2,7 +2,9 @@ package bun
import (
"context"
"crypto/rand"
"database/sql"
"encoding/hex"
"fmt"
"reflect"
"strings"
@ -141,13 +143,19 @@ func (db *DB) Dialect() schema.Dialect {
}
func (db *DB) ScanRows(ctx context.Context, rows *sql.Rows, dest ...interface{}) error {
defer rows.Close()
model, err := newModel(db, dest)
if err != nil {
return err
}
_, err = model.ScanRows(ctx, rows)
return err
if err != nil {
return err
}
return rows.Err()
}
func (db *DB) ScanRow(ctx context.Context, rows *sql.Rows, dest ...interface{}) error {
@ -362,6 +370,46 @@ func (c Conn) NewDropColumn() *DropColumnQuery {
return NewDropColumnQuery(c.db).Conn(c)
}
// RunInTx runs the function in a transaction. If the function returns an error,
// the transaction is rolled back. Otherwise, the transaction is committed.
func (c Conn) RunInTx(
ctx context.Context, opts *sql.TxOptions, fn func(ctx context.Context, tx Tx) error,
) error {
tx, err := c.BeginTx(ctx, opts)
if err != nil {
return err
}
var done bool
defer func() {
if !done {
_ = tx.Rollback()
}
}()
if err := fn(ctx, tx); err != nil {
return err
}
done = true
return tx.Commit()
}
func (c Conn) BeginTx(ctx context.Context, opts *sql.TxOptions) (Tx, error) {
ctx, event := c.db.beforeQuery(ctx, nil, "BEGIN", nil, "BEGIN", nil)
tx, err := c.Conn.BeginTx(ctx, opts)
c.db.afterQuery(ctx, event, nil, err)
if err != nil {
return Tx{}, err
}
return Tx{
ctx: ctx,
db: c.db,
Tx: tx,
}, nil
}
//------------------------------------------------------------------------------
type Stmt struct {
@ -385,6 +433,8 @@ func (db *DB) PrepareContext(ctx context.Context, query string) (Stmt, error) {
type Tx struct {
ctx context.Context
db *DB
// name is the name of a savepoint
name string
*sql.Tx
}
@ -433,19 +483,51 @@ func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (Tx, error) {
}
func (tx Tx) Commit() error {
if tx.name == "" {
return tx.commitTX()
}
return tx.commitSP()
}
func (tx Tx) commitTX() error {
ctx, event := tx.db.beforeQuery(tx.ctx, nil, "COMMIT", nil, "COMMIT", nil)
err := tx.Tx.Commit()
tx.db.afterQuery(ctx, event, nil, err)
return err
}
func (tx Tx) commitSP() error {
if tx.Dialect().Features().Has(feature.MSSavepoint) {
return nil
}
query := "RELEASE SAVEPOINT " + tx.name
_, err := tx.ExecContext(tx.ctx, query)
return err
}
func (tx Tx) Rollback() error {
if tx.name == "" {
return tx.rollbackTX()
}
return tx.rollbackSP()
}
func (tx Tx) rollbackTX() error {
ctx, event := tx.db.beforeQuery(tx.ctx, nil, "ROLLBACK", nil, "ROLLBACK", nil)
err := tx.Tx.Rollback()
tx.db.afterQuery(ctx, event, nil, err)
return err
}
func (tx Tx) rollbackSP() error {
query := "ROLLBACK TO SAVEPOINT " + tx.name
if tx.Dialect().Features().Has(feature.MSSavepoint) {
query = "ROLLBACK TRANSACTION " + tx.name
}
_, err := tx.ExecContext(tx.ctx, query)
return err
}
func (tx Tx) Exec(query string, args ...interface{}) (sql.Result, error) {
return tx.ExecContext(context.TODO(), query, args...)
}
@ -488,6 +570,60 @@ func (tx Tx) QueryRowContext(ctx context.Context, query string, args ...interfac
//------------------------------------------------------------------------------
func (tx Tx) Begin() (Tx, error) {
return tx.BeginTx(tx.ctx, nil)
}
// BeginTx will save a point in the running transaction.
func (tx Tx) BeginTx(ctx context.Context, _ *sql.TxOptions) (Tx, error) {
// mssql savepoint names are limited to 32 characters
sp := make([]byte, 14)
_, err := rand.Read(sp)
if err != nil {
return Tx{}, err
}
qName := "SP_" + hex.EncodeToString(sp)
query := "SAVEPOINT " + qName
if tx.Dialect().Features().Has(feature.MSSavepoint) {
query = "SAVE TRANSACTION " + qName
}
_, err = tx.ExecContext(ctx, query)
if err != nil {
return Tx{}, err
}
return Tx{
ctx: ctx,
db: tx.db,
Tx: tx.Tx,
name: qName,
}, nil
}
func (tx Tx) RunInTx(
ctx context.Context, _ *sql.TxOptions, fn func(ctx context.Context, tx Tx) error,
) error {
sp, err := tx.BeginTx(ctx, nil)
if err != nil {
return err
}
var done bool
defer func() {
if !done {
_ = sp.Rollback()
}
}()
if err := fn(ctx, sp); err != nil {
return err
}
done = true
return sp.Commit()
}
func (tx Tx) Dialect() schema.Dialect {
return tx.db.Dialect()
}

View File

@ -29,4 +29,6 @@ const (
OffsetFetch
SelectExists
UpdateFromTable
MSSavepoint
GeneratedIdentity
)

View File

@ -307,3 +307,58 @@ func arrayAppendString(b []byte, s string) []byte {
b = append(b, '"')
return b
}
//------------------------------------------------------------------------------
var mapStringStringType = reflect.TypeOf(map[string]string(nil))
func (d *Dialect) hstoreAppender(typ reflect.Type) schema.AppenderFunc {
kind := typ.Kind()
switch kind {
case reflect.Ptr:
if fn := d.hstoreAppender(typ.Elem()); fn != nil {
return schema.PtrAppender(fn)
}
case reflect.Map:
// ok:
default:
return nil
}
if typ.Key() == stringType && typ.Elem() == stringType {
return appendMapStringStringValue
}
return func(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
err := fmt.Errorf("bun: Hstore(unsupported %s)", v.Type())
return dialect.AppendError(b, err)
}
}
func appendMapStringString(b []byte, m map[string]string) []byte {
if m == nil {
return dialect.AppendNull(b)
}
b = append(b, '\'')
for key, value := range m {
b = arrayAppendString(b, key)
b = append(b, '=', '>')
b = arrayAppendString(b, value)
b = append(b, ',')
}
if len(m) > 0 {
b = b[:len(b)-1] // Strip trailing comma.
}
b = append(b, '\'')
return b
}
func appendMapStringStringValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
m := v.Convert(mapStringStringType).Interface().(map[string]string)
return appendMapStringString(b, m)
}

View File

@ -8,17 +8,13 @@ import (
)
type arrayParser struct {
b []byte
i int
buf []byte
*streamParser
err error
}
func newArrayParser(b []byte) *arrayParser {
p := &arrayParser{
b: b,
i: 1,
streamParser: newStreamParser(b, 1),
}
if len(b) < 2 || b[0] != '{' || b[len(b)-1] != '}' {
p.err = fmt.Errorf("bun: can't parse array: %q", b)
@ -135,31 +131,3 @@ func (p *arrayParser) readSubstring() ([]byte, error) {
return p.buf, nil
}
func (p *arrayParser) valid() bool {
return p.i < len(p.b)
}
func (p *arrayParser) readByte() (byte, error) {
if p.valid() {
c := p.b[p.i]
p.i++
return c, nil
}
return 0, io.EOF
}
func (p *arrayParser) unreadByte() {
p.i--
}
func (p *arrayParser) peek() byte {
if p.valid() {
return p.b[p.i]
}
return 0
}
func (p *arrayParser) skipNext() {
p.i++
}

View File

@ -46,7 +46,8 @@ func New() *Dialect {
feature.TableTruncate |
feature.TableNotExists |
feature.InsertOnConflict |
feature.SelectExists
feature.SelectExists |
feature.GeneratedIdentity
return d
}
@ -73,7 +74,7 @@ func (d *Dialect) OnTable(table *schema.Table) {
func (d *Dialect) onField(field *schema.Field) {
field.DiscoveredSQLType = fieldSQLType(field)
if field.AutoIncrement {
if field.AutoIncrement && !field.Identity {
switch field.DiscoveredSQLType {
case sqltype.SmallInt:
field.CreateTableSQLType = pgTypeSmallSerial
@ -88,6 +89,11 @@ func (d *Dialect) onField(field *schema.Field) {
field.Append = d.arrayAppender(field.StructField.Type)
field.Scan = arrayScanner(field.StructField.Type)
}
if field.DiscoveredSQLType == sqltype.HSTORE {
field.Append = d.hstoreAppender(field.StructField.Type)
field.Scan = hstoreScanner(field.StructField.Type)
}
}
func (d *Dialect) IdentQuote() byte {

View File

@ -0,0 +1,73 @@
package pgdialect
import (
"database/sql"
"fmt"
"reflect"
"github.com/uptrace/bun/schema"
)
type HStoreValue struct {
v reflect.Value
append schema.AppenderFunc
scan schema.ScannerFunc
}
// HStore accepts a map[string]string and returns a wrapper for working with PostgreSQL
// hstore data type.
//
// For struct fields you can use hstore tag:
//
// Attrs map[string]string `bun:",hstore"`
func HStore(vi interface{}) *HStoreValue {
v := reflect.ValueOf(vi)
if !v.IsValid() {
panic(fmt.Errorf("bun: HStore(nil)"))
}
typ := v.Type()
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
if typ.Kind() != reflect.Map {
panic(fmt.Errorf("bun: Hstore(unsupported %s)", typ))
}
return &HStoreValue{
v: v,
append: pgDialect.hstoreAppender(v.Type()),
scan: hstoreScanner(v.Type()),
}
}
var (
_ schema.QueryAppender = (*HStoreValue)(nil)
_ sql.Scanner = (*HStoreValue)(nil)
)
func (h *HStoreValue) AppendQuery(fmter schema.Formatter, b []byte) ([]byte, error) {
if h.append == nil {
panic(fmt.Errorf("bun: HStore(unsupported %s)", h.v.Type()))
}
return h.append(fmter, b, h.v), nil
}
func (h *HStoreValue) Scan(src interface{}) error {
if h.scan == nil {
return fmt.Errorf("bun: HStore(unsupported %s)", h.v.Type())
}
if h.v.Kind() != reflect.Ptr {
return fmt.Errorf("bun: HStore(non-pointer %s)", h.v.Type())
}
return h.scan(h.v.Elem(), src)
}
func (h *HStoreValue) Value() interface{} {
if h.v.IsValid() {
return h.v.Interface()
}
return nil
}

View File

@ -0,0 +1,142 @@
package pgdialect
import (
"bytes"
"fmt"
)
type hstoreParser struct {
*streamParser
err error
}
func newHStoreParser(b []byte) *hstoreParser {
p := &hstoreParser{
streamParser: newStreamParser(b, 0),
}
if len(b) < 6 || b[0] != '"' {
p.err = fmt.Errorf("bun: can't parse hstore: %q", b)
}
return p
}
func (p *hstoreParser) NextKey() (string, error) {
if p.err != nil {
return "", p.err
}
err := p.skipByte('"')
if err != nil {
return "", err
}
key, err := p.readSubstring()
if err != nil {
return "", err
}
const separator = "=>"
for i := range separator {
err = p.skipByte(separator[i])
if err != nil {
return "", err
}
}
return string(key), nil
}
func (p *hstoreParser) NextValue() (string, error) {
if p.err != nil {
return "", p.err
}
c, err := p.readByte()
if err != nil {
return "", err
}
switch c {
case '"':
value, err := p.readSubstring()
if err != nil {
return "", err
}
if p.peek() == ',' {
p.skipNext()
}
if p.peek() == ' ' {
p.skipNext()
}
return string(value), nil
default:
value := p.readSimple()
if bytes.Equal(value, []byte("NULL")) {
value = nil
}
if p.peek() == ',' {
p.skipNext()
}
return string(value), nil
}
}
func (p *hstoreParser) readSimple() []byte {
p.unreadByte()
if i := bytes.IndexByte(p.b[p.i:], ','); i >= 0 {
b := p.b[p.i : p.i+i]
p.i += i
return b
}
b := p.b[p.i:len(p.b)]
p.i = len(p.b)
return b
}
func (p *hstoreParser) readSubstring() ([]byte, error) {
c, err := p.readByte()
if err != nil {
return nil, err
}
p.buf = p.buf[:0]
for {
if c == '"' {
break
}
next, err := p.readByte()
if err != nil {
return nil, err
}
if c == '\\' {
switch next {
case '\\', '"':
p.buf = append(p.buf, next)
c, err = p.readByte()
if err != nil {
return nil, err
}
default:
p.buf = append(p.buf, '\\')
c = next
}
continue
}
p.buf = append(p.buf, c)
c = next
}
return p.buf, nil
}

View File

@ -0,0 +1,82 @@
package pgdialect
import (
"fmt"
"io"
"reflect"
"github.com/uptrace/bun/schema"
)
func hstoreScanner(typ reflect.Type) schema.ScannerFunc {
kind := typ.Kind()
switch kind {
case reflect.Ptr:
if fn := hstoreScanner(typ.Elem()); fn != nil {
return schema.PtrScanner(fn)
}
case reflect.Map:
// ok:
default:
return nil
}
if typ.Key() == stringType && typ.Elem() == stringType {
return scanMapStringStringValue
}
return func(dest reflect.Value, src interface{}) error {
return fmt.Errorf("bun: Hstore(unsupported %s)", dest.Type())
}
}
func scanMapStringStringValue(dest reflect.Value, src interface{}) error {
dest = reflect.Indirect(dest)
if !dest.CanSet() {
return fmt.Errorf("bun: Scan(non-settable %s)", dest.Type())
}
m, err := decodeMapStringString(src)
if err != nil {
return err
}
dest.Set(reflect.ValueOf(m))
return nil
}
func decodeMapStringString(src interface{}) (map[string]string, error) {
if src == nil {
return nil, nil
}
b, err := toBytes(src)
if err != nil {
return nil, err
}
m := make(map[string]string)
p := newHStoreParser(b)
for {
key, err := p.NextKey()
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
value, err := p.NextValue()
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
m[key] = value
}
return m, nil
}

View File

@ -53,11 +53,11 @@ func fieldSQLType(field *schema.Field) string {
if v, ok := field.Tag.Option("composite"); ok {
return v
}
if _, ok := field.Tag.Option("hstore"); ok {
return "hstore"
if field.Tag.HasOption("hstore") {
return sqltype.HSTORE
}
if _, ok := field.Tag.Options["array"]; ok {
if field.Tag.HasOption("array") {
switch field.IndirectType.Kind() {
case reflect.Slice, reflect.Array:
sqlType := sqlType(field.IndirectType.Elem())

View File

@ -0,0 +1,60 @@
package pgdialect
import (
"fmt"
"io"
)
type streamParser struct {
b []byte
i int
buf []byte
}
func newStreamParser(b []byte, start int) *streamParser {
return &streamParser{
b: b,
i: start,
}
}
func (p *streamParser) valid() bool {
return p.i < len(p.b)
}
func (p *streamParser) skipByte(skip byte) error {
c, err := p.readByte()
if err != nil {
return err
}
if c == skip {
return nil
}
p.unreadByte()
return fmt.Errorf("got %q, wanted %q", c, skip)
}
func (p *streamParser) readByte() (byte, error) {
if p.valid() {
c := p.b[p.i]
p.i++
return c, nil
}
return 0, io.EOF
}
func (p *streamParser) unreadByte() {
p.i--
}
func (p *streamParser) peek() byte {
if p.valid() {
return p.b[p.i]
}
return 0
}
func (p *streamParser) skipNext() {
p.i++
}

View File

@ -2,5 +2,5 @@ package pgdialect
// Version is the current release version.
func Version() string {
return "1.1.3"
return "1.1.7"
}

View File

@ -2,5 +2,5 @@ package sqlitedialect
// Version is the current release version.
func Version() string {
return "1.1.3"
return "1.1.7"
}

View File

@ -12,4 +12,5 @@ const (
Timestamp = "TIMESTAMP"
JSON = "JSON"
JSONB = "JSONB"
HSTORE = "HSTORE"
)

View File

@ -25,11 +25,11 @@ type Migration struct {
Down MigrationFunc `bun:"-"`
}
func (m *Migration) String() string {
func (m Migration) String() string {
return m.Name
}
func (m *Migration) IsApplied() bool {
func (m Migration) IsApplied() bool {
return m.ID > 0
}
@ -89,6 +89,22 @@ func NewSQLMigrationFunc(fsys fs.FS, name string) MigrationFunc {
idb = conn
}
var retErr error
defer func() {
if tx, ok := idb.(bun.Tx); ok {
retErr = tx.Commit()
return
}
if conn, ok := idb.(bun.Conn); ok {
retErr = conn.Close()
return
}
panic("not reached")
}()
for _, q := range queries {
_, err = idb.ExecContext(ctx, q)
if err != nil {
@ -96,13 +112,7 @@ func NewSQLMigrationFunc(fsys fs.FS, name string) MigrationFunc {
}
}
if tx, ok := idb.(bun.Tx); ok {
return tx.Commit()
} else if conn, ok := idb.(bun.Conn); ok {
return conn.Close()
}
panic("not reached")
return retErr
}
}
@ -222,11 +232,11 @@ type MigrationGroup struct {
Migrations MigrationSlice
}
func (g *MigrationGroup) IsZero() bool {
func (g MigrationGroup) IsZero() bool {
return g.ID == 0 && len(g.Migrations) == 0
}
func (g *MigrationGroup) String() string {
func (g MigrationGroup) String() string {
if g.IsZero() {
return "nil"
}

View File

@ -26,14 +26,23 @@ func WithLocksTableName(table string) MigratorOption {
}
}
// WithMarkAppliedOnSuccess sets the migrator to only mark migrations as applied/unapplied
// when their up/down is successful
func WithMarkAppliedOnSuccess(enabled bool) MigratorOption {
return func(m *Migrator) {
m.markAppliedOnSuccess = enabled
}
}
type Migrator struct {
db *bun.DB
migrations *Migrations
ms MigrationSlice
table string
locksTable string
table string
locksTable string
markAppliedOnSuccess bool
}
func NewMigrator(db *bun.DB, migrations *Migrations, opts ...MigratorOption) *Migrator {
@ -148,9 +157,10 @@ func (m *Migrator) Migrate(ctx context.Context, opts ...MigrationOption) (*Migra
migration := &migrations[i]
migration.GroupID = group.ID
// Always mark migration as applied so the rollback has a chance to fix the database.
if err := m.MarkApplied(ctx, migration); err != nil {
return group, err
if !m.markAppliedOnSuccess {
if err := m.MarkApplied(ctx, migration); err != nil {
return group, err
}
}
group.Migrations = migrations[:i+1]
@ -160,6 +170,12 @@ func (m *Migrator) Migrate(ctx context.Context, opts ...MigrationOption) (*Migra
return group, err
}
}
if m.markAppliedOnSuccess {
if err := m.MarkApplied(ctx, migration); err != nil {
return group, err
}
}
}
return group, nil
@ -187,14 +203,21 @@ func (m *Migrator) Rollback(ctx context.Context, opts ...MigrationOption) (*Migr
for i := len(lastGroup.Migrations) - 1; i >= 0; i-- {
migration := &lastGroup.Migrations[i]
// Always mark migration as unapplied to match migrate behavior.
if err := m.MarkUnapplied(ctx, migration); err != nil {
return nil, err
if !m.markAppliedOnSuccess {
if err := m.MarkUnapplied(ctx, migration); err != nil {
return lastGroup, err
}
}
if !cfg.nop && migration.Down != nil {
if err := migration.Down(ctx, m.db); err != nil {
return nil, err
return lastGroup, err
}
}
if m.markAppliedOnSuccess {
if err := m.MarkUnapplied(ctx, migration); err != nil {
return lastGroup, err
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "uptrace/bun",
"version": "1.1.3",
"name": "gobun",
"version": "1.1.7",
"main": "index.js",
"repository": "git@github.com:uptrace/bun.git",
"author": "Vladimir Mihailenco <vladimir.webdev@gmail.com>",

View File

@ -57,6 +57,9 @@ type IDB interface {
NewTruncateTable() *TruncateTableQuery
NewAddColumn() *AddColumnQuery
NewDropColumn() *DropColumnQuery
BeginTx(ctx context.Context, opts *sql.TxOptions) (Tx, error)
RunInTx(ctx context.Context, opts *sql.TxOptions, f func(ctx context.Context, tx Tx) error) error
}
var (
@ -227,13 +230,14 @@ func (q *baseQuery) whereAllWithDeleted() {
q.setErr(err)
return
}
q.flags = q.flags.Set(allWithDeletedFlag)
q.flags = q.flags.Remove(deletedFlag)
q.flags = q.flags.Set(allWithDeletedFlag).Remove(deletedFlag)
}
func (q *baseQuery) isSoftDelete() bool {
if q.table != nil {
return q.table.SoftDeleteField != nil && !q.flags.Has(allWithDeletedFlag)
return q.table.SoftDeleteField != nil &&
!q.flags.Has(allWithDeletedFlag) &&
!q.flags.Has(forceDeleteFlag)
}
return false
}
@ -1095,3 +1099,235 @@ func (q cascadeQuery) appendCascade(fmter schema.Formatter, b []byte) []byte {
}
return b
}
//------------------------------------------------------------------------------
type idxHintsQuery struct {
use *indexHints
ignore *indexHints
force *indexHints
}
type indexHints struct {
names []schema.QueryWithArgs
forJoin []schema.QueryWithArgs
forOrderBy []schema.QueryWithArgs
forGroupBy []schema.QueryWithArgs
}
func (ih *idxHintsQuery) lazyUse() *indexHints {
if ih.use == nil {
ih.use = new(indexHints)
}
return ih.use
}
func (ih *idxHintsQuery) lazyIgnore() *indexHints {
if ih.ignore == nil {
ih.ignore = new(indexHints)
}
return ih.ignore
}
func (ih *idxHintsQuery) lazyForce() *indexHints {
if ih.force == nil {
ih.force = new(indexHints)
}
return ih.force
}
func (ih *idxHintsQuery) appendIndexes(hints []schema.QueryWithArgs, indexes ...string) []schema.QueryWithArgs {
for _, idx := range indexes {
hints = append(hints, schema.UnsafeIdent(idx))
}
return hints
}
func (ih *idxHintsQuery) addUseIndex(indexes ...string) {
if len(indexes) == 0 {
return
}
ih.lazyUse().names = ih.appendIndexes(ih.use.names, indexes...)
}
func (ih *idxHintsQuery) addUseIndexForJoin(indexes ...string) {
if len(indexes) == 0 {
return
}
ih.lazyUse().forJoin = ih.appendIndexes(ih.use.forJoin, indexes...)
}
func (ih *idxHintsQuery) addUseIndexForOrderBy(indexes ...string) {
if len(indexes) == 0 {
return
}
ih.lazyUse().forOrderBy = ih.appendIndexes(ih.use.forOrderBy, indexes...)
}
func (ih *idxHintsQuery) addUseIndexForGroupBy(indexes ...string) {
if len(indexes) == 0 {
return
}
ih.lazyUse().forGroupBy = ih.appendIndexes(ih.use.forGroupBy, indexes...)
}
func (ih *idxHintsQuery) addIgnoreIndex(indexes ...string) {
if len(indexes) == 0 {
return
}
ih.lazyIgnore().names = ih.appendIndexes(ih.ignore.names, indexes...)
}
func (ih *idxHintsQuery) addIgnoreIndexForJoin(indexes ...string) {
if len(indexes) == 0 {
return
}
ih.lazyIgnore().forJoin = ih.appendIndexes(ih.ignore.forJoin, indexes...)
}
func (ih *idxHintsQuery) addIgnoreIndexForOrderBy(indexes ...string) {
if len(indexes) == 0 {
return
}
ih.lazyIgnore().forOrderBy = ih.appendIndexes(ih.ignore.forOrderBy, indexes...)
}
func (ih *idxHintsQuery) addIgnoreIndexForGroupBy(indexes ...string) {
if len(indexes) == 0 {
return
}
ih.lazyIgnore().forGroupBy = ih.appendIndexes(ih.ignore.forGroupBy, indexes...)
}
func (ih *idxHintsQuery) addForceIndex(indexes ...string) {
if len(indexes) == 0 {
return
}
ih.lazyForce().names = ih.appendIndexes(ih.force.names, indexes...)
}
func (ih *idxHintsQuery) addForceIndexForJoin(indexes ...string) {
if len(indexes) == 0 {
return
}
ih.lazyForce().forJoin = ih.appendIndexes(ih.force.forJoin, indexes...)
}
func (ih *idxHintsQuery) addForceIndexForOrderBy(indexes ...string) {
if len(indexes) == 0 {
return
}
ih.lazyForce().forOrderBy = ih.appendIndexes(ih.force.forOrderBy, indexes...)
}
func (ih *idxHintsQuery) addForceIndexForGroupBy(indexes ...string) {
if len(indexes) == 0 {
return
}
ih.lazyForce().forGroupBy = ih.appendIndexes(ih.force.forGroupBy, indexes...)
}
func (ih *idxHintsQuery) appendIndexHints(
fmter schema.Formatter, b []byte,
) ([]byte, error) {
type IdxHint struct {
Name string
Values []schema.QueryWithArgs
}
var hints []IdxHint
if ih.use != nil {
hints = append(hints, []IdxHint{
{
Name: "USE INDEX",
Values: ih.use.names,
},
{
Name: "USE INDEX FOR JOIN",
Values: ih.use.forJoin,
},
{
Name: "USE INDEX FOR ORDER BY",
Values: ih.use.forOrderBy,
},
{
Name: "USE INDEX FOR GROUP BY",
Values: ih.use.forGroupBy,
},
}...)
}
if ih.ignore != nil {
hints = append(hints, []IdxHint{
{
Name: "IGNORE INDEX",
Values: ih.ignore.names,
},
{
Name: "IGNORE INDEX FOR JOIN",
Values: ih.ignore.forJoin,
},
{
Name: "IGNORE INDEX FOR ORDER BY",
Values: ih.ignore.forOrderBy,
},
{
Name: "IGNORE INDEX FOR GROUP BY",
Values: ih.ignore.forGroupBy,
},
}...)
}
if ih.force != nil {
hints = append(hints, []IdxHint{
{
Name: "FORCE INDEX",
Values: ih.force.names,
},
{
Name: "FORCE INDEX FOR JOIN",
Values: ih.force.forJoin,
},
{
Name: "FORCE INDEX FOR ORDER BY",
Values: ih.force.forOrderBy,
},
{
Name: "FORCE INDEX FOR GROUP BY",
Values: ih.force.forGroupBy,
},
}...)
}
var err error
for _, h := range hints {
b, err = ih.bufIndexHint(h.Name, h.Values, fmter, b)
if err != nil {
return nil, err
}
}
return b, nil
}
func (ih *idxHintsQuery) bufIndexHint(
name string,
hints []schema.QueryWithArgs,
fmter schema.Formatter, b []byte,
) ([]byte, error) {
var err error
if len(hints) == 0 {
return b, nil
}
b = append(b, fmt.Sprintf(" %s (", name)...)
for i, f := range hints {
if i > 0 {
b = append(b, ", "...)
}
b, err = f.AppendQuery(fmter, b)
if err != nil {
return nil, err
}
}
b = append(b, ")"...)
return b, nil
}

View File

@ -287,12 +287,32 @@ func (q *DeleteQuery) afterDeleteHook(ctx context.Context) error {
return nil
}
func (q *DeleteQuery) String() string {
buf, err := q.AppendQuery(q.db.Formatter(), nil)
if err != nil {
panic(err)
}
return string(buf)
}
//------------------------------------------------------------------------------
func (q *DeleteQuery) QueryBuilder() QueryBuilder {
return &deleteQueryBuilder{q}
}
func (q *DeleteQuery) ApplyQueryBuilder(fn func(QueryBuilder) QueryBuilder) *DeleteQuery {
return fn(q.QueryBuilder()).Unwrap().(*DeleteQuery)
}
type deleteQueryBuilder struct {
*DeleteQuery
}
func (q *deleteQueryBuilder) WhereGroup(sep string, fn func(QueryBuilder) QueryBuilder) QueryBuilder {
func (q *deleteQueryBuilder) WhereGroup(
sep string, fn func(QueryBuilder) QueryBuilder,
) QueryBuilder {
q.DeleteQuery = q.DeleteQuery.WhereGroup(sep, func(qs *DeleteQuery) *DeleteQuery {
return fn(q).(*deleteQueryBuilder).DeleteQuery
})
@ -327,7 +347,3 @@ func (q *deleteQueryBuilder) WherePK(cols ...string) QueryBuilder {
func (q *deleteQueryBuilder) Unwrap() interface{} {
return q.DeleteQuery
}
func (q *DeleteQuery) Query() QueryBuilder {
return &deleteQueryBuilder{q}
}

View File

@ -641,3 +641,12 @@ func (q *InsertQuery) tryLastInsertID(res sql.Result, dest []interface{}) error
return nil
}
func (q *InsertQuery) String() string {
buf, err := q.AppendQuery(q.db.Formatter(), nil)
if err != nil {
panic(err)
}
return string(buf)
}

48
vendor/github.com/uptrace/bun/query_raw.go generated vendored Normal file
View File

@ -0,0 +1,48 @@
package bun
import (
"context"
"github.com/uptrace/bun/schema"
)
type RawQuery struct {
baseQuery
query string
args []interface{}
}
func (db *DB) Raw(query string, args ...interface{}) *RawQuery {
return &RawQuery{
baseQuery: baseQuery{
db: db,
conn: db.DB,
},
query: query,
args: args,
}
}
func (q *RawQuery) Scan(ctx context.Context, dest ...interface{}) error {
if q.err != nil {
return q.err
}
model, err := q.getModel(dest)
if err != nil {
return err
}
query := q.db.format(q.query, q.args)
_, err = q.scan(ctx, q, query, model, true)
return err
}
func (q *RawQuery) AppendQuery(fmter schema.Formatter, b []byte) ([]byte, error) {
return fmter.AppendQuery(b, q.query, q.args...), nil
}
func (q *RawQuery) Operation() string {
return "SELECT"
}

View File

@ -10,6 +10,8 @@ import (
"strings"
"sync"
"github.com/uptrace/bun/dialect"
"github.com/uptrace/bun/dialect/feature"
"github.com/uptrace/bun/internal"
"github.com/uptrace/bun/schema"
@ -22,6 +24,7 @@ type union struct {
type SelectQuery struct {
whereBaseQuery
idxHintsQuery
distinctOn []schema.QueryWithArgs
joins []joinQuery
@ -159,6 +162,92 @@ func (q *SelectQuery) WhereAllWithDeleted() *SelectQuery {
//------------------------------------------------------------------------------
func (q *SelectQuery) UseIndex(indexes ...string) *SelectQuery {
if q.db.dialect.Name() == dialect.MySQL {
q.addUseIndex(indexes...)
}
return q
}
func (q *SelectQuery) UseIndexForJoin(indexes ...string) *SelectQuery {
if q.db.dialect.Name() == dialect.MySQL {
q.addUseIndexForJoin(indexes...)
}
return q
}
func (q *SelectQuery) UseIndexForOrderBy(indexes ...string) *SelectQuery {
if q.db.dialect.Name() == dialect.MySQL {
q.addUseIndexForOrderBy(indexes...)
}
return q
}
func (q *SelectQuery) UseIndexForGroupBy(indexes ...string) *SelectQuery {
if q.db.dialect.Name() == dialect.MySQL {
q.addUseIndexForGroupBy(indexes...)
}
return q
}
func (q *SelectQuery) IgnoreIndex(indexes ...string) *SelectQuery {
if q.db.dialect.Name() == dialect.MySQL {
q.addIgnoreIndex(indexes...)
}
return q
}
func (q *SelectQuery) IgnoreIndexForJoin(indexes ...string) *SelectQuery {
if q.db.dialect.Name() == dialect.MySQL {
q.addIgnoreIndexForJoin(indexes...)
}
return q
}
func (q *SelectQuery) IgnoreIndexForOrderBy(indexes ...string) *SelectQuery {
if q.db.dialect.Name() == dialect.MySQL {
q.addIgnoreIndexForOrderBy(indexes...)
}
return q
}
func (q *SelectQuery) IgnoreIndexForGroupBy(indexes ...string) *SelectQuery {
if q.db.dialect.Name() == dialect.MySQL {
q.addIgnoreIndexForGroupBy(indexes...)
}
return q
}
func (q *SelectQuery) ForceIndex(indexes ...string) *SelectQuery {
if q.db.dialect.Name() == dialect.MySQL {
q.addForceIndex(indexes...)
}
return q
}
func (q *SelectQuery) ForceIndexForJoin(indexes ...string) *SelectQuery {
if q.db.dialect.Name() == dialect.MySQL {
q.addForceIndexForJoin(indexes...)
}
return q
}
func (q *SelectQuery) ForceIndexForOrderBy(indexes ...string) *SelectQuery {
if q.db.dialect.Name() == dialect.MySQL {
q.addForceIndexForOrderBy(indexes...)
}
return q
}
func (q *SelectQuery) ForceIndexForGroupBy(indexes ...string) *SelectQuery {
if q.db.dialect.Name() == dialect.MySQL {
q.addForceIndexForGroupBy(indexes...)
}
return q
}
//------------------------------------------------------------------------------
func (q *SelectQuery) Group(columns ...string) *SelectQuery {
for _, column := range columns {
q.group = append(q.group, schema.UnsafeIdent(column))
@ -305,8 +394,31 @@ func (q *SelectQuery) Relation(name string, apply ...func(*SelectQuery) *SelectQ
return q
}
var apply1, apply2 func(*SelectQuery) *SelectQuery
if len(join.Relation.Condition) > 0 {
apply1 = func(q *SelectQuery) *SelectQuery {
for _, opt := range join.Relation.Condition {
q.addWhere(schema.SafeQueryWithSep(opt, nil, " AND "))
}
return q
}
}
if len(apply) == 1 {
join.apply = apply[0]
apply2 = apply[0]
}
join.apply = func(q *SelectQuery) *SelectQuery {
if apply1 != nil {
q = apply1(q)
}
if apply2 != nil {
q = apply2(q)
}
return q
}
return q
@ -441,6 +553,11 @@ func (q *SelectQuery) appendQuery(
}
}
b, err = q.appendIndexHints(fmter, b)
if err != nil {
return nil, err
}
b, err = q.appendWhere(fmter, b, true)
if err != nil {
return nil, err
@ -481,7 +598,7 @@ func (q *SelectQuery) appendQuery(
}
if fmter.Dialect().Features().Has(feature.OffsetFetch) {
if q.offset != 0 {
if q.limit > 0 && q.offset > 0 {
b = append(b, " OFFSET "...)
b = strconv.AppendInt(b, int64(q.offset), 10)
b = append(b, " ROWS"...)
@ -489,13 +606,23 @@ func (q *SelectQuery) appendQuery(
b = append(b, " FETCH NEXT "...)
b = strconv.AppendInt(b, int64(q.limit), 10)
b = append(b, " ROWS ONLY"...)
} else if q.limit > 0 {
b = append(b, " OFFSET 0 ROWS"...)
b = append(b, " FETCH NEXT "...)
b = strconv.AppendInt(b, int64(q.limit), 10)
b = append(b, " ROWS ONLY"...)
} else if q.offset > 0 {
b = append(b, " OFFSET "...)
b = strconv.AppendInt(b, int64(q.offset), 10)
b = append(b, " ROWS"...)
}
} else {
if q.limit != 0 {
if q.limit > 0 {
b = append(b, " LIMIT "...)
b = strconv.AppendInt(b, int64(q.limit), 10)
}
if q.offset != 0 {
if q.offset > 0 {
b = append(b, " OFFSET "...)
b = strconv.AppendInt(b, int64(q.offset), 10)
}
@ -920,12 +1047,32 @@ func (q *SelectQuery) whereExists(ctx context.Context) (bool, error) {
return n == 1, nil
}
func (q *SelectQuery) String() string {
buf, err := q.AppendQuery(q.db.Formatter(), nil)
if err != nil {
panic(err)
}
return string(buf)
}
//------------------------------------------------------------------------------
func (q *SelectQuery) QueryBuilder() QueryBuilder {
return &selectQueryBuilder{q}
}
func (q *SelectQuery) ApplyQueryBuilder(fn func(QueryBuilder) QueryBuilder) *SelectQuery {
return fn(q.QueryBuilder()).Unwrap().(*SelectQuery)
}
type selectQueryBuilder struct {
*SelectQuery
}
func (q *selectQueryBuilder) WhereGroup(sep string, fn func(QueryBuilder) QueryBuilder) QueryBuilder {
func (q *selectQueryBuilder) WhereGroup(
sep string, fn func(QueryBuilder) QueryBuilder,
) QueryBuilder {
q.SelectQuery = q.SelectQuery.WhereGroup(sep, func(qs *SelectQuery) *SelectQuery {
return fn(q).(*selectQueryBuilder).SelectQuery
})
@ -961,10 +1108,6 @@ func (q *selectQueryBuilder) Unwrap() interface{} {
return q.SelectQuery
}
func (q *SelectQuery) Query() QueryBuilder {
return &selectQueryBuilder{q}
}
//------------------------------------------------------------------------------
type joinQuery struct {

View File

@ -105,13 +105,16 @@ func (q *CreateTableQuery) TableSpace(tablespace string) *CreateTableQuery {
func (q *CreateTableQuery) WithForeignKeys() *CreateTableQuery {
for _, relation := range q.tableModel.Table().Relations {
if relation.Type == schema.ManyToManyRelation ||
relation.Type == schema.HasManyRelation {
relation.Type == schema.HasManyRelation {
continue
}
q = q.ForeignKey("(?) REFERENCES ? (?)",
}
q = q.ForeignKey("(?) REFERENCES ? (?) ? ?",
Safe(appendColumns(nil, "", relation.BaseFields)),
relation.JoinTable.SQLName,
Safe(appendColumns(nil, "", relation.JoinFields)),
Safe(relation.OnUpdate),
Safe(relation.OnDelete),
)
}
return q
@ -165,6 +168,11 @@ func (q *CreateTableQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []by
b = append(b, " IDENTITY"...)
}
}
if field.Identity {
if fmter.Dialect().Features().Has(feature.GeneratedIdentity) {
b = append(b, " GENERATED BY DEFAULT AS IDENTITY"...)
}
}
if field.SQLDefault != "" {
b = append(b, " DEFAULT "...)
b = append(b, field.SQLDefault...)

View File

@ -6,6 +6,8 @@ import (
"errors"
"fmt"
"github.com/uptrace/bun/dialect"
"github.com/uptrace/bun/dialect/feature"
"github.com/uptrace/bun/internal"
"github.com/uptrace/bun/schema"
@ -16,6 +18,7 @@ type UpdateQuery struct {
returningQuery
customValueQuery
setQuery
idxHintsQuery
omitZero bool
}
@ -204,6 +207,11 @@ func (q *UpdateQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, e
return nil, err
}
b, err = q.appendIndexHints(fmter, b)
if err != nil {
return nil, err
}
b, err = q.mustAppendSet(fmter, b)
if err != nil {
return nil, err
@ -273,7 +281,13 @@ func (q *UpdateQuery) appendSetStruct(
isTemplate := fmter.IsNop()
pos := len(b)
for _, f := range fields {
if q.omitZero && f.HasZeroValue(model.strct) {
if f.SkipUpdate() {
continue
}
app, hasValue := q.modelValues[f.Name]
if !hasValue && q.omitZero && f.HasZeroValue(model.strct) {
continue
}
@ -290,8 +304,7 @@ func (q *UpdateQuery) appendSetStruct(
continue
}
app, ok := q.modelValues[f.Name]
if ok {
if hasValue {
b, err = app.AppendQuery(fmter, b)
if err != nil {
return nil, err
@ -487,12 +500,32 @@ func (q *UpdateQuery) hasTableAlias(fmter schema.Formatter) bool {
return fmter.HasFeature(feature.UpdateMultiTable | feature.UpdateTableAlias)
}
func (q *UpdateQuery) String() string {
buf, err := q.AppendQuery(q.db.Formatter(), nil)
if err != nil {
panic(err)
}
return string(buf)
}
//------------------------------------------------------------------------------
func (q *UpdateQuery) QueryBuilder() QueryBuilder {
return &updateQueryBuilder{q}
}
func (q *UpdateQuery) ApplyQueryBuilder(fn func(QueryBuilder) QueryBuilder) *UpdateQuery {
return fn(q.QueryBuilder()).Unwrap().(*UpdateQuery)
}
type updateQueryBuilder struct {
*UpdateQuery
}
func (q *updateQueryBuilder) WhereGroup(sep string, fn func(QueryBuilder) QueryBuilder) QueryBuilder {
func (q *updateQueryBuilder) WhereGroup(
sep string, fn func(QueryBuilder) QueryBuilder,
) QueryBuilder {
q.UpdateQuery = q.UpdateQuery.WhereGroup(sep, func(qs *UpdateQuery) *UpdateQuery {
return fn(q).(*updateQueryBuilder).UpdateQuery
})
@ -528,6 +561,25 @@ func (q *updateQueryBuilder) Unwrap() interface{} {
return q.UpdateQuery
}
func (q *UpdateQuery) Query() QueryBuilder {
return &updateQueryBuilder{q}
//------------------------------------------------------------------------------
func (q *UpdateQuery) UseIndex(indexes ...string) *UpdateQuery {
if q.db.dialect.Name() == dialect.MySQL {
q.addUseIndex(indexes...)
}
return q
}
func (q *UpdateQuery) IgnoreIndex(indexes ...string) *UpdateQuery {
if q.db.dialect.Name() == dialect.MySQL {
q.addIgnoreIndex(indexes...)
}
return q
}
func (q *UpdateQuery) ForceIndex(indexes ...string) *UpdateQuery {
if q.db.dialect.Name() == dialect.MySQL {
q.addForceIndex(indexes...)
}
return q
}

View File

@ -32,6 +32,7 @@ type Field struct {
NotNull bool
NullZero bool
AutoIncrement bool
Identity bool
Append AppenderFunc
Scan ScannerFunc
@ -120,6 +121,10 @@ func (f *Field) ScanValue(strct reflect.Value, src interface{}) error {
return f.ScanWithCheck(fv, src)
}
func (f *Field) SkipUpdate() bool {
return f.Tag.HasOption("skipupdate")
}
func indexEqual(ind1, ind2 []int) bool {
if len(ind1) != len(ind2) {
return false

View File

@ -18,6 +18,9 @@ type Relation struct {
JoinTable *Table
BaseFields []*Field
JoinFields []*Field
OnUpdate string
OnDelete string
Condition []string
PolymorphicField *Field
PolymorphicValue string

View File

@ -449,6 +449,11 @@ func PtrScanner(fn ScannerFunc) ScannerFunc {
if dest.IsNil() {
dest.Set(reflect.New(dest.Type().Elem()))
}
if dest.Kind() == reflect.Map {
return fn(dest, src)
}
return fn(dest.Elem(), src)
}
}

View File

@ -353,6 +353,9 @@ func (t *Table) newField(f reflect.StructField, prefix string, index []int) *Fie
field.AutoIncrement = true
field.NullZero = true
}
if tag.HasOption("identity") {
field.Identity = true
}
if v, ok := tag.Options["unique"]; ok {
var names []string
@ -374,6 +377,7 @@ func (t *Table) newField(f reflect.StructField, prefix string, index []int) *Fie
}
if s, ok := tag.Option("default"); ok {
field.SQLDefault = s
field.NullZero = true
}
if s, ok := field.Tag.Option("type"); ok {
field.UserSQLType = s
@ -478,6 +482,39 @@ func (t *Table) belongsToRelation(field *Field) *Relation {
JoinTable: joinTable,
}
if field.Tag.HasOption("join_on") {
rel.Condition = field.Tag.Options["join_on"]
}
rel.OnUpdate = "ON UPDATE NO ACTION"
if onUpdate, ok := field.Tag.Options["on_update"]; ok {
if len(onUpdate) > 1 {
panic(fmt.Errorf("bun: %s belongs-to %s: on_update option must be a single field", t.TypeName, field.GoName))
}
rule := strings.ToUpper(onUpdate[0])
if !isKnownFKRule(rule) {
internal.Warn.Printf("bun: %s belongs-to %s: unknown on_update rule %s", t.TypeName, field.GoName, rule)
}
s := fmt.Sprintf("ON UPDATE %s", rule)
rel.OnUpdate = s
}
rel.OnDelete = "ON DELETE NO ACTION"
if onDelete, ok := field.Tag.Options["on_delete"]; ok {
if len(onDelete) > 1 {
panic(fmt.Errorf("bun: %s belongs-to %s: on_delete option must be a single field", t.TypeName, field.GoName))
}
rule := strings.ToUpper(onDelete[0])
if !isKnownFKRule(rule) {
internal.Warn.Printf("bun: %s belongs-to %s: unknown on_delete rule %s", t.TypeName, field.GoName, rule)
}
s := fmt.Sprintf("ON DELETE %s", rule)
rel.OnDelete = s
}
if join, ok := field.Tag.Options["join"]; ok {
baseColumns, joinColumns := parseRelationJoin(join)
for i, baseColumn := range baseColumns {
@ -539,6 +576,10 @@ func (t *Table) hasOneRelation(field *Field) *Relation {
JoinTable: joinTable,
}
if field.Tag.HasOption("join_on") {
rel.Condition = field.Tag.Options["join_on"]
}
if join, ok := field.Tag.Options["join"]; ok {
baseColumns, joinColumns := parseRelationJoin(join)
for i, baseColumn := range baseColumns {
@ -605,6 +646,11 @@ func (t *Table) hasManyRelation(field *Field) *Relation {
Field: field,
JoinTable: joinTable,
}
if field.Tag.HasOption("join_on") {
rel.Condition = field.Tag.Options["join_on"]
}
var polymorphicColumn string
if join, ok := field.Tag.Options["join"]; ok {
@ -715,6 +761,11 @@ func (t *Table) m2mRelation(field *Field) *Relation {
JoinTable: joinTable,
M2MTable: m2mTable,
}
if field.Tag.HasOption("join_on") {
rel.Condition = field.Tag.Options["join_on"]
}
var leftColumn, rightColumn string
if join, ok := field.Tag.Options["join"]; ok {
@ -853,13 +904,29 @@ func isKnownFieldOption(name string) bool {
"unique",
"soft_delete",
"scanonly",
"skipupdate",
"pk",
"autoincrement",
"rel",
"join",
"join_on",
"on_update",
"on_delete",
"m2m",
"polymorphic":
"polymorphic",
"identity":
return true
}
return false
}
func isKnownFKRule(name string) bool {
switch name {
case "CASCADE",
"RESTRICT",
"SET NULL",
"SET DEFAULT":
return true
}
return false

View File

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