From 71ba24436b8e71be49c65df98821352274623b75 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Nieto?= <jose.carlos@menteslibres.net>
Date: Tue, 3 Nov 2015 21:31:35 -0600
Subject: [PATCH] wip: Making builder independent from db.

---
 db.go                             | 83 ++++++++++++++++++++++---------
 error.go                          |  7 +--
 internal/sqlutil/result/result.go | 20 ++++++--
 postgresql/database.go            |  2 +-
 postgresql/database_test.go       | 24 ++++-----
 5 files changed, 89 insertions(+), 47 deletions(-)

diff --git a/db.go b/db.go
index d44de71b..80e6288d 100644
--- a/db.go
+++ b/db.go
@@ -64,6 +64,14 @@ import (
 //	db.Cond { "age $lt": 18 }
 type Cond map[string]interface{}
 
+func (m Cond) Constraints() []builder.Constraint {
+	c := make([]builder.Constraint, 0, len(m))
+	for k, v := range m {
+		c = append(c, builder.NewConstraint(k, builder.OperatorDefault, v))
+	}
+	return c
+}
+
 // Func is a struct that represents database functions.
 //
 // Examples:
@@ -79,9 +87,21 @@ type Cond map[string]interface{}
 //
 //	// RTRIM("Hello   ")
 //	db.Func{"RTRIM", "Hello  "}
-type Func struct {
-	Name string
-	Args interface{}
+type dbFunc struct {
+	name string
+	args []interface{}
+}
+
+func (f *dbFunc) Arguments() []interface{} {
+	return f.args
+}
+
+func (f *dbFunc) Name() string {
+	return f.name
+}
+
+func Func(name string, args ...interface{}) builder.Function {
+	return &dbFunc{name: name, args: args}
 }
 
 // And is an array of interfaces that is used to join two or more expressions
@@ -104,11 +124,19 @@ type Func struct {
 // 		},
 // 		db.Cond{ "last_name": "Mouse" },
 // 	}
-type And []interface{}
+type And []builder.Constraints
 
 // And adds a new expression to an And conditions array.
-func (a And) And(exp ...interface{}) And {
-	return append(a, exp...)
+func (a And) And(t ...builder.Constraints) And {
+	return append(a, t...)
+}
+
+func (a And) Constraints() []builder.Constraints {
+	return a
+}
+
+func (a And) Operator() builder.ConstraintOperator {
+	return builder.OperatorAnd
 }
 
 // Or is an array of interfaced that is used to join two or more expressions
@@ -122,11 +150,19 @@ func (a And) And(exp ...interface{}) And {
 // 		db.Cond{"year": 2012},
 // 		db.Cond{"year": 1987},
 // 	}
-type Or []interface{}
+type Or []builder.Constraints
 
 // Or adds a new expression to an Or conditions array.
-func (o Or) Or(exp ...interface{}) Or {
-	return append(o, exp...)
+func (o Or) Or(t ...builder.Constraints) Or {
+	return append(o, t...)
+}
+
+func (o Or) Constraints() []builder.Constraints {
+	return o
+}
+
+func (o Or) Operator() builder.ConstraintOperator {
+	return builder.OperatorOr
 }
 
 // Raw holds chunks of data to be passed to the database without any filtering.
@@ -143,8 +179,8 @@ func (o Or) Or(exp ...interface{}) Or {
 //
 //	// SQL: SOUNDEX('Hello')
 //	Raw{"SOUNDEX('Hello')"}
-type Raw struct {
-	Value interface{}
+func Raw(s string) builder.RawValue {
+	return builder.RawString(s)
 }
 
 // Database is an interface that defines methods that must be provided by
@@ -332,15 +368,11 @@ type ConnectionURL interface {
 
 // Marshaler is the interface implemented by structs that can marshal
 // themselves into data suitable for storage.
-type Marshaler interface {
-	MarshalDB() (interface{}, error)
-}
+type Marshaler builder.Marshaler
 
 // Unmarshaler is the interface implemented by structs that can transform
 // themselves from storage data into a valid value.
-type Unmarshaler interface {
-	UnmarshalDB(interface{}) error
-}
+type Unmarshaler builder.Unmarshaler
 
 // IDSetter is the interface implemented by structs that can set their own ID
 // after calling Append().
@@ -348,12 +380,6 @@ type IDSetter interface {
 	SetID(map[string]interface{}) error
 }
 
-// Constrainer is the interface implemented by structs that can delimit
-// themselves.
-type Constrainer interface {
-	Constraint() Cond
-}
-
 // Int64IDSetter implements a common pattern for setting int64 IDs.
 type Int64IDSetter interface {
 	SetID(int64) error
@@ -377,3 +403,14 @@ type Uint64IDSetter interface {
 //
 //	UPPERIO_DB_DEBUG=1 ./go-program
 const EnvEnableDebug = `UPPERIO_DB_DEBUG`
+
+type Constrainer interface {
+	Constraints() Cond
+}
+
+var (
+	_ = builder.Constraints(Cond{})
+	_ = builder.Criteria(And{})
+	_ = builder.Criteria(Or{})
+	_ = builder.Function(&dbFunc{})
+)
diff --git a/error.go b/error.go
index 3bd51dcf..51a5f9fb 100644
--- a/error.go
+++ b/error.go
@@ -23,15 +23,12 @@ package db
 
 import (
 	"errors"
+	builder "upper.io/builder/meta"
 )
 
 // Shared error messages.
 var (
-	ErrExpectingPointer        = errors.New(`Argument must be an address.`)
-	ErrExpectingSlicePointer   = errors.New(`Argument must be a slice address.`)
-	ErrExpectingSliceMapStruct = errors.New(`Argument must be a slice address of maps or structs.`)
-	ErrExpectingMapOrStruct    = errors.New(`Argument must be either a map or a struct.`)
-	ErrNoMoreRows              = errors.New(`There are no more rows in this result set.`)
+	ErrNoMoreRows              = builder.ErrNoMoreRows
 	ErrNotConnected            = errors.New(`You're currently not connected.`)
 	ErrMissingDatabaseName     = errors.New(`Missing database name.`)
 	ErrMissingCollectionName   = errors.New(`Missing collection name.`)
diff --git a/internal/sqlutil/result/result.go b/internal/sqlutil/result/result.go
index 590096bc..36c9ab26 100644
--- a/internal/sqlutil/result/result.go
+++ b/internal/sqlutil/result/result.go
@@ -39,6 +39,16 @@ type Result struct {
 	conds   []interface{}
 }
 
+func filter(conds []interface{}) []interface{} {
+	for i := range conds {
+		switch v := conds[i].(type) {
+		case db.Constrainer:
+			conds[i] = v.Constraints()
+		}
+	}
+	return conds
+}
+
 // 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 builder.QueryBuilder, table string, conds []interface{}) *Result {
@@ -113,7 +123,7 @@ func (r *Result) Next(dst interface{}) (err error) {
 // Removes the matching items from the collection.
 func (r *Result) Remove() error {
 	q := r.b.DeleteFrom(r.table).
-		Where(r.conds...).
+		Where(filter(r.conds)...).
 		Limit(r.limit)
 
 	_, err := q.Exec()
@@ -133,7 +143,7 @@ func (r *Result) Close() error {
 func (r *Result) Update(values interface{}) error {
 	q := r.b.Update(r.table).
 		Set(values).
-		Where(r.conds...).
+		Where(filter(r.conds)...).
 		Limit(r.limit)
 
 	_, err := q.Exec()
@@ -146,9 +156,9 @@ func (r *Result) Count() (uint64, error) {
 		Count uint64 `db:"_t"`
 	}{}
 
-	q := r.b.Select(db.Raw{"count(1) AS _t"}).
+	q := r.b.Select(db.Raw("count(1) AS _t")).
 		From(r.table).
-		Where(r.conds...).
+		Where(filter(r.conds)...).
 		GroupBy(r.groupBy...).
 		Limit(1)
 
@@ -166,7 +176,7 @@ func (r *Result) buildSelect() builder.Selector {
 	q := r.b.Select(r.fields...)
 
 	q.From(r.table)
-	q.Where(r.conds...)
+	q.Where(filter(r.conds)...)
 	q.Limit(r.limit)
 	q.Offset(r.offset)
 
diff --git a/postgresql/database.go b/postgresql/database.go
index 2b0584b8..10cfd544 100644
--- a/postgresql/database.go
+++ b/postgresql/database.go
@@ -186,7 +186,7 @@ func (d *database) PopulateSchema() (err error) {
 
 	d.NewSchema()
 
-	q := d.Builder().Select(db.Raw{"CURRENT_DATABASE() AS name"})
+	q := d.Builder().Select(db.Raw("CURRENT_DATABASE() AS name"))
 
 	var dbName string
 
diff --git a/postgresql/database_test.go b/postgresql/database_test.go
index bb678809..0fcbe29b 100644
--- a/postgresql/database_test.go
+++ b/postgresql/database_test.go
@@ -107,12 +107,11 @@ type itemWithKey struct {
 	SomeVal string `db:"some_val"`
 }
 
-func (item itemWithKey) Constraint() db.Cond {
-	cond := db.Cond{
+func (item itemWithKey) Constraints() db.Cond {
+	return db.Cond{
 		"code":    item.Code,
 		"user_id": item.UserID,
 	}
-	return cond
 }
 
 func (item *itemWithKey) SetID(keys map[string]interface{}) error {
@@ -124,6 +123,8 @@ func (item *itemWithKey) SetID(keys map[string]interface{}) error {
 	return errors.New(`Expecting exactly two keys.`)
 }
 
+var _ = db.Constrainer(itemWithKey{})
+
 var testValues testValuesStruct
 
 func init() {
@@ -883,7 +884,7 @@ func TestFunction(t *testing.T) {
 	}
 
 	// Testing conditions
-	res = artist.Find(db.Cond{"id": db.Func{"NOT IN", []int{0, -1}}})
+	res = artist.Find(db.Cond{"id NOT": db.Func("IN", 0, -1)})
 
 	if err = res.One(&rowStruct); err != nil {
 		t.Fatal(err)
@@ -899,7 +900,7 @@ func TestFunction(t *testing.T) {
 
 	// Testing DISTINCT (function)
 	res = artist.Find().Select(
-		db.Func{`DISTINCT`, `name`},
+		db.Func("DISTINCT", "name"),
 	)
 
 	if err = res.One(&rowMap); err != nil {
@@ -916,7 +917,7 @@ func TestFunction(t *testing.T) {
 
 	// Testing DISTINCT (raw)
 	res = artist.Find().Select(
-		db.Raw{`DISTINCT(name)`},
+		db.Raw("DISTINCT(name)"),
 	)
 
 	if err = res.One(&rowMap); err != nil {
@@ -1061,15 +1062,12 @@ func TestGroup(t *testing.T) {
 		}
 	}
 
-	// db.Func{"COUNT", 1},
-	// db.Func{"SUM", `value`},
-
 	// Testing GROUP BY
 	res := stats.Find().Select(
-		`numeric`,
-		db.Raw{`COUNT(1) AS counter`},
-		db.Raw{`SUM(value) AS total`},
-	).Group(`numeric`)
+		"numeric",
+		db.Raw("COUNT(1) AS counter"),
+		db.Raw("SUM(value) AS total"),
+	).Group("numeric")
 
 	var results []map[string]interface{}
 
-- 
GitLab