From 25367f37caa6b23c27cac92fa14e2579e5621fe8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Nieto?= <jose.carlos@menteslibres.net>
Date: Sat, 1 Aug 2015 07:30:03 -0500
Subject: [PATCH] Provide Driver() method for SQL database transactions. This
 can be used to execute raw SQL queries within a transaction block. Closes
 #57.

---
 mysql/database.go           |  7 +------
 mysql/database_test.go      | 14 ++++++++-----
 mysql/tx.go                 | 39 +++++++++++++++++++++++++++++++++++++
 postgresql/database.go      |  7 +------
 postgresql/database_test.go | 12 +++++++++---
 postgresql/tx.go            | 39 +++++++++++++++++++++++++++++++++++++
 ql/database.go              |  7 +------
 ql/database_test.go         | 12 +++++++++---
 ql/tx.go                    | 39 +++++++++++++++++++++++++++++++++++++
 sqlite/database.go          |  7 +------
 sqlite/database_test.go     | 12 +++++++++---
 sqlite/tx.go                | 39 +++++++++++++++++++++++++++++++++++++
 12 files changed, 196 insertions(+), 38 deletions(-)
 create mode 100644 mysql/tx.go
 create mode 100644 postgresql/tx.go
 create mode 100644 ql/tx.go
 create mode 100644 sqlite/tx.go

diff --git a/mysql/database.go b/mysql/database.go
index 85fd6a68..a392a08a 100644
--- a/mysql/database.go
+++ b/mysql/database.go
@@ -49,11 +49,6 @@ type database struct {
 	cachedStatements *cache.Cache
 }
 
