From c01d95d57566a36825b98ff52026c75bf1ae23b2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Nieto?= <jose.carlos@menteslibres.net>
Date: Tue, 10 Jun 2014 20:26:39 -0500
Subject: [PATCH] Upgrading MySQL adapter to use templates.

---
 mysql/_dumps/structs.sql |  20 ++
 mysql/_example/main.go   |  12 +-
 mysql/_example/test.sh   |   2 +-
 mysql/collection.go      | 355 +++++++++----------
 mysql/database.go        | 407 ++++++++++++---------
 mysql/database_test.go   | 742 ++++++++++++++++++++++++++-------------
 mysql/layout.go          | 124 +++++++
 mysql/result.go          | 193 +++++-----
 mysql/tx.go              |  34 ++
 9 files changed, 1193 insertions(+), 696 deletions(-)
 create mode 100644 mysql/layout.go
 create mode 100644 mysql/tx.go

diff --git a/mysql/_dumps/structs.sql b/mysql/_dumps/structs.sql
index 1058dfcd..a6046cbe 100644
--- a/mysql/_dumps/structs.sql
+++ b/mysql/_dumps/structs.sql
@@ -8,6 +8,26 @@ CREATE TABLE artist (
   name VARCHAR(60)
 );
 
+DROP TABLE IF EXISTS publication;
+
+CREATE TABLE publication (
+  id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+  PRIMARY KEY(id),
+  title VARCHAR(80),
+  author_id BIGINT(20)
+);
+
+DROP TABLE IF EXISTS review;
+
+CREATE TABLE review (
+  id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+  PRIMARY KEY(id),
+  publication_id BIGINT(20),
+  name VARCHAR(80),
+  comments TEXT,
+  created DATETIME
+);
+
 DROP TABLE IF EXISTS data_types;
 
 CREATE TABLE data_types (
diff --git a/mysql/_example/main.go b/mysql/_example/main.go
index 0d5df5f3..f7d963f6 100644
--- a/mysql/_example/main.go
+++ b/mysql/_example/main.go
@@ -9,17 +9,17 @@ import (
 )
 
 var settings = db.Settings{
-	Database: `upperio_tests`,               // Database name
-	Socket:   `/var/run/mysqld/mysqld.sock`, // Using unix sockets.
-	User:     `upperio`,                     // Database username.
-	Password: `upperio`,                     // Database password.
+	Database: `upperio_tests`, // Database name
+	Host:     `testserver.local`,
+	User:     `upperio`, // Database username.
+	Password: `upperio`, // Database password.
 }
 
 type Birthday struct {
 	// Maps the "Name" property to the "name" column of the "birthdays" table.
-	Name string `field:"name"`
+	Name string `db:"name"`
 	// Maps the "Born" property to the "born" column of the "birthdays" table.
-	Born time.Time `field:"born"`
+	Born time.Time `db:"born"`
 }
 
 func main() {
diff --git a/mysql/_example/test.sh b/mysql/_example/test.sh
index d61a8716..518f1cf6 100755
--- a/mysql/_example/test.sh
+++ b/mysql/_example/test.sh
@@ -1,2 +1,2 @@
-cat example.sql | mysql -uupperio -pupperio upperio_tests
+cat example.sql | mysql -uupperio -pupperio upperio_tests -htestserver.local
 go run main.go
diff --git a/mysql/collection.go b/mysql/collection.go
index cf4baea2..a358e0ae 100644
--- a/mysql/collection.go
+++ b/mysql/collection.go
@@ -1,204 +1,249 @@
-/*
-  Copyright (c) 2012-2014 JosĂŠ Carlos Nieto, https://menteslibres.net/xiam
-
-  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.
-*/
+// Copyright (c) 2012-2014 JosĂŠ Carlos Nieto, https://menteslibres.net/xiam
+//
+// 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 (
 	"fmt"
-	_ "github.com/go-sql-driver/mysql"
 	"menteslibres.net/gosexy/to"
 	"reflect"
 	"strings"
 	"time"
 	"upper.io/db"
+	"upper.io/db/util/sqlgen"
 	"upper.io/db/util/sqlutil"
 )
 
-// Mysql table/collection.
+const defaultOperator = `=`
+
 type Table struct {
-	source *Source
 	sqlutil.T
+	source *Source
+	names  []string
 }
 
-// Creates a filter with the given terms.
-func (self *Table) Find(terms ...interface{}) db.Result {
+func whereValues(term interface{}) (where sqlgen.Where, args []interface{}) {
 
-	queryChunks := sqlutil.NewQueryChunks()
+	args = []interface{}{}
 
-	// No specific fields given.
-	if len(queryChunks.Fields) == 0 {
-		queryChunks.Fields = []string{`*`}
+	switch t := term.(type) {
+	case []interface{}:
+		l := len(t)
+		where = make(sqlgen.Where, 0, l)
+		for _, cond := range t {
+			w, v := whereValues(cond)
+			args = append(args, v...)
+			where = append(where, w...)
+		}
+	case db.And:
+		and := make(sqlgen.And, 0, len(t))
+		for _, cond := range t {
+			k, v := whereValues(cond)
+			args = append(args, v...)
+			and = append(and, k...)
+		}
+		where = append(where, and)
+	case db.Or:
+		or := make(sqlgen.Or, 0, len(t))
+		for _, cond := range t {
+			k, v := whereValues(cond)
+			args = append(args, v...)
+			or = append(or, k...)
+		}
+		where = append(where, or)
+	case db.Raw:
+		if s, ok := t.Value.(string); ok == true {
+			where = append(where, sqlgen.Raw{s})
+		}
+	case db.Cond:
+		k, v := conditionValues(t)
+		args = append(args, v...)
+		for _, kk := range k {
+			where = append(where, kk)
+		}
 	}
 
-	// Compiling conditions
-	queryChunks.Conditions, queryChunks.Arguments = self.compileConditions(terms)
+	return where, args
+}
 
-	if queryChunks.Conditions == "" {
-		queryChunks.Conditions = `1 = 1`
-	}
+func interfaceArgs(value interface{}) (args []interface{}) {
 
-	// Creating a result handler.
-	result := &Result{
-		self,
-		queryChunks,
-		nil,
+	if value == nil {
+		return nil
 	}
 
-	return result
-}
+	value_v := reflect.ValueOf(value)
 
-// Transforms conditions into arguments for sql.Exec/sql.Query
-func (self *Table) compileConditions(term interface{}) (string, []interface{}) {
-	sql := []string{}
-	args := []interface{}{}
+	switch value_v.Type().Kind() {
+	case reflect.Slice:
+		var i, total int
 
-	switch t := term.(type) {
-	case []interface{}:
-		for i := range t {
-			rsql, rargs := self.compileConditions(t[i])
-			if rsql != "" {
-				sql = append(sql, rsql)
-				args = append(args, rargs...)
-			}
-		}
-		if len(sql) > 0 {
-			return `(` + strings.Join(sql, ` AND `) + `)`, args
-		}
-	case db.Or:
-		for i := range t {
-			rsql, rargs := self.compileConditions(t[i])
-			if rsql != "" {
-				sql = append(sql, rsql)
-				args = append(args, rargs...)
-			}
-		}
-		if len(sql) > 0 {
-			return `(` + strings.Join(sql, ` OR `) + `)`, args
-		}
-	case db.And:
-		for i := range t {
-			rsql, rargs := self.compileConditions(t[i])
-			if rsql != "" {
-				sql = append(sql, rsql)
-				args = append(args, rargs...)
+		total = value_v.Len()
+		if total > 0 {
+			args = make([]interface{}, total)
+
+			for i = 0; i < total; i++ {
+				args[i] = toInternal(value_v.Index(i).Interface())
 			}
+
+			return args
+		} else {
+			return nil
 		}
-		if len(sql) > 0 {
-			return `(` + strings.Join(sql, ` AND `) + `)`, args
-		}
-	case db.Cond:
-		return self.compileStatement(t)
+	default:
+		args = []interface{}{toInternal(value)}
 	}
 
-	return "", args
+	return args
 }
 
-func (self *Table) compileStatement(cond db.Cond) (string, []interface{}) {
+func conditionValues(cond db.Cond) (columnValues sqlgen.ColumnValues, args []interface{}) {
 
-	total := len(cond)
+	args = []interface{}{}
 
-	str := make([]string, 0, total)
-	arg := make([]interface{}, 0, total)
+	for column, value := range cond {
+		var columnValue sqlgen.ColumnValue
 
-	// Walking over conditions
-	for field, value := range cond {
-		// Removing leading or trailing spaces.
-		field = strings.TrimSpace(field)
+		// Guessing operator from input, or using a default one.
+		column := strings.TrimSpace(column)
+		chunks := strings.SplitN(column, ` `, 2)
 
-		chunks := strings.SplitN(field, ` `, 2)
-
-		// Default operator.
-		op := `=`
+		columnValue.Column = sqlgen.Column{chunks[0]}
 
 		if len(chunks) > 1 {
-			// User has defined a different operator.
-			op = chunks[1]
+			columnValue.Operator = chunks[1]
+		} else {
+			columnValue.Operator = defaultOperator
 		}
 
 		switch value := value.(type) {
 		case db.Func:
+			// Catches functions.
 			value_i := interfaceArgs(value.Args)
+			columnValue.Operator = value.Name
+
 			if value_i == nil {
-				str = append(str, fmt.Sprintf(`%s %s ()`, chunks[0], value.Name))
+				// A function with no arguments.
+				columnValue.Value = sqlgen.Value{sqlgen.Raw{`()`}}
 			} else {
-				str = append(str, fmt.Sprintf(`%s %s (?%s)`, chunks[0], value.Name, strings.Repeat(`,?`, len(value_i)-1)))
-				arg = append(arg, value_i...)
+				// A function with one or more arguments.
+				columnValue.Value = sqlgen.Value{sqlgen.Raw{fmt.Sprintf(`(?%s)`, strings.Repeat(`, ?`, len(value_i)-1))}}
 			}
+
+			args = append(args, value_i...)
 		default:
+			// Catches everything else.
 			value_i := interfaceArgs(value)
-			if value_i == nil {
-				str = append(str, fmt.Sprintf(`%s %s ()`, chunks[0], op))
+			l := len(value_i)
+			if value_i == nil || l == 0 {
+				// Nil value given.
+				columnValue.Value = sqlgen.Value{sqlgen.Raw{`NULL`}}
 			} else {
-				str = append(str, fmt.Sprintf(`%s %s (?%s)`, chunks[0], op, strings.Repeat(`,?`, len(value_i)-1)))
-				arg = append(arg, value_i...)
+				if l > 1 {
+					// Array value given.
+					columnValue.Value = sqlgen.Value{sqlgen.Raw{fmt.Sprintf(`(?%s)`, strings.Repeat(`, ?`, len(value_i)-1))}}
+				} else {
+					// Single value given.
+					columnValue.Value = sqlPlaceholder
+				}
+				args = append(args, value_i...)
 			}
 		}
+
+		columnValues = append(columnValues, columnValue)
 	}
 
-	switch len(str) {
-	case 1:
-		return str[0], arg
-	case 0:
-		return "", []interface{}{}
+	return columnValues, args
+}
+
+func (self *Table) Find(terms ...interface{}) db.Result {
+	where, arguments := whereValues(terms)
+
+	result := &Result{
+		table:     self,
+		where:     where,
+		arguments: arguments,
 	}
 
-	return `(` + strings.Join(str, ` AND `) + `)`, arg
+	return result
+}
+
+func (self *Table) tableN(i int) string {
+	if len(self.names) > i {
+		chunks := strings.SplitN(self.names[i], " ", 2)
+		if len(chunks) > 0 {
+			return chunks[0]
+		}
+	}
+	return ""
 }
 
 // Deletes all the rows within the collection.
 func (self *Table) Truncate() error {
 
-	_, err := self.source.doExec(
-		fmt.Sprintf("TRUNCATE TABLE `%s`", self.Name()),
-	)
+	_, err := self.source.doExec(sqlgen.Statement{
+		Type:  sqlgen.SqlTruncate,
+		Table: sqlgen.Table{self.tableN(0)},
+	})
+
+	if err != nil {
+		return err
+	}
 
-	return err
+	return nil
 }
 
 // Appends an item (map or struct) into the collection.
 func (self *Table) Append(item interface{}) (interface{}, error) {
-	var id interface{}
 
-	fields, values, err := self.FieldValues(item, toInternal)
+	cols, vals, err := self.FieldValues(item, toInternal)
+
+	var columns sqlgen.Columns
+	var values sqlgen.Values
+
+	for _, col := range cols {
+		columns = append(columns, sqlgen.Column{col})
+	}
+
+	for i := 0; i < len(vals); i++ {
+		values = append(values, sqlPlaceholder)
+	}
 
 	// Error ocurred, stop appending.
 	if err != nil {
 		return nil, err
 	}
 
-	res, err := self.source.doExec(
-		fmt.Sprintf("INSERT INTO `%s`", self.Name()),
-		sqlFields(fields),
-		"VALUES",
-		sqlValues(values),
-	)
+	res, err := self.source.doExec(sqlgen.Statement{
+		Type:    sqlgen.SqlInsert,
+		Table:   sqlgen.Table{self.tableN(0)},
+		Columns: columns,
+		Values:  values,
+	}, vals...)
 
-	// Error ocurred, stop appending.
 	if err != nil {
 		return nil, err
 	}
 
-	// Last inserted ID could be zero too.
+	var id int64
 	id, _ = res.LastInsertId()
 
 	return id, nil
@@ -206,33 +251,18 @@ func (self *Table) Append(item interface{}) (interface{}, error) {
 
 // Returns true if the collection exists.
 func (self *Table) Exists() bool {
-	rows, err := self.source.doQuery(
-		fmt.Sprintf(`
-				SELECT table_name
-					FROM information_schema.tables
-				WHERE table_schema = '%s' AND table_name = '%s'
-			`,
-			self.source.Name(),
-			self.Name(),
-		),
-	)
-
-	if err != nil {
+	if err := self.source.tableExists(self.names...); err != nil {
 		return false
 	}
-
-	defer rows.Close()
-
-	return rows.Next()
+	return true
 }
 
-func toInternalInterface(val interface{}) interface{} {
-	return toInternal(val)
+func (self *Table) Name() string {
+	return strings.Join(self.names, `, `)
 }
 
 // Converts a Go value into internal database representation.
 func toInternal(val interface{}) interface{} {
-
 	switch t := val.(type) {
 	case []byte:
 		return string(t)
@@ -242,47 +272,10 @@ func toInternal(val interface{}) interface{} {
 		return fmt.Sprintf(TimeFormat, int(t/time.Hour), int(t/time.Minute%60), int(t/time.Second%60), t%time.Second/time.Millisecond)
 	case bool:
 		if t == true {
-			return "1"
+			return `1`
 		} else {
-			return "0"
+			return `0`
 		}
 	}
-
 	return to.String(val)
 }
-
-// Converts a database representation (after auto-conversion) into a Go value.
-func toNative(val interface{}) interface{} {
-	return val
-}
-
-func interfaceArgs(value interface{}) (args []interface{}) {
-
-	if value == nil {
-		return nil
-	}
-
-	value_v := reflect.ValueOf(value)
-
-	switch value_v.Type().Kind() {
-	case reflect.Slice:
-		var i, total int
-
-		total = value_v.Len()
-		if total > 0 {
-			args = make([]interface{}, total)
-
-			for i = 0; i < total; i++ {
-				args[i] = toInternal(value_v.Index(i).Interface())
-			}
-
-			return args
-		} else {
-			return nil
-		}
-	default:
-		args = []interface{}{toInternal(value)}
-	}
-
-	return args
-}
diff --git a/mysql/database.go b/mysql/database.go
index 77eeef41..378affda 100644
--- a/mysql/database.go
+++ b/mysql/database.go
@@ -1,25 +1,23 @@
-/*
-  Copyright (c) 2012-2014 JosĂŠ Carlos Nieto, https://menteslibres.net/xiam
-
-  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.
-*/
+// Copyright (c) 2012-2014 JosĂŠ Carlos Nieto, https://menteslibres.net/xiam
+//
+// 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
 
@@ -27,35 +25,39 @@ import (
 	"database/sql"
 	"fmt"
 	_ "github.com/go-sql-driver/mysql"
-	"log"
 	"os"
 	"reflect"
 	"regexp"
 	"strings"
 	"upper.io/db"
+	"upper.io/db/util/sqlgen"
+	"upper.io/db/util/sqlutil"
 )
 
-// Format for saving dates.
-const DateFormat = "2006-01-02 15:04:05.000"
+const Driver = `mysql`
 
-// Format for saving times.
-const TimeFormat = "%d:%02d:%02d.%03d"
-
-var columnPattern = regexp.MustCompile(`^([a-z]+)\(?([0-9,]+)?\)?\s?([a-z]*)?`)
-
-const driverName = `mysql`
+var (
+	// Format for saving dates.
+	DateFormat = "2006-01-02 15:04:05.000"
+	// Format for saving times.
+	TimeFormat = "%d:%02d:%02d.%03d"
+)
 
-type sqlValues_t []interface{}
+var (
+	columnPattern  = regexp.MustCompile(`^([a-z]+)\(?([0-9,]+)?\)?\s?([a-z]*)?`)
+	sqlPlaceholder = sqlgen.Value{sqlgen.Raw{`?`}}
+)
 
 type Source struct {
-	session     *sql.DB
 	config      db.Settings
+	session     *sql.DB
 	collections map[string]db.Collection
+	tx          *sql.Tx
 }
 
-type sqlQuery struct {
-	Query []string
-	Args  []interface{}
+type columnSchema_t struct {
+	ColumnName string `db:"column_name"`
+	DataType   string `db:"data_type"`
 }
 
 func debugEnabled() bool {
@@ -66,89 +68,93 @@ func debugEnabled() bool {
 }
 
 func init() {
-	db.Register(driverName, &Source{})
-}
 
-func debugLogQuery(s string, q *sqlQuery) {
-	log.Printf("SQL: %s\nARGS: %v\n", strings.TrimSpace(s), q.Args)
+	sqlgen.SetTemplate(sqlgen.Template{
+		mysqlColumnSeparator,
+		mysqlIdentifierSeparator,
+		mysqlIdentifierQuote,
+		mysqlValueSeparator,
+		mysqlValueQuote,
+		mysqlAndKeyword,
+		mysqlOrKeyword,
+		mysqlNotKeyword,
+		mysqlDescKeyword,
+		mysqlAscKeyword,
+		mysqlDefaultOperator,
+		mysqlClauseGroup,
+		mysqlClauseOperator,
+		mysqlColumnValue,
+		mysqlTableAliasLayout,
+		mysqlColumnAliasLayout,
+		mysqlSortByColumnLayout,
+		mysqlWhereLayout,
+		mysqlOrderByLayout,
+		mysqlInsertLayout,
+		mysqlSelectLayout,
+		mysqlUpdateLayout,
+		mysqlDeleteLayout,
+		mysqlTruncateLayout,
+		mysqlDropDatabaseLayout,
+		mysqlDropTableLayout,
+		mysqlSelectCountLayout,
+	})
+
+	db.Register(Driver, &Source{})
 }
 
-func sqlCompile(terms []interface{}) *sqlQuery {
-	q := &sqlQuery{}
-
-	q.Query = []string{}
+func (self *Source) doExec(stmt sqlgen.Statement, args ...interface{}) (sql.Result, error) {
 
-	for _, term := range terms {
-		switch t := term.(type) {
-		case sqlValues_t:
-			args := make([]string, len(t))
-			for i, arg := range t {
-				args[i] = `?`
-				q.Args = append(q.Args, arg)
-			}
-			q.Query = append(q.Query, `(`+strings.Join(args, `, `)+`)`)
-		case string:
-			q.Query = append(q.Query, t)
-		default:
-			if reflect.TypeOf(t).Kind() == reflect.Slice {
-				var v = reflect.ValueOf(t)
-				for i := 0; i < v.Len(); i++ {
-					q.Args = append(q.Args, v.Index(i).Interface())
-				}
-			} else {
-				q.Args = append(q.Args, t)
-			}
-		}
+	if self.session == nil {
+		return nil, db.ErrNotConnected
 	}
 
-	return q
-}
+	query := stmt.Compile()
 
-func sqlFields(names []string) string {
-	for i, _ := range names {
-		names[i] = strings.Replace(names[i], "`", "``", -1)
+	if debugEnabled() == true {
+		sqlutil.DebugQuery(query, args)
 	}
-	return "(`" + strings.Join(names, "`, `") + "`)"
-}
 
-func sqlValues(values []interface{}) sqlValues_t {
-	ret := make(sqlValues_t, len(values))
-	for i, _ := range values {
-		ret[i] = values[i]
+	if self.tx != nil {
+		return self.tx.Exec(query, args...)
 	}
-	return ret
+
+	return self.session.Exec(query, args...)
 }
 
-func (self *Source) doExec(terms ...interface{}) (sql.Result, error) {
+func (self *Source) doQuery(stmt sqlgen.Statement, args ...interface{}) (*sql.Rows, error) {
 	if self.session == nil {
 		return nil, db.ErrNotConnected
 	}
 
-	chunks := sqlCompile(terms)
-
-	query := strings.Join(chunks.Query, ` `)
+	query := stmt.Compile()
 
 	if debugEnabled() == true {
-		debugLogQuery(query, chunks)
+		sqlutil.DebugQuery(query, args)
+	}
+
+	if self.tx != nil {
+		return self.tx.Query(query, args...)
 	}
 
-	return self.session.Exec(query, chunks.Args...)
+	return self.session.Query(query, args...)
 }
 
-func (self *Source) doQuery(terms ...interface{}) (*sql.Rows, error) {
+func (self *Source) doQueryRow(stmt sqlgen.Statement, args ...interface{}) (*sql.Row, error) {
 	if self.session == nil {
 		return nil, db.ErrNotConnected
 	}
 
-	chunks := sqlCompile(terms)
-
-	query := strings.Join(chunks.Query, " ")
+	query := stmt.Compile()
 
 	if debugEnabled() == true {
-		debugLogQuery(query, chunks)
+		sqlutil.DebugQuery(query, args)
+	}
+
+	if self.tx != nil {
+		return self.tx.QueryRow(query, args...), nil
 	}
 
-	return self.session.Query(query, chunks.Args...)
+	return self.session.QueryRow(query, args...), nil
 }
 
 // Returns the string name of the database.
@@ -156,10 +162,50 @@ func (self *Source) Name() string {
 	return self.config.Database
 }
 
+//  Ping verifies a connection to the database is still alive,
+//  establishing a connection if necessary.
+func (self *Source) Ping() error {
+	return self.session.Ping()
+}
+
+func (self *Source) clone() (*Source, error) {
+	src := &Source{}
+	src.Setup(self.config)
+
+	if err := src.Open(); err != nil {
+		return nil, err
+	}
+
+	return src, nil
+}
+
+func (self *Source) Clone() (db.Database, error) {
+	return self.clone()
+}
+
+func (self *Source) Transaction() (db.Tx, error) {
+	var err error
+	var clone *Source
+	var sqlTx *sql.Tx
+
+	if sqlTx, err = self.session.Begin(); err != nil {
+		return nil, err
+	}
+
+	if clone, err = self.clone(); err != nil {
+		return nil, err
+	}
+
+	tx := &Tx{clone}
+
+	clone.tx = sqlTx
+
+	return tx, nil
+}
+
 // Stores database settings.
 func (self *Source) Setup(config db.Settings) error {
 	self.config = config
-	self.session = nil
 	self.collections = make(map[string]db.Collection)
 	return self.Open()
 }
@@ -223,25 +269,17 @@ func (self *Source) Close() error {
 // Changes the active database.
 func (self *Source) Use(database string) error {
 	self.config.Database = database
-	_, err := self.session.Exec(fmt.Sprintf("USE `%s`", database))
-	return err
-}
-
-// Starts a transaction block.
-func (self *Source) Begin() error {
-	_, err := self.session.Exec(`START TRANSACTION`)
-	return err
-}
-
-// Ends a transaction block.
-func (self *Source) End() error {
-	_, err := self.session.Exec(`COMMIT`)
-	return err
+	return self.Open()
 }
 
 // Drops the currently active database.
 func (self *Source) Drop() error {
-	_, err := self.session.Exec(fmt.Sprintf("DROP DATABASE `%s`", self.config.Database))
+
+	_, err := self.doQuery(sqlgen.Statement{
+		Type:     sqlgen.SqlDropDatabase,
+		Database: sqlgen.Database{self.config.Database},
+	})
+
 	return err
 }
 
@@ -250,7 +288,16 @@ func (self *Source) Collections() ([]string, error) {
 	var collections []string
 	var collection string
 
-	rows, err := self.session.Query(`SHOW TABLES`)
+	rows, err := self.doQuery(sqlgen.Statement{
+		Type:  sqlgen.SqlSelect,
+		Table: sqlgen.Table{`information_schema.tables`},
+		Columns: sqlgen.Columns{
+			{`table_name`},
+		},
+		Where: sqlgen.Where{
+			sqlgen.ColumnValue{sqlgen.Column{`table_schema`}, `=`, sqlPlaceholder},
+		},
+	}, self.config.Database)
 
 	if err != nil {
 		return nil, err
@@ -266,85 +313,121 @@ func (self *Source) Collections() ([]string, error) {
 	return collections, nil
 }
 
-// Returns a collection instance by name.
-func (self *Source) Collection(name string) (db.Collection, error) {
+func (self *Source) tableExists(names ...string) error {
+	for _, name := range names {
+
+		rows, err := self.doQuery(sqlgen.Statement{
+			Type:  sqlgen.SqlSelect,
+			Table: sqlgen.Table{`information_schema.tables`},
+			Columns: sqlgen.Columns{
+				{`table_name`},
+			},
+			Where: sqlgen.Where{
+				sqlgen.ColumnValue{sqlgen.Column{`table_schema`}, `=`, sqlPlaceholder},
+				sqlgen.ColumnValue{sqlgen.Column{`table_name`}, `=`, sqlPlaceholder},
+			},
+		}, self.config.Database, name)
+
+		if err != nil {
+			return db.ErrCollectionDoesNotExists
+		}
+
+		defer rows.Close()
 
-	if col, ok := self.collections[name]; ok == true {
-		return col, nil
+		if rows.Next() == false {
+			return db.ErrCollectionDoesNotExists
+		}
 	}
 
-	table := &Table{}
+	return nil
+}
 
-	table.source = self
-	table.DB = self
+// Returns a collection instance by name.
+func (self *Source) Collection(names ...string) (db.Collection, error) {
 
-	table.SetName = name
+	if len(names) == 0 {
+		return nil, db.ErrMissingCollectionName
+	}
 
-	// Table exists?
-	if table.Exists() == false {
-		return table, db.ErrCollectionDoesNotExists
+	col := &Table{
+		source: self,
+		names:  names,
 	}
 
-	// Fetching table datatypes and mapping to internal gotypes.
-	rows, err := table.source.doQuery(
-		fmt.Sprintf(
-			"SHOW COLUMNS FROM `%s`",
-			table.Name(),
-		),
-	)
+	col.PrimaryKey = `id`
 
-	if err != nil {
-		return table, err
-	}
+	columns_t := []columnSchema_t{}
 
-	columns := []struct {
-		Field string
-		Type  string
-	}{}
+	for _, name := range names {
+		chunks := strings.SplitN(name, " ", 2)
 
-	err = table.FetchRows(&columns, rows)
+		if len(chunks) > 0 {
 
-	if err != nil {
-		return nil, err
-	}
+			name = chunks[0]
+
+			if err := self.tableExists(name); err != nil {
+				return nil, err
+			}
 
-	table.ColumnTypes = make(map[string]reflect.Kind, len(columns))
+			rows, err := self.doQuery(sqlgen.Statement{
+				Type:  sqlgen.SqlSelect,
+				Table: sqlgen.Table{`information_schema.columns`},
+				Columns: sqlgen.Columns{
+					{`column_name`},
+					{`data_type`},
+				},
+				Where: sqlgen.Where{
+					sqlgen.ColumnValue{sqlgen.Column{`table_schema`}, `=`, sqlPlaceholder},
+					sqlgen.ColumnValue{sqlgen.Column{`table_name`}, `=`, sqlPlaceholder},
+				},
+			}, self.config.Database, name)
+
+			if err != nil {
+				return nil, err
+			}
 
-	for _, column := range columns {
+			if err = col.FetchRows(&columns_t, rows); err != nil {
+				return nil, err
+			}
 
-		column.Field = strings.ToLower(column.Field)
-		column.Type = strings.ToLower(column.Type)
+			col.ColumnTypes = make(map[string]reflect.Kind, len(columns_t))
 
-		results := columnPattern.FindStringSubmatch(column.Type)
+			for _, column := range columns_t {
 
-		// Default properties.
-		dextra := ""
-		dtype := `varchar`
+				column.ColumnName = strings.ToLower(column.ColumnName)
+				column.DataType = strings.ToLower(column.DataType)
 
-		dtype = results[1]
+				results := columnPattern.FindStringSubmatch(column.DataType)
 
-		if len(results) > 3 {
-			dextra = results[3]
-		}
+				// Default properties.
+				dextra := ""
+				dtype := `varchar`
+
+				dtype = results[1]
+
+				if len(results) > 3 {
+					dextra = results[3]
+				}
 
-		ctype := reflect.String
+				ctype := reflect.String
+
+				// Guessing datatypes.
+				switch dtype {
+				case `tinyint`, `smallint`, `mediumint`, `int`, `bigint`:
+					if dextra == `unsigned` {
+						ctype = reflect.Uint64
+					} else {
+						ctype = reflect.Int64
+					}
+				case `decimal`, `float`, `double`:
+					ctype = reflect.Float64
+				}
 
-		// Guessing datatypes.
-		switch dtype {
-		case `tinyint`, `smallint`, `mediumint`, `int`, `bigint`:
-			if dextra == `unsigned` {
-				ctype = reflect.Uint64
-			} else {
-				ctype = reflect.Int64
+				col.ColumnTypes[column.ColumnName] = ctype
 			}
-		case `decimal`, `float`, `double`:
-			ctype = reflect.Float64
-		}
 
-		table.ColumnTypes[column.Field] = ctype
+		}
 	}
 
-	self.collections[name] = table
-
-	return table, nil
+	return col, nil
 }
diff --git a/mysql/database_test.go b/mysql/database_test.go
index f50e1ca7..9f41024a 100644
--- a/mysql/database_test.go
+++ b/mysql/database_test.go
@@ -1,40 +1,36 @@
-/*
-  Copyright (c) 2012-2014 JosĂŠ Carlos Nieto, https://menteslibres.net/xiam
-
-  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.
-*/
-
-/*
-	Tests for the mysql wrapper.
-
-	Execute the Makefile in ./_dumps/ to create the expected database structure.
-
-	cd _dumps
-	make
-	cd ..
-	go test
-*/
+// Copyright (c) 2012-2014 JosĂŠ Carlos Nieto, https://menteslibres.net/xiam
+//
+// 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
 
+// In order to execute these tests you must initialize the database first:
+//
+// cd _dumps
+// make
+// cd ..
+// go test
+
 import (
 	"database/sql"
+	"flag"
 	"menteslibres.net/gosexy/to"
 	"os"
 	"reflect"
@@ -44,25 +40,20 @@ import (
 	"upper.io/db"
 )
 
-// Wrapper.
-const wrapperName = "mysql"
-
-// Wrapper settings.
 const (
-	host     = "testserver.local"
-	dbname   = "upperio_tests"
+	database = "upperio_tests"
 	username = "upperio"
 	password = "upperio"
 )
 
-// Global settings for tests.
 var settings = db.Settings{
-	Database: dbname,
-	Host:     host,
+	Database: database,
 	User:     username,
 	Password: password,
 }
 
+var host = flag.String("host", "testserver.local", "Testing server address.")
+
 // Structure for testing conversions and datatypes.
 type testValuesStruct struct {
 	Uint   uint   `field:"_uint"`
@@ -98,30 +89,37 @@ var testValues = testValuesStruct{
 	time.Second * time.Duration(7331),
 }
 
-// Enabling outputting some information to stdout (like the SQL query and its
+func init() {
+	flag.Parse()
+	settings.Host = *host
+}
+
+// Loggin some information to stdout (like the SQL query and its
 // arguments), useful for development.
 func TestEnableDebug(t *testing.T) {
 	os.Setenv(db.EnvEnableDebug, "TRUE")
 }
 
-// Trying to open an empty datasource, it must fail.
+// Attempts to open an empty datasource.
 func TestOpenFailed(t *testing.T) {
-	_, err := db.Open(wrapperName, db.Settings{})
+	var err error
 
-	if err == nil {
-		t.Errorf("Expecting an error.")
+	// Attempt to open an empty database.
+	if _, err = db.Open(Driver, db.Settings{}); err == nil {
+		// Must fail.
+		t.Fatalf("Expecting an error.")
 	}
 }
 
-// Truncates all collections.
+// Attempts to get all collections and truncate each one of them.
 func TestTruncate(t *testing.T) {
-
 	var err error
+	var sess db.Database
+	var collections []string
+	var col db.Collection
 
 	// Opening database.
-	sess, err := db.Open(wrapperName, settings)
-
-	if err != nil {
+	if sess, err = db.Open(Driver, settings); err != nil {
 		t.Fatalf(err.Error())
 	}
 
@@ -129,158 +127,162 @@ func TestTruncate(t *testing.T) {
 	defer sess.Close()
 
 	// Getting a list of all collections in this database.
-	collections, err := sess.Collections()
-
-	if err != nil {
+	if collections, err = sess.Collections(); err != nil {
 		t.Fatalf(err.Error())
 	}
 
+	if len(collections) == 0 {
+		t.Fatalf("Expecting some collections.")
+	}
+
+	// Walking over collections.
 	for _, name := range collections {
 
-		// Pointing the collection.
-		col, err := sess.Collection(name)
-		if err != nil {
+		// Getting a collection.
+		if col, err = sess.Collection(name); err != nil {
 			t.Fatalf(err.Error())
 		}
 
-		// Since this is a SQL collection (table), the structure must exists before
-		// we can use it.
-		exists := col.Exists()
-
-		if exists == true {
-			// Truncating the structure, if exists.
-			err = col.Truncate()
-
-			if err != nil {
+		// Table must exists before we can use it.
+		if col.Exists() == true {
+			// Truncating the table.
+			if err = col.Truncate(); err != nil {
 				t.Fatalf(err.Error())
 			}
 		}
-
 	}
 }
 
-// This test appends some data into the "artist" table.
+// Attempts to append some data into the "artist" table.
 func TestAppend(t *testing.T) {
-
 	var err error
 	var id interface{}
+	var sess db.Database
+	var artist db.Collection
+	var total uint64
 
-	// Opening database.
-	sess, err := db.Open(wrapperName, settings)
-
-	if err != nil {
+	if sess, err = db.Open(Driver, settings); err != nil {
 		t.Fatalf(err.Error())
 	}
 
-	// We should close the database when it's no longer in use.
 	defer sess.Close()
 
-	// Getting a pointer to the "artist" collection.
-	artist, err := sess.Collection("artist")
-
-	if err != nil {
+	if artist, err = sess.Collection("artist"); err != nil {
 		t.Fatalf(err.Error())
 	}
 
-	// Appending a map.
-	id, err = artist.Append(map[string]string{
+	// Attempt to append a map.
+	item_m := map[string]string{
 		"name": "Ozzie",
-	})
+	}
+
+	if id, err = artist.Append(item_m); err != nil {
+		t.Fatalf(err.Error())
+	}
 
 	if to.Int64(id) == 0 {
 		t.Fatalf("Expecting an ID.")
 	}
 
-	// Appending a struct.
-	id, err = artist.Append(struct {
-		Name string `field:name`
+	// Attempt to append a struct.
+	item_s := struct {
+		Name string `db:"name"`
 	}{
 		"Flea",
-	})
+	}
+
+	if id, err = artist.Append(item_s); err != nil {
+		t.Fatalf(err.Error())
+	}
 
 	if to.Int64(id) == 0 {
 		t.Fatalf("Expecting an ID.")
 	}
 
-	// Appending a struct (using tags to specify the field name).
-	id, err = artist.Append(struct {
-		ArtistName string `field:"name"`
+	// Append to append a tagged struct.
+	item_t := struct {
+		ArtistName string `db:"name"`
 	}{
 		"Slash",
-	})
+	}
+
+	if id, err = artist.Append(item_t); err != nil {
+		t.Fatalf(err.Error())
+	}
 
 	if to.Int64(id) == 0 {
 		t.Fatalf("Expecting an ID.")
 	}
 
+	// Counting elements, must be exactly 3 elements.
+	if total, err = artist.Find().Count(); err != nil {
+		t.Fatalf(err.Error())
+	}
+
+	if total != 3 {
+		t.Fatalf("Expecting exactly 3 rows.")
+	}
+
 }
 
-// This test tries to use an empty filter and count how many elements were
-// added into the artist collection.
+// Attempts to count all rows in our newly defined set.
 func TestResultCount(t *testing.T) {
-
 	var err error
 	var res db.Result
+	var sess db.Database
+	var artist db.Collection
+	var total uint64
 
-	// Opening database.
-	sess, err := db.Open(wrapperName, settings)
-
-	if err != nil {
+	if sess, err = db.Open(Driver, settings); err != nil {
 		t.Fatalf(err.Error())
 	}
 
 	defer sess.Close()
 
 	// We should close the database when it's no longer in use.
-	artist, _ := sess.Collection("artist")
+	if artist, err = sess.Collection("artist"); err != nil {
+		t.Fatalf(err.Error())
+	}
 
+	// Defining a set with no conditions.
 	res = artist.Find()
 
 	// Counting all the matching rows.
-	total, err := res.Count()
-
-	if err != nil {
+	if total, err = res.Count(); err != nil {
 		t.Fatalf(err.Error())
 	}
 
 	if total == 0 {
-		t.Fatalf("Should not be empty, we've just added some rows!")
+		t.Fatalf("Counter should not be zero, we've just added some rows!")
 	}
-
 }
 
-// This test uses and result and tries to fetch items one by one.
+// Attempts to fetch results one by one.
 func TestResultFetch(t *testing.T) {
-
 	var err error
 	var res db.Result
+	var sess db.Database
+	var artist db.Collection
 
-	// Opening database.
-	sess, err := db.Open(wrapperName, settings)
-
-	if err != nil {
+	if sess, err = db.Open(Driver, settings); err != nil {
 		t.Fatalf(err.Error())
 	}
 
-	// We should close the database when it's no longer in use.
 	defer sess.Close()
 
-	artist, err := sess.Collection("artist")
-
-	if err != nil {
+	if artist, err = sess.Collection("artist"); err != nil {
 		t.Fatalf(err.Error())
 	}
 
-	// Testing map
-	res = artist.Find()
-
+	// Dumping into a map.
 	row_m := map[string]interface{}{}
 
+	res = artist.Find()
+
 	for {
 		err = res.Next(&row_m)
 
 		if err == db.ErrNoMoreRows {
-			// No more row_ms left.
 			break
 		}
 
@@ -298,7 +300,7 @@ func TestResultFetch(t *testing.T) {
 
 	res.Close()
 
-	// Testing struct
+	// Dumping into an struct with no tags.
 	row_s := struct {
 		Id   uint64
 		Name string
@@ -310,7 +312,6 @@ func TestResultFetch(t *testing.T) {
 		err = res.Next(&row_s)
 
 		if err == db.ErrNoMoreRows {
-			// No more row_s' left.
 			break
 		}
 
@@ -328,7 +329,7 @@ func TestResultFetch(t *testing.T) {
 
 	res.Close()
 
-	// Testing tagged struct
+	// Dumping into a tagged struct.
 	row_t := struct {
 		Value1 uint64 `field:"id"`
 		Value2 string `field:"name"`
@@ -340,7 +341,6 @@ func TestResultFetch(t *testing.T) {
 		err = res.Next(&row_t)
 
 		if err == db.ErrNoMoreRows {
-			// No more row_t's left.
 			break
 		}
 
@@ -358,54 +358,62 @@ func TestResultFetch(t *testing.T) {
 
 	res.Close()
 
-	// Testing Result.All() with a slice of maps.
-	res = artist.Find()
-
+	// Dumping into an slice of maps.
 	all_rows_m := []map[string]interface{}{}
-	err = res.All(&all_rows_m)
 
-	if err != nil {
+	res = artist.Find()
+	if err = res.All(&all_rows_m); err != nil {
 		t.Fatalf(err.Error())
 	}
 
+	if len(all_rows_m) != 3 {
+		t.Fatalf("Expecting 3 items.")
+	}
+
 	for _, single_row_m := range all_rows_m {
 		if to.Int64(single_row_m["id"]) == 0 {
 			t.Fatalf("Expecting a not null ID.")
 		}
 	}
 
-	// Testing Result.All() with a slice of structs.
-	res = artist.Find()
+	// Dumping into an slice of structs.
 
 	all_rows_s := []struct {
 		Id   uint64
 		Name string
 	}{}
-	err = res.All(&all_rows_s)
 
-	if err != nil {
+	res = artist.Find()
+	if err = res.All(&all_rows_s); err != nil {
 		t.Fatalf(err.Error())
 	}
 
+	if len(all_rows_s) != 3 {
+		t.Fatalf("Expecting 3 items.")
+	}
+
 	for _, single_row_s := range all_rows_s {
 		if single_row_s.Id == 0 {
 			t.Fatalf("Expecting a not null ID.")
 		}
 	}
 
-	// Testing Result.All() with a slice of tagged structs.
-	res = artist.Find()
-
+	// Dumping into an slice of tagged structs.
 	all_rows_t := []struct {
 		Value1 uint64 `field:"id"`
 		Value2 string `field:"name"`
 	}{}
-	err = res.All(&all_rows_t)
 
-	if err != nil {
+	res = artist.Find()
+
+	if err = res.All(&all_rows_t); err != nil {
 		t.Fatalf(err.Error())
 	}
 
+	if len(all_rows_t) != 3 {
+		t.Fatalf("Expecting 3 items.")
+	}
+
 	for _, single_row_t := range all_rows_t {
 		if single_row_t.Value1 == 0 {
 			t.Fatalf("Expecting a not null ID.")
@@ -413,28 +421,23 @@ func TestResultFetch(t *testing.T) {
 	}
 }
 
-// This test tries to update some previously added rows.
+// Attempts to modify previously added rows.
 func TestUpdate(t *testing.T) {
 	var err error
+	var sess db.Database
+	var artist db.Collection
 
-	// Opening database.
-	sess, err := db.Open(wrapperName, settings)
-
-	if err != nil {
+	if sess, err = db.Open(Driver, settings); err != nil {
 		t.Fatalf(err.Error())
 	}
 
-	// We should close the database when it's no longer in use.
 	defer sess.Close()
 
-	// Getting a pointer to the "artist" collection.
-	artist, err := sess.Collection("artist")
-
-	if err != nil {
+	if artist, err = sess.Collection("artist"); err != nil {
 		t.Fatalf(err.Error())
 	}
 
-	// Value
+	// Defining destination struct
 	value := struct {
 		Id   uint64
 		Name string
@@ -443,96 +446,83 @@ func TestUpdate(t *testing.T) {
 	// Getting the first artist.
 	res := artist.Find(db.Cond{"id !=": 0}).Limit(1)
 
-	err = res.One(&value)
-
-	if err != nil {
+	if err = res.One(&value); err != nil {
 		t.Fatalf(err.Error())
 	}
 
-	// Updating with a map
+	// Updating set with a map
 	row_m := map[string]interface{}{
 		"name": strings.ToUpper(value.Name),
 	}
 
-	err = res.Update(row_m)
-
-	if err != nil {
+	if err = res.Update(row_m); err != nil {
 		t.Fatalf(err.Error())
 	}
 
-	err = res.One(&value)
-
-	if err != nil {
+	// Pulling it again.
+	if err = res.One(&value); err != nil {
 		t.Fatalf(err.Error())
 	}
 
+	// Verifying.
 	if value.Name != row_m["name"] {
 		t.Fatalf("Expecting a modification.")
 	}
 
-	// Updating with a struct
+	// Updating set with a struct
 	row_s := struct {
 		Name string
 	}{strings.ToLower(value.Name)}
 
-	err = res.Update(row_s)
-
-	if err != nil {
+	if err = res.Update(row_s); err != nil {
 		t.Fatalf(err.Error())
 	}
 
-	err = res.One(&value)
-
-	if err != nil {
+	// Pulling it again.
+	if err = res.One(&value); err != nil {
 		t.Fatalf(err.Error())
 	}
 
+	// Verifying
 	if value.Name != row_s.Name {
 		t.Fatalf("Expecting a modification.")
 	}
 
-	// Updating with a tagged struct
+	// Updating set with a tagged struct
 	row_t := struct {
-		Value1 string `field:"name"`
+		Value1 string `db:"name"`
 	}{strings.Replace(value.Name, "z", "Z", -1)}
 
-	err = res.Update(row_t)
-
-	if err != nil {
+	if err = res.Update(row_t); err != nil {
 		t.Fatalf(err.Error())
 	}
 
-	err = res.One(&value)
-
-	if err != nil {
+	// Pulling it again.
+	if err = res.One(&value); err != nil {
 		t.Fatalf(err.Error())
 	}
 
+	// Verifying
 	if value.Name != row_t.Value1 {
 		t.Fatalf("Expecting a modification.")
 	}
-
 }
 
-// Test database functions
+// Attempts to use functions within database queries.
 func TestFunction(t *testing.T) {
 	var err error
 	var res db.Result
+	var sess db.Database
+	var artist db.Collection
+	var total uint64
 
-	// Opening database.
-	sess, err := db.Open(wrapperName, settings)
-
-	if err != nil {
+	if sess, err = db.Open(Driver, settings); err != nil {
 		t.Fatalf(err.Error())
 	}
 
-	// We should close the database when it's no longer in use.
 	defer sess.Close()
 
-	// Getting a pointer to the "artist" collection.
-	artist, err := sess.Collection("artist")
-
-	if err != nil {
+	if artist, err = sess.Collection("artist"); err != nil {
 		t.Fatalf(err.Error())
 	}
 
@@ -544,98 +534,289 @@ func TestFunction(t *testing.T) {
 	res = artist.Find(db.Cond{"id NOT IN": []int{0, -1}})
 
 	if err = res.One(&row_s); err != nil {
-		t.Fatalf("One: %q", err)
+		t.Fatalf(err.Error())
+	}
+
+	if total, err = res.Count(); err != nil {
+		t.Fatalf(err.Error())
+	}
+
+	if total != 3 {
+		t.Fatalf("Expecting 3 items.")
 	}
 
 	res = artist.Find(db.Cond{"id": db.Func{"NOT IN", []int{0, -1}}})
 
 	if err = res.One(&row_s); err != nil {
-		t.Fatalf("One: %q", err)
+		t.Fatalf(err.Error())
+	}
+
+	if total, err = res.Count(); err != nil {
+		t.Fatalf(err.Error())
+	}
+
+	if total != 3 {
+		t.Fatalf("Expecting 3 items.")
 	}
 
 	res.Close()
 }
 
-// This test tries to remove some previously added rows.
+// Attempts to delete previously added rows.
 func TestRemove(t *testing.T) {
+	var err error
+	var res db.Result
+	var sess db.Database
+	var artist db.Collection
+
+	if sess, err = db.Open(Driver, settings); err != nil {
+		t.Fatalf(err.Error())
+	}
+
+	defer sess.Close()
+
+	if artist, err = sess.Collection("artist"); err != nil {
+		t.Fatalf(err.Error())
+	}
 
+	// Getting the artist with id = 1
+	res = artist.Find(db.Cond{"id": 1})
+
+	// Trying to remove the row.
+	if err = res.Remove(); err != nil {
+		t.Fatalf(err.Error())
+	}
+}
+
+// Attempts to use SQL raw statements.
+func TestRawRelations(t *testing.T) {
+	var sess db.Database
 	var err error
 
-	// Opening database.
-	sess, err := db.Open(wrapperName, settings)
+	var artist db.Collection
+	var publication db.Collection
+	var review db.Collection
 
-	if err != nil {
+	type artist_t struct {
+		Id   int64  `db:"id,omitempty"`
+		Name string `db:"name"`
+	}
+
+	type publication_t struct {
+		Id       int64  `db:"id,omitempty"`
+		Title    string `db:"title"`
+		AuthorId int64  `db:"author_id"`
+	}
+
+	type review_t struct {
+		Id            int64     `db:"id,omitempty"`
+		PublicationId int64     `db:"publication_id"`
+		Name          string    `db:"name"`
+		Comments      string    `db:"comments"`
+		Created       time.Time `db:"created"`
+	}
+
+	if sess, err = db.Open(Driver, settings); err != nil {
 		t.Fatalf(err.Error())
 	}
 
-	// We should close the database when it's no longer in use.
 	defer sess.Close()
 
-	// Getting a pointer to the "artist" collection.
-	artist, err := sess.Collection("artist")
+	// Artist collection.
+	if artist, err = sess.Collection("artist"); err != nil {
+		t.Fatalf(err.Error())
+	}
 
-	if err != nil {
+	if err = artist.Truncate(); err != nil {
 		t.Fatalf(err.Error())
 	}
 
-	// Getting the artist with id = 1
-	res := artist.Find(db.Cond{"id": 1})
+	// Publication collection.
+	if publication, err = sess.Collection("publication"); err != nil {
+		t.Fatalf(err.Error())
+	}
 
-	// Trying to remove the row.
-	err = res.Remove()
+	if err = publication.Truncate(); err != nil {
+		t.Fatalf(err.Error())
+	}
+
+	// Review collection.
+	if review, err = sess.Collection("review"); err != nil {
+		t.Fatalf(err.Error())
+	}
+
+	if err = review.Truncate(); err != nil {
+		t.Fatalf(err.Error())
+	}
+
+	// Adding some artists.
+	var miyazakiId interface{}
+	miyazaki := artist_t{Name: `Hayao Miyazaki`}
+	if miyazakiId, err = artist.Append(miyazaki); err != nil {
+		t.Fatalf(err.Error())
+	}
+	miyazaki.Id = miyazakiId.(int64)
+
+	var asimovId interface{}
+	asimov := artist_t{Name: `Isaac Asimov`}
+	if asimovId, err = artist.Append(asimov); err != nil {
+		t.Fatalf(err.Error())
+	}
+
+	var marquezId interface{}
+	marquez := artist_t{Name: `Gabriel GarcĂ­a MĂĄrquez`}
+	if marquezId, err = artist.Append(marquez); err != nil {
+		t.Fatalf(err.Error())
+	}
+
+	// Adding some publications.
+	publication.Append(publication_t{
+		Title:    `Tonari no Totoro`,
+		AuthorId: miyazakiId.(int64),
+	})
+
+	publication.Append(publication_t{
+		Title:    `Howl's Moving Castle`,
+		AuthorId: miyazakiId.(int64),
+	})
+
+	publication.Append(publication_t{
+		Title:    `Ponyo`,
+		AuthorId: miyazakiId.(int64),
+	})
+
+	publication.Append(publication_t{
+		Title:    `Memoria de mis Putas Tristes`,
+		AuthorId: marquezId.(int64),
+	})
+
+	publication.Append(publication_t{
+		Title:    `El Coronel no tiene quien le escriba`,
+		AuthorId: marquezId.(int64),
+	})
 
+	publication.Append(publication_t{
+		Title:    `El Amor en los tiempos del CĂłlera`,
+		AuthorId: marquezId.(int64),
+	})
+
+	publication.Append(publication_t{
+		Title:    `I, Robot`,
+		AuthorId: asimovId.(int64),
+	})
+
+	var foundationId interface{}
+	foundationId, err = publication.Append(publication_t{
+		Title:    `Foundation`,
+		AuthorId: asimovId.(int64),
+	})
 	if err != nil {
 		t.Fatalf(err.Error())
 	}
+
+	publication.Append(publication_t{
+		Title:    `The Robots of Dawn`,
+		AuthorId: asimovId.(int64),
+	})
+
+	// Adding reviews for foundation.
+	review.Append(review_t{
+		PublicationId: foundationId.(int64),
+		Name:          "John Doe",
+		Comments:      "I love The Foundation series.",
+		Created:       time.Now(),
+	})
+
+	review.Append(review_t{
+		PublicationId: foundationId.(int64),
+		Name:          "Edr Pls",
+		Comments:      "The Foundation series made me fall in love with Isaac Asimov.",
+		Created:       time.Now(),
+	})
+
+	// Exec'ing a raw query.
+	var artistPublication db.Collection
+	if artistPublication, err = sess.Collection(`artist AS a, publication AS p`); err != nil {
+		t.Fatalf(err.Error())
+	}
+
+	res := artistPublication.Find(
+		db.Raw{`a.id = p.author_id`},
+	).Select(
+		"p.id",
+		"p.title as publication_title",
+		"a.name AS artist_name",
+	)
+
+	type artistPublication_t struct {
+		Id               int64  `db:"id"`
+		PublicationTitle string `db:"publication_title"`
+		ArtistName       string `db:"artist_name"`
+	}
+
+	all := []artistPublication_t{}
+
+	if err = res.All(&all); err != nil {
+		t.Fatalf(err.Error())
+	}
+
+	if len(all) != 9 {
+		t.Fatalf("Expecting some rows.")
+	}
+
 }
 
-// This test tries to add many different datatypes to a single row in a
-// collection, then it tries to get the stored datatypes and check if the
-// stored and the original values match.
+// Attempts to add many different datatypes to a single row in a collection,
+// then it tries to get the stored datatypes and check if the stored and the
+// original values match.
 func TestDataTypes(t *testing.T) {
 	var res db.Result
+	var sess db.Database
+	var dataTypes db.Collection
+	var err error
+	var id interface{}
+	var exists uint64
 
-	// Opening database.
-	sess, err := db.Open(wrapperName, settings)
-
-	if err != nil {
+	if sess, err = db.Open(Driver, settings); err != nil {
 		t.Fatalf(err.Error())
 	}
 
-	// We should close the database when it's no longer in use.
 	defer sess.Close()
 
 	// Getting a pointer to the "data_types" collection.
-	dataTypes, err := sess.Collection("data_types")
-	dataTypes.Truncate()
+	if dataTypes, err = sess.Collection("data_types"); err != nil {
+		t.Fatalf(err.Error())
+	}
 
-	// Appending our test subject.
-	id, err := dataTypes.Append(testValues)
+	// Removing all data.
+	if err = dataTypes.Truncate(); err != nil {
+		t.Fatalf(err.Error())
+	}
 
-	if err != nil {
+	// Appending our test subject.
+	if id, err = dataTypes.Append(testValues); err != nil {
 		t.Fatalf(err.Error())
 	}
 
-	// Trying to get the same subject we added.
+	// Defining our set.
 	res = dataTypes.Find(db.Cond{"id": id})
 
-	exists, err := res.Count()
-
-	if err != nil {
+	if exists, err = res.Count(); err != nil {
 		t.Fatalf(err.Error())
 	}
 
 	if exists == 0 {
-		t.Errorf("Expecting an item.")
+		t.Fatalf("Expecting an item.")
 	}
 
 	// Trying to dump the subject into an empty structure of the same type.
 	var item testValuesStruct
+
 	res.One(&item)
 
 	// The original value and the test subject must match.
 	if reflect.DeepEqual(item, testValues) == false {
-		t.Errorf("Struct is different.")
+		t.Fatalf("Struct is different.")
 	}
 }
 
@@ -645,24 +826,25 @@ func TestDisableDebug(t *testing.T) {
 }
 
 // Benchmarking raw database/sql.
-func BenchmarkAppendRaw(b *testing.B) {
-	sess, err := db.Open(wrapperName, settings)
+func BenchmarkAppendRawSQL(b *testing.B) {
+	var err error
+	var sess db.Database
 
-	if err != nil {
+	if sess, err = db.Open(Driver, settings); err != nil {
 		b.Fatalf(err.Error())
 	}
 
 	defer sess.Close()
 
-	artist, err := sess.Collection("artist")
-	artist.Truncate()
-
 	driver := sess.Driver().(*sql.DB)
 
+	if _, err = driver.Exec("TRUNCATE TABLE `artist`"); err != nil {
+		b.Fatalf(err.Error())
+	}
+
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		_, err := driver.Exec(`INSERT INTO artist (name) VALUES('Hayao Miyazaki')`)
-		if err != nil {
+		if _, err = driver.Exec("INSERT INTO `artist` (`name`) VALUES('Hayao Miyazaki')"); err != nil {
 			b.Fatalf(err.Error())
 		}
 	}
@@ -672,8 +854,8 @@ func BenchmarkAppendRaw(b *testing.B) {
 //
 // Contributed by wei2912
 // See: https://github.com/gosexy/db/issues/20#issuecomment-20097801
-func BenchmarkAppendDbItem(b *testing.B) {
-	sess, err := db.Open(wrapperName, settings)
+func BenchmarkAppendUpper(b *testing.B) {
+	sess, err := db.Open(Driver, settings)
 
 	if err != nil {
 		b.Fatalf(err.Error())
@@ -684,68 +866,128 @@ func BenchmarkAppendDbItem(b *testing.B) {
 	artist, err := sess.Collection("artist")
 	artist.Truncate()
 
+	item := struct {
+		Name string `db:"name"`
+	}{"Hayao Miyazaki"}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		if _, err = artist.Append(item); err != nil {
+			b.Fatalf(err.Error())
+		}
+	}
+}
+
+// Benchmarking raw database/sql.
+func BenchmarkAppendTxRawSQL(b *testing.B) {
+	var err error
+	var sess db.Database
+	var tx *sql.Tx
+
+	if sess, err = db.Open(Driver, settings); err != nil {
+		b.Fatalf(err.Error())
+	}
+
+	defer sess.Close()
+
+	driver := sess.Driver().(*sql.DB)
+
+	if tx, err = driver.Begin(); err != nil {
+		b.Fatalf(err.Error())
+	}
+
+	if _, err = tx.Exec("TRUNCATE TABLE `artist`"); err != nil {
+		b.Fatalf(err.Error())
+	}
+
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		_, err = artist.Append(map[string]string{"name": "Leonardo DaVinci"})
-		if err != nil {
+		if _, err = tx.Exec("INSERT INTO `artist` (`name`) VALUES('Hayao Miyazaki')"); err != nil {
 			b.Fatalf(err.Error())
 		}
 	}
+
+	if err = tx.Commit(); err != nil {
+		b.Fatalf(err.Error())
+	}
 }
 
 // Benchmarking Append() with transactions.
-//
-// Contributed by wei2912
-// See: https://github.com/gosexy/db/issues/20#issuecomment-20167939
-// Applying the BEGIN and END transaction optimizations.
-func BenchmarkAppendDbItem_Transaction(b *testing.B) {
-	sess, err := db.Open(wrapperName, settings)
+func BenchmarkAppendTxUpper(b *testing.B) {
+	var sess db.Database
+	var err error
 
-	if err != nil {
+	if sess, err = db.Open(Driver, settings); err != nil {
 		b.Fatalf(err.Error())
 	}
 
 	defer sess.Close()
 
-	artist, err := sess.Collection("artist")
-	artist.Truncate()
+	var tx db.Tx
+	if tx, err = sess.Transaction(); err != nil {
+		b.Fatalf(err.Error())
+	}
 
-	err = sess.Begin()
-	if err != nil {
+	var artist db.Collection
+	if artist, err = tx.Collection("artist"); err != nil {
 		b.Fatalf(err.Error())
 	}
 
+	if err = artist.Truncate(); err != nil {
+		b.Fatalf(err.Error())
+	}
+
+	item := struct {
+		Name string `db:"name"`
+	}{"Hayao Miyazaki"}
+
+	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		_, err = artist.Append(map[string]string{"name": "Isaac Asimov"})
-		if err != nil {
+		if _, err = artist.Append(item); err != nil {
 			b.Fatalf(err.Error())
 		}
 	}
 
-	err = sess.End()
-	if err != nil {
+	if err = tx.Commit(); err != nil {
 		b.Fatalf(err.Error())
 	}
 }
 
-// Benchmarking Append with a struct.
-func BenchmarkAppendStruct(b *testing.B) {
-	sess, err := db.Open(wrapperName, settings)
+// Benchmarking Append() with map.
+func BenchmarkAppendTxUpperMap(b *testing.B) {
+	var sess db.Database
+	var err error
 
-	if err != nil {
+	if sess, err = db.Open(Driver, settings); err != nil {
 		b.Fatalf(err.Error())
 	}
 
 	defer sess.Close()
 
-	artist, err := sess.Collection("artist")
-	artist.Truncate()
+	var tx db.Tx
+	if tx, err = sess.Transaction(); err != nil {
+		b.Fatalf(err.Error())
+	}
+
+	var artist db.Collection
+	if artist, err = tx.Collection("artist"); err != nil {
+		b.Fatalf(err.Error())
+	}
+
+	if err = artist.Truncate(); err != nil {
+		b.Fatalf(err.Error())
+	}
+
+	item := map[string]string{"name": "Hayao Miyazaki"}
 
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		_, err = artist.Append(struct{ Name string }{"John Lennon"})
-		if err != nil {
+		if _, err = artist.Append(item); err != nil {
 			b.Fatalf(err.Error())
 		}
 	}
+
+	if err = tx.Commit(); err != nil {
+		b.Fatalf(err.Error())
+	}
 }
diff --git a/mysql/layout.go b/mysql/layout.go
new file mode 100644
index 00000000..98568de3
--- /dev/null
+++ b/mysql/layout.go
@@ -0,0 +1,124 @@
+// Copyright (c) 2012-2014 JosĂŠ Carlos Nieto, https://menteslibres.net/xiam
+//
+// 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
+
+const (
+	mysqlColumnSeparator     = `.`
+	mysqlIdentifierSeparator = `, `
+	mysqlIdentifierQuote     = "`{{.Raw}}`"
+	mysqlValueSeparator      = `, `
+	mysqlValueQuote          = `'{{.}}'`
+	mysqlAndKeyword          = `AND`
+	mysqlOrKeyword           = `OR`
+	mysqlNotKeyword          = `NOT`
+	mysqlDescKeyword         = `DESC`
+	mysqlAscKeyword          = `ASC`
+	mysqlDefaultOperator     = `=`
+	mysqlClauseGroup         = `({{.}})`
+	mysqlClauseOperator      = ` {{.}} `
+	mysqlColumnValue         = `{{.Column}} {{.Operator}} {{.Value}}`
+	mysqlTableAliasLayout    = `{{.Name}}{{if .Alias}} AS {{.Alias}}{{end}}`
+	mysqlColumnAliasLayout   = `{{.Name}}{{if .Alias}} AS {{.Alias}}{{end}}`
+	mysqlSortByColumnLayout  = `{{.Column}} {{.Sort}}`
+
+	mysqlOrderByLayout = `
+		{{if .SortColumns}}
+			ORDER BY {{.SortColumns}}
+		{{end}}
+	`
+
+	mysqlWhereLayout = `
+		{{if .Conds}}
+			WHERE {{.Conds}}
+		{{end}}
+	`
+
+	mysqlSelectLayout = `
+		SELECT
+
+			{{if .Columns}}
+				{{.Columns}}
+			{{else}}
+				*
+			{{end}}
+
+			FROM {{.Table}}
+
+			{{.Where}}
+
+			{{.OrderBy}}
+
+			{{if .Limit}}
+				LIMIT {{.Limit}}
+			{{end}}
+
+			{{if .Offset}}
+				OFFSET {{.Offset}}
+			{{end}}
+	`
+	mysqlDeleteLayout = `
+		DELETE
+			FROM {{.Table}}
+			{{.Where}}
+	`
+	mysqlUpdateLayout = `
+		UPDATE
+			{{.Table}}
+		SET {{.ColumnValues}}
+			{{ .Where }}
+	`
+
+	mysqlSelectCountLayout = `
+		SELECT
+			COUNT(1) AS _t
+		FROM {{.Table}}
+			{{.Where}}
+
+			{{if .Limit}}
+				LIMIT {{.Limit}}
+			{{end}}
+
+			{{if .Offset}}
+				OFFSET {{.Offset}}
+			{{end}}
+	`
+
+	mysqlInsertLayout = `
+		INSERT INTO {{.Table}}
+			({{.Columns}})
+		VALUES
+			({{.Values}})
+		{{.Extra}}
+	`
+
+	mysqlTruncateLayout = `
+		TRUNCATE TABLE {{.Table}}
+	`
+
+	mysqlDropDatabaseLayout = `
+		DROP DATABASE {{.Database}}
+	`
+
+	mysqlDropTableLayout = `
+		DROP TABLE {{.Table}}
+	`
+)
diff --git a/mysql/result.go b/mysql/result.go
index b12fbbff..0564f8f9 100644
--- a/mysql/result.go
+++ b/mysql/result.go
@@ -1,44 +1,46 @@
-/*
-  Copyright (c) 2012-2014 JosĂŠ Carlos Nieto, https://menteslibres.net/xiam
-
-  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.
-*/
+// Copyright (c) 2012-2014 JosĂŠ Carlos Nieto, https://menteslibres.net/xiam
+//
+// 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 (
 	"database/sql"
-	"fmt"
 	"strings"
 	"upper.io/db"
-	"upper.io/db/util/sqlutil"
+	"upper.io/db/util/sqlgen"
 )
 
 type counter struct {
-	Total uint64 `field:"total"`
+	Total uint64 `db:"_t"`
 }
 
 type Result struct {
-	table       *Table
-	queryChunks *sqlutil.QueryChunks
-	cursor      *sql.Rows // This query cursor keeps results for Next().
+	table     *Table
+	cursor    *sql.Rows // This is the main query cursor. It starts as a nil value.
+	limit     sqlgen.Limit
+	offset    sqlgen.Offset
+	columns   sqlgen.Columns
+	where     sqlgen.Where
+	orderBy   sqlgen.OrderBy
+	arguments []interface{}
 }
 
 // Executes a SELECT statement that can feed Next(), All() or One().
@@ -46,38 +48,28 @@ func (self *Result) setCursor() error {
 	var err error
 	// We need a cursor, if the cursor does not exists yet then we create one.
 	if self.cursor == nil {
-		self.cursor, err = self.table.source.doQuery(
-			// Mandatory SQL.
-			fmt.Sprintf(
-				"SELECT %s FROM `%s` WHERE %s",
-				// Fields.
-				strings.Join(self.queryChunks.Fields, `, `),
-				// Table name
-				self.table.Name(),
-				// Conditions
-				self.queryChunks.Conditions,
-			),
-			// Arguments
-			self.queryChunks.Arguments,
-			// Optional SQL
-			self.queryChunks.Sort,
-			self.queryChunks.Limit,
-			self.queryChunks.Offset,
-		)
+		self.cursor, err = self.table.source.doQuery(sqlgen.Statement{
+			Type:    sqlgen.SqlSelect,
+			Table:   sqlgen.Table{self.table.Name()},
+			Columns: self.columns,
+			Limit:   self.limit,
+			Offset:  self.offset,
+			Where:   self.where,
+		}, self.arguments...)
 	}
 	return err
 }
 
 // Determines the maximum limit of results to be returned.
 func (self *Result) Limit(n uint) db.Result {
-	self.queryChunks.Limit = fmt.Sprintf(`LIMIT %d`, n)
+	self.limit = sqlgen.Limit(n)
 	return self
 }
 
 // Determines how many documents will be skipped before starting to grab
 // results.
 func (self *Result) Skip(n uint) db.Result {
-	self.queryChunks.Offset = fmt.Sprintf(`OFFSET %d`, n)
+	self.offset = sqlgen.Offset(n)
 	return self
 }
 
@@ -85,24 +77,43 @@ func (self *Result) Skip(n uint) db.Result {
 // prefixed by - (minus) which means descending order, ascending order would be
 // used otherwise.
 func (self *Result) Sort(fields ...string) db.Result {
-	sort := make([]string, 0, len(fields))
+
+	sortColumns := make(sqlgen.SortColumns, 0, len(fields))
 
 	for _, field := range fields {
-		if strings.HasPrefix(field, `-`) == true {
-			sort = append(sort, field[1:]+` DESC`)
+		var sort sqlgen.SortColumn
+
+		if strings.HasPrefix(field, `-`) {
+			// Explicit descending order.
+			sort = sqlgen.SortColumn{
+				sqlgen.Column{field[1:]},
+				sqlgen.SqlSortDesc,
+			}
 		} else {
-			sort = append(sort, field+` ASC`)
+			// Ascending order.
+			sort = sqlgen.SortColumn{
+				sqlgen.Column{field},
+				sqlgen.SqlSortAsc,
+			}
 		}
+
+		sortColumns = append(sortColumns, sort)
 	}
 
-	self.queryChunks.Sort = `ORDER BY ` + strings.Join(sort, `, `)
+	self.orderBy.SortColumns = sortColumns
 
 	return self
 }
 
 // Retrieves only the given fields.
 func (self *Result) Select(fields ...string) db.Result {
-	self.queryChunks.Fields = fields
+	self.columns = make(sqlgen.Columns, 0, len(fields))
+
+	l := len(fields)
+	for i := 0; i < l; i++ {
+		self.columns = append(self.columns, sqlgen.Column{fields[i]})
+	}
+
 	return self
 }
 
@@ -115,11 +126,13 @@ func (self *Result) All(dst interface{}) error {
 	}
 
 	// Current cursor.
-	if err = self.setCursor(); err != nil {
+	err = self.setCursor()
+
+	if err != nil {
 		return err
 	}
 
-	defer self.Close() // Make sure the result set closes.
+	defer self.Close()
 
 	// Fetching all results within the cursor.
 	err = self.table.T.FetchRows(dst, self.cursor)
@@ -127,7 +140,7 @@ func (self *Result) All(dst interface{}) error {
 	return err
 }
 
-// Fetches only one result from the result set.
+// Fetches only one result from the resultset.
 func (self *Result) One(dst interface{}) error {
 	var err error
 
@@ -148,13 +161,16 @@ func (self *Result) Next(dst interface{}) error {
 	var err error
 
 	// Current cursor.
-	if err = self.setCursor(); err != nil {
+	err = self.setCursor()
+
+	if err != nil {
 		self.Close()
 	}
 
 	// Fetching the next result from the cursor.
-	if err = self.table.T.FetchRow(dst, self.cursor); err != nil {
-		// Closing result set on error.
+	err = self.table.T.FetchRow(dst, self.cursor)
+
+	if err != nil {
 		self.Close()
 	}
 
@@ -164,14 +180,11 @@ func (self *Result) Next(dst interface{}) error {
 // Removes the matching items from the collection.
 func (self *Result) Remove() error {
 	var err error
-	_, err = self.table.source.doExec(
-		fmt.Sprintf(
-			"DELETE FROM `%s` WHERE %s",
-			self.table.Name(),
-			self.queryChunks.Conditions,
-		),
-		self.queryChunks.Arguments,
-	)
+	_, err = self.table.source.doExec(sqlgen.Statement{
+		Type:  sqlgen.SqlDelete,
+		Table: sqlgen.Table{self.table.Name()},
+		Where: self.where,
+	}, self.arguments...)
 	return err
 
 }
@@ -182,30 +195,19 @@ func (self *Result) Update(values interface{}) error {
 
 	ff, vv, err := self.table.FieldValues(values, toInternal)
 
-	if err != nil {
-		return err
-	}
-
 	total := len(ff)
 
-	updateFields := make([]string, total)
-	updateArgs := make([]interface{}, total)
+	cvs := make(sqlgen.ColumnValues, 0, total)
 
 	for i := 0; i < total; i++ {
-		updateFields[i] = fmt.Sprintf(`%s = ?`, ff[i])
-		updateArgs[i] = vv[i]
+		cvs = append(cvs, sqlgen.ColumnValue{sqlgen.Column{ff[i]}, "=", sqlPlaceholder})
 	}
 
-	_, err = self.table.source.doExec(
-		fmt.Sprintf(
-			"UPDATE `%s` SET %s WHERE %s",
-			self.table.Name(),
-			strings.Join(updateFields, `, `),
-			self.queryChunks.Conditions,
-		),
-		updateArgs,
-		self.queryChunks.Arguments,
-	)
+	_, err = self.table.source.doExec(sqlgen.Statement{
+		Type:         sqlgen.SqlUpdate,
+		Table:        sqlgen.Table{self.table.Name()},
+		ColumnValues: cvs,
+	}, vv...)
 
 	return err
 }
@@ -223,23 +225,22 @@ func (self *Result) Close() error {
 // Counts matching elements.
 func (self *Result) Count() (uint64, error) {
 
-	rows, err := self.table.source.doQuery(
-		fmt.Sprintf(
-			"SELECT COUNT(1) AS total FROM `%s` WHERE %s",
-			self.table.Name(),
-			self.queryChunks.Conditions,
-		),
-		self.queryChunks.Arguments,
-	)
+	rows, err := self.table.source.doQuery(sqlgen.Statement{
+		Type:   sqlgen.SqlSelectCount,
+		Table:  sqlgen.Table{self.table.Name()},
+		Where:  self.where,
+		Limit:  self.limit,
+		Offset: self.offset,
+	}, self.arguments...)
 
 	if err != nil {
 		return 0, err
 	}
 
+	defer rows.Close()
+
 	dst := counter{}
 	self.table.T.FetchRow(&dst, rows)
 
-	rows.Close()
-
 	return dst.Total, nil
 }
diff --git a/mysql/tx.go b/mysql/tx.go
new file mode 100644
index 00000000..37ae6825
--- /dev/null
+++ b/mysql/tx.go
@@ -0,0 +1,34 @@
+// Copyright (c) 2012-2014 JosĂŠ Carlos Nieto, https://menteslibres.net/xiam
+//
+// 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
+
+type Tx struct {
+	*Source
+}
+
+func (self *Tx) Commit() error {
+	return self.Source.tx.Commit()
+}
+
+func (self *Tx) Rollback() error {
+	return self.Source.tx.Rollback()
+}
-- 
GitLab