Skip to content

[STALE] V1.0 #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 27 additions & 17 deletions columnfilter.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@ package sqldb

import (
"reflect"

"github.com/domonda/go-sqldb/reflection"
)

type ColumnFilter interface {
IgnoreColumn(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool
IgnoreColumn(name string, flags reflection.StructFieldFlags, fieldType reflect.StructField, fieldValue reflect.Value) bool
}

type ColumnFilterFunc func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool
type ColumnFilterFunc func(name string, flags reflection.StructFieldFlags, fieldType reflect.StructField, fieldValue reflect.Value) bool

func (f ColumnFilterFunc) IgnoreColumn(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
func (f ColumnFilterFunc) IgnoreColumn(name string, flags reflection.StructFieldFlags, fieldType reflect.StructField, fieldValue reflect.Value) bool {
return f(name, flags, fieldType, fieldValue)
}

func IgnoreColumns(names ...string) ColumnFilter {
return ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
return ColumnFilterFunc(func(name string, flags reflection.StructFieldFlags, fieldType reflect.StructField, fieldValue reflect.Value) bool {
for _, ignore := range names {
if name == ignore {
return true
Expand All @@ -26,7 +28,7 @@ func IgnoreColumns(names ...string) ColumnFilter {
}

func OnlyColumns(names ...string) ColumnFilter {
return ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
return ColumnFilterFunc(func(name string, flags reflection.StructFieldFlags, fieldType reflect.StructField, fieldValue reflect.Value) bool {
for _, include := range names {
if name == include {
return false
Expand All @@ -37,7 +39,7 @@ func OnlyColumns(names ...string) ColumnFilter {
}

func IgnoreStructFields(names ...string) ColumnFilter {
return ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
return ColumnFilterFunc(func(name string, flags reflection.StructFieldFlags, fieldType reflect.StructField, fieldValue reflect.Value) bool {
for _, ignore := range names {
if fieldType.Name == ignore {
return true
Expand All @@ -48,7 +50,7 @@ func IgnoreStructFields(names ...string) ColumnFilter {
}

func OnlyStructFields(names ...string) ColumnFilter {
return ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
return ColumnFilterFunc(func(name string, flags reflection.StructFieldFlags, fieldType reflect.StructField, fieldValue reflect.Value) bool {
for _, include := range names {
if fieldType.Name == include {
return false
Expand All @@ -58,32 +60,40 @@ func OnlyStructFields(names ...string) ColumnFilter {
})
}

func IgnoreFlags(ignore FieldFlag) ColumnFilter {
return ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
func IgnoreFlags(ignore reflection.StructFieldFlags) ColumnFilter {
return ColumnFilterFunc(func(name string, flags reflection.StructFieldFlags, fieldType reflect.StructField, fieldValue reflect.Value) bool {
return flags&ignore != 0
})
}

var IgnoreDefault ColumnFilter = ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
return flags.Default()
var IgnoreHasDefault ColumnFilter = ColumnFilterFunc(func(name string, flags reflection.StructFieldFlags, fieldType reflect.StructField, fieldValue reflect.Value) bool {
return flags.HasDefault()
})

var IgnorePrimaryKey ColumnFilter = ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
var IgnorePrimaryKey ColumnFilter = ColumnFilterFunc(func(name string, flags reflection.StructFieldFlags, fieldType reflect.StructField, fieldValue reflect.Value) bool {
return flags.PrimaryKey()
})

var IgnoreReadOnly ColumnFilter = ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
var IgnoreReadOnly ColumnFilter = ColumnFilterFunc(func(name string, flags reflection.StructFieldFlags, fieldType reflect.StructField, fieldValue reflect.Value) bool {
return flags.ReadOnly()
})

var IgnoreNull ColumnFilter = ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
var IgnoreNull ColumnFilter = ColumnFilterFunc(func(name string, flags reflection.StructFieldFlags, fieldType reflect.StructField, fieldValue reflect.Value) bool {
return IsNull(fieldValue)
})

var IgnoreNullOrZero ColumnFilter = ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
var IgnoreNullOrZero ColumnFilter = ColumnFilterFunc(func(name string, flags reflection.StructFieldFlags, fieldType reflect.StructField, fieldValue reflect.Value) bool {
return IsNullOrZero(fieldValue)
})

var IgnoreNullOrZeroDefault ColumnFilter = ColumnFilterFunc(func(name string, flags FieldFlag, fieldType reflect.StructField, fieldValue reflect.Value) bool {
return flags.Default() && IsNullOrZero(fieldValue)
var IgnoreHasDefaultNullOrZero ColumnFilter = ColumnFilterFunc(func(name string, flags reflection.StructFieldFlags, fieldType reflect.StructField, fieldValue reflect.Value) bool {
return flags.HasDefault() && IsNullOrZero(fieldValue)
})

type noColumnFilter struct{}

func (noColumnFilter) IgnoreColumn(name string, flags reflection.StructFieldFlags, fieldType reflect.StructField, fieldValue reflect.Value) bool {
return false
}

var AllColumns noColumnFilter
61 changes: 46 additions & 15 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sqldb
import (
"context"
"database/sql"
"errors"
"fmt"
"net/url"
"time"
Expand All @@ -11,40 +12,70 @@ import (
// Config for a connection.
// For tips see https://www.alexedwards.net/blog/configuring-sqldb
type Config struct {
Driver string `json:"driver"`
Host string `json:"host"`
Port uint16 `json:"port,omitempty"`
User string `json:"user,omitempty"`
Password string `json:"password,omitempty"`
Database string `json:"database"`
Extra map[string]string `json:"misc,omitempty"`
MaxOpenConns int `json:"maxOpenConns,omitempty"`
MaxIdleConns int `json:"maxIdleConns,omitempty"`
ConnMaxLifetime time.Duration `json:"connMaxLifetime,omitempty"`
Driver string `json:"driver"`
Host string `json:"host"`
Port uint16 `json:"port,omitempty"`
User string `json:"user,omitempty"`
Password string `json:"password,omitempty"`
Database string `json:"database"`
Extra map[string]string `json:"misc,omitempty"`
MaxOpenConns int `json:"maxOpenConns,omitempty"`
MaxIdleConns int `json:"maxIdleConns,omitempty"`
ConnMaxLifetime time.Duration `json:"connMaxLifetime,omitempty"`

// ValidateColumnName returns an error
// if the passed name is not valid for a
// column of the connection's database.
ValidateColumnName func(name string) error `json:"-"`

// ParamPlaceholder returns a parameter value placeholder
// for the parameter with the passed zero based index
// specific to the database type of the connection.
ParamPlaceholderFormatter `json:"-"`

DefaultIsolationLevel sql.IsolationLevel `json:"-"`
Err error `json:"-"`

// Err will be returned from Connection.Err()
Err error `json:"-"`
}

// func (c *DBConnection) ValidateColumnName(name string) error {
// if name == "" {
// return errors.New("empty column name")
// }
// return nil
// }

// func (c *DBConnection) ParamPlaceholder(index int) string {
// return fmt.Sprintf(":%d", index+1)
// }

// Validate returns Config.Err if it is not nil
// or an error if the Config does not have
// a Driver, Host, or Database.
func (c *Config) Validate() error {
if c.Err != nil {
return c.Err
}
if c.ValidateColumnName == nil {
return errors.New("missing sqldb.Config.ValidateColumnName")
}
if c.ParamPlaceholderFormatter == nil {
return errors.New("missing sqldb.Config.ParamPlaceholderFormatter")
}
if c.Driver == "" {
return fmt.Errorf("missing sqldb.Config.Driver")
return errors.New("missing sqldb.Config.Driver")
}
if c.Host == "" {
return fmt.Errorf("missing sqldb.Config.Host")
return errors.New("missing sqldb.Config.Host")
}
if c.Database == "" {
return fmt.Errorf("missing sqldb.Config.Database")
return errors.New("missing sqldb.Config.Database")
}
return nil
}

// ConnectURL for connecting to a database
// ConnectURL returns a connection URL for the Config
func (c *Config) ConnectURL() string {
extra := make(url.Values)
for key, val := range c.Extra {
Expand Down
107 changes: 16 additions & 91 deletions connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,119 +16,44 @@ type (

// Connection represents a database connection or transaction
type Connection interface {
// Context that all connection operations use.
// See also WithContext.
Context() context.Context

// WithContext returns a connection that uses the passed
// context for its operations.
WithContext(ctx context.Context) Connection

// WithStructFieldMapper returns a copy of the connection
// that will use the passed StructFieldMapper.
WithStructFieldMapper(StructFieldMapper) Connection
// Config returns the configuration used
// to create this connection.
Config() *Config

// StructFieldMapper used by methods of this Connection.
StructFieldMapper() StructFieldMapper
// Stats returns the sql.DBStats of this connection.
Stats() sql.DBStats

// Ping returns an error if the database
// does not answer on this connection
// with an optional timeout.
// The passed timeout has to be greater zero
// to be considered.
Ping(timeout time.Duration) error

// Stats returns the sql.DBStats of this connection.
Stats() sql.DBStats

// Config returns the configuration used
// to create this connection.
Config() *Config

// ValidateColumnName returns an error
// if the passed name is not valid for a
// column of the connection's database.
ValidateColumnName(name string) error
Ping(ctx context.Context, timeout time.Duration) error

// Now returns the result of the SQL now()
// function for the current connection.
// Useful for getting the timestamp of a
// SQL transaction for use in Go code.
Now() (time.Time, error)
// Err returns any current error of the connection
Err() error

// Exec executes a query with optional args.
Exec(query string, args ...any) error

// Insert a new row into table using the values.
Insert(table string, values Values) error

// InsertUnique inserts a new row into table using the passed values
// or does nothing if the onConflict statement applies.
// Returns if a row was inserted.
InsertUnique(table string, values Values, onConflict string) (inserted bool, err error)

// InsertReturning inserts a new row into table using values
// and returns values from the inserted row listed in returning.
InsertReturning(table string, values Values, returning string) RowScanner

// InsertStruct inserts a new row into table using the connection's
// StructFieldMapper to map struct fields to column names.
// Optional ColumnFilter can be passed to ignore mapped columns.
InsertStruct(table string, rowStruct any, ignoreColumns ...ColumnFilter) error

// InsertUniqueStruct inserts a new row into table using the connection's
// StructFieldMapper to map struct fields to column names.
// Optional ColumnFilter can be passed to ignore mapped columns.
// Does nothing if the onConflict statement applies
// and returns if a row was inserted.
InsertUniqueStruct(table string, rowStruct any, onConflict string, ignoreColumns ...ColumnFilter) (inserted bool, err error)

// Update table rows(s) with values using the where statement with passed in args starting at $1.
Update(table string, values Values, where string, args ...any) error

// UpdateReturningRow updates a table row with values using the where statement with passed in args starting at $1
// and returning a single row with the columns specified in returning argument.
UpdateReturningRow(table string, values Values, returning, where string, args ...any) RowScanner

// UpdateReturningRows updates table rows with values using the where statement with passed in args starting at $1
// and returning multiple rows with the columns specified in returning argument.
UpdateReturningRows(table string, values Values, returning, where string, args ...any) RowsScanner

// UpdateStruct updates a row in a table using the exported fields
// of rowStruct which have a `db` tag that is not "-".
// If restrictToColumns are provided, then only struct fields with a `db` tag
// matching any of the passed column names will be used.
// The struct must have at least one field with a `db` tag value having a ",pk" suffix
// to mark primary key column(s).
UpdateStruct(table string, rowStruct any, ignoreColumns ...ColumnFilter) error

// UpsertStruct upserts a row to table using the exported fields
// of rowStruct which have a `db` tag that is not "-".
// If restrictToColumns are provided, then only struct fields with a `db` tag
// matching any of the passed column names will be used.
// The struct must have at least one field with a `db` tag value having a ",pk" suffix
// to mark primary key column(s).
// If inserting conflicts on the primary key column(s), then an update is performed.
UpsertStruct(table string, rowStruct any, ignoreColumns ...ColumnFilter) error
Exec(ctx context.Context, query string, args ...any) error

// QueryRow queries a single row and returns a RowScanner for the results.
QueryRow(query string, args ...any) RowScanner
QueryRow(ctx context.Context, query string, args ...any) Row

// QueryRows queries multiple rows and returns a RowsScanner for the results.
QueryRows(query string, args ...any) RowsScanner
QueryRows(ctx context.Context, query string, args ...any) Rows

// IsTransaction returns if the connection is a transaction
IsTransaction() bool

// TransactionOptions returns the sql.TxOptions of the
// current transaction and true as second result value,
// or false if the connection is not a transaction.
TransactionOptions() (*sql.TxOptions, bool)
// TxOptions returns the sql.TxOptions of the
// current transaction which can be nil for the default options.
// Use IsTransaction to check if the connection is a transaction.
TxOptions() *sql.TxOptions

// Begin a new transaction.
// If the connection is already a transaction, a brand
// new transaction will begin on the parent's connection.
Begin(opts *sql.TxOptions) (Connection, error)
Begin(ctx context.Context, opts *sql.TxOptions) (Connection, error)

// Commit the current transaction.
// Returns ErrNotWithinTransaction if the connection
Expand Down
12 changes: 8 additions & 4 deletions db/config.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
package db

import (
"context"
"errors"

"github.com/domonda/go-sqldb"
"github.com/domonda/go-sqldb/reflection"
)

var (
// Number of retries used for a SerializedTransaction
// before it fails
SerializedTransactionRetries = 10

// DefaultStructFieldMapping provides the default StructFieldTagNaming
// using "db" as NameTag and IgnoreStructField as UntaggedNameFunc.
// Implements StructFieldMapper.
DefaultStructFieldMapping = reflection.NewTaggedStructFieldMapping()
)

var (
conn = sqldb.ConnectionWithError(
context.Background(),
globalConn = sqldb.ConnectionWithError(
errors.New("database connection not initialized"),
)
connCtxKey int
globalConnCtxKey int
serializedTransactionCtxKey int
)
Loading