-type tx struct {
-	*sqltx.Tx
-	*database
-}
-
 type cachedStatement struct {
 	*sqlx.Stmt
 	query string
@@ -357,7 +352,7 @@ func (d *database) Transaction() (db.Tx, error) {
 
 	clone.tx = sqltx.New(sqlTx)
 
-	return tx{Tx: clone.tx, database: clone}, nil
+	return &tx{Tx: clone.tx, database: clone}, nil
 }
 
 // Exec compiles and executes a statement that does not return any rows.
diff --git a/mysql/database_test.go b/mysql/database_test.go
index 0dc9f8d6..010cc476 100644
--- a/mysql/database_test.go
+++ b/mysql/database_test.go
@@ -25,7 +25,6 @@ import (
 	"database/sql"
 	"errors"
 	"fmt"
-	"log"
 	"math/rand"
 	"os"
 	"reflect"
@@ -617,7 +616,6 @@ func TestResultFetch(t *testing.T) {
 		}
 
 		if err == nil {
-			log.Println("rowMap[id]:", rowMap["id"], reflect.TypeOf(rowMap["id"]))
 			if pk, ok := rowMap["id"].(int64); !ok || pk == 0 {
 				t.Fatalf("Expecting a not null ID.")
 			}
@@ -1301,6 +1299,12 @@ func TestTransactionsAndRollback(t *testing.T) {
 		t.Fatal(err)
 	}
 
+	// Won't fail
+	sqlxTx := tx.Driver().(*sqlx.Tx)
+	if _, err = sqlxTx.Exec("INSERT INTO `artist` (`id`, `name`) VALUES(?, ?)", 4, "Fourth"); err != nil {
+		t.Fatal(err)
+	}
+
 	if err = tx.Commit(); err != nil {
 		t.Fatal(err)
 	}
@@ -1309,7 +1313,7 @@ func TestTransactionsAndRollback(t *testing.T) {
 		t.Fatalf("Should have failed, as we've already commited.")
 	}
 
-	// Let's verify we have 3 rows.
+	// Let's verify we have 4 rows.
 	if artist, err = sess.Collection("artist"); err != nil {
 		t.Fatal(err)
 	}
@@ -1318,8 +1322,8 @@ func TestTransactionsAndRollback(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if count != 3 {
-		t.Fatalf("Expecting 3 elements.")
+	if count != 4 {
+		t.Fatalf("Expecting exactly 4 results.")
 	}
 
 }
diff --git a/mysql/tx.go b/mysql/tx.go
new file mode 100644
index 00000000..9d31c083
--- /dev/null
+++ b/mysql/tx.go
@@ -0,0 +1,39 @@
+// 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 mysql
+
+import (
+	"upper.io/db/util/sqlutil/tx"
+)
+
+type tx struct {
+	*sqltx.Tx
+	*database
+}
+
+// Driver returns the current transaction session.
+func (t *tx) Driver() interface{} {
+	if t != nil && t.Tx != nil {
+		return t.Tx.Tx
+	}
+	return nil
+}
diff --git a/postgresql/database.go b/postgresql/database.go
index b8048a92..42fe1bbb 100644
--- a/postgresql/database.go
+++ b/postgresql/database.go
@@ -50,11 +50,6 @@ type database struct {
 	cachedStatements *cache.Cache
 }
 
-type tx struct {
-	*sqltx.Tx
-	*database
-}
-
 type cachedStatement struct {
 	*sqlx.Stmt
 	query string
@@ -348,7 +343,7 @@ func (d *database) Transaction() (db.Tx, error) {
 
 	clone.tx = sqltx.New(sqlTx)
 
-	return tx{Tx: clone.tx, database: clone}, nil
+	return &tx{Tx: clone.tx, database: clone}, nil
 }
 
 // Exec compiles and executes a statement that does not return any rows.
diff --git a/postgresql/database_test.go b/postgresql/database_test.go
index 65c3443d..3abfbde7 100644
--- a/postgresql/database_test.go
+++ b/postgresql/database_test.go
@@ -1495,6 +1495,12 @@ func TestTransactionsAndRollback(t *testing.T) {
 		t.Fatal(err)
 	}
 
+	// Won't fail
+	sqlTx := tx.Driver().(*sqlx.Tx)
+	if _, err = sqlTx.Exec(`INSERT INTO "artist" ("id", "name") VALUES($1, $2)`, 4, "Fourth"); err != nil {
+		t.Fatal(err)
+	}
+
 	if err = tx.Commit(); err != nil {
 		t.Fatal(err)
 	}
@@ -1503,7 +1509,7 @@ func TestTransactionsAndRollback(t *testing.T) {
 		t.Fatalf("Should have failed, as we've already commited.")
 	}
 
-	// Let's verify we have 3 rows.
+	// Let's verify we have 4 rows.
 	if artist, err = sess.Collection("artist"); err != nil {
 		t.Fatal(err)
 	}
@@ -1512,8 +1518,8 @@ func TestTransactionsAndRollback(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if count != 3 {
-		t.Fatalf("Expecting only one element.")
+	if count != 4 {
+		t.Fatalf("Expecting exactly 4 results.")
 	}
 
 }
diff --git a/postgresql/tx.go b/postgresql/tx.go
new file mode 100644
index 00000000..ed0eea5f
--- /dev/null
+++ b/postgresql/tx.go
@@ -0,0 +1,39 @@
+// 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 postgresql
+
+import (
+	"upper.io/db/util/sqlutil/tx"
+)
+
+type tx struct {
+	*sqltx.Tx
+	*database
+}
+
+// Driver returns the current transaction session.
+func (t *tx) Driver() interface{} {
+	if t != nil && t.Tx != nil {
+		return t.Tx.Tx
+	}
+	return nil
+}
diff --git a/ql/database.go b/ql/database.go
index e091fe7b..590c37f5 100644
--- a/ql/database.go
+++ b/ql/database.go
@@ -50,11 +50,6 @@ type database struct {
 	cachedStatements *cache.Cache
 }
 
-type tx struct {
-	*sqltx.Tx
-	*database
-}
-
 type cachedStatement struct {
 	*sqlx.Stmt
 	query string
@@ -333,7 +328,7 @@ func (d *database) Transaction() (db.Tx, error) {
 
 	clone.tx = sqltx.New(sqlTx)
 
-	return tx{Tx: clone.tx, database: clone}, nil
+	return &tx{Tx: clone.tx, database: clone}, nil
 }
 
 // Exec compiles and executes a statement that does not return any rows.
diff --git a/ql/database_test.go b/ql/database_test.go
index 326f7691..7bdf01fc 100644
--- a/ql/database_test.go
+++ b/ql/database_test.go
@@ -1123,6 +1123,12 @@ func TestTransactionsAndRollback(t *testing.T) {
 		t.Fatal(err)
 	}
 
+	// Won't fail
+	sqlxTx := tx.Driver().(*sqlx.Tx)
+	if _, err = sqlxTx.Exec(`INSERT INTO artist (name) VALUES($1)`, "Fourth"); err != nil {
+		t.Fatal(err)
+	}
+
 	if err = tx.Commit(); err != nil {
 		t.Fatal(err)
 	}
@@ -1131,7 +1137,7 @@ func TestTransactionsAndRollback(t *testing.T) {
 		t.Fatalf("Should have failed, as we've already commited.")
 	}
 
-	// Let's verify we have 3 rows.
+	// Let's verify we have 4 rows.
 	if artist, err = sess.Collection("artist"); err != nil {
 		t.Fatal(err)
 	}
@@ -1140,8 +1146,8 @@ func TestTransactionsAndRollback(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if count != 3 {
-		t.Fatalf("Expecting only one element.")
+	if count != 4 {
+		t.Fatalf("Expecting exactly 4 results.")
 	}
 
 }
diff --git a/ql/tx.go b/ql/tx.go
new file mode 100644
index 00000000..7cc58c37
--- /dev/null
+++ b/ql/tx.go
@@ -0,0 +1,39 @@
+// 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 ql
+
+import (
+	"upper.io/db/util/sqlutil/tx"
+)
+
+type tx struct {
+	*sqltx.Tx
+	*database
+}
+
+// Driver returns the current transaction session.
+func (t *tx) Driver() interface{} {
+	if t != nil && t.Tx != nil {
+		return t.Tx.Tx
+	}
+	return nil
+}
diff --git a/sqlite/database.go b/sqlite/database.go
index f725a861..c0f685dd 100644
--- a/sqlite/database.go
+++ b/sqlite/database.go
@@ -54,11 +54,6 @@ type database struct {
 	cachedStatements *cache.Cache
 }
 
-type tx struct {
-	*sqltx.Tx
-	*database
-}
-
 type cachedStatement struct {
 	*sqlx.Stmt
 	query string
@@ -340,7 +335,7 @@ func (d *database) Transaction() (db.Tx, error) {
 
 	clone.tx = sqltx.New(sqlTx)
 
-	return tx{Tx: clone.tx, database: clone}, nil
+	return &tx{Tx: clone.tx, database: clone}, nil
 }
 
 // Exec compiles and executes a statement that does not return any rows.
diff --git a/sqlite/database_test.go b/sqlite/database_test.go
index 082b8434..1fa39c00 100644
--- a/sqlite/database_test.go
+++ b/sqlite/database_test.go
@@ -1230,6 +1230,12 @@ func TestTransactionsAndRollback(t *testing.T) {
 		t.Fatal(err)
 	}
 
+	// Won't fail
+	sqlxTx := tx.Driver().(*sqlx.Tx)
+	if _, err = sqlxTx.Exec(`INSERT INTO "artist" ("id", "name") VALUES(?, ?)`, 4, "Fourth"); err != nil {
+		t.Fatal(err)
+	}
+
 	if err = tx.Commit(); err != nil {
 		t.Fatal(err)
 	}
@@ -1238,7 +1244,7 @@ func TestTransactionsAndRollback(t *testing.T) {
 		t.Fatalf("Should have failed, as we've already commited.")
 	}
 
-	// Let's verify we have 3 rows.
+	// Let's verify we have 4 rows.
 	if artist, err = sess.Collection("artist"); err != nil {
 		t.Fatal(err)
 	}
@@ -1247,8 +1253,8 @@ func TestTransactionsAndRollback(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if count != 3 {
-		t.Fatalf("Expecting 3 elements.")
+	if count != 4 {
+		t.Fatalf("Expecting exactly 4 results.")
 	}
 
 }
diff --git a/sqlite/tx.go b/sqlite/tx.go
new file mode 100644
index 00000000..04ff10be
--- /dev/null
+++ b/sqlite/tx.go
@@ -0,0 +1,39 @@
+// 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 sqlite
+
+import (
+	"upper.io/db/util/sqlutil/tx"
+)
+
+type tx struct {
+	*sqltx.Tx
+	*database
+}
+
+// Driver returns the current transaction session.
+func (t *tx) Driver() interface{} {
+	if t != nil && t.Tx != nil {
+		return t.Tx.Tx
+	}
+	return nil
+}
-- 
GitLab