From 469d748fd47d5c8d1faa5716b828e1c8be90d22e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Nieto?= <jose.carlos@menteslibres.net>
Date: Tue, 13 Dec 2016 04:25:07 +0000
Subject: [PATCH] Make db.And and db.Or immutable

---
 db.go                          | 45 +++++++++++++++++++++++-----------
 lib/sqlbuilder/builder_test.go |  2 +-
 2 files changed, 32 insertions(+), 15 deletions(-)

diff --git a/db.go b/db.go
index bd665050..ed27d671 100644
--- a/db.go
+++ b/db.go
@@ -261,11 +261,16 @@ func (r rawValue) Empty() bool {
 }
 
 type compound struct {
-	conds []Compound
+	prev *compound
+	fn   func() []Compound
 }
 
-func newCompound(c ...Compound) *compound {
-	return &compound{c}
+func newCompound(conds ...Compound) *compound {
+	return &compound{
+		fn: func() []Compound {
+			return conds
+		},
+	}
 }
 
 func defaultJoin(in ...Compound) []Compound {
@@ -278,7 +283,7 @@ func defaultJoin(in ...Compound) []Compound {
 }
 
 func (c *compound) Sentences() []Compound {
-	return c.conds
+	return compoundFastForward(c)
 }
 
 func (c *compound) Operator() CompoundOperator {
@@ -286,12 +291,26 @@ func (c *compound) Operator() CompoundOperator {
 }
 
 func (c *compound) Empty() bool {
-	return len(c.conds) == 0
+	if c.fn == nil {
+		return false
+	}
+	return true
 }
 
-func (c *compound) push(a ...Compound) *compound {
-	c.conds = append(c.conds, a...)
-	return c
+func (c *compound) frame(a []Compound) *compound {
+	if len(a) == 0 {
+		return c
+	}
+	nc := newCompound(a...)
+	nc.prev = c
+	return nc
+}
+
+func compoundFastForward(curr *compound) []Compound {
+	if curr == nil {
+		return []Compound{}
+	}
+	return append(compoundFastForward(curr.prev), curr.fn()...)
 }
 
 // Union represents a compound joined by OR.
@@ -301,8 +320,7 @@ type Union struct {
 
 // Or adds more terms to the compound.
 func (o *Union) Or(conds ...Compound) *Union {
-	o.compound.push(defaultJoin(conds...)...)
-	return o
+	return &Union{o.compound.frame(conds)}
 }
 
 // Operator returns the OR operator.
@@ -317,8 +335,7 @@ func (o *Union) Empty() bool {
 
 // And adds more terms to the compound.
 func (a *Intersection) And(conds ...Compound) *Intersection {
-	a.compound.push(conds...)
-	return a
+	return &Intersection{a.compound.frame(conds)}
 }
 
 // Empty returns false if this struct holds no conditions.
@@ -415,7 +432,7 @@ func (f *dbFunc) Name() string {
 //		db.Cond{"last_name": "Mouse"},
 //	)
 func And(conds ...Compound) *Intersection {
-	return &Intersection{compound: newCompound(conds...)}
+	return &Intersection{newCompound(conds...)}
 }
 
 // Or joins conditions under logical disjunction. Conditions can be represented
@@ -429,7 +446,7 @@ func And(conds ...Compound) *Intersection {
 //		db.Cond{"year": 1987},
 //	)
 func Or(conds ...Compound) *Union {
-	return &Union{compound: newCompound(defaultJoin(conds...)...)}
+	return &Union{newCompound(defaultJoin(conds...)...)}
 }
 
 // Raw marks chunks of data as protected, so they pass directly to the query
diff --git a/lib/sqlbuilder/builder_test.go b/lib/sqlbuilder/builder_test.go
index e4c72565..85ac2fd3 100644
--- a/lib/sqlbuilder/builder_test.go
+++ b/lib/sqlbuilder/builder_test.go
@@ -657,7 +657,7 @@ func TestSelect(t *testing.T) {
 			db.Raw("a.id IN ?", sq),
 		)
 
-		cond.Or(db.Cond{"ml.mailing_list_id": []int{4, 5, 6}})
+		cond = cond.Or(db.Cond{"ml.mailing_list_id": []int{4, 5, 6}})
 
 		sel := b.
 			Select(db.Raw("DISTINCT ON(a.id) a.id"), db.Raw("COALESCE(NULLIF(ml.name,''), a.name) as name"), "a.email").
-- 
GitLab