From be92d13951ed4901b1f7d63b88a879f93948e615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Nieto?= <jose.carlos@menteslibres.net> Date: Wed, 7 Oct 2015 09:06:17 -0500 Subject: [PATCH] Moving result, tx and debug. --- internal/debug/debug.go | 83 +++++++++++++++ internal/sqladapter/database.go | 12 +-- internal/sqlutil/result/result.go | 170 ++++++++++++++++++++++++++++++ internal/sqlutil/tx/tx.go | 46 ++++++++ postgresql/database.go | 4 +- postgresql/postgresql.go | 10 +- 6 files changed, 312 insertions(+), 13 deletions(-) create mode 100644 internal/debug/debug.go create mode 100644 internal/sqlutil/result/result.go create mode 100644 internal/sqlutil/tx/tx.go diff --git a/internal/debug/debug.go b/internal/debug/debug.go new file mode 100644 index 00000000..826bbcb5 --- /dev/null +++ b/internal/debug/debug.go @@ -0,0 +1,83 @@ +// Copyright (c) 2012-2015 The upper.io/db authors. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package debug + +import ( + "fmt" + "log" + "os" + "regexp" + "strings" + + "upper.io/db" +) + +var ( + reInvisibleChars = regexp.MustCompile(`[\s\r\n\t]+`) + reColumnCompareExclude = regexp.MustCompile(`[^a-zA-Z0-9]`) +) + +func init() { + if os.Getenv(db.EnvEnableDebug) != "" { + db.Debug = true + } +} + +// Debug is used for printing SQL queries and arguments. +type Debug struct { + SQL string + Args []interface{} + Err error + Start int64 + End int64 +} + +// Print prints a debug message to stdlog. +func (d *Debug) Print() { + d.SQL = reInvisibleChars.ReplaceAllString(d.SQL, ` `) + d.SQL = strings.TrimSpace(d.SQL) + + s := make([]string, 0, 3) + + if d.SQL != "" { + s = append(s, fmt.Sprintf(`Q: %s`, d.SQL)) + } + + if len(d.Args) > 0 { + s = append(s, fmt.Sprintf(`A: %v`, d.Args)) + } + + if d.Err != nil { + s = append(s, fmt.Sprintf(`E: %q`, d.Err)) + } + + s = append(s, fmt.Sprintf(`T: %0.5fs`, float64(d.End-d.Start)/float64(1e9))) + + log.Printf("\n\t%s\n\n", strings.Join(s, "\n\t")) +} + +func Log(query string, args []interface{}, err error, start int64, end int64) { + if db.Debug { + d := Debug{query, args, err, start, end} + d.Print() + } +} diff --git a/internal/sqladapter/database.go b/internal/sqladapter/database.go index 294d80fa..830c5a75 100644 --- a/internal/sqladapter/database.go +++ b/internal/sqladapter/database.go @@ -6,13 +6,13 @@ import ( "time" "github.com/jmoiron/sqlx" + "upper.io/builder" + "upper.io/builder/sqlgen" "upper.io/cache" "upper.io/db" - "upper.io/builder" "upper.io/db/internal/adapter" + "upper.io/db/internal/debug" "upper.io/db/internal/schema" - "upper.io/builder/sqlgen" - "upper.io/db/internal/sqlutil" "upper.io/db/internal/sqlutil/tx" ) @@ -186,7 +186,7 @@ func (d *BaseDatabase) Exec(stmt *sqlgen.Statement, args ...interface{}) (sql.Re defer func() { end = time.Now().UnixNano() - sqlutil.Log(query, args, err, start, end) + debug.Log(query, args, err, start, end) }() } @@ -209,7 +209,7 @@ func (d *BaseDatabase) Query(stmt *sqlgen.Statement, args ...interface{}) (*sqlx defer func() { end = time.Now().UnixNano() - sqlutil.Log(query, args, err, start, end) + debug.Log(query, args, err, start, end) }() } @@ -232,7 +232,7 @@ func (d *BaseDatabase) QueryRow(stmt *sqlgen.Statement, args ...interface{}) (*s defer func() { end = time.Now().UnixNano() - sqlutil.Log(query, args, err, start, end) + debug.Log(query, args, err, start, end) }() } diff --git a/internal/sqlutil/result/result.go b/internal/sqlutil/result/result.go new file mode 100644 index 00000000..2e50932e --- /dev/null +++ b/internal/sqlutil/result/result.go @@ -0,0 +1,170 @@ +// Copyright (c) 2012-2015 The upper.io/db authors. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package result + +import ( + "upper.io/db" +) + +type Result struct { + b db.QueryBuilder + table string + iter db.Iterator + limit int + offset int + fields []interface{} + columns []interface{} + orderBy []interface{} + groupBy []interface{} + conds []interface{} +} + +// NewResult creates and results a new result set on the given table, this set +// is limited by the given sqlgen.Where conditions. +func NewResult(b db.QueryBuilder, table string, conds []interface{}) *Result { + return &Result{ + b: b, + table: table, + conds: conds, + } +} + +// Sets conditions for reducing the working set. +func (r *Result) Where(conds ...interface{}) db.Result { + r.conds = conds + return r +} + +// Determines the maximum limit of results to be returned. +func (r *Result) Limit(n uint) db.Result { + r.limit = int(n) + return r +} + +// Determines how many documents will be skipped before starting to grab +// results. +func (r *Result) Skip(n uint) db.Result { + r.offset = int(n) + return r +} + +// Used to group results that have the same value in the same column or +// columns. +func (r *Result) Group(fields ...interface{}) db.Result { + r.groupBy = fields + return r +} + +// Determines sorting of results according to the provided names. Fields may be +// prefixed by - (minus) which means descending order, ascending order would be +// used otherwise. +func (r *Result) Sort(fields ...interface{}) db.Result { + r.orderBy = fields + return r +} + +// Retrieves only the given fields. +func (r *Result) Select(fields ...interface{}) db.Result { + r.fields = fields + return r +} + +// Dumps all results into a pointer to an slice of structs or maps. +func (r *Result) All(dst interface{}) error { + return r.buildSelect().Iterator().All(dst) +} + +// Fetches only one result from the resultset. +func (r *Result) One(dst interface{}) error { + return r.buildSelect().Iterator().One(dst) +} + +// Fetches the next result from the resultset. +func (r *Result) Next(dst interface{}) (err error) { + if r.iter == nil { + r.iter = r.buildSelect().Iterator() + } + if !r.iter.Next(dst) { + return r.iter.Err() + } + return nil +} + +// Removes the matching items from the collection. +func (r *Result) Remove() error { + q := r.b.DeleteFrom(r.table). + Where(r.conds...). + Limit(r.limit) + + _, err := q.Exec() + return err +} + +// Closes the result set. +func (r *Result) Close() error { + if r.iter != nil { + return r.iter.Close() + } + return nil +} + +// Updates matching items from the collection with values of the given map or +// struct. +func (r *Result) Update(values interface{}) error { + q := r.b.Update(r.table). + Set(values). + Where(r.conds...). + Limit(r.limit) + + _, err := q.Exec() + return err +} + +// Counts the elements within the main conditions of the set. +func (r *Result) Count() (uint64, error) { + counter := struct { + Count uint64 `db:"_t"` + }{} + + q := r.buildSelect() + q.Columns(db.Raw{"COUNT(1) AS _t"}).Limit(1) + + if err := q.Iterator().One(&counter); err != nil { + return 0, err + } + + return counter.Count, nil +} + +func (r *Result) buildSelect() db.QuerySelector { + q := r.b.Select(r.fields...) + + q.From(r.table) + q.Where(r.conds...) + q.Limit(r.limit) + q.Offset(r.offset) + + q.GroupBy(r.groupBy...) + q.OrderBy(r.orderBy...) + + return q +} diff --git a/internal/sqlutil/tx/tx.go b/internal/sqlutil/tx/tx.go new file mode 100644 index 00000000..18ebbf3c --- /dev/null +++ b/internal/sqlutil/tx/tx.go @@ -0,0 +1,46 @@ +// Copyright (c) 2012-2015 The upper.io/db authors. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package sqltx + +import ( + "github.com/jmoiron/sqlx" +) + +type Tx struct { + *sqlx.Tx + done bool +} + +func New(tx *sqlx.Tx) *Tx { + return &Tx{Tx: tx} +} + +func (t *Tx) Done() bool { + return t.done +} + +func (t *Tx) Commit() (err error) { + if err = t.Tx.Commit(); err == nil { + t.done = true + } + return err +} diff --git a/postgresql/database.go b/postgresql/database.go index dfd11052..6c112a03 100644 --- a/postgresql/database.go +++ b/postgresql/database.go @@ -27,9 +27,9 @@ import ( "github.com/jmoiron/sqlx" _ "github.com/lib/pq" // PostgreSQL driver. + "upper.io/builder/sqlgen" "upper.io/db" "upper.io/db/internal/sqladapter" - "upper.io/builder/sqlgen" ) type database struct { @@ -89,7 +89,7 @@ func (d *database) Open() error { // Setup configures the adapter. func (d *database) Setup(connURL db.ConnectionURL) error { - d.BaseDatabase = sqladapter.NewDatabase(d, connURL, template.Template) + d.BaseDatabase = sqladapter.NewDatabase(d, connURL, template) return d.Open() } diff --git a/postgresql/postgresql.go b/postgresql/postgresql.go index f0d51db4..bb1fa95b 100644 --- a/postgresql/postgresql.go +++ b/postgresql/postgresql.go @@ -22,19 +22,19 @@ package postgresql // import "upper.io/db/postgresql" import ( + "upper.io/builder/sqlgen" "upper.io/cache" "upper.io/db" - "upper.io/builder/sqlgen" - "upper.io/db/internal/sqlutil" ) // Adapter is the public name of the adapter. const Adapter = `postgresql` -var template *sqlutil.TemplateWithUtils +var template *sqlgen.Template func init() { - template = sqlutil.NewTemplateWithUtils(&sqlgen.Template{ + + template = &sqlgen.Template{ ColumnSeparator: adapterColumnSeparator, IdentifierSeparator: adapterIdentifierSeparator, IdentifierQuote: adapterIdentifierQuote, @@ -68,7 +68,7 @@ func init() { CountLayout: adapterSelectCountLayout, GroupByLayout: adapterGroupByLayout, Cache: cache.NewCache(), - }) + } db.Register(Adapter, &database{}) } -- GitLab