Repository: upper/db
Pull request #588 (2020-08-18 07:07:48)

v4: integrate sqlbuilder and transactions into main db package


Patch:
diff --git a/.travis.yml b/.travis.yml
index 4c0e290..72924f5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -19,6 +19,7 @@ notifications:
 go:
   - "1.13"
   - "1.14"
+  - "1.15"
 
 services:
   - docker
@@ -32,7 +33,7 @@ before_install:
   - sudo service mysql stop
   - sudo service postgresql stop
   - sudo apt-get install -y parallel
-  - sleep 30
+  - sleep 10
 
 install:
   - go get -t -v -d ./...
diff --git a/Makefile b/Makefile
index 5d7ed62..adc3da8 100644
--- a/Makefile
+++ b/Makefile
@@ -10,9 +10,9 @@ export TEST_FLAGS
 export PARALLEL_FLAGS
 export UPPER_DB_LOG
 
-test: go-test-sqlbuilder go-test-internal test-adapters
+test: go-test-internal test-adapters
 
-benchmark: go-benchmark-internal go-benchmark-sqlbuilder
+benchmark: go-benchmark-internal
 
 go-benchmark-%:
 	go test -v -benchtime=500ms -bench=. ./$*/...
diff --git a/adapter/cockroachdb/cockroachdb.go b/adapter/cockroachdb/cockroachdb.go
index 627e6c4..1ef4e61 100644
--- a/adapter/cockroachdb/cockroachdb.go
+++ b/adapter/cockroachdb/cockroachdb.go
@@ -26,7 +26,7 @@ import (
 
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/sqladapter"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 // Adapter is the unique name that you can use to refer to this adapter.
@@ -35,8 +35,8 @@ const Adapter = `cockroachdb`
 var registeredAdapter = sqladapter.RegisterAdapter(Adapter, &database{})
 
 // Open establishes a connection to the database server and returns a
-// sqlbuilder.Session instance (which is compatible with db.Session).
-func Open(connURL db.ConnectionURL) (sqlbuilder.Session, error) {
+// db.Session instance (which is compatible with db.Session).
+func Open(connURL db.ConnectionURL) (db.Session, error) {
 	return registeredAdapter.OpenDSN(connURL)
 }
 
@@ -46,6 +46,6 @@ func NewTx(sqlTx *sql.Tx) (sqlbuilder.Tx, error) {
 }
 
 // New creates a sqlbuilder.Sesion instance by wrapping a *sql.DB value.
-func New(sqlDB *sql.DB) (sqlbuilder.Session, error) {
+func New(sqlDB *sql.DB) (db.Session, error) {
 	return registeredAdapter.New(sqlDB)
 }
diff --git a/adapter/cockroachdb/collection.go b/adapter/cockroachdb/collection.go
index c2ed5eb..b14f6d7 100644
--- a/adapter/cockroachdb/collection.go
+++ b/adapter/cockroachdb/collection.go
@@ -32,7 +32,7 @@ type collectionAdapter struct {
 func (*collectionAdapter) Insert(col sqladapter.Collection, item interface{}) (interface{}, error) {
 	pKey := col.PrimaryKeys()
 
-	q := col.SQLBuilder().InsertInto(col.Name()).Values(item)
+	q := col.SQL().InsertInto(col.Name()).Values(item)
 
 	if len(pKey) == 0 {
 		// There is no primary key.
diff --git a/adapter/cockroachdb/custom_types.go b/adapter/cockroachdb/custom_types.go
index 8ea7cd1..f9f667d 100644
--- a/adapter/cockroachdb/custom_types.go
+++ b/adapter/cockroachdb/custom_types.go
@@ -28,7 +28,7 @@ import (
 	"reflect"
 
 	"github.com/lib/pq"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 // Array returns a sqlbuilder.ScannerValuer for any given slice. Slice elements
diff --git a/adapter/cockroachdb/database.go b/adapter/cockroachdb/database.go
index 710fde2..25feffa 100644
--- a/adapter/cockroachdb/database.go
+++ b/adapter/cockroachdb/database.go
@@ -37,7 +37,7 @@ import (
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/sqladapter"
 	"github.com/upper/db/v4/internal/sqladapter/exql"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 type database struct {
@@ -53,7 +53,8 @@ func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) {
 }
 
 func (*database) Collections(sess sqladapter.Session) (collections []string, err error) {
-	q := sess.Select("table_name").
+	q := sess.SQL().
+		Select("table_name").
 		From("information_schema.tables").
 		Where("table_schema = ?", "public")
 
@@ -148,7 +149,8 @@ func (*database) NewCollection() sqladapter.CollectionAdapter {
 }
 
 func (*database) LookupName(sess sqladapter.Session) (string, error) {
-	q := sess.Select(db.Raw("CURRENT_DATABASE() AS name"))
+	q := sess.SQL().
+		Select(db.Raw("CURRENT_DATABASE() AS name"))
 
 	iter := q.Iterator()
 	defer iter.Close()
@@ -163,7 +165,8 @@ func (*database) LookupName(sess sqladapter.Session) (string, error) {
 }
 
 func (*database) TableExists(sess sqladapter.Session, name string) error {
-	q := sess.Select("table_name").
+	q := sess.SQL().
+		Select("table_name").
 		From("information_schema.tables").
 		Where("table_catalog = ? AND table_name = ?", sess.Name(), name)
 
@@ -185,7 +188,8 @@ func (*database) TableExists(sess sqladapter.Session, name string) error {
 }
 
 func (*database) PrimaryKeys(sess sqladapter.Session, tableName string) ([]string, error) {
-	q := sess.Select("pg_attribute.attname AS pkey").
+	q := sess.SQL().
+		Select("pg_attribute.attname AS pkey").
 		From("pg_index", "pg_class", "pg_attribute").
 		Where(`
 			pg_class.oid = '` + quotedTableName(tableName) + `'::regclass
diff --git a/adapter/mongo/database.go b/adapter/mongo/database.go
index 8f08761..9ec3673 100644
--- a/adapter/mongo/database.go
+++ b/adapter/mongo/database.go
@@ -25,6 +25,8 @@
 package mongo
 
 import (
+	"context"
+	"database/sql"
 	"strings"
 	"sync"
 	"time"
@@ -71,6 +73,19 @@ func Open(settings db.ConnectionURL) (db.Session, error) {
 	return d, nil
 }
 
+func (s *Source) TxContext(context.Context, func(tx db.Session) error, *sql.TxOptions) error {
+	return db.ErrNotSupportedByAdapter
+}
+
+func (s *Source) Tx(func(db.Session) error) error {
+	return db.ErrNotSupportedByAdapter
+}
+
+func (s *Source) SQL() db.SQL {
+	// Not supported
+	panic("sql builder is not supported by mongodb")
+}
+
 func (s *Source) ConnectionURL() db.ConnectionURL {
 	return s.connURL
 }
@@ -117,12 +132,6 @@ func (s *Source) Clone() (db.Session, error) {
 	return clone, nil
 }
 
-// NewTransaction should support transactions, but it doesn't as MongoDB
-// currently does not support them.
-func (s *Source) NewTransaction() (db.Tx, error) {
-	return nil, db.ErrUnsupported
-}
-
 // Ping checks whether a connection to the database is still alive by pinging
 // it, establishing a connection if necessary.
 func (s *Source) Ping() error {
diff --git a/adapter/mssql/collection.go b/adapter/mssql/collection.go
index b70ed15..8f7416f 100644
--- a/adapter/mssql/collection.go
+++ b/adapter/mssql/collection.go
@@ -24,7 +24,7 @@ package mssql
 import (
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/sqladapter"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 type collectionAdapter struct {
@@ -56,7 +56,7 @@ func (adt *collectionAdapter) Insert(col sqladapter.Collection, item interface{}
 			var hasIdentityColumn bool
 			var identityColumns int
 
-			row, err := col.SQLBuilder().QueryRow("SELECT COUNT(1) FROM sys.identity_columns WHERE OBJECT_NAME(object_id) = ?", col.Name())
+			row, err := col.SQL().QueryRow("SELECT COUNT(1) FROM sys.identity_columns WHERE OBJECT_NAME(object_id) = ?", col.Name())
 			if err != nil {
 				return nil, err
 			}
@@ -74,17 +74,17 @@ func (adt *collectionAdapter) Insert(col sqladapter.Collection, item interface{}
 		}
 
 		if *adt.hasIdentityColumn {
-			_, err = col.SQLBuilder().Exec("SET IDENTITY_INSERT " + col.Name() + " ON")
+			_, err = col.SQL().Exec("SET IDENTITY_INSERT " + col.Name() + " ON")
 			if err != nil {
 				return nil, err
 			}
 			defer func() {
-				_, _ = col.SQLBuilder().Exec("SET IDENTITY_INSERT " + col.Name() + " OFF")
+				_, _ = col.SQL().Exec("SET IDENTITY_INSERT " + col.Name() + " OFF")
 			}()
 		}
 	}
 
-	q := col.SQLBuilder().InsertInto(col.Name()).
+	q := col.SQL().InsertInto(col.Name()).
 		Columns(columnNames...).
 		Values(columnValues...)
 
diff --git a/adapter/mssql/database.go b/adapter/mssql/database.go
index 4b7df21..7d2d9e9 100644
--- a/adapter/mssql/database.go
+++ b/adapter/mssql/database.go
@@ -47,7 +47,8 @@ func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) {
 }
 
 func (*database) Collections(sess sqladapter.Session) (collections []string, err error) {
-	q := sess.Select(`table_name`).
+	q := sess.SQL().
+		Select(`table_name`).
 		From(`information_schema.tables`).
 		Where(`table_type`, `BASE TABLE`).
 		And(`table_catalog`, sess.Name())
@@ -85,7 +86,8 @@ func (*database) NewCollection() sqladapter.CollectionAdapter {
 }
 
 func (*database) LookupName(sess sqladapter.Session) (string, error) {
-	q := sess.Select(db.Raw(`DB_NAME() AS name`))
+	q := sess.SQL().
+		Select(db.Raw(`DB_NAME() AS name`))
 
 	iter := q.Iterator()
 	defer iter.Close()
@@ -100,7 +102,8 @@ func (*database) LookupName(sess sqladapter.Session) (string, error) {
 }
 
 func (*database) TableExists(sess sqladapter.Session, name string) error {
-	q := sess.Select(`table_name`).
+	q := sess.SQL().
+		Select(`table_name`).
 		From(`information_schema.tables`).
 		Where(`table_schema`, sess.Name()).
 		And(`table_name`, name)
@@ -123,7 +126,8 @@ func (*database) TableExists(sess sqladapter.Session, name string) error {
 }
 
 func (*database) PrimaryKeys(sess sqladapter.Session, tableName string) ([]string, error) {
-	q := sess.Select(`k.column_name`).
+	q := sess.SQL().
+		Select(`k.column_name`).
 		From(
 			`information_schema.table_constraints AS t`,
 			`information_schema.key_column_usage AS k`,
diff --git a/adapter/mssql/mssql.go b/adapter/mssql/mssql.go
index 95dbaa2..3ae3250 100644
--- a/adapter/mssql/mssql.go
+++ b/adapter/mssql/mssql.go
@@ -26,7 +26,7 @@ import (
 
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/sqladapter"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 // Adapter is the public name of the adapter.
@@ -35,8 +35,8 @@ const Adapter = `mssql`
 var registeredAdapter = sqladapter.RegisterAdapter(Adapter, &database{})
 
 // Open establishes a connection to the database server and returns a
-// sqlbuilder.Session instance (which is compatible with db.Session).
-func Open(connURL db.ConnectionURL) (sqlbuilder.Session, error) {
+// db.Session instance (which is compatible with db.Session).
+func Open(connURL db.ConnectionURL) (db.Session, error) {
 	return registeredAdapter.OpenDSN(connURL)
 }
 
@@ -46,6 +46,6 @@ func NewTx(sqlTx *sql.Tx) (sqlbuilder.Tx, error) {
 }
 
 // New creates a sqlbuilder.Sesion instance by wrapping a *sql.DB value.
-func New(sqlDB *sql.DB) (sqlbuilder.Session, error) {
+func New(sqlDB *sql.DB) (db.Session, error) {
 	return registeredAdapter.New(sqlDB)
 }
diff --git a/adapter/mysql/collection.go b/adapter/mysql/collection.go
index c4d14ab..44243c4 100644
--- a/adapter/mysql/collection.go
+++ b/adapter/mysql/collection.go
@@ -24,7 +24,7 @@ package mysql
 import (
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/sqladapter"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 type collectionAdapter struct {
@@ -38,7 +38,7 @@ func (*collectionAdapter) Insert(col sqladapter.Collection, item interface{}) (i
 
 	pKey := col.PrimaryKeys()
 
-	q := col.SQLBuilder().InsertInto(col.Name()).
+	q := col.SQL().InsertInto(col.Name()).
 		Columns(columnNames...).
 		Values(columnValues...)
 
diff --git a/adapter/mysql/custom_types.go b/adapter/mysql/custom_types.go
index 76471fc..a451c78 100644
--- a/adapter/mysql/custom_types.go
+++ b/adapter/mysql/custom_types.go
@@ -27,7 +27,7 @@ import (
 	"errors"
 	"reflect"
 
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 // JSON represents a MySQL's JSON value:
diff --git a/adapter/mysql/database.go b/adapter/mysql/database.go
index 0777ac1..f8f9c91 100644
--- a/adapter/mysql/database.go
+++ b/adapter/mysql/database.go
@@ -36,7 +36,7 @@ import (
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/sqladapter"
 	"github.com/upper/db/v4/internal/sqladapter/exql"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 // database is the actual implementation of Database
@@ -52,7 +52,8 @@ func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) {
 }
 
 func (*database) Collections(sess sqladapter.Session) (collections []string, err error) {
-	q := sess.Select("table_name").
+	q := sess.SQL().
+		Select("table_name").
 		From("information_schema.tables").
 		Where("table_schema = ?", sess.Name())
 
@@ -110,7 +111,8 @@ func (*database) NewCollection() sqladapter.CollectionAdapter {
 }
 
 func (*database) LookupName(sess sqladapter.Session) (string, error) {
-	q := sess.Select(db.Raw("DATABASE() AS name"))
+	q := sess.SQL().
+		Select(db.Raw("DATABASE() AS name"))
 
 	iter := q.Iterator()
 	defer iter.Close()
@@ -127,7 +129,8 @@ func (*database) LookupName(sess sqladapter.Session) (string, error) {
 }
 
 func (*database) TableExists(sess sqladapter.Session, name string) error {
-	q := sess.Select("table_name").
+	q := sess.SQL().
+		Select("table_name").
 		From("information_schema.tables").
 		Where("table_schema = ? AND table_name = ?", sess.Name(), name)
 
@@ -149,7 +152,8 @@ func (*database) TableExists(sess sqladapter.Session, name string) error {
 }
 
 func (*database) PrimaryKeys(sess sqladapter.Session, tableName string) ([]string, error) {
-	q := sess.Select("k.column_name").
+	q := sess.SQL().
+		Select("k.column_name").
 		From("information_schema.key_column_usage AS k").
 		Where(`
 			k.constraint_name = 'PRIMARY'
diff --git a/adapter/mysql/mysql.go b/adapter/mysql/mysql.go
index 39b90d3..06c91f1 100644
--- a/adapter/mysql/mysql.go
+++ b/adapter/mysql/mysql.go
@@ -26,7 +26,7 @@ import (
 
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/sqladapter"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 // Adapter is the public name of the adapter.
@@ -35,8 +35,8 @@ const Adapter = `mysql`
 var registeredAdapter = sqladapter.RegisterAdapter(Adapter, &database{})
 
 // Open establishes a connection to the database server and returns a
-// sqlbuilder.Session instance (which is compatible with db.Session).
-func Open(connURL db.ConnectionURL) (sqlbuilder.Session, error) {
+// db.Session instance (which is compatible with db.Session).
+func Open(connURL db.ConnectionURL) (db.Session, error) {
 	return registeredAdapter.OpenDSN(connURL)
 }
 
@@ -46,6 +46,6 @@ func NewTx(sqlTx *sql.Tx) (sqlbuilder.Tx, error) {
 }
 
 // New creates a sqlbuilder.Sesion instance by wrapping a *sql.DB value.
-func New(sqlDB *sql.DB) (sqlbuilder.Session, error) {
+func New(sqlDB *sql.DB) (db.Session, error) {
 	return registeredAdapter.New(sqlDB)
 }
diff --git a/adapter/postgresql/collection.go b/adapter/postgresql/collection.go
index 1841f60..e71d799 100644
--- a/adapter/postgresql/collection.go
+++ b/adapter/postgresql/collection.go
@@ -32,7 +32,7 @@ type collectionAdapter struct {
 func (*collectionAdapter) Insert(col sqladapter.Collection, item interface{}) (interface{}, error) {
 	pKey := col.PrimaryKeys()
 
-	q := col.SQLBuilder().InsertInto(col.Name()).Values(item)
+	q := col.SQL().InsertInto(col.Name()).Values(item)
 
 	if len(pKey) == 0 {
 		// There is no primary key.
diff --git a/adapter/postgresql/custom_types.go b/adapter/postgresql/custom_types.go
index ead6a2d..01b172b 100644
--- a/adapter/postgresql/custom_types.go
+++ b/adapter/postgresql/custom_types.go
@@ -28,7 +28,7 @@ import (
 	"reflect"
 
 	"github.com/lib/pq"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 // Array returns a sqlbuilder.ScannerValuer for any given slice. Slice elements
diff --git a/adapter/postgresql/database.go b/adapter/postgresql/database.go
index 32a3b3e..27b8ec1 100644
--- a/adapter/postgresql/database.go
+++ b/adapter/postgresql/database.go
@@ -36,7 +36,7 @@ import (
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/sqladapter"
 	"github.com/upper/db/v4/internal/sqladapter/exql"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 type database struct {
@@ -51,7 +51,8 @@ func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) {
 }
 
 func (*database) Collections(sess sqladapter.Session) (collections []string, err error) {
-	q := sess.Select("table_name").
+	q := sess.SQL().
+		Select("table_name").
 		From("information_schema.tables").
 		Where("table_schema = ?", "public")
 
@@ -140,7 +141,8 @@ func (*database) NewCollection() sqladapter.CollectionAdapter {
 }
 
 func (*database) LookupName(sess sqladapter.Session) (string, error) {
-	q := sess.Select(db.Raw("CURRENT_DATABASE() AS name"))
+	q := sess.SQL().
+		Select(db.Raw("CURRENT_DATABASE() AS name"))
 
 	iter := q.Iterator()
 	defer iter.Close()
@@ -157,7 +159,8 @@ func (*database) LookupName(sess sqladapter.Session) (string, error) {
 }
 
 func (*database) TableExists(sess sqladapter.Session, name string) error {
-	q := sess.Select("table_name").
+	q := sess.SQL().
+		Select("table_name").
 		From("information_schema.tables").
 		Where("table_catalog = ? AND table_name = ?", sess.Name(), name)
 
@@ -179,7 +182,8 @@ func (*database) TableExists(sess sqladapter.Session, name string) error {
 }
 
 func (*database) PrimaryKeys(sess sqladapter.Session, tableName string) ([]string, error) {
-	q := sess.Select("pg_attribute.attname AS pkey").
+	q := sess.SQL().
+		Select("pg_attribute.attname AS pkey").
 		From("pg_index", "pg_class", "pg_attribute").
 		Where(`
 			pg_class.oid = '` + quotedTableName(tableName) + `'::regclass
diff --git a/adapter/postgresql/postgresql.go b/adapter/postgresql/postgresql.go
index ff69f23..577f4af 100644
--- a/adapter/postgresql/postgresql.go
+++ b/adapter/postgresql/postgresql.go
@@ -26,7 +26,7 @@ import (
 
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/sqladapter"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 // Adapter is the internal name of the adapter.
@@ -36,7 +36,7 @@ var registeredAdapter = sqladapter.RegisterAdapter(Adapter, &database{})
 
 // Open establishes a connection to the database server and returns a
 // sqlbuilder.Session instance (which is compatible with db.Session).
-func Open(connURL db.ConnectionURL) (sqlbuilder.Session, error) {
+func Open(connURL db.ConnectionURL) (db.Session, error) {
 	return registeredAdapter.OpenDSN(connURL)
 }
 
@@ -46,6 +46,6 @@ func NewTx(sqlTx *sql.Tx) (sqlbuilder.Tx, error) {
 }
 
 // New creates a sqlbuilder.Sesion instance by wrapping a *sql.DB value.
-func New(sqlDB *sql.DB) (sqlbuilder.Session, error) {
+func New(sqlDB *sql.DB) (db.Session, error) {
 	return registeredAdapter.New(sqlDB)
 }
diff --git a/adapter/ql/collection.go b/adapter/ql/collection.go
index e708a91..a49d48b 100644
--- a/adapter/ql/collection.go
+++ b/adapter/ql/collection.go
@@ -26,7 +26,7 @@ import (
 
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/sqladapter"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 type resultProxy struct {
@@ -41,7 +41,7 @@ func (r *resultProxy) Select(fields ...interface{}) db.Result {
 			var columns []struct {
 				Name string `db:"Name"`
 			}
-			err := r.col.SQLBuilder().Select("Name").
+			err := r.col.SQL().Select("Name").
 				From("__Column").
 				Where("TableName", r.col.Name()).
 				Iterator().All(&columns)
@@ -86,7 +86,7 @@ func (*collectionAdapter) Insert(col sqladapter.Collection, item interface{}) (i
 		return nil, err
 	}
 
-	q := col.SQLBuilder().InsertInto(col.Name()).
+	q := col.SQL().InsertInto(col.Name()).
 		Columns(columnNames...).
 		Values(columnValues...)
 
diff --git a/adapter/ql/database.go b/adapter/ql/database.go
index 6f3e139..754c417 100644
--- a/adapter/ql/database.go
+++ b/adapter/ql/database.go
@@ -33,7 +33,7 @@ import (
 	"github.com/upper/db/v4/internal/sqladapter"
 	"github.com/upper/db/v4/internal/sqladapter/compat"
 	"github.com/upper/db/v4/internal/sqladapter/exql"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 	_ "modernc.org/ql/driver" // QL driver
 )
 
@@ -50,7 +50,8 @@ func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) {
 }
 
 func (*database) Collections(sess sqladapter.Session) (collections []string, err error) {
-	q := sess.Select("Name").
+	q := sess.SQL().
+		Select("Name").
 		From("__Table")
 
 	iter := q.Iterator()
@@ -89,7 +90,7 @@ func (*database) StatementExec(sess sqladapter.Session, ctx context.Context, que
 		return compat.ExecContext(sess.Driver().(*sql.Tx), ctx, query, args)
 	}
 
-	sqlTx, err := compat.BeginTx(sess.Driver().(*sql.DB), ctx, sess.TxOptions())
+	sqlTx, err := compat.BeginTx(sess.Driver().(*sql.DB), ctx, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -118,7 +119,7 @@ func (*database) LookupName(sess sqladapter.Session) (string, error) {
 }
 
 func (*database) TableExists(sess sqladapter.Session, name string) error {
-	q := sess.Select("Name").
+	q := sess.SQL().Select("Name").
 		From("__Table").
 		Where("Name == ?", name)
 
diff --git a/adapter/ql/ql.go b/adapter/ql/ql.go
index 6e49473..3ad284c 100644
--- a/adapter/ql/ql.go
+++ b/adapter/ql/ql.go
@@ -26,7 +26,7 @@ import (
 
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/sqladapter"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 // Adapter is the public name of the adapter.
@@ -35,8 +35,8 @@ const Adapter = `ql`
 var registeredAdapter = sqladapter.RegisterAdapter(Adapter, &database{})
 
 // Open establishes a connection to the database server and returns a
-// sqlbuilder.Session instance (which is compatible with db.Session).
-func Open(connURL db.ConnectionURL) (sqlbuilder.Session, error) {
+// db.Session instance (which is compatible with db.Session).
+func Open(connURL db.ConnectionURL) (db.Session, error) {
 	return registeredAdapter.OpenDSN(connURL)
 }
 
@@ -46,6 +46,6 @@ func NewTx(sqlTx *sql.Tx) (sqlbuilder.Tx, error) {
 }
 
 // New creates a sqlbuilder.Sesion instance by wrapping a *sql.DB value.
-func New(sqlDB *sql.DB) (sqlbuilder.Session, error) {
+func New(sqlDB *sql.DB) (db.Session, error) {
 	return registeredAdapter.New(sqlDB)
 }
diff --git a/adapter/sqlite/collection.go b/adapter/sqlite/collection.go
index e5b44f0..855917c 100644
--- a/adapter/sqlite/collection.go
+++ b/adapter/sqlite/collection.go
@@ -26,7 +26,7 @@ import (
 
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/sqladapter"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 type collectionAdapter struct {
@@ -40,7 +40,7 @@ func (*collectionAdapter) Insert(col sqladapter.Collection, item interface{}) (i
 
 	pKey := col.PrimaryKeys()
 
-	q := col.SQLBuilder().InsertInto(col.Name()).
+	q := col.SQL().InsertInto(col.Name()).
 		Columns(columnNames...).
 		Values(columnValues...)
 
diff --git a/adapter/sqlite/database.go b/adapter/sqlite/database.go
index 80e5c48..fd8d0d6 100644
--- a/adapter/sqlite/database.go
+++ b/adapter/sqlite/database.go
@@ -34,7 +34,7 @@ import (
 	"github.com/upper/db/v4/internal/sqladapter"
 	"github.com/upper/db/v4/internal/sqladapter/compat"
 	"github.com/upper/db/v4/internal/sqladapter/exql"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 // database is the actual implementation of Database
@@ -50,7 +50,8 @@ func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) {
 }
 
 func (*database) Collections(sess sqladapter.Session) (collections []string, err error) {
-	q := sess.Select("tbl_name").
+	q := sess.SQL().
+		Select("tbl_name").
 		From("sqlite_master").
 		Where("type = ?", "table")
 
@@ -76,7 +77,7 @@ func (*database) StatementExec(sess sqladapter.Session, ctx context.Context, que
 		return compat.ExecContext(sess.Driver().(*sql.Tx), ctx, query, args)
 	}
 
-	sqlTx, err := compat.BeginTx(sess.Driver().(*sql.DB), ctx, sess.TxOptions())
+	sqlTx, err := compat.BeginTx(sess.Driver().(*sql.DB), ctx, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -105,7 +106,8 @@ func (*database) LookupName(sess sqladapter.Session) (string, error) {
 }
 
 func (*database) TableExists(sess sqladapter.Session, name string) error {
-	q := sess.Select("tbl_name").
+	q := sess.SQL().
+		Select("tbl_name").
 		From("sqlite_master").
 		Where("type = 'table' AND tbl_name = ?", name)
 
@@ -131,7 +133,7 @@ func (*database) PrimaryKeys(sess sqladapter.Session, tableName string) ([]strin
 
 	stmt := exql.RawSQL(fmt.Sprintf("PRAGMA TABLE_INFO('%s')", tableName))
 
-	rows, err := sess.Query(stmt)
+	rows, err := sess.SQL().Query(stmt)
 	if err != nil {
 		return nil, err
 	}
diff --git a/adapter/sqlite/sqlite.go b/adapter/sqlite/sqlite.go
index 948187a..bd2ca68 100644
--- a/adapter/sqlite/sqlite.go
+++ b/adapter/sqlite/sqlite.go
@@ -27,7 +27,7 @@ import (
 	db "github.com/upper/db/v4"
 
 	"github.com/upper/db/v4/internal/sqladapter"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 // Adapter is the public name of the adapter.
@@ -36,8 +36,8 @@ const Adapter = `sqlite`
 var registeredAdapter = sqladapter.RegisterAdapter(Adapter, &database{})
 
 // Open establishes a connection to the database server and returns a
-// sqlbuilder.Session instance (which is compatible with db.Session).
-func Open(connURL db.ConnectionURL) (sqlbuilder.Session, error) {
+// db.Session instance (which is compatible with db.Session).
+func Open(connURL db.ConnectionURL) (db.Session, error) {
 	return registeredAdapter.OpenDSN(connURL)
 }
 
@@ -47,6 +47,6 @@ func NewTx(sqlTx *sql.Tx) (sqlbuilder.Tx, error) {
 }
 
 // New creates a sqlbuilder.Sesion instance by wrapping a *sql.DB value.
-func New(sqlDB *sql.DB) (sqlbuilder.Session, error) {
+func New(sqlDB *sql.DB) (db.Session, error) {
 	return registeredAdapter.New(sqlDB)
 }
diff --git a/sqlbuilder/interfaces.go b/clauses.go
similarity index 67%
rename from sqlbuilder/interfaces.go
rename to clauses.go
index e55fcf0..673b796 100644
--- a/sqlbuilder/interfaces.go
+++ b/clauses.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2015 The github.com/upper/db/sqlbuilder authors. All rights reserved.
+// Copyright (c) 2012-present 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
@@ -19,144 +19,13 @@
 // 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 sqlbuilder
+package db
 
 import (
 	"context"
-	"database/sql"
 	"fmt"
 )
 
-// SQLBuilder defines methods that can be used to build a SQL query with
-// chainable method calls.
-//
-// Queries are immutable, so every call to any method will return a new
-// pointer, if you want to build a query using variables you need to reassign
-// them, like this:
-//
-//  a = builder.Select("name").From("foo") // "a" is created
-//
-//  a.Where(...) // No effect, the value returned from Where is ignored.
-//
-//  a = a.Where(...) // "a" is reassigned and points to a different address.
-//
-type SQLBuilder interface {
-
-	// Select initializes and returns a Selector, it accepts column names as
-	// parameters.
-	//
-	// The returned Selector does not initially point to any table, a call to
-	// From() is required after Select() to complete a valid query.
-	//
-	// Example:
-	//
-	//  q := sqlbuilder.Select("first_name", "last_name").From("people").Where(...)
-	Select(columns ...interface{}) Selector
-
-	// SelectFrom creates a Selector that selects all columns (like SELECT *)
-	// from the given table.
-	//
-	// Example:
-	//
-	//  q := sqlbuilder.SelectFrom("people").Where(...)
-	SelectFrom(table ...interface{}) Selector
-
-	// InsertInto prepares and returns an Inserter targeted at the given table.
-	//
-	// Example:
-	//
-	//   q := sqlbuilder.InsertInto("books").Columns(...).Values(...)
-	InsertInto(table string) Inserter
-
-	// DeleteFrom prepares a Deleter targeted at the given table.
-	//
-	// Example:
-	//
-	//  q := sqlbuilder.DeleteFrom("tasks").Where(...)
-	DeleteFrom(table string) Deleter
-
-	// Update prepares and returns an Updater targeted at the given table.
-	//
-	// Example:
-	//
-	//  q := sqlbuilder.Update("profile").Set(...).Where(...)
-	Update(table string) Updater
-
-	// Exec executes a SQL query that does not return any rows, like sql.Exec.
-	// Queries can be either strings or upper-db statements.
-	//
-	// Example:
-	//
-	//  sqlbuilder.Exec(`INSERT INTO books (title) VALUES("La Ciudad y los Perros")`)
-	Exec(query interface{}, args ...interface{}) (sql.Result, error)
-
-	// ExecContext executes a SQL query that does not return any rows, like sql.ExecContext.
-	// Queries can be either strings or upper-db statements.
-	//
-	// Example:
-	//
-	//  sqlbuilder.ExecContext(ctx, `INSERT INTO books (title) VALUES(?)`, "La Ciudad y los Perros")
-	ExecContext(ctx context.Context, query interface{}, args ...interface{}) (sql.Result, error)
-
-	// Prepare creates a prepared statement for later queries or executions. The
-	// caller must call the statement's Close method when the statement is no
-	// longer needed.
-	Prepare(query interface{}) (*sql.Stmt, error)
-
-	// Prepare creates a prepared statement on the guiven context for later
-	// queries or executions. The caller must call the statement's Close method
-	// when the statement is no longer needed.
-	PrepareContext(ctx context.Context, query interface{}) (*sql.Stmt, error)
-
-	// Query executes a SQL query that returns rows, like sql.Query.  Queries can
-	// be either strings or upper-db statements.
-	//
-	// Example:
-	//
-	//  sqlbuilder.Query(`SELECT * FROM people WHERE name = "Mateo"`)
-	Query(query interface{}, args ...interface{}) (*sql.Rows, error)
-
-	// QueryContext executes a SQL query that returns rows, like
-	// sql.QueryContext.  Queries can be either strings or upper-db statements.
-	//
-	// Example:
-	//
-	//  sqlbuilder.QueryContext(ctx, `SELECT * FROM people WHERE name = ?`, "Mateo")
-	QueryContext(ctx context.Context, query interface{}, args ...interface{}) (*sql.Rows, error)
-
-	// QueryRow executes a SQL query that returns one row, like sql.QueryRow.
-	// Queries can be either strings or upper-db statements.
-	//
-	// Example:
-	//
-	//  sqlbuilder.QueryRow(`SELECT * FROM people WHERE name = "Haruki" AND last_name = "Murakami" LIMIT 1`)
-	QueryRow(query interface{}, args ...interface{}) (*sql.Row, error)
-
-	// QueryRowContext executes a SQL query that returns one row, like
-	// sql.QueryRowContext.  Queries can be either strings or upper-db statements.
-	//
-	// Example:
-	//
-	//  sqlbuilder.QueryRowContext(ctx, `SELECT * FROM people WHERE name = "Haruki" AND last_name = "Murakami" LIMIT 1`)
-	QueryRowContext(ctx context.Context, query interface{}, args ...interface{}) (*sql.Row, error)
-
-	// Iterator executes a SQL query that returns rows and creates an Iterator
-	// with it.
-	//
-	// Example:
-	//
-	//  sqlbuilder.Iterator(`SELECT * FROM people WHERE name LIKE "M%"`)
-	Iterator(query interface{}, args ...interface{}) Iterator
-
-	// IteratorContext executes a SQL query that returns rows and creates an Iterator
-	// with it.
-	//
-	// Example:
-	//
-	//  sqlbuilder.IteratorContext(ctx, `SELECT * FROM people WHERE name LIKE "M%"`)
-	IteratorContext(ctx context.Context, query interface{}, args ...interface{}) Iterator
-}
-
 // Selector represents a SELECT statement.
 type Selector interface {
 	// Columns defines which columns to retrive.
@@ -359,12 +228,12 @@ type Selector interface {
 	// the Selector.
 	IteratorContext(ctx context.Context) Iterator
 
-	// Preparer provides methods for creating prepared statements.
-	Preparer
+	// SQLPreparer provides methods for creating prepared statements.
+	SQLPreparer
 
-	// Getter provides methods to compile and execute a query that returns
+	// SQLGetter provides methods to compile and execute a query that returns
 	// results.
-	Getter
+	SQLGetter
 
 	// ResultMapper provides methods to retrieve and map results.
 	ResultMapper
@@ -420,17 +289,17 @@ type Inserter interface {
 	// Batch provies a BatchInserter that can be used to insert many elements at
 	// once by issuing several calls to Values(). It accepts a size parameter
 	// which defines the batch size. If size is < 1, the batch size is set to 1.
-	Batch(size int) *BatchInserter
+	Batch(size int) BatchInserter
 
-	// Execer provides the Exec method.
-	Execer
+	// SQLExecer provides the Exec method.
+	SQLExecer
 
-	// Preparer provides methods for creating prepared statements.
-	Preparer
+	// SQLPreparer provides methods for creating prepared statements.
+	SQLPreparer
 
-	// Getter provides methods to return query results from INSERT statements
+	// SQLGetter provides methods to return query results from INSERT statements
 	// that support such feature (e.g.: queries with Returning).
-	Getter
+	SQLGetter
 
 	// fmt.Stringer provides `String() string`, you can use `String()` to compile
 	// the `Inserter` into a string.
@@ -457,11 +326,11 @@ type Deleter interface {
 	// database server.
 	Amend(func(queryIn string) (queryOut string)) Deleter
 
-	// Preparer provides methods for creating prepared statements.
-	Preparer
+	// SQLPreparer provides methods for creating prepared statements.
+	SQLPreparer
 
-	// Execer provides the Exec method.
-	Execer
+	// SQLExecer provides the Exec method.
+	SQLExecer
 
 	// fmt.Stringer provides `String() string`, you can use `String()` to compile
 	// the `Inserter` into a string.
@@ -490,11 +359,11 @@ type Updater interface {
 	// See Selector.Limit for documentation and usage examples.
 	Limit(int) Updater
 
-	// Preparer provides methods for creating prepared statements.
-	Preparer
+	// SQLPreparer provides methods for creating prepared statements.
+	SQLPreparer
 
-	// Execer provides the Exec method.
-	Execer
+	// SQLExecer provides the Exec method.
+	SQLExecer
 
 	// fmt.Stringer provides `String() string`, you can use `String()` to compile
 	// the `Inserter` into a string.
@@ -508,40 +377,6 @@ type Updater interface {
 	Amend(func(queryIn string) (queryOut string)) Updater
 }
 
-// Execer provides methods for executing statements that do not return results.
-type Execer interface {
-	// Exec executes a statement and returns sql.Result.
-	Exec() (sql.Result, error)
-
-	// ExecContext executes a statement and returns sql.Result.
-	ExecContext(context.Context) (sql.Result, error)
-}
-
-// Preparer provides the Prepare and PrepareContext methods for creating
-// prepared statements.
-type Preparer interface {
-	// Prepare creates a prepared statement.
-	Prepare() (*sql.Stmt, error)
-
-	// PrepareContext creates a prepared statement.
-	PrepareContext(context.Context) (*sql.Stmt, error)
-}
-
-// Getter provides methods for executing statements that return results.
-type Getter interface {
-	// Query returns *sql.Rows.
-	Query() (*sql.Rows, error)
-
-	// QueryContext returns *sql.Rows.
-	QueryContext(context.Context) (*sql.Rows, error)
-
-	// QueryRow returns only one row.
-	QueryRow() (*sql.Row, error)
-
-	// QueryRowContext returns only one row.
-	QueryRowContext(ctx context.Context) (*sql.Row, error)
-}
-
 // Paginator provides tools for splitting the results of a query into chunks
 // containing a fixed number of items.
 type Paginator interface {
@@ -583,12 +418,12 @@ type Paginator interface {
 	// TotalEntries returns the total number of entries in the query.
 	TotalEntries() (uint64, error)
 
-	// Preparer provides methods for creating prepared statements.
-	Preparer
+	// SQLPreparer provides methods for creating prepared statements.
+	SQLPreparer
 
-	// Getter provides methods to compile and execute a query that returns
+	// SQLGetter provides methods to compile and execute a query that returns
 	// results.
-	Getter
+	SQLGetter
 
 	// Iterator provides methods to iterate over the results returned by the
 	// Selector.
@@ -658,16 +493,10 @@ type Iterator interface {
 	Close() error
 }
 
-// SQLEngine represents a SQL engine that can execute SQL queries. This is
-// compatible with *sql.DB.
-type SQLEngine interface {
-	Exec(string, ...interface{}) (sql.Result, error)
-	Prepare(string) (*sql.Stmt, error)
-	Query(string, ...interface{}) (*sql.Rows, error)
-	QueryRow(string, ...interface{}) *sql.Row
-
-	ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
-	PrepareContext(context.Context, string) (*sql.Stmt, error)
-	QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
-	QueryRowContext(context.Context, string, ...interface{}) *sql.Row
+type BatchInserter interface {
+	Values(...interface{}) BatchInserter
+	NextResult(interface{}) bool
+	Done()
+	Wait() error
+	Err() error
 }
diff --git a/errors.go b/errors.go
index 8864436..6ee09d5 100644
--- a/errors.go
+++ b/errors.go
@@ -58,4 +58,6 @@ var (
 	ErrMissingPrimaryKeys       = errors.New(`upper: collection has no primary keys`)
 	ErrWarnSlowQuery            = errors.New(`upper: slow query`)
 	ErrTransactionAborted       = errors.New(`upper: transaction was aborted`)
+	ErrNotWithinTransaction     = errors.New(`upper: not within transaction`)
+	ErrNotSupportedByAdapter    = errors.New(`upper: not supported by adapter`)
 )
diff --git a/go.sum b/go.sum
index a615af3..14c9be7 100644
--- a/go.sum
+++ b/go.sum
@@ -33,6 +33,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/upper/db v1.0.1 h1:AFzy0fFO7EZ5woZGE/LVl9BWLKqyAWlglu+ZyHQTlbU=
+github.com/upper/db v3.7.1+incompatible h1:ehR+Q0msV25yZWXdFYD+3n4BvY9jefI336lmZy7FlAQ=
 gitlab.com/cznic/ebnf2y v1.0.0/go.mod h1:jx14dqOldV2pRvSi8HASTB/k5fkIv2TwjYAp5py0MTs=
 gitlab.com/cznic/golex v1.0.0/go.mod h1:vkWdDgqbbThjRHoOLU7yNPgMxaubAkwnvF/4zeG8cvU=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
diff --git a/internal/sqladapter/collection.go b/internal/sqladapter/collection.go
index 2419c87..4cf2e8c 100644
--- a/internal/sqladapter/collection.go
+++ b/internal/sqladapter/collection.go
@@ -6,7 +6,7 @@ import (
 
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/sqladapter/exql"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 // CollectionAdapter defines methods to be implemented by SQL adapters.
@@ -53,8 +53,8 @@ type Collection interface {
 	// PrimaryKeys returns the names of all primary keys in the table.
 	PrimaryKeys() []string
 
-	// SQLBuilder returns a sqlbuilder.SQLBuilder instance.
-	SQLBuilder() sqlbuilder.SQLBuilder
+	// SQLBuilder returns a db.SQL instance.
+	SQL() db.SQL
 }
 
 type finder interface {
@@ -88,8 +88,8 @@ func NewCollection(sess Session, name string, adapter CollectionAdapter) Collect
 	return c
 }
 
-func (c *collection) SQLBuilder() sqlbuilder.SQLBuilder {
-	return c.sess.(sqlbuilder.SQLBuilder)
+func (c *collection) SQL() db.SQL {
+	return c.sess.SQL()
 }
 
 func (c *collection) Session() db.Session {
@@ -143,7 +143,7 @@ func (c *collection) Find(conds ...interface{}) db.Result {
 	}
 
 	res := NewResult(
-		c.sess,
+		c.sess.SQL(),
 		c.Name(),
 		c.filterConds(conds...),
 	)
@@ -174,20 +174,17 @@ func (c *collection) InsertReturning(item interface{}) error {
 		return fmt.Errorf(db.ErrMissingPrimaryKeys.Error(), c.Name())
 	}
 
-	var tx SessionTx
-	inTx := false
-
-	if currTx := c.sess.Transaction(); currTx != nil {
-		tx = NewSessionTx(c.sess)
-		inTx = true
+	var tx Session
+	isTransaction := c.sess.IsTransaction()
+	if isTransaction {
+		tx = c.sess
 	} else {
-		// Not within a transaction, let's create one.
 		var err error
-		tx, err = c.sess.NewSessionTx(c.sess.Context())
+		tx, err = c.sess.NewTransaction(c.sess.Context(), nil)
 		if err != nil {
 			return err
 		}
-		defer tx.(Session).Close()
+		defer tx.Close()
 	}
 
 	// Allocate a clone of item.
@@ -196,7 +193,7 @@ func (c *collection) InsertReturning(item interface{}) error {
 
 	itemValue := reflect.ValueOf(item)
 
-	col := tx.(Session).Collection(c.Name())
+	col := tx.Collection(c.Name())
 
 	// Insert item as is and grab the returning ID.
 	var newItemRes db.Result
@@ -244,7 +241,7 @@ func (c *collection) InsertReturning(item interface{}) error {
 		goto cancel
 	}
 
-	if !inTx {
+	if !isTransaction {
 		// This is only executed if t.Session() was **not** a transaction and if
 		// sess was created with sess.NewTransaction().
 		return tx.Commit()
@@ -256,7 +253,7 @@ cancel:
 	// This goto label should only be used when we got an error within a
 	// transaction and we don't want to continue.
 
-	if !inTx {
+	if !isTransaction {
 		// This is only executed if t.Session() was **not** a transaction and if
 		// sess was created with sess.NewTransaction().
 		_ = tx.Rollback()
@@ -278,20 +275,19 @@ func (c *collection) UpdateReturning(item interface{}) error {
 		return fmt.Errorf(db.ErrMissingPrimaryKeys.Error(), c.Name())
 	}
 
-	var tx SessionTx
-	inTx := false
+	var tx Session
+	isTransaction := c.sess.IsTransaction()
 
-	if currTx := c.sess.Transaction(); currTx != nil {
-		tx = NewSessionTx(c.sess)
-		inTx = true
+	if isTransaction {
+		tx = c.sess
 	} else {
 		// Not within a transaction, let's create one.
 		var err error
-		tx, err = c.sess.NewSessionTx(c.sess.Context())
+		tx, err = c.sess.NewTransaction(c.sess.Context(), nil)
 		if err != nil {
 			return err
 		}
-		defer tx.(Session).Close()
+		defer tx.Close()
 	}
 
 	// Allocate a clone of item.
@@ -336,7 +332,7 @@ func (c *collection) UpdateReturning(item interface{}) error {
 		panic("default")
 	}
 
-	if !inTx {
+	if !isTransaction {
 		// This is only executed if t.Session() was **not** a transaction and if
 		// sess was created with sess.NewTransaction().
 		return tx.Commit()
@@ -347,7 +343,7 @@ cancel:
 	// This goto label should only be used when we got an error within a
 	// transaction and we don't want to continue.
 
-	if !inTx {
+	if !isTransaction {
 		// This is only executed if t.Session() was **not** a transaction and if
 		// sess was created with sess.NewTransaction().
 		_ = tx.Rollback()
@@ -360,7 +356,7 @@ func (c *collection) Truncate() error {
 		Type:  exql.Truncate,
 		Table: exql.TableWithName(c.Name()),
 	}
-	if _, err := c.sess.Exec(&stmt); err != nil {
+	if _, err := c.sess.SQL().Exec(&stmt); err != nil {
 		return err
 	}
 	return nil
diff --git a/internal/sqladapter/result.go b/internal/sqladapter/result.go
index e9c7ed9..4fbfa77 100644
--- a/internal/sqladapter/result.go
+++ b/internal/sqladapter/result.go
@@ -28,15 +28,14 @@ import (
 
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/immutable"
-	"github.com/upper/db/v4/sqlbuilder"
 )
 
 type Result struct {
-	builder sqlbuilder.SQLBuilder
+	builder db.SQL
 
 	err atomic.Value
 
-	iter   sqlbuilder.Iterator
+	iter   db.Iterator
 	iterMu sync.Mutex
 
 	prev *Result
@@ -68,7 +67,7 @@ func filter(conds []interface{}) []interface{} {
 
 // NewResult creates and Results a new Result set on the given table, this set
 // is limited by the given exql.Where conditions.
-func NewResult(builder sqlbuilder.SQLBuilder, table string, conds []interface{}) *Result {
+func NewResult(builder db.SQL, table string, conds []interface{}) *Result {
 	r := &Result{
 		builder: builder,
 	}
@@ -79,11 +78,11 @@ func (r *Result) frame(fn func(*result) error) *Result {
 	return &Result{prev: r, fn: fn}
 }
 
-func (r *Result) SQLBuilder() sqlbuilder.SQLBuilder {
+func (r *Result) SQL() db.SQL {
 	if r.prev == nil {
 		return r.builder
 	}
-	return r.prev.SQLBuilder()
+	return r.prev.SQL()
 }
 
 func (r *Result) from(table string) *Result {
@@ -391,7 +390,7 @@ func (r *Result) Count() (uint64, error) {
 	return counter.Count, nil
 }
 
-func (r *Result) buildPaginator() (sqlbuilder.Paginator, error) {
+func (r *Result) buildPaginator() (db.Paginator, error) {
 	if err := r.Err(); err != nil {
 		return nil, err
 	}
@@ -401,7 +400,7 @@ func (r *Result) buildPaginator() (sqlbuilder.Paginator, error) {
 		return nil, err
 	}
 
-	sel := r.SQLBuilder().Select(res.fields...).
+	sel := r.SQL().Select(res.fields...).
 		From(res.table).
 		Limit(res.limit).
 		Offset(res.offset).
@@ -427,7 +426,7 @@ func (r *Result) buildPaginator() (sqlbuilder.Paginator, error) {
 	return pag, nil
 }
 
-func (r *Result) buildDelete() (sqlbuilder.Deleter, error) {
+func (r *Result) buildDelete() (db.Deleter, error) {
 	if err := r.Err(); err != nil {
 		return nil, err
 	}
@@ -437,7 +436,7 @@ func (r *Result) buildDelete() (sqlbuilder.Deleter, error) {
 		return nil, err
 	}
 
-	del := r.SQLBuilder().DeleteFrom(res.table).
+	del := r.SQL().DeleteFrom(res.table).
 		Limit(res.limit)
 
 	for i := range res.conds {
@@ -447,7 +446,7 @@ func (r *Result) buildDelete() (sqlbuilder.Deleter, error) {
 	return del, nil
 }
 
-func (r *Result) buildUpdate(values interface{}) (sqlbuilder.Updater, error) {
+func (r *Result) buildUpdate(values interface{}) (db.Updater, error) {
 	if err := r.Err(); err != nil {
 		return nil, err
 	}
@@ -457,7 +456,7 @@ func (r *Result) buildUpdate(values interface{}) (sqlbuilder.Updater, error) {
 		return nil, err
 	}
 
-	upd := r.SQLBuilder().Update(res.table).
+	upd := r.SQL().Update(res.table).
 		Set(values).
 		Limit(res.limit)
 
@@ -468,7 +467,7 @@ func (r *Result) buildUpdate(values interface{}) (sqlbuilder.Updater, error) {
 	return upd, nil
 }
 
-func (r *Result) buildCount() (sqlbuilder.Selector, error) {
+func (r *Result) buildCount() (db.Selector, error) {
 	if err := r.Err(); err != nil {
 		return nil, err
 	}
@@ -478,7 +477,7 @@ func (r *Result) buildCount() (sqlbuilder.Selector, error) {
 		return nil, err
 	}
 
-	sel := r.SQLBuilder().Select(db.Raw("count(1) AS _t")).
+	sel := r.SQL().Select(db.Raw("count(1) AS _t")).
 		From(res.table).
 		GroupBy(res.groupBy...)
 
diff --git a/internal/sqladapter/database.go b/internal/sqladapter/session.go
similarity index 87%
rename from internal/sqladapter/database.go
rename to internal/sqladapter/session.go
index d6ba201..3ca1a7c 100644
--- a/internal/sqladapter/database.go
+++ b/internal/sqladapter/session.go
@@ -3,6 +3,8 @@ package sqladapter
 import (
 	"context"
 	"database/sql"
+	"errors"
+	"fmt"
 	"math"
 	"reflect"
 	"strconv"
@@ -14,7 +16,7 @@ import (
 	"github.com/upper/db/v4/internal/cache"
 	"github.com/upper/db/v4/internal/sqladapter/compat"
 	"github.com/upper/db/v4/internal/sqladapter/exql"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 type hasPrimaryKeys interface {
@@ -26,7 +28,10 @@ var (
 	lastTxID   uint64
 )
 
-var slowQueryThreshold = time.Millisecond * 100
+var (
+	slowQueryThreshold       = time.Millisecond * 200
+	retryTransactionWaitTime = time.Millisecond * 20
+)
 
 // hasCleanUp is implemented by structs that have a clean up routine that needs
 // to be called before Close().
@@ -80,7 +85,7 @@ type AdapterSession interface {
 
 // Session satisfies db.Session.
 type Session interface {
-	sqlbuilder.SQLBuilder
+	SQL() db.SQL
 
 	// PrimaryKeys returns all primary keys on the table.
 	PrimaryKeys(tableName string) ([]string, error)
@@ -137,7 +142,7 @@ type Session interface {
 	BindTx(context.Context, *sql.Tx) error
 
 	// Returns the current transaction the session is using.
-	Transaction() BaseTx
+	Transaction() *sql.Tx
 
 	// NewClone clones the database using the given AdapterSession as base.
 	NewClone(AdapterSession, bool) (Session, error)
@@ -148,39 +153,36 @@ type Session interface {
 	// SetContext sets a default context for the session.
 	SetContext(context.Context)
 
-	// TxOptions returns the default TxOptions for new transactions in the
-	// session.
-	TxOptions() *sql.TxOptions
+	NewTransaction(ctx context.Context, opts *sql.TxOptions) (Session, error)
 
-	// SetTxOptions sets default TxOptions for the session.
-	SetTxOptions(txOptions sql.TxOptions)
+	Tx(fn func(sess db.Session) error) error
 
-	NewSessionTx(ctx context.Context) (SessionTx, error)
+	TxContext(ctx context.Context, fn func(sess db.Session) error, opts *sql.TxOptions) error
 
-	NewTx(ctx context.Context) (sqlbuilder.Tx, error)
+	WithContext(context.Context) db.Session
 
-	Tx(fn func(sess db.Session) error) error
+	IsTransaction() bool
 
-	TxContext(ctx context.Context, fn func(sess db.Session) error) error
+	Commit() error
 
-	WithContext(context.Context) sqlbuilder.Session
+	Rollback() error
 
 	db.Settings
 }
 
-// NewTx wraps a *sql.Tx and returns a sqlbuilder.Tx.
-func NewTx(adapter AdapterSession, tx *sql.Tx) (sqlbuilder.Tx, error) {
+// NewTx wraps a *sql.Tx and returns a Tx.
+func NewTx(adapter AdapterSession, tx *sql.Tx) (Session, error) {
 	sess := &session{
 		Settings: db.DefaultSettings,
 
+		sqlTx:             tx,
 		adapter:           adapter,
 		cachedPKs:         cache.NewCache(),
 		cachedCollections: cache.NewCache(),
 		cachedStatements:  cache.NewCache(),
 	}
-	sess.SQLBuilder = sqlbuilder.WithSession(sess, adapter.Template())
-	sess.baseTx = newBaseTx(tx)
-	return &txWrapper{SessionTx: NewSessionTx(sess)}, nil
+	sess.builder = sqlbuilder.WithSession(sess, adapter.Template())
+	return sess, nil
 }
 
 // NewSession creates a new Session.
@@ -194,18 +196,18 @@ func NewSession(connURL db.ConnectionURL, adapter AdapterSession) Session {
 		cachedCollections: cache.NewCache(),
 		cachedStatements:  cache.NewCache(),
 	}
-	sess.SQLBuilder = sqlbuilder.WithSession(sess, adapter.Template())
+	sess.builder = sqlbuilder.WithSession(sess, adapter.Template())
 	return sess
 }
 
 type session struct {
 	db.Settings
 
-	sqlbuilder.SQLBuilder
+	adapter AdapterSession
 
 	connURL db.ConnectionURL
 
-	adapter AdapterSession
+	builder db.SQL
 
 	lookupNameOnce sync.Once
 	name           string
@@ -215,8 +217,9 @@ type session struct {
 	txOptions *sql.TxOptions
 
 	sqlDBMu sync.Mutex // guards sess, baseTx
-	sqlDB   *sql.DB
-	baseTx  BaseTx
+
+	sqlDB *sql.DB
+	sqlTx *sql.Tx
 
 	sessID uint64
 	txID   uint64
@@ -233,17 +236,21 @@ var (
 	_ = db.Session(&session{})
 )
 
-func (sess *session) WithContext(ctx context.Context) sqlbuilder.Session {
+func (sess *session) WithContext(ctx context.Context) db.Session {
 	newDB, _ := sess.NewClone(sess.adapter, false)
 	return newDB
 }
 
 func (sess *session) Tx(fn func(sess db.Session) error) error {
-	return TxContext(sess.Context(), sess, fn)
+	return TxContext(sess.Context(), sess, fn, nil)
 }
 
-func (sess *session) TxContext(ctx context.Context, fn func(sess db.Session) error) error {
-	return TxContext(ctx, sess, fn)
+func (sess *session) TxContext(ctx context.Context, fn func(sess db.Session) error, opts *sql.TxOptions) error {
+	return TxContext(ctx, sess, fn, opts)
+}
+
+func (sess *session) SQL() db.SQL {
+	return sess.builder
 }
 
 func (sess *session) Err(errIn error) (errOur error) {
@@ -273,8 +280,7 @@ func (sess *session) TableExists(name string) error {
 	return sess.adapter.TableExists(sess, name)
 }
 
-// NewSessionTx begins a transaction block.
-func (sess *session) NewSessionTx(ctx context.Context) (SessionTx, error) {
+func (sess *session) NewTransaction(ctx context.Context, opts *sql.TxOptions) (Session, error) {
 	if ctx == nil {
 		ctx = context.Background()
 	}
@@ -282,11 +288,9 @@ func (sess *session) NewSessionTx(ctx context.Context) (SessionTx, error) {
 	if err != nil {
 		return nil, err
 	}
-	//clone.mu.Lock()
-	//defer clone.mu.Unlock()
 
 	connFn := func() error {
-		sqlTx, err := compat.BeginTx(clone.DB(), ctx, clone.TxOptions())
+		sqlTx, err := compat.BeginTx(clone.DB(), clone.Context(), opts)
 		if err == nil {
 			return clone.BindTx(ctx, sqlTx)
 		}
@@ -297,7 +301,7 @@ func (sess *session) NewSessionTx(ctx context.Context) (SessionTx, error) {
 		return nil, err
 	}
 
-	return NewSessionTx(clone), nil
+	return clone, nil
 }
 
 func (sess *session) Collections() ([]db.Collection, error) {
@@ -521,19 +525,41 @@ func (sess *session) TxOptions() *sql.TxOptions {
 	return sess.txOptions
 }
 
-func (sess *session) BindTx(ctx context.Context, t *sql.Tx) error {
+func (sess *session) BindTx(ctx context.Context, tx *sql.Tx) error {
 	sess.sqlDBMu.Lock()
 	defer sess.sqlDBMu.Unlock()
 
-	sess.baseTx = newBaseTx(t)
-
+	sess.sqlTx = tx
 	sess.SetContext(ctx)
+
 	sess.txID = newBaseTxID()
+
 	return nil
 }
 
-func (sess *session) Transaction() BaseTx {
-	return sess.baseTx
+func (sess *session) Commit() error {
+	if sess.sqlTx != nil {
+		return sess.sqlTx.Commit()
+	}
+	return db.ErrNotWithinTransaction
+}
+
+func (sess *session) Rollback() error {
+	if sess.sqlTx != nil {
+		return sess.sqlTx.Rollback()
+	}
+	return db.ErrNotWithinTransaction
+}
+
+func (sess *session) IsTransaction() bool {
+	if sess.sqlTx != nil {
+		return true
+	}
+	return false
+}
+
+func (sess *session) Transaction() *sql.Tx {
+	return sess.sqlTx
 }
 
 func (sess *session) Name() string {
@@ -633,9 +659,10 @@ func (sess *session) Close() error {
 	defer func() {
 		sess.sqlDBMu.Lock()
 		sess.sqlDB = nil
-		sess.baseTx = nil
+		sess.sqlTx = nil
 		sess.sqlDBMu.Unlock()
 	}()
+
 	if sess.sqlDB == nil {
 		return nil
 	}
@@ -643,8 +670,7 @@ func (sess *session) Close() error {
 	sess.cachedCollections.Clear()
 	sess.cachedStatements.Clear() // Closes prepared statements as well.
 
-	tx := sess.Transaction()
-	if tx == nil {
+	if !sess.IsTransaction() {
 		if cleaner, ok := sess.adapter.(hasCleanUp); ok {
 			if err := cleaner.CleanUp(); err != nil {
 				return err
@@ -654,21 +680,9 @@ func (sess *session) Close() error {
 		return sess.sqlDB.Close()
 	}
 
-	if !tx.Committed() {
-		_ = tx.Rollback()
-	}
 	return nil
 }
 
-func (sess *session) NewTx(ctx context.Context) (sqlbuilder.Tx, error) {
-	newTx, err := sess.NewSessionTx(ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	return &txWrapper{SessionTx: newTx}, nil
-}
-
 func (sess *session) Collection(name string) db.Collection {
 	sess.cacheMu.Lock()
 	defer sess.cacheMu.Unlock()
@@ -725,7 +739,7 @@ func (sess *session) StatementPrepare(ctx context.Context, stmt *exql.Statement)
 
 	tx := sess.Transaction()
 	if tx != nil {
-		sqlStmt, err = compat.PrepareContext(tx.(*baseTx), ctx, query)
+		sqlStmt, err = compat.PrepareContext(tx, ctx, query)
 		return
 	}
 
@@ -778,7 +792,6 @@ func (sess *session) StatementExec(ctx context.Context, stmt *exql.Statement, ar
 	}
 
 	tx := sess.Transaction()
-
 	if sess.Settings.PreparedStatementCacheEnabled() && tx == nil {
 		var p *Stmt
 		if p, query, args, err = sess.prepareStatement(ctx, stmt, args); err != nil {
@@ -796,7 +809,7 @@ func (sess *session) StatementExec(ctx context.Context, stmt *exql.Statement, ar
 	}
 
 	if tx != nil {
-		res, err = compat.ExecContext(tx.(*baseTx), ctx, query, args)
+		res, err = compat.ExecContext(tx, ctx, query, args)
 		return
 	}
 
@@ -840,7 +853,7 @@ func (sess *session) StatementQuery(ctx context.Context, stmt *exql.Statement, a
 		return nil, err
 	}
 	if tx != nil {
-		rows, err = compat.QueryContext(tx.(*baseTx), ctx, query, args)
+		rows, err = compat.QueryContext(tx, ctx, query, args)
 		return
 	}
 
@@ -886,7 +899,7 @@ func (sess *session) StatementQueryRow(ctx context.Context, stmt *exql.Statement
 		return nil, err
 	}
 	if tx != nil {
-		row = compat.QueryRowContext(tx.(*baseTx), ctx, query, args)
+		row = compat.QueryRowContext(tx, ctx, query, args)
 		return
 	}
 
@@ -896,9 +909,8 @@ func (sess *session) StatementQueryRow(ctx context.Context, stmt *exql.Statement
 
 // Driver returns the underlying *sql.DB or *sql.Tx instance.
 func (sess *session) Driver() interface{} {
-	if tx := sess.Transaction(); tx != nil {
-		// A transaction
-		return tx.(*baseTx).Tx
+	if sess.sqlTx != nil {
+		return sess.sqlTx
 	}
 	return sess.sqlDB
 }
@@ -950,7 +962,7 @@ func (sess *session) prepareStatement(ctx context.Context, stmt *exql.Statement,
 	}
 	sqlStmt, err := func(query *string) (*sql.Stmt, error) {
 		if tx != nil {
-			return compat.PrepareContext(tx.(*baseTx), ctx, *query)
+			return compat.PrepareContext(tx, ctx, *query)
 		}
 		return compat.PrepareContext(sess.sqlDB, ctx, *query)
 	}(&query)
@@ -1039,11 +1051,6 @@ func copySettings(from Session, into Session) {
 	into.SetConnMaxLifetime(from.ConnMaxLifetime())
 	into.SetMaxIdleConns(from.MaxIdleConns())
 	into.SetMaxOpenConns(from.MaxOpenConns())
-
-	txOptions := from.TxOptions()
-	if txOptions != nil {
-		into.SetTxOptions(*txOptions)
-	}
 }
 
 func newSessionID() uint64 {
@@ -1062,4 +1069,43 @@ func newBaseTxID() uint64 {
 	return atomic.AddUint64(&lastTxID, 1)
 }
 
-var _ sqlbuilder.Session = &session{}
+var _ db.Session = &session{}
+
+// TxContext creates a transaction context and runs fn within it.
+func TxContext(ctx context.Context, sess db.Session, fn func(tx db.Session) error, opts *sql.TxOptions) error {
+	txFn := func(sess db.Session) error {
+		tx, err := sess.(Session).NewTransaction(ctx, opts)
+		if err != nil {
+			return err
+		}
+		defer tx.Close()
+
+		if err := fn(tx); err != nil {
+			if rollbackErr := tx.Rollback(); rollbackErr != nil {
+				return fmt.Errorf("%s: %w", rollbackErr, err)
+			}
+			return err
+		}
+		return tx.Commit()
+	}
+
+	retryTime := retryTransactionWaitTime
+
+	var txErr error
+	for i := 0; i < sess.MaxTransactionRetries(); i++ {
+		txErr = sess.(*session).Err(txFn(sess))
+		if txErr == nil {
+			return nil
+		}
+		if errors.Is(txErr, db.ErrTransactionAborted) {
+			time.Sleep(retryTime)
+			if retryTime < time.Second {
+				retryTime = retryTime * 2
+			}
+			continue
+		}
+		return txErr
+	}
+
+	return fmt.Errorf("db: giving up trying to commit transaction: %w", txErr)
+}
diff --git a/internal/sqladapter/sqladapter.go b/internal/sqladapter/sqladapter.go
index 52d778b..aaeb2c4 100644
--- a/internal/sqladapter/sqladapter.go
+++ b/internal/sqladapter/sqladapter.go
@@ -27,7 +27,7 @@ import (
 	"database/sql/driver"
 
 	"github.com/upper/db/v4"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 // IsKeyValue reports whether v is a valid value for a primary key that can be
@@ -51,7 +51,7 @@ type sqlAdapterWrapper struct {
 	adapter AdapterSession
 }
 
-func (w *sqlAdapterWrapper) OpenDSN(dsn db.ConnectionURL) (sqlbuilder.Session, error) {
+func (w *sqlAdapterWrapper) OpenDSN(dsn db.ConnectionURL) (db.Session, error) {
 	sess := NewSession(dsn, w.adapter)
 	if err := sess.Open(); err != nil {
 		return nil, err
@@ -67,7 +67,7 @@ func (w *sqlAdapterWrapper) NewTx(sqlTx *sql.Tx) (sqlbuilder.Tx, error) {
 	return tx, nil
 }
 
-func (w *sqlAdapterWrapper) New(sqlDB *sql.DB) (sqlbuilder.Session, error) {
+func (w *sqlAdapterWrapper) New(sqlDB *sql.DB) (db.Session, error) {
 	sess := NewSession(nil, w.adapter)
 	if err := sess.BindDB(sqlDB); err != nil {
 		return nil, err
diff --git a/internal/sqladapter/tx.go b/internal/sqladapter/tx.go
deleted file mode 100644
index 1cc4f94..0000000
--- a/internal/sqladapter/tx.go
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright (c) 2012-present 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 sqladapter
-
-import (
-	"context"
-	"database/sql"
-	"errors"
-	"fmt"
-	"sync/atomic"
-	"time"
-
-	db "github.com/upper/db/v4"
-	"github.com/upper/db/v4/sqlbuilder"
-)
-
-// SessionTx represents a database session within a transaction.
-type SessionTx interface {
-	Session
-
-	BaseTx
-}
-
-// BaseTx provides logic for methods that can be shared across all SQL
-// adapters.
-type BaseTx interface {
-	db.Tx
-
-	// Committed returns true if the transaction was already commited.
-	Committed() bool
-}
-
-type sessionTx struct {
-	Session
-	BaseTx
-}
-
-// NewSessionTx creates a database session within a transaction.
-func NewSessionTx(sess Session) SessionTx {
-	newSessionTx := &sessionTx{
-		Session: sess,
-		BaseTx:  sess.Transaction(),
-	}
-	return newSessionTx
-}
-
-type baseTx struct {
-	*sql.Tx
-	committed atomic.Value
-}
-
-func newBaseTx(tx *sql.Tx) BaseTx {
-	return &baseTx{Tx: tx}
-}
-
-func (b *baseTx) Committed() bool {
-	committed := b.committed.Load()
-	return committed != nil
-}
-
-func (b *baseTx) Commit() (err error) {
-	err = b.Tx.Commit()
-	if err != nil {
-		return err
-	}
-	b.committed.Store(struct{}{})
-	return nil
-}
-
-func (w *sessionTx) Commit() error {
-	defer w.Session.Close() // Automatic close on commit.
-	return w.BaseTx.Commit()
-}
-
-func (w *sessionTx) Rollback() error {
-	defer w.Session.Close() // Automatic close on rollback.
-	return w.BaseTx.Rollback()
-}
-
-// TxContext creates a transaction context and runs fn within it.
-func TxContext(ctx context.Context, sess sqlbuilder.Session, fn func(tx db.Session) error) error {
-
-	txFn := func(sess sqlbuilder.Session) error {
-		tx, err := sess.NewTx(ctx)
-		if err != nil {
-			return err
-		}
-		defer tx.Close()
-
-		if err := fn(tx); err != nil {
-			if rollbackErr := tx.Rollback(); rollbackErr != nil {
-				return fmt.Errorf("%s: %w", rollbackErr, err)
-			}
-			return err
-		}
-		return tx.Commit()
-	}
-
-	var txErr error
-	for i := 0; i < sess.MaxTransactionRetries(); i++ {
-		txErr = sess.(*session).Err(txFn(sess))
-		if txErr == nil {
-			return nil
-		}
-		if errors.Is(txErr, db.ErrTransactionAborted) {
-			time.Sleep(time.Millisecond * 100 * time.Duration(i))
-			continue
-		}
-		return txErr
-	}
-
-	return fmt.Errorf("db: giving up trying to commit transaction: %w", txErr)
-
-}
-
-var (
-	_ = BaseTx(&baseTx{})
-	_ = SessionTx(&sessionTx{})
-)
-
-type txWrapper struct {
-	SessionTx
-}
-
-var (
-	_ = sqlbuilder.Tx(&txWrapper{})
-	_ = sqlbuilder.Session(&txWrapper{})
-)
-
-func (t *txWrapper) WithContext(ctx context.Context) sqlbuilder.Session {
-	newTx := *t
-	newTx.SessionTx.SetContext(ctx)
-	return &newTx
-}
diff --git a/sqlbuilder/batch.go b/internal/sqlbuilder/batch.go
similarity index 93%
rename from sqlbuilder/batch.go
rename to internal/sqlbuilder/batch.go
index a8a8b14..c9465e9 100644
--- a/sqlbuilder/batch.go
+++ b/internal/sqlbuilder/batch.go
@@ -1,5 +1,9 @@
 package sqlbuilder
 
+import (
+	"github.com/upper/db/v4"
+)
+
 // BatchInserter provides a helper that can be used to do massive insertions in
 // batches.
 type BatchInserter struct {
@@ -22,7 +26,7 @@ func newBatchInserter(inserter *inserter, size int) *BatchInserter {
 }
 
 // Values pushes column values to be inserted as part of the batch.
-func (b *BatchInserter) Values(values ...interface{}) *BatchInserter {
+func (b *BatchInserter) Values(values ...interface{}) db.BatchInserter {
 	b.values <- values
 	return b
 }
diff --git a/sqlbuilder/builder.go b/internal/sqlbuilder/builder.go
similarity index 95%
rename from sqlbuilder/builder.go
rename to internal/sqlbuilder/builder.go
index 6ad9cce..fde9caa 100644
--- a/sqlbuilder/builder.go
+++ b/internal/sqlbuilder/builder.go
@@ -99,7 +99,7 @@ type sqlBuilder struct {
 }
 
 // WithSession returns a query builder that is bound to the given database session.
-func WithSession(sess interface{}, t *exql.Template) SQLBuilder {
+func WithSession(sess interface{}, t *exql.Template) db.SQL {
 	if sqlDB, ok := sess.(*sql.DB); ok {
 		sess = sqlDB
 	}
@@ -110,22 +110,22 @@ func WithSession(sess interface{}, t *exql.Template) SQLBuilder {
 }
 
 // WithTemplate returns a builder that is based on the given template.
-func WithTemplate(t *exql.Template) SQLBuilder {
+func WithTemplate(t *exql.Template) db.SQL {
 	return &sqlBuilder{
 		t: newTemplateWithUtils(t),
 	}
 }
 
-// NewIterator creates an iterator using the given *sql.Rows.
-func NewIterator(rows *sql.Rows) Iterator {
+// Newdb.Iterator creates an iterator using the given *sql.Rows.
+func NewIterator(rows *sql.Rows) db.Iterator {
 	return &iterator{nil, rows, nil}
 }
 
-func (b *sqlBuilder) Iterator(query interface{}, args ...interface{}) Iterator {
+func (b *sqlBuilder) Iterator(query interface{}, args ...interface{}) db.Iterator {
 	return b.IteratorContext(b.sess.Context(), query, args...)
 }
 
-func (b *sqlBuilder) IteratorContext(ctx context.Context, query interface{}, args ...interface{}) Iterator {
+func (b *sqlBuilder) IteratorContext(ctx context.Context, query interface{}, args ...interface{}) db.Iterator {
 	rows, err := b.QueryContext(ctx, query, args...)
 	return &iterator{b.sess, rows, err}
 }
@@ -198,35 +198,35 @@ func (b *sqlBuilder) QueryRowContext(ctx context.Context, query interface{}, arg
 	}
 }
 
-func (b *sqlBuilder) SelectFrom(table ...interface{}) Selector {
+func (b *sqlBuilder) SelectFrom(table ...interface{}) db.Selector {
 	qs := &selector{
 		builder: b,
 	}
 	return qs.From(table...)
 }
 
-func (b *sqlBuilder) Select(columns ...interface{}) Selector {
+func (b *sqlBuilder) Select(columns ...interface{}) db.Selector {
 	qs := &selector{
 		builder: b,
 	}
 	return qs.Columns(columns...)
 }
 
-func (b *sqlBuilder) InsertInto(table string) Inserter {
+func (b *sqlBuilder) InsertInto(table string) db.Inserter {
 	qi := &inserter{
 		builder: b,
 	}
 	return qi.Into(table)
 }
 
-func (b *sqlBuilder) DeleteFrom(table string) Deleter {
+func (b *sqlBuilder) DeleteFrom(table string) db.Deleter {
 	qd := &deleter{
 		builder: b,
 	}
 	return qd.setTable(table)
 }
 
-func (b *sqlBuilder) Update(table string) Updater {
+func (b *sqlBuilder) Update(table string) db.Updater {
 	qu := &updater{
 		builder: b,
 	}
@@ -356,7 +356,7 @@ func columnFragments(columns []interface{}) ([]exql.Fragment, []interface{}, err
 				return nil, nil, err
 			}
 			q, a := Preprocess(c, v.Arguments())
-			if _, ok := v.(Selector); ok {
+			if _, ok := v.(db.Selector); ok {
 				q = "(" + q + ")"
 			}
 			f[i] = exql.RawValue(q)
@@ -575,7 +575,7 @@ func (p *exprProxy) StatementQueryRow(ctx context.Context, stmt *exql.Statement,
 }
 
 var (
-	_ = SQLBuilder(&sqlBuilder{})
+	_ = db.SQL(&sqlBuilder{})
 	_ = exprDB(&exprProxy{})
 )
 
diff --git a/sqlbuilder/builder_test.go b/internal/sqlbuilder/builder_test.go
similarity index 100%
rename from sqlbuilder/builder_test.go
rename to internal/sqlbuilder/builder_test.go
diff --git a/sqlbuilder/comparison.go b/internal/sqlbuilder/comparison.go
similarity index 100%
rename from sqlbuilder/comparison.go
rename to internal/sqlbuilder/comparison.go
diff --git a/sqlbuilder/convert.go b/internal/sqlbuilder/convert.go
similarity index 100%
rename from sqlbuilder/convert.go
rename to internal/sqlbuilder/convert.go
diff --git a/sqlbuilder/custom_types.go b/internal/sqlbuilder/custom_types.go
similarity index 100%
rename from sqlbuilder/custom_types.go
rename to internal/sqlbuilder/custom_types.go
diff --git a/sqlbuilder/delete.go b/internal/sqlbuilder/delete.go
similarity index 82%
rename from sqlbuilder/delete.go
rename to internal/sqlbuilder/delete.go
index 779bc2e..1cae2df 100644
--- a/sqlbuilder/delete.go
+++ b/internal/sqlbuilder/delete.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"database/sql"
 
+	"github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/immutable"
 	"github.com/upper/db/v4/internal/sqladapter/exql"
 )
@@ -58,15 +59,15 @@ type deleter struct {
 
 var _ = immutable.Immutable(&deleter{})
 
-func (del *deleter) SQLBuilder() *sqlBuilder {
+func (del *deleter) SQL() *sqlBuilder {
 	if del.prev == nil {
 		return del.builder
 	}
-	return del.prev.SQLBuilder()
+	return del.prev.SQL()
 }
 
 func (del *deleter) template() *exql.Template {
-	return del.SQLBuilder().t.Template
+	return del.SQL().t.Template
 }
 
 func (del *deleter) String() string {
@@ -88,27 +89,27 @@ func (del *deleter) frame(fn func(*deleterQuery) error) *deleter {
 	return &deleter{prev: del, fn: fn}
 }
 
-func (del *deleter) Where(terms ...interface{}) Deleter {
+func (del *deleter) Where(terms ...interface{}) db.Deleter {
 	return del.frame(func(dq *deleterQuery) error {
 		dq.where, dq.whereArgs = &exql.Where{}, []interface{}{}
-		return dq.and(del.SQLBuilder(), terms...)
+		return dq.and(del.SQL(), terms...)
 	})
 }
 
-func (del *deleter) And(terms ...interface{}) Deleter {
+func (del *deleter) And(terms ...interface{}) db.Deleter {
 	return del.frame(func(dq *deleterQuery) error {
-		return dq.and(del.SQLBuilder(), terms...)
+		return dq.and(del.SQL(), terms...)
 	})
 }
 
-func (del *deleter) Limit(limit int) Deleter {
+func (del *deleter) Limit(limit int) db.Deleter {
 	return del.frame(func(dq *deleterQuery) error {
 		dq.limit = limit
 		return nil
 	})
 }
 
-func (del *deleter) Amend(fn func(string) string) Deleter {
+func (del *deleter) Amend(fn func(string) string) db.Deleter {
 	return del.frame(func(dq *deleterQuery) error {
 		dq.amendFn = fn
 		return nil
@@ -128,7 +129,7 @@ func (del *deleter) Arguments() []interface{} {
 }
 
 func (del *deleter) Prepare() (*sql.Stmt, error) {
-	return del.PrepareContext(del.SQLBuilder().sess.Context())
+	return del.PrepareContext(del.SQL().sess.Context())
 }
 
 func (del *deleter) PrepareContext(ctx context.Context) (*sql.Stmt, error) {
@@ -136,11 +137,11 @@ func (del *deleter) PrepareContext(ctx context.Context) (*sql.Stmt, error) {
 	if err != nil {
 		return nil, err
 	}
-	return del.SQLBuilder().sess.StatementPrepare(ctx, dq.statement())
+	return del.SQL().sess.StatementPrepare(ctx, dq.statement())
 }
 
 func (del *deleter) Exec() (sql.Result, error) {
-	return del.ExecContext(del.SQLBuilder().sess.Context())
+	return del.ExecContext(del.SQL().sess.Context())
 }
 
 func (del *deleter) ExecContext(ctx context.Context) (sql.Result, error) {
@@ -148,7 +149,7 @@ func (del *deleter) ExecContext(ctx context.Context) (sql.Result, error) {
 	if err != nil {
 		return nil, err
 	}
-	return del.SQLBuilder().sess.StatementExec(ctx, dq.statement(), dq.arguments()...)
+	return del.SQL().sess.StatementExec(ctx, dq.statement(), dq.arguments()...)
 }
 
 func (del *deleter) statement() (*exql.Statement, error) {
diff --git a/sqlbuilder/errors.go b/internal/sqlbuilder/errors.go
similarity index 100%
rename from sqlbuilder/errors.go
rename to internal/sqlbuilder/errors.go
diff --git a/sqlbuilder/fetch.go b/internal/sqlbuilder/fetch.go
similarity index 100%
rename from sqlbuilder/fetch.go
rename to internal/sqlbuilder/fetch.go
diff --git a/sqlbuilder/insert.go b/internal/sqlbuilder/insert.go
similarity index 81%
rename from sqlbuilder/insert.go
rename to internal/sqlbuilder/insert.go
index f318530..d4c9594 100644
--- a/sqlbuilder/insert.go
+++ b/internal/sqlbuilder/insert.go
@@ -5,6 +5,7 @@ import (
 	"database/sql"
 	"errors"
 
+	"github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/immutable"
 	"github.com/upper/db/v4/internal/sqladapter/exql"
 )
@@ -100,15 +101,15 @@ type inserter struct {
 
 var _ = immutable.Immutable(&inserter{})
 
-func (ins *inserter) SQLBuilder() *sqlBuilder {
+func (ins *inserter) SQL() *sqlBuilder {
 	if ins.prev == nil {
 		return ins.builder
 	}
-	return ins.prev.SQLBuilder()
+	return ins.prev.SQL()
 }
 
 func (ins *inserter) template() *exql.Template {
-	return ins.SQLBuilder().t.Template
+	return ins.SQL().t.Template
 }
 
 func (ins *inserter) String() string {
@@ -123,11 +124,11 @@ func (ins *inserter) frame(fn func(*inserterQuery) error) *inserter {
 	return &inserter{prev: ins, fn: fn}
 }
 
-func (ins *inserter) Batch(n int) *BatchInserter {
+func (ins *inserter) Batch(n int) db.BatchInserter {
 	return newBatchInserter(ins, n)
 }
 
-func (ins *inserter) Amend(fn func(string) string) Inserter {
+func (ins *inserter) Amend(fn func(string) string) db.Inserter {
 	return ins.frame(func(iq *inserterQuery) error {
 		iq.amendFn = fn
 		return nil
@@ -142,7 +143,7 @@ func (ins *inserter) Arguments() []interface{} {
 	return iq.arguments
 }
 
-func (ins *inserter) Returning(columns ...string) Inserter {
+func (ins *inserter) Returning(columns ...string) db.Inserter {
 	return ins.frame(func(iq *inserterQuery) error {
 		columnsToFragments(&iq.returning, columns)
 		return nil
@@ -150,7 +151,7 @@ func (ins *inserter) Returning(columns ...string) Inserter {
 }
 
 func (ins *inserter) Exec() (sql.Result, error) {
-	return ins.ExecContext(ins.SQLBuilder().sess.Context())
+	return ins.ExecContext(ins.SQL().sess.Context())
 }
 
 func (ins *inserter) ExecContext(ctx context.Context) (sql.Result, error) {
@@ -158,11 +159,11 @@ func (ins *inserter) ExecContext(ctx context.Context) (sql.Result, error) {
 	if err != nil {
 		return nil, err
 	}
-	return ins.SQLBuilder().sess.StatementExec(ctx, iq.statement(), iq.arguments...)
+	return ins.SQL().sess.StatementExec(ctx, iq.statement(), iq.arguments...)
 }
 
 func (ins *inserter) Prepare() (*sql.Stmt, error) {
-	return ins.PrepareContext(ins.SQLBuilder().sess.Context())
+	return ins.PrepareContext(ins.SQL().sess.Context())
 }
 
 func (ins *inserter) PrepareContext(ctx context.Context) (*sql.Stmt, error) {
@@ -170,11 +171,11 @@ func (ins *inserter) PrepareContext(ctx context.Context) (*sql.Stmt, error) {
 	if err != nil {
 		return nil, err
 	}
-	return ins.SQLBuilder().sess.StatementPrepare(ctx, iq.statement())
+	return ins.SQL().sess.StatementPrepare(ctx, iq.statement())
 }
 
 func (ins *inserter) Query() (*sql.Rows, error) {
-	return ins.QueryContext(ins.SQLBuilder().sess.Context())
+	return ins.QueryContext(ins.SQL().sess.Context())
 }
 
 func (ins *inserter) QueryContext(ctx context.Context) (*sql.Rows, error) {
@@ -182,11 +183,11 @@ func (ins *inserter) QueryContext(ctx context.Context) (*sql.Rows, error) {
 	if err != nil {
 		return nil, err
 	}
-	return ins.SQLBuilder().sess.StatementQuery(ctx, iq.statement(), iq.arguments...)
+	return ins.SQL().sess.StatementQuery(ctx, iq.statement(), iq.arguments...)
 }
 
 func (ins *inserter) QueryRow() (*sql.Row, error) {
-	return ins.QueryRowContext(ins.SQLBuilder().sess.Context())
+	return ins.QueryRowContext(ins.SQL().sess.Context())
 }
 
 func (ins *inserter) QueryRowContext(ctx context.Context) (*sql.Row, error) {
@@ -194,33 +195,33 @@ func (ins *inserter) QueryRowContext(ctx context.Context) (*sql.Row, error) {
 	if err != nil {
 		return nil, err
 	}
-	return ins.SQLBuilder().sess.StatementQueryRow(ctx, iq.statement(), iq.arguments...)
+	return ins.SQL().sess.StatementQueryRow(ctx, iq.statement(), iq.arguments...)
 }
 
-func (ins *inserter) Iterator() Iterator {
-	return ins.IteratorContext(ins.SQLBuilder().sess.Context())
+func (ins *inserter) Iterator() db.Iterator {
+	return ins.IteratorContext(ins.SQL().sess.Context())
 }
 
-func (ins *inserter) IteratorContext(ctx context.Context) Iterator {
+func (ins *inserter) IteratorContext(ctx context.Context) db.Iterator {
 	rows, err := ins.QueryContext(ctx)
-	return &iterator{ins.SQLBuilder().sess, rows, err}
+	return &iterator{ins.SQL().sess, rows, err}
 }
 
-func (ins *inserter) Into(table string) Inserter {
+func (ins *inserter) Into(table string) db.Inserter {
 	return ins.frame(func(iq *inserterQuery) error {
 		iq.table = table
 		return nil
 	})
 }
 
-func (ins *inserter) Columns(columns ...string) Inserter {
+func (ins *inserter) Columns(columns ...string) db.Inserter {
 	return ins.frame(func(iq *inserterQuery) error {
 		columnsToFragments(&iq.columns, columns)
 		return nil
 	})
 }
 
-func (ins *inserter) Values(values ...interface{}) Inserter {
+func (ins *inserter) Values(values ...interface{}) db.Inserter {
 	return ins.frame(func(iq *inserterQuery) error {
 		iq.enqueuedValues = append(iq.enqueuedValues, values)
 		return nil
diff --git a/sqlbuilder/paginate.go b/internal/sqlbuilder/paginate.go
similarity index 91%
rename from sqlbuilder/paginate.go
rename to internal/sqlbuilder/paginate.go
index 17c24c3..906b527 100644
--- a/sqlbuilder/paginate.go
+++ b/internal/sqlbuilder/paginate.go
@@ -16,7 +16,7 @@ var (
 )
 
 type paginatorQuery struct {
-	sel Selector
+	sel db.Selector
 
 	cursorColumn       string
 	cursorValue        interface{}
@@ -27,7 +27,7 @@ type paginatorQuery struct {
 	pageNumber uint
 }
 
-func newPaginator(sel Selector, pageSize uint) Paginator {
+func newPaginator(sel db.Selector, pageSize uint) db.Paginator {
 	pag := &paginator{}
 	return pag.frame(func(pq *paginatorQuery) error {
 		pq.pageSize = pageSize
@@ -67,7 +67,7 @@ func (pag *paginator) frame(fn func(*paginatorQuery) error) *paginator {
 	return &paginator{prev: pag, fn: fn}
 }
 
-func (pag *paginator) Page(pageNumber uint) Paginator {
+func (pag *paginator) Page(pageNumber uint) db.Paginator {
 	return pag.frame(func(pq *paginatorQuery) error {
 		if pageNumber < 1 {
 			pageNumber = 1
@@ -77,7 +77,7 @@ func (pag *paginator) Page(pageNumber uint) Paginator {
 	})
 }
 
-func (pag *paginator) Cursor(column string) Paginator {
+func (pag *paginator) Cursor(column string) db.Paginator {
 	return pag.frame(func(pq *paginatorQuery) error {
 		pq.cursorColumn = column
 		pq.cursorValue = nil
@@ -85,7 +85,7 @@ func (pag *paginator) Cursor(column string) Paginator {
 	})
 }
 
-func (pag *paginator) NextPage(cursorValue interface{}) Paginator {
+func (pag *paginator) NextPage(cursorValue interface{}) db.Paginator {
 	return pag.frame(func(pq *paginatorQuery) error {
 		if pq.cursorValue != nil && pq.cursorColumn == "" {
 			return errMissingCursorColumn
@@ -105,7 +105,7 @@ func (pag *paginator) NextPage(cursorValue interface{}) Paginator {
 	})
 }
 
-func (pag *paginator) PrevPage(cursorValue interface{}) Paginator {
+func (pag *paginator) PrevPage(cursorValue interface{}) db.Paginator {
 	return pag.frame(func(pq *paginatorQuery) error {
 		if pq.cursorValue != nil && pq.cursorColumn == "" {
 			return errMissingCursorColumn
@@ -167,19 +167,19 @@ func (pag *paginator) One(dest interface{}) error {
 	return pq.sel.One(dest)
 }
 
-func (pag *paginator) Iterator() Iterator {
+func (pag *paginator) Iterator() db.Iterator {
 	pq, err := pag.buildWithCursor()
 	if err != nil {
-		sess := pq.sel.(*selector).SQLBuilder().sess
+		sess := pq.sel.(*selector).SQL().sess
 		return &iterator{sess, nil, err}
 	}
 	return pq.sel.Iterator()
 }
 
-func (pag *paginator) IteratorContext(ctx context.Context) Iterator {
+func (pag *paginator) IteratorContext(ctx context.Context) db.Iterator {
 	pq, err := pag.buildWithCursor()
 	if err != nil {
-		sess := pq.sel.(*selector).SQLBuilder().sess
+		sess := pq.sel.(*selector).SQL().sess
 		return &iterator{sess, nil, err}
 	}
 	return pq.sel.IteratorContext(ctx)
@@ -310,7 +310,7 @@ func (pag *paginator) buildWithCursor() (*paginatorQuery, error) {
 
 	if pqq.cursorColumn != "" {
 		if pqq.cursorReverseOrder {
-			pqq.sel = pqq.sel.(*selector).SQLBuilder().
+			pqq.sel = pqq.sel.(*selector).SQL().
 				SelectFrom(db.Raw("? AS p0", pqq.sel)).
 				OrderBy(pqq.cursorColumn)
 		} else {
diff --git a/sqlbuilder/placeholder_test.go b/internal/sqlbuilder/placeholder_test.go
similarity index 100%
rename from sqlbuilder/placeholder_test.go
rename to internal/sqlbuilder/placeholder_test.go
diff --git a/sqlbuilder/scanner.go b/internal/sqlbuilder/scanner.go
similarity index 100%
rename from sqlbuilder/scanner.go
rename to internal/sqlbuilder/scanner.go
diff --git a/sqlbuilder/select.go b/internal/sqlbuilder/select.go
similarity index 81%
rename from sqlbuilder/select.go
rename to internal/sqlbuilder/select.go
index 0daddc7..73a2fb8 100644
--- a/sqlbuilder/select.go
+++ b/internal/sqlbuilder/select.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"strings"
 
+	"github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/adapter"
 	"github.com/upper/db/v4/internal/immutable"
 	"github.com/upper/db/v4/internal/sqladapter/exql"
@@ -114,11 +115,11 @@ type selector struct {
 
 var _ = immutable.Immutable(&selector{})
 
-func (sel *selector) SQLBuilder() *sqlBuilder {
+func (sel *selector) SQL() *sqlBuilder {
 	if sel.prev == nil {
 		return sel.builder
 	}
-	return sel.prev.SQLBuilder()
+	return sel.prev.SQL()
 }
 
 func (sel *selector) String() string {
@@ -133,13 +134,13 @@ func (sel *selector) frame(fn func(*selectorQuery) error) *selector {
 	return &selector{prev: sel, fn: fn}
 }
 
-func (sel *selector) clone() Selector {
+func (sel *selector) clone() db.Selector {
 	return sel.frame(func(*selectorQuery) error {
 		return nil
 	})
 }
 
-func (sel *selector) From(tables ...interface{}) Selector {
+func (sel *selector) From(tables ...interface{}) db.Selector {
 	return sel.frame(
 		func(sq *selectorQuery) error {
 			fragments, args, err := columnFragments(tables)
@@ -153,14 +154,14 @@ func (sel *selector) From(tables ...interface{}) Selector {
 	)
 }
 
-func (sel *selector) setColumns(columns ...interface{}) Selector {
+func (sel *selector) setColumns(columns ...interface{}) db.Selector {
 	return sel.frame(func(sq *selectorQuery) error {
 		sq.columns = nil
 		return sq.pushColumns(columns...)
 	})
 }
 
-func (sel *selector) Columns(columns ...interface{}) Selector {
+func (sel *selector) Columns(columns ...interface{}) db.Selector {
 	return sel.frame(func(sq *selectorQuery) error {
 		return sq.pushColumns(columns...)
 	})
@@ -184,30 +185,30 @@ func (sq *selectorQuery) pushColumns(columns ...interface{}) error {
 	return nil
 }
 
-func (sel *selector) Distinct(exps ...interface{}) Selector {
+func (sel *selector) Distinct(exps ...interface{}) db.Selector {
 	return sel.frame(func(sq *selectorQuery) error {
 		sq.distinct = true
 		return sq.pushColumns(exps...)
 	})
 }
 
-func (sel *selector) Where(terms ...interface{}) Selector {
+func (sel *selector) Where(terms ...interface{}) db.Selector {
 	return sel.frame(func(sq *selectorQuery) error {
 		if len(terms) == 1 && terms[0] == nil {
 			sq.where, sq.whereArgs = &exql.Where{}, []interface{}{}
 			return nil
 		}
-		return sq.and(sel.SQLBuilder(), terms...)
+		return sq.and(sel.SQL(), terms...)
 	})
 }
 
-func (sel *selector) And(terms ...interface{}) Selector {
+func (sel *selector) And(terms ...interface{}) db.Selector {
 	return sel.frame(func(sq *selectorQuery) error {
-		return sq.and(sel.SQLBuilder(), terms...)
+		return sq.and(sel.SQL(), terms...)
 	})
 }
 
-func (sel *selector) Amend(fn func(string) string) Selector {
+func (sel *selector) Amend(fn func(string) string) db.Selector {
 	return sel.frame(func(sq *selectorQuery) error {
 		sq.amendFn = fn
 		return nil
@@ -222,7 +223,7 @@ func (sel *selector) Arguments() []interface{} {
 	return sq.arguments()
 }
 
-func (sel *selector) GroupBy(columns ...interface{}) Selector {
+func (sel *selector) GroupBy(columns ...interface{}) db.Selector {
 	return sel.frame(func(sq *selectorQuery) error {
 		fragments, args, err := columnFragments(columns)
 		if err != nil {
@@ -238,7 +239,7 @@ func (sel *selector) GroupBy(columns ...interface{}) Selector {
 	})
 }
 
-func (sel *selector) OrderBy(columns ...interface{}) Selector {
+func (sel *selector) OrderBy(columns ...interface{}) db.Selector {
 	return sel.frame(func(sq *selectorQuery) error {
 
 		if len(columns) == 1 && columns[0] == nil {
@@ -303,7 +304,7 @@ func (sel *selector) OrderBy(columns ...interface{}) Selector {
 	})
 }
 
-func (sel *selector) Using(columns ...interface{}) Selector {
+func (sel *selector) Using(columns ...interface{}) db.Selector {
 	return sel.frame(func(sq *selectorQuery) error {
 
 		joins := len(sq.joins)
@@ -328,37 +329,37 @@ func (sel *selector) Using(columns ...interface{}) Selector {
 	})
 }
 
-func (sel *selector) FullJoin(tables ...interface{}) Selector {
+func (sel *selector) FullJoin(tables ...interface{}) db.Selector {
 	return sel.frame(func(sq *selectorQuery) error {
 		return sq.pushJoin("FULL", tables)
 	})
 }
 
-func (sel *selector) CrossJoin(tables ...interface{}) Selector {
+func (sel *selector) CrossJoin(tables ...interface{}) db.Selector {
 	return sel.frame(func(sq *selectorQuery) error {
 		return sq.pushJoin("CROSS", tables)
 	})
 }
 
-func (sel *selector) RightJoin(tables ...interface{}) Selector {
+func (sel *selector) RightJoin(tables ...interface{}) db.Selector {
 	return sel.frame(func(sq *selectorQuery) error {
 		return sq.pushJoin("RIGHT", tables)
 	})
 }
 
-func (sel *selector) LeftJoin(tables ...interface{}) Selector {
+func (sel *selector) LeftJoin(tables ...interface{}) db.Selector {
 	return sel.frame(func(sq *selectorQuery) error {
 		return sq.pushJoin("LEFT", tables)
 	})
 }
 
-func (sel *selector) Join(tables ...interface{}) Selector {
+func (sel *selector) Join(tables ...interface{}) db.Selector {
 	return sel.frame(func(sq *selectorQuery) error {
 		return sq.pushJoin("", tables)
 	})
 }
 
-func (sel *selector) On(terms ...interface{}) Selector {
+func (sel *selector) On(terms ...interface{}) db.Selector {
 	return sel.frame(func(sq *selectorQuery) error {
 		joins := len(sq.joins)
 
@@ -371,7 +372,7 @@ func (sel *selector) On(terms ...interface{}) Selector {
 			return errors.New(`cannot use Using() and On() with the same Join() expression`)
 		}
 
-		w, a := sel.SQLBuilder().t.toWhereWithArguments(terms)
+		w, a := sel.SQL().t.toWhereWithArguments(terms)
 		o := exql.On(w)
 
 		lastJoin.On = &o
@@ -382,7 +383,7 @@ func (sel *selector) On(terms ...interface{}) Selector {
 	})
 }
 
-func (sel *selector) Limit(n int) Selector {
+func (sel *selector) Limit(n int) db.Selector {
 	return sel.frame(func(sq *selectorQuery) error {
 		if n < 0 {
 			n = 0
@@ -392,7 +393,7 @@ func (sel *selector) Limit(n int) Selector {
 	})
 }
 
-func (sel *selector) Offset(n int) Selector {
+func (sel *selector) Offset(n int) db.Selector {
 	return sel.frame(func(sq *selectorQuery) error {
 		if n < 0 {
 			n = 0
@@ -403,10 +404,10 @@ func (sel *selector) Offset(n int) Selector {
 }
 
 func (sel *selector) template() *exql.Template {
-	return sel.SQLBuilder().t.Template
+	return sel.SQL().t.Template
 }
 
-func (sel *selector) As(alias string) Selector {
+func (sel *selector) As(alias string) db.Selector {
 	return sel.frame(func(sq *selectorQuery) error {
 		if sq.table == nil {
 			return errors.New("Cannot use As() without a preceding From() expression")
@@ -429,7 +430,7 @@ func (sel *selector) statement() *exql.Statement {
 }
 
 func (sel *selector) QueryRow() (*sql.Row, error) {
-	return sel.QueryRowContext(sel.SQLBuilder().sess.Context())
+	return sel.QueryRowContext(sel.SQL().sess.Context())
 }
 
 func (sel *selector) QueryRowContext(ctx context.Context) (*sql.Row, error) {
@@ -438,11 +439,11 @@ func (sel *selector) QueryRowContext(ctx context.Context) (*sql.Row, error) {
 		return nil, err
 	}
 
-	return sel.SQLBuilder().sess.StatementQueryRow(ctx, sq.statement(), sq.arguments()...)
+	return sel.SQL().sess.StatementQueryRow(ctx, sq.statement(), sq.arguments()...)
 }
 
 func (sel *selector) Prepare() (*sql.Stmt, error) {
-	return sel.PrepareContext(sel.SQLBuilder().sess.Context())
+	return sel.PrepareContext(sel.SQL().sess.Context())
 }
 
 func (sel *selector) PrepareContext(ctx context.Context) (*sql.Stmt, error) {
@@ -450,11 +451,11 @@ func (sel *selector) PrepareContext(ctx context.Context) (*sql.Stmt, error) {
 	if err != nil {
 		return nil, err
 	}
-	return sel.SQLBuilder().sess.StatementPrepare(ctx, sq.statement())
+	return sel.SQL().sess.StatementPrepare(ctx, sq.statement())
 }
 
 func (sel *selector) Query() (*sql.Rows, error) {
-	return sel.QueryContext(sel.SQLBuilder().sess.Context())
+	return sel.QueryContext(sel.SQL().sess.Context())
 }
 
 func (sel *selector) QueryContext(ctx context.Context) (*sql.Rows, error) {
@@ -462,15 +463,15 @@ func (sel *selector) QueryContext(ctx context.Context) (*sql.Rows, error) {
 	if err != nil {
 		return nil, err
 	}
-	return sel.SQLBuilder().sess.StatementQuery(ctx, sq.statement(), sq.arguments()...)
+	return sel.SQL().sess.StatementQuery(ctx, sq.statement(), sq.arguments()...)
 }
 
-func (sel *selector) Iterator() Iterator {
-	return sel.IteratorContext(sel.SQLBuilder().sess.Context())
+func (sel *selector) Iterator() db.Iterator {
+	return sel.IteratorContext(sel.SQL().sess.Context())
 }
 
-func (sel *selector) IteratorContext(ctx context.Context) Iterator {
-	sess := sel.SQLBuilder().sess
+func (sel *selector) IteratorContext(ctx context.Context) db.Iterator {
+	sess := sel.SQL().sess
 	sq, err := sel.build()
 	if err != nil {
 		return &iterator{sess, nil, err}
@@ -480,7 +481,7 @@ func (sel *selector) IteratorContext(ctx context.Context) Iterator {
 	return &iterator{sess, rows, err}
 }
 
-func (sel *selector) Paginate(pageSize uint) Paginator {
+func (sel *selector) Paginate(pageSize uint) db.Paginator {
 	return newPaginator(sel.clone(), pageSize)
 }
 
diff --git a/sqlbuilder/sqlbuilder.go b/internal/sqlbuilder/sqlbuilder.go
similarity index 90%
rename from sqlbuilder/sqlbuilder.go
rename to internal/sqlbuilder/sqlbuilder.go
index 5be9d5b..af90a57 100644
--- a/sqlbuilder/sqlbuilder.go
+++ b/internal/sqlbuilder/sqlbuilder.go
@@ -11,7 +11,7 @@ import (
 type Engine interface {
 	db.Session
 
-	SQLBuilder
+	db.SQL
 }
 
 func lookupAdapter(adapterName string) (Adapter, error) {
@@ -19,7 +19,6 @@ func lookupAdapter(adapterName string) (Adapter, error) {
 	if sqlAdapter, ok := adapter.(Adapter); ok {
 		return sqlAdapter, nil
 	}
-
 	return nil, fmt.Errorf("%w %q", db.ErrMissingAdapter, adapterName)
 }
 
@@ -28,16 +27,14 @@ func BindTx(adapterName string, tx *sql.Tx) (Tx, error) {
 	if err != nil {
 		return nil, err
 	}
-
 	return adapter.NewTx(tx)
 }
 
 // Bind creates a binding between an adapter and a *sql.Tx or a *sql.DB.
-func BindDB(adapterName string, sess *sql.DB) (Session, error) {
+func BindDB(adapterName string, sess *sql.DB) (db.Session, error) {
 	adapter, err := lookupAdapter(adapterName)
 	if err != nil {
 		return nil, err
 	}
-
 	return adapter.New(sess)
 }
diff --git a/sqlbuilder/template.go b/internal/sqlbuilder/template.go
similarity index 100%
rename from sqlbuilder/template.go
rename to internal/sqlbuilder/template.go
diff --git a/sqlbuilder/template_test.go b/internal/sqlbuilder/template_test.go
similarity index 100%
rename from sqlbuilder/template_test.go
rename to internal/sqlbuilder/template_test.go
diff --git a/sqlbuilder/update.go b/internal/sqlbuilder/update.go
similarity index 81%
rename from sqlbuilder/update.go
rename to internal/sqlbuilder/update.go
index 0cbbd43..8f433a1 100644
--- a/sqlbuilder/update.go
+++ b/internal/sqlbuilder/update.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"database/sql"
 
+	"github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/immutable"
 	"github.com/upper/db/v4/internal/sqladapter/exql"
 )
@@ -70,15 +71,15 @@ type updater struct {
 
 var _ = immutable.Immutable(&updater{})
 
-func (upd *updater) SQLBuilder() *sqlBuilder {
+func (upd *updater) SQL() *sqlBuilder {
 	if upd.prev == nil {
 		return upd.builder
 	}
-	return upd.prev.SQLBuilder()
+	return upd.prev.SQL()
 }
 
 func (upd *updater) template() *exql.Template {
-	return upd.SQLBuilder().t.Template
+	return upd.SQL().t.Template
 }
 
 func (upd *updater) String() string {
@@ -100,7 +101,7 @@ func (upd *updater) frame(fn func(*updaterQuery) error) *updater {
 	return &updater{prev: upd, fn: fn}
 }
 
-func (upd *updater) Set(terms ...interface{}) Updater {
+func (upd *updater) Set(terms ...interface{}) db.Updater {
 	return upd.frame(func(uq *updaterQuery) error {
 		if uq.columnValues == nil {
 			uq.columnValues = &exql.ColumnValues{}
@@ -115,11 +116,11 @@ func (upd *updater) Set(terms ...interface{}) Updater {
 				for i := range ff {
 					cv := &exql.ColumnValue{
 						Column:   exql.ColumnWithName(ff[i]),
-						Operator: upd.SQLBuilder().t.AssignmentOperator,
+						Operator: upd.SQL().t.AssignmentOperator,
 					}
 
 					var localArgs []interface{}
-					cv.Value, localArgs = upd.SQLBuilder().t.PlaceholderValue(vv[i])
+					cv.Value, localArgs = upd.SQL().t.PlaceholderValue(vv[i])
 
 					args = append(args, localArgs...)
 					cvs = append(cvs, cv)
@@ -132,14 +133,14 @@ func (upd *updater) Set(terms ...interface{}) Updater {
 			}
 		}
 
-		cv, arguments := upd.SQLBuilder().t.setColumnValues(terms)
+		cv, arguments := upd.SQL().t.setColumnValues(terms)
 		uq.columnValues.Insert(cv.ColumnValues...)
 		uq.columnValuesArgs = append(uq.columnValuesArgs, arguments...)
 		return nil
 	})
 }
 
-func (upd *updater) Amend(fn func(string) string) Updater {
+func (upd *updater) Amend(fn func(string) string) db.Updater {
 	return upd.frame(func(uq *updaterQuery) error {
 		uq.amendFn = fn
 		return nil
@@ -154,21 +155,21 @@ func (upd *updater) Arguments() []interface{} {
 	return uq.arguments()
 }
 
-func (upd *updater) Where(terms ...interface{}) Updater {
+func (upd *updater) Where(terms ...interface{}) db.Updater {
 	return upd.frame(func(uq *updaterQuery) error {
 		uq.where, uq.whereArgs = &exql.Where{}, []interface{}{}
-		return uq.and(upd.SQLBuilder(), terms...)
+		return uq.and(upd.SQL(), terms...)
 	})
 }
 
-func (upd *updater) And(terms ...interface{}) Updater {
+func (upd *updater) And(terms ...interface{}) db.Updater {
 	return upd.frame(func(uq *updaterQuery) error {
-		return uq.and(upd.SQLBuilder(), terms...)
+		return uq.and(upd.SQL(), terms...)
 	})
 }
 
 func (upd *updater) Prepare() (*sql.Stmt, error) {
-	return upd.PrepareContext(upd.SQLBuilder().sess.Context())
+	return upd.PrepareContext(upd.SQL().sess.Context())
 }
 
 func (upd *updater) PrepareContext(ctx context.Context) (*sql.Stmt, error) {
@@ -176,11 +177,11 @@ func (upd *updater) PrepareContext(ctx context.Context) (*sql.Stmt, error) {
 	if err != nil {
 		return nil, err
 	}
-	return upd.SQLBuilder().sess.StatementPrepare(ctx, uq.statement())
+	return upd.SQL().sess.StatementPrepare(ctx, uq.statement())
 }
 
 func (upd *updater) Exec() (sql.Result, error) {
-	return upd.ExecContext(upd.SQLBuilder().sess.Context())
+	return upd.ExecContext(upd.SQL().sess.Context())
 }
 
 func (upd *updater) ExecContext(ctx context.Context) (sql.Result, error) {
@@ -188,10 +189,10 @@ func (upd *updater) ExecContext(ctx context.Context) (sql.Result, error) {
 	if err != nil {
 		return nil, err
 	}
-	return upd.SQLBuilder().sess.StatementExec(ctx, uq.statement(), uq.arguments()...)
+	return upd.SQL().sess.StatementExec(ctx, uq.statement(), uq.arguments()...)
 }
 
-func (upd *updater) Limit(limit int) Updater {
+func (upd *updater) Limit(limit int) db.Updater {
 	return upd.frame(func(uq *updaterQuery) error {
 		uq.limit = limit
 		return nil
diff --git a/sqlbuilder/wrapper.go b/internal/sqlbuilder/wrapper.go
similarity index 50%
rename from sqlbuilder/wrapper.go
rename to internal/sqlbuilder/wrapper.go
index 3c5ae59..a16c398 100644
--- a/sqlbuilder/wrapper.go
+++ b/internal/sqlbuilder/wrapper.go
@@ -22,7 +22,6 @@
 package sqlbuilder
 
 import (
-	"context"
 	"database/sql"
 
 	db "github.com/upper/db/v4"
@@ -40,73 +39,9 @@ type Tx interface {
 	// run on the same transaction.
 	db.Session
 
-	// All SQLBuilder methods are available on transaction sessions. They will
-	// run on the same transaction.
-	SQLBuilder
-
-	// db.Tx adds Commit and Rollback methods to the transaction.
-	db.Tx
-
-	// Context returns the context used as default for queries on this transaction.
-	// If no context has been set, a default context.Background() is returned.
-	Context() context.Context
-
-	// WithContext returns a copy of the transaction that uses the given context
-	// as default. Copies are safe to use concurrently but they're backed by the
-	// same *sql.Tx, so any copy may commit or rollback the parent transaction.
-	// WithContext(context.Context) Tx
-
-	// SetTxOptions sets the default TxOptions that is going to be used for new
-	// transactions created in the session.
-	SetTxOptions(sql.TxOptions)
-
-	// TxOptions returns the defaultx TxOptions.
-	TxOptions() *sql.TxOptions
-}
-
-// Session represents a SQL database.
-type Session interface {
-	// All db.Session methods are available on this session.
-	db.Session
-
-	// All SQLBuilder methods are available on this session.
-	SQLBuilder
-
-	// NewTx creates and returns a transaction that runs on the given context.
-	// If a nil context is given, then the transaction will use the session's
-	// default context.  The user is responsible for committing or rolling back
-	// the session.
-	NewTx(ctx context.Context) (Tx, error)
-
-	// Tx creates a transaction block on the default database context and passes
-	// it to the function fn. If fn returns no error the transaction is commited,
-	// else the transaction is rolled back. After being commited or rolled back
-	// the transaction is closed automatically.
-	Tx(fn func(sess db.Session) error) error
-
-	// TxContext creates a transaction block on the given context and passes it to
-	// the function fn. If fn returns no error the transaction is commited, else
-	// the transaction is rolled back. After being commited or rolled back the
-	// transaction is closed automatically.
-	TxContext(ctx context.Context, fn func(sess db.Session) error) error
-
-	// Context returns the context used as default for queries on this session
-	// and for new transactions.  If no context has been set, a default
-	// context.Background() is returned.
-	Context() context.Context
-
-	// WithContext returns a copy of the session that uses the given context as
-	// default. Copies are safe to use concurrently but they're backed by the
-	// same *sql.DB. You may close a copy at any point but that won't close the
-	// parent session.
-	WithContext(context.Context) Session
-
-	// SetTxOptions sets the default TxOptions that is going to be used for new
-	// transactions created in the session.
-	SetTxOptions(sql.TxOptions)
+	Commit() error
 
-	// TxOptions returns the defaultx TxOptions.
-	TxOptions() *sql.TxOptions
+	Rollback() error
 }
 
 // Adapter represents a SQL adapter.
@@ -118,7 +53,7 @@ type Adapter interface {
 	// This method is internally used by upper-db to create a builder backed by the
 	// given database.  You may want to use your adapter's New function instead of
 	// this one.
-	New(*sql.DB) (Session, error)
+	New(*sql.DB) (db.Session, error)
 
 	// NewTx wraps an active *sql.Tx transation and returns a SQLBuilder
 	// transaction.  The adapter needs to be imported to the blank namespace in
@@ -130,7 +65,7 @@ type Adapter interface {
 	NewTx(*sql.Tx) (Tx, error)
 
 	// Open opens a SQL database.
-	OpenDSN(db.ConnectionURL) (Session, error)
+	OpenDSN(db.ConnectionURL) (db.Session, error)
 }
 
 type dbAdapter struct {
diff --git a/session.go b/session.go
index 40fc3b1..113dc1c 100644
--- a/session.go
+++ b/session.go
@@ -21,6 +21,11 @@
 
 package db
 
+import (
+	"context"
+	"database/sql"
+)
+
 // Session is an interface that defines methods for database adapters.
 type Session interface {
 	// ConnectionURL returns the DSN that was used to set up the adapter.
@@ -62,5 +67,20 @@ type Session interface {
 	//  internalSQLDriver := sess.Driver().(*sql.DB)
 	Driver() interface{}
 
+	// SQL returns a special interface for SQL databases.
+	SQL() SQL
+
+	// Tx creates a transaction block on the default database context and passes
+	// it to the function fn. If fn returns no error the transaction is commited,
+	// else the transaction is rolled back. After being commited or rolled back
+	// the transaction is closed automatically.
+	Tx(fn func(sess Session) error) error
+
+	// TxContext creates a transaction block on the given context and passes it to
+	// the function fn. If fn returns no error the transaction is commited, else
+	// the transaction is rolled back. After being commited or rolled back the
+	// transaction is closed automatically.
+	TxContext(ctx context.Context, fn func(sess Session) error, opts *sql.TxOptions) error
+
 	Settings
 }
diff --git a/sql.go b/sql.go
new file mode 100644
index 0000000..4f227d3
--- /dev/null
+++ b/sql.go
@@ -0,0 +1,206 @@
+// Copyright (c) 2012-present 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 db
+
+import (
+	"context"
+	"database/sql"
+)
+
+// SQL defines methods that can be used to build a SQL query with chainable
+// method calls.
+//
+// Queries are immutable, so every call to any method will return a new
+// pointer, if you want to build a query using variables you need to reassign
+// them, like this:
+//
+//  a = builder.Select("name").From("foo") // "a" is created
+//
+//  a.Where(...) // No effect, the value returned from Where is ignored.
+//
+//  a = a.Where(...) // "a" is reassigned and points to a different address.
+//
+type SQL interface {
+
+	// Select initializes and returns a Selector, it accepts column names as
+	// parameters.
+	//
+	// The returned Selector does not initially point to any table, a call to
+	// From() is required after Select() to complete a valid query.
+	//
+	// Example:
+	//
+	//  q := sqlbuilder.Select("first_name", "last_name").From("people").Where(...)
+	Select(columns ...interface{}) Selector
+
+	// SelectFrom creates a Selector that selects all columns (like SELECT *)
+	// from the given table.
+	//
+	// Example:
+	//
+	//  q := sqlbuilder.SelectFrom("people").Where(...)
+	SelectFrom(table ...interface{}) Selector
+
+	// InsertInto prepares and returns an Inserter targeted at the given table.
+	//
+	// Example:
+	//
+	//   q := sqlbuilder.InsertInto("books").Columns(...).Values(...)
+	InsertInto(table string) Inserter
+
+	// DeleteFrom prepares a Deleter targeted at the given table.
+	//
+	// Example:
+	//
+	//  q := sqlbuilder.DeleteFrom("tasks").Where(...)
+	DeleteFrom(table string) Deleter
+
+	// Update prepares and returns an Updater targeted at the given table.
+	//
+	// Example:
+	//
+	//  q := sqlbuilder.Update("profile").Set(...).Where(...)
+	Update(table string) Updater
+
+	// Exec executes a SQL query that does not return any rows, like sql.Exec.
+	// Queries can be either strings or upper-db statements.
+	//
+	// Example:
+	//
+	//  sqlbuilder.Exec(`INSERT INTO books (title) VALUES("La Ciudad y los Perros")`)
+	Exec(query interface{}, args ...interface{}) (sql.Result, error)
+
+	// ExecContext executes a SQL query that does not return any rows, like sql.ExecContext.
+	// Queries can be either strings or upper-db statements.
+	//
+	// Example:
+	//
+	//  sqlbuilder.ExecContext(ctx, `INSERT INTO books (title) VALUES(?)`, "La Ciudad y los Perros")
+	ExecContext(ctx context.Context, query interface{}, args ...interface{}) (sql.Result, error)
+
+	// Prepare creates a prepared statement for later queries or executions. The
+	// caller must call the statement's Close method when the statement is no
+	// longer needed.
+	Prepare(query interface{}) (*sql.Stmt, error)
+
+	// Prepare creates a prepared statement on the guiven context for later
+	// queries or executions. The caller must call the statement's Close method
+	// when the statement is no longer needed.
+	PrepareContext(ctx context.Context, query interface{}) (*sql.Stmt, error)
+
+	// Query executes a SQL query that returns rows, like sql.Query.  Queries can
+	// be either strings or upper-db statements.
+	//
+	// Example:
+	//
+	//  sqlbuilder.Query(`SELECT * FROM people WHERE name = "Mateo"`)
+	Query(query interface{}, args ...interface{}) (*sql.Rows, error)
+
+	// QueryContext executes a SQL query that returns rows, like
+	// sql.QueryContext.  Queries can be either strings or upper-db statements.
+	//
+	// Example:
+	//
+	//  sqlbuilder.QueryContext(ctx, `SELECT * FROM people WHERE name = ?`, "Mateo")
+	QueryContext(ctx context.Context, query interface{}, args ...interface{}) (*sql.Rows, error)
+
+	// QueryRow executes a SQL query that returns one row, like sql.QueryRow.
+	// Queries can be either strings or upper-db statements.
+	//
+	// Example:
+	//
+	//  sqlbuilder.QueryRow(`SELECT * FROM people WHERE name = "Haruki" AND last_name = "Murakami" LIMIT 1`)
+	QueryRow(query interface{}, args ...interface{}) (*sql.Row, error)
+
+	// QueryRowContext executes a SQL query that returns one row, like
+	// sql.QueryRowContext.  Queries can be either strings or upper-db statements.
+	//
+	// Example:
+	//
+	//  sqlbuilder.QueryRowContext(ctx, `SELECT * FROM people WHERE name = "Haruki" AND last_name = "Murakami" LIMIT 1`)
+	QueryRowContext(ctx context.Context, query interface{}, args ...interface{}) (*sql.Row, error)
+
+	// Iterator executes a SQL query that returns rows and creates an Iterator
+	// with it.
+	//
+	// Example:
+	//
+	//  sqlbuilder.Iterator(`SELECT * FROM people WHERE name LIKE "M%"`)
+	Iterator(query interface{}, args ...interface{}) Iterator
+
+	// IteratorContext executes a SQL query that returns rows and creates an Iterator
+	// with it.
+	//
+	// Example:
+	//
+	//  sqlbuilder.IteratorContext(ctx, `SELECT * FROM people WHERE name LIKE "M%"`)
+	IteratorContext(ctx context.Context, query interface{}, args ...interface{}) Iterator
+}
+
+// SQLExecer provides methods for executing statements that do not return
+// results.
+type SQLExecer interface {
+	// Exec executes a statement and returns sql.Result.
+	Exec() (sql.Result, error)
+
+	// ExecContext executes a statement and returns sql.Result.
+	ExecContext(context.Context) (sql.Result, error)
+}
+
+// SQLPreparer provides the Prepare and PrepareContext methods for creating
+// prepared statements.
+type SQLPreparer interface {
+	// Prepare creates a prepared statement.
+	Prepare() (*sql.Stmt, error)
+
+	// PrepareContext creates a prepared statement.
+	PrepareContext(context.Context) (*sql.Stmt, error)
+}
+
+// SQLGetter provides methods for executing statements that return results.
+type SQLGetter interface {
+	// Query returns *sql.Rows.
+	Query() (*sql.Rows, error)
+
+	// QueryContext returns *sql.Rows.
+	QueryContext(context.Context) (*sql.Rows, error)
+
+	// QueryRow returns only one row.
+	QueryRow() (*sql.Row, error)
+
+	// QueryRowContext returns only one row.
+	QueryRowContext(ctx context.Context) (*sql.Row, error)
+}
+
+// SQLEngine represents a SQL engine that can execute SQL queries. This is
+// compatible with *sql.DB.
+type SQLEngine interface {
+	Exec(string, ...interface{}) (sql.Result, error)
+	Prepare(string) (*sql.Stmt, error)
+	Query(string, ...interface{}) (*sql.Rows, error)
+	QueryRow(string, ...interface{}) *sql.Row
+
+	ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
+	PrepareContext(context.Context, string) (*sql.Stmt, error)
+	QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
+	QueryRowContext(context.Context, string, ...interface{}) *sql.Row
+}
diff --git a/tx.go b/tx.go
deleted file mode 100644
index 39766bb..0000000
--- a/tx.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2012-present 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 db
-
-// Tx defines methods for transactions.
-type Tx interface {
-	// Rollback discards all the operations executed on the current transaction.
-	Rollback() error
-
-	// Commit commits the current transaction.
-	Commit() error
-}


Test patch:
diff --git a/adapter/cockroachdb/cockroachdb_test.go b/adapter/cockroachdb/cockroachdb_test.go
index 025c006..18b9b5e 100644
--- a/adapter/cockroachdb/cockroachdb_test.go
+++ b/adapter/cockroachdb/cockroachdb_test.go
@@ -38,7 +38,6 @@ import (
 	"github.com/stretchr/testify/suite"
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/testsuite"
-	"github.com/upper/db/v4/sqlbuilder"
 )
 
 type customJSONB struct {
@@ -135,11 +134,11 @@ func (s *AdapterTests) SetupSuite() {
 func (s *AdapterTests) SkipTest_Issue469_BadConnection() {
 	// Unsupported?
 
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	// Ask the CockroachDB server to disconnect sessions that remain inactive for
 	// more than 1 second.
-	_, err := sess.Exec(`SET statement_timeout=1000`)
+	_, err := sess.SQL().Exec(`SET statement_timeout=1000`)
 	s.NoError(err)
 
 	// Remain inactive for 2 seconds.
@@ -151,7 +150,7 @@ func (s *AdapterTests) SkipTest_Issue469_BadConnection() {
 
 	// This is a new session, ask the CockroachDB server to disconnect sessions that
 	// remain inactive for more than 1 second.
-	_, err = sess.Exec(`SET statement_timeout=1000`)
+	_, err = sess.SQL().Exec(`SET statement_timeout=1000`)
 	s.NoError(err)
 
 	// Remain inactive for 2 seconds.
@@ -172,7 +171,7 @@ func (s *AdapterTests) SkipTest_Issue469_BadConnection() {
 
 	// This is a new session, ask the CockroachDB server to disconnect sessions that
 	// remain inactive for more than 1 second.
-	_, err = sess.Exec(`SET statement_timeout=1000`)
+	_, err = sess.SQL().Exec(`SET statement_timeout=1000`)
 	s.NoError(err)
 
 	err = sess.Tx(func(sess db.Session) error {
@@ -200,7 +199,7 @@ func (s *AdapterTests) SkipTest_Issue469_BadConnection() {
 	s.Error(err, "Expecting an error (can't recover from this)")
 }
 
-func testPostgreSQLTypes(t *testing.T, sess sqlbuilder.Session) {
+func testPostgreSQLTypes(t *testing.T, sess db.Session) {
 	type PGTypeInline struct {
 		IntegerArrayPtr *Int64Array  `db:"integer_array_ptr,omitempty"`
 		StringArrayPtr  *StringArray `db:"string_array_ptr,omitempty"`
@@ -512,7 +511,7 @@ func testPostgreSQLTypes(t *testing.T, sess sqlbuilder.Session) {
 		}
 
 		for i := range pgTypeTests {
-			row, err := sess.InsertInto("pg_types").Values(pgTypeTests[i]).Returning("id").QueryRow()
+			row, err := sess.SQL().InsertInto("pg_types").Values(pgTypeTests[i]).Returning("id").QueryRow()
 			assert.NoError(t, err)
 
 			var id int64
@@ -529,12 +528,12 @@ func testPostgreSQLTypes(t *testing.T, sess sqlbuilder.Session) {
 			assert.Equal(t, expected, actual)
 
 			var actual2 PGType
-			err = sess.SelectFrom("pg_types").Where("id = ?", id).One(&actual2)
+			err = sess.SQL().SelectFrom("pg_types").Where("id = ?", id).One(&actual2)
 			assert.NoError(t, err)
 			assert.Equal(t, expected, actual2)
 		}
 
-		inserter := sess.InsertInto("pg_types")
+		inserter := sess.SQL().InsertInto("pg_types")
 		for i := range pgTypeTests {
 			inserter = inserter.Values(pgTypeTests[i])
 		}
@@ -544,7 +543,7 @@ func testPostgreSQLTypes(t *testing.T, sess sqlbuilder.Session) {
 		err = sess.Collection("pg_types").Truncate()
 		assert.NoError(t, err)
 
-		batch := sess.InsertInto("pg_types").Batch(50)
+		batch := sess.SQL().InsertInto("pg_types").Batch(50)
 		go func() {
 			defer batch.Done()
 			for i := range pgTypeTests {
@@ -556,7 +555,7 @@ func testPostgreSQLTypes(t *testing.T, sess sqlbuilder.Session) {
 		assert.NoError(t, err)
 
 		var values []PGType
-		err = sess.SelectFrom("pg_types").All(&values)
+		err = sess.SQL().SelectFrom("pg_types").All(&values)
 		assert.NoError(t, err)
 
 		for i := range values {
@@ -568,7 +567,7 @@ func testPostgreSQLTypes(t *testing.T, sess sqlbuilder.Session) {
 }
 
 func (s *AdapterTests) TestOptionTypes() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	optionTypes := sess.Collection("option_types")
 	err := optionTypes.Truncate()
@@ -733,7 +732,7 @@ func (s Settings) Value() (driver.Value, error) {
 }
 
 func (s *AdapterTests) TestOptionTypeJsonbStruct() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	optionTypes := sess.Collection("option_types")
 
@@ -772,7 +771,7 @@ func (s *AdapterTests) TestOptionTypeJsonbStruct() {
 
 func (s *AdapterTests) SkipTestSchemaCollection() {
 	// Unsupported?
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	col := sess.Collection("test_schema.test")
 	_, err := col.Insert(map[string]int{"id": 9})
@@ -786,7 +785,7 @@ func (s *AdapterTests) SkipTestSchemaCollection() {
 }
 
 func (s *AdapterTests) Test_Issue340_MaxOpenConns() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	sess.SetMaxOpenConns(5)
 
@@ -796,7 +795,7 @@ func (s *AdapterTests) Test_Issue340_MaxOpenConns() {
 		go func(i int) {
 			defer wg.Done()
 
-			_, err := sess.Exec(fmt.Sprintf(`SELECT pg_sleep(1.%d)`, i))
+			_, err := sess.SQL().Exec(fmt.Sprintf(`SELECT pg_sleep(1.%d)`, i))
 			if err != nil {
 				s.T().Errorf("%v", err)
 			}
@@ -809,7 +808,7 @@ func (s *AdapterTests) Test_Issue340_MaxOpenConns() {
 }
 
 func (s *AdapterTests) Test_Issue370_InsertUUID() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	{
 		type itemT struct {
@@ -904,7 +903,7 @@ func (s *AdapterTests) Test_Issue370_InsertUUID() {
 }
 
 func (s *AdapterTests) TestInsertVarcharPrimaryKey() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	{
 		type itemT struct {
@@ -937,33 +936,35 @@ func (s *AdapterTests) TestInsertVarcharPrimaryKey() {
 }
 
 func (s *AdapterTests) Test_Issue409_TxOptions() {
-	sess := s.SQLBuilder()
-
-	sess.SetTxOptions(sql.TxOptions{
-		ReadOnly: true,
-	})
+	sess := s.Session()
 
 	{
-		col := sess.Collection("publication")
+		err := sess.TxContext(context.Background(), func(tx db.Session) error {
+			col := tx.Collection("publication")
 
-		row := map[string]interface{}{
-			"title":     "foo",
-			"author_id": 1,
-		}
-		err := col.InsertReturning(&row)
-		s.Error(err)
+			row := map[string]interface{}{
+				"title":     "foo",
+				"author_id": 1,
+			}
+			err := col.InsertReturning(&row)
+			s.Error(err)
 
+			return err
+		}, &sql.TxOptions{
+			ReadOnly: true,
+		})
+		s.Error(err)
 		s.True(strings.Contains(err.Error(), "read-only transaction"))
 	}
 }
 
 func (s *AdapterTests) TestEscapeQuestionMark() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	var val bool
 
 	{
-		res, err := sess.QueryRow(`SELECT '{"mykey":["val1", "val2"]}'::jsonb->'mykey' ?? ?`, "val2")
+		res, err := sess.SQL().QueryRow(`SELECT '{"mykey":["val1", "val2"]}'::jsonb->'mykey' ?? ?`, "val2")
 		s.NoError(err)
 
 		err = res.Scan(&val)
@@ -972,7 +973,7 @@ func (s *AdapterTests) TestEscapeQuestionMark() {
 	}
 
 	{
-		res, err := sess.QueryRow(`SELECT ?::jsonb->'mykey' ?? ?`, `{"mykey":["val1", "val2"]}`, `val2`)
+		res, err := sess.SQL().QueryRow(`SELECT ?::jsonb->'mykey' ?? ?`, `{"mykey":["val1", "val2"]}`, `val2`)
 		s.NoError(err)
 
 		err = res.Scan(&val)
@@ -983,7 +984,7 @@ func (s *AdapterTests) TestEscapeQuestionMark() {
 	/*
 		// Unsupported
 		{
-			res, err := sess.QueryRow(`SELECT ?::jsonb->? ?? ?`, `{"mykey":["val1", "val2"]}`, `mykey`, `val2`)
+			res, err := sess.SQL().QueryRow(`SELECT ?::jsonb->? ?? ?`, `{"mykey":["val1", "val2"]}`, `mykey`, `val2`)
 			s.NoError(err)
 
 			err = res.Scan(&val)
@@ -994,7 +995,7 @@ func (s *AdapterTests) TestEscapeQuestionMark() {
 }
 
 func (s *AdapterTests) Test_Issue391_TextMode() {
-	testPostgreSQLTypes(s.T(), s.SQLBuilder())
+	testPostgreSQLTypes(s.T(), s.Session())
 }
 
 func (s *AdapterTests) Test_Issue391_BinaryMode() {
@@ -1020,7 +1021,7 @@ func (s *AdapterTests) Test_Issue391_BinaryMode() {
 }
 
 func (s *AdapterTests) TestStringAndInt64Array() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 	driver := sess.Driver().(*sql.DB)
 
 	defer func() {
@@ -1088,50 +1089,76 @@ func (s *AdapterTests) TestStringAndInt64Array() {
 }
 
 func (s *AdapterTests) Test_Issue210() {
-	list := []string{
-		`DROP TABLE IF EXISTS testing123`,
-		`CREATE TABLE IF NOT EXISTS testing123 (
+
+	var err error
+	sess := s.Session()
+
+	err = sess.Tx(func(tx db.Session) error {
+		var err error
+		sqlb := tx.SQL()
+
+		_, err = sqlb.Exec(`DROP TABLE IF EXISTS testing123`)
+		if err != nil {
+			return err
+		}
+
+		_, err = sqlb.Exec(`DROP TABLE IF EXISTS hello`)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	})
+	s.NoError(err)
+
+	err = sess.Tx(func(tx db.Session) error {
+		var err error
+		sqlb := tx.SQL()
+
+		_, err = sqlb.Exec(`CREATE TABLE IF NOT EXISTS testing123 (
 			ID INT PRIMARY KEY     NOT NULL,
 			NAME           TEXT    NOT NULL
-		)
-		`,
-		`DROP TABLE IF EXISTS hello`,
-		`CREATE TABLE IF NOT EXISTS hello (
+		)`)
+		if err != nil {
+			return err
+		}
+
+		_, err = sqlb.Exec(`CREATE TABLE IF NOT EXISTS hello (
 			ID INT PRIMARY KEY     NOT NULL,
 			NAME           TEXT    NOT NULL
-		)`,
-	}
-
-	sess := s.SQLBuilder()
+		)`)
+		if err != nil {
+			return err
+		}
 
-	tx, err := sess.NewTx(context.Background())
+		return nil
+	})
 	s.NoError(err)
 
-	for i := range list {
-		_, err = tx.Exec(list[i])
-		s.NoError(err)
-	}
-
-	err = tx.Commit()
-	s.NoError(err)
+	err = sess.Tx(func(tx db.Session) error {
+		var err error
+		_, err = tx.Collection("testing123").Find().Count()
+		if err != nil {
+			return err
+		}
 
-	/*
-		// This is probably fixed on newer versions?
-		_, err = sess.Collection("testing123").Find().Count()
-		s.NoError(err)
+		_, err = tx.Collection("hello").Find().Count()
+		if err != nil {
+			return err
+		}
 
-		_, err = sess.Collection("hello").Find().Count()
-		s.NoError(err)
-	*/
+		return nil
+	})
+	s.NoError(err)
 }
 
 func (s *AdapterTests) TestPreparedStatements() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	var val int
 
 	{
-		stmt, err := sess.Prepare(`SELECT 1`)
+		stmt, err := sess.SQL().Prepare(`SELECT 1`)
 		s.NoError(err)
 		s.NotNil(stmt)
 
@@ -1153,35 +1180,34 @@ func (s *AdapterTests) TestPreparedStatements() {
 	}
 
 	{
-		tx, err := sess.NewTx(context.Background())
-		s.NoError(err)
-
-		stmt, err := tx.Prepare(`SELECT 2`)
-		s.NoError(err)
-		s.NotNil(stmt)
+		err := sess.Tx(func(tx db.Session) error {
+			stmt, err := tx.SQL().Prepare(`SELECT 2`)
+			s.NoError(err)
+			s.NotNil(stmt)
 
-		q, err := stmt.Query()
-		s.NoError(err)
-		s.NotNil(q)
-		s.True(q.Next())
+			q, err := stmt.Query()
+			s.NoError(err)
+			s.NotNil(q)
+			s.True(q.Next())
 
-		err = q.Scan(&val)
-		s.NoError(err)
+			err = q.Scan(&val)
+			s.NoError(err)
 
-		err = q.Close()
-		s.NoError(err)
+			err = q.Close()
+			s.NoError(err)
 
-		s.Equal(2, val)
+			s.Equal(2, val)
 
-		err = stmt.Close()
-		s.NoError(err)
+			err = stmt.Close()
+			s.NoError(err)
 
-		err = tx.Commit()
+			return nil
+		})
 		s.NoError(err)
 	}
 
 	{
-		stmt, err := sess.Select(3).Prepare()
+		stmt, err := sess.SQL().Select(3).Prepare()
 		s.NoError(err)
 		s.NotNil(stmt)
 
@@ -1204,7 +1230,7 @@ func (s *AdapterTests) TestPreparedStatements() {
 }
 
 func (s *AdapterTests) TestNonTrivialSubqueries() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	// Creating test data
 	artist := sess.Collection("artist")
@@ -1218,9 +1244,9 @@ func (s *AdapterTests) TestNonTrivialSubqueries() {
 	}
 
 	{
-		q, err := sess.Query(`WITH test AS (?) ?`,
-			sess.Select("id AS foo").From("artist"),
-			sess.Select("foo").From("test").Where("foo > ?", 0),
+		q, err := sess.SQL().Query(`WITH test AS (?) ?`,
+			sess.SQL().Select("id AS foo").From("artist"),
+			sess.SQL().Select("foo").From("test").Where("foo > ?", 0),
 		)
 
 		s.NoError(err)
@@ -1236,9 +1262,9 @@ func (s *AdapterTests) TestNonTrivialSubqueries() {
 	}
 
 	{
-		row, err := sess.QueryRow(`WITH test AS (?) ?`,
-			sess.Select("id AS foo").From("artist"),
-			sess.Select("foo").From("test").Where("foo > ?", 0),
+		row, err := sess.SQL().QueryRow(`WITH test AS (?) ?`,
+			sess.SQL().Select("id AS foo").From("artist"),
+			sess.SQL().Select("foo").From("test").Where("foo > ?", 0),
 		)
 
 		s.NoError(err)
@@ -1253,9 +1279,9 @@ func (s *AdapterTests) TestNonTrivialSubqueries() {
 	/*
 		// Not supported
 		{
-			res, err := sess.Exec(
+			res, err := sess.SQL().Exec(
 				`UPDATE artist a1 SET id = ?`,
-				sess.Select(db.Raw("id + 5")).
+				sess.SQL().Select(db.Raw("id + 5")).
 					From("artist a2").
 					Where("a2.id = a1.id"),
 			)
@@ -1266,9 +1292,9 @@ func (s *AdapterTests) TestNonTrivialSubqueries() {
 	*/
 
 	{
-		q, err := sess.Query(db.Raw(`WITH test AS (?) ?`,
-			sess.Select("id AS foo").From("artist"),
-			sess.Select("foo").From("test").Where("foo > ?", 0).OrderBy("foo"),
+		q, err := sess.SQL().Query(db.Raw(`WITH test AS (?) ?`,
+			sess.SQL().Select("id AS foo").From("artist"),
+			sess.SQL().Select("foo").From("test").Where("foo > ?", 0).OrderBy("foo"),
 		))
 
 		s.NoError(err)
@@ -1286,8 +1312,8 @@ func (s *AdapterTests) TestNonTrivialSubqueries() {
 	/*
 		// Not supported
 		{
-			res, err := sess.Exec(db.Raw(`UPDATE artist a1 SET id = ?`,
-				sess.Select(db.Raw("id + 7")).From("artist a2").Where("a2.id = a1.id"),
+			res, err := sess.SQL().Exec(db.Raw(`UPDATE artist a1 SET id = ?`,
+				sess.SQL().Select(db.Raw("id + 7")).From("artist a2").Where("a2.id = a1.id"),
 			))
 
 			s.NoError(err)
diff --git a/adapter/cockroachdb/helper_test.go b/adapter/cockroachdb/helper_test.go
index 387b6be..bb77409 100644
--- a/adapter/cockroachdb/helper_test.go
+++ b/adapter/cockroachdb/helper_test.go
@@ -29,7 +29,6 @@ import (
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/sqladapter"
 	"github.com/upper/db/v4/internal/testsuite"
-	"github.com/upper/db/v4/sqlbuilder"
 )
 
 var settings = ConnectionURL{
@@ -44,10 +43,10 @@ var settings = ConnectionURL{
 }
 
 type Helper struct {
-	sess sqlbuilder.Session
+	sess db.Session
 }
 
-func cleanUp(sess sqlbuilder.Session) error {
+func cleanUp(sess db.Session) error {
 	if activeStatements := sqladapter.NumActiveStatements(); activeStatements > 128 {
 		return fmt.Errorf("Expecting active statements to be less than 128, got %d", activeStatements)
 	}
@@ -66,7 +65,7 @@ func cleanUp(sess sqlbuilder.Session) error {
 	return nil
 }
 
-func getStats(sess sqlbuilder.Session) (map[string]int, error) {
+func getStats(sess db.Session) (map[string]int, error) {
 	stats := make(map[string]int)
 
 	value := 0
@@ -90,10 +89,6 @@ func (h *Helper) Session() db.Session {
 	return h.sess
 }
 
-func (h *Helper) SQLBuilder() sqlbuilder.Session {
-	return h.sess
-}
-
 func (h *Helper) Adapter() string {
 	return Adapter
 }
diff --git a/adapter/cockroachdb/template_test.go b/adapter/cockroachdb/template_test.go
index 7428693..129449d 100644
--- a/adapter/cockroachdb/template_test.go
+++ b/adapter/cockroachdb/template_test.go
@@ -5,7 +5,7 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	db "github.com/upper/db/v4"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 func TestTemplateSelect(t *testing.T) {
diff --git a/adapter/mongo/helper_test.go b/adapter/mongo/helper_test.go
index a9151f7..15b3fdb 100644
--- a/adapter/mongo/helper_test.go
+++ b/adapter/mongo/helper_test.go
@@ -29,7 +29,6 @@ import (
 
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/testsuite"
-	"github.com/upper/db/v4/sqlbuilder"
 )
 
 var settings = ConnectionURL{
@@ -43,10 +42,6 @@ type Helper struct {
 	sess db.Session
 }
 
-func (h *Helper) SQLBuilder() sqlbuilder.Session {
-	panic("mongo adapter is incompatile with SQLBuilder interface")
-}
-
 func (h *Helper) Session() db.Session {
 	return h.sess
 }
diff --git a/adapter/mssql/helper_test.go b/adapter/mssql/helper_test.go
index 2a71849..b3016ca 100644
--- a/adapter/mssql/helper_test.go
+++ b/adapter/mssql/helper_test.go
@@ -27,7 +27,6 @@ import (
 
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/testsuite"
-	"github.com/upper/db/v4/sqlbuilder"
 )
 
 var settings = ConnectionURL{
@@ -39,17 +38,13 @@ var settings = ConnectionURL{
 }
 
 type Helper struct {
-	sess sqlbuilder.Session
+	sess db.Session
 }
 
 func (h *Helper) Session() db.Session {
 	return h.sess
 }
 
-func (h *Helper) SQLBuilder() sqlbuilder.Session {
-	return h.sess
-}
-
 func (h *Helper) Adapter() string {
 	return "mssql"
 }
diff --git a/adapter/mssql/template_test.go b/adapter/mssql/template_test.go
index 1a197c5..ccc1f0b 100644
--- a/adapter/mssql/template_test.go
+++ b/adapter/mssql/template_test.go
@@ -5,7 +5,7 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	db "github.com/upper/db/v4"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 func TestTemplateSelect(t *testing.T) {
diff --git a/adapter/mysql/helper_test.go b/adapter/mysql/helper_test.go
index c745f2c..fac4bbf 100644
--- a/adapter/mysql/helper_test.go
+++ b/adapter/mysql/helper_test.go
@@ -29,8 +29,8 @@ import (
 
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/sqladapter"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 	"github.com/upper/db/v4/internal/testsuite"
-	"github.com/upper/db/v4/sqlbuilder"
 )
 
 var settings = ConnectionURL{
@@ -48,10 +48,10 @@ var settings = ConnectionURL{
 }
 
 type Helper struct {
-	sess sqlbuilder.Session
+	sess db.Session
 }
 
-func cleanUp(sess sqlbuilder.Session) error {
+func cleanUp(sess db.Session) error {
 	if activeStatements := sqladapter.NumActiveStatements(); activeStatements > 128 {
 		return fmt.Errorf("Expecting active statements to be at most 128, got %d", activeStatements)
 	}
@@ -81,7 +81,7 @@ func cleanUp(sess sqlbuilder.Session) error {
 	return err
 }
 
-func getStats(sess sqlbuilder.Session) (map[string]int, error) {
+func getStats(sess db.Session) (map[string]int, error) {
 	stats := make(map[string]int)
 
 	res, err := sess.Driver().(*sql.DB).Query(`SHOW GLOBAL STATUS LIKE '%stmt%'`)
@@ -105,10 +105,6 @@ func (h *Helper) Session() db.Session {
 	return h.sess
 }
 
-func (h *Helper) SQLBuilder() sqlbuilder.Session {
-	return h.sess
-}
-
 func (h *Helper) Adapter() string {
 	return "mysql"
 }
diff --git a/adapter/mysql/mysql_test.go b/adapter/mysql/mysql_test.go
index 4204c25..fd40dbb 100644
--- a/adapter/mysql/mysql_test.go
+++ b/adapter/mysql/mysql_test.go
@@ -95,7 +95,7 @@ func (s *AdapterTests) SetupSuite() {
 }
 
 func (s *AdapterTests) TestInsertReturningCompositeKey_Issue383() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	type Admin struct {
 		ID            int       `db:"ID,omitempty"`
@@ -138,11 +138,11 @@ func (s *AdapterTests) TestInsertReturningCompositeKey_Issue383() {
 
 func (s *AdapterTests) TestIssue469_BadConnection() {
 	var err error
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	// Ask the MySQL server to disconnect sessions that remain inactive for more
 	// than 1 second.
-	_, err = sess.Exec(`SET SESSION wait_timeout=1`)
+	_, err = sess.SQL().Exec(`SET SESSION wait_timeout=1`)
 	s.NoError(err)
 
 	// Remain inactive for 2 seconds.
@@ -154,7 +154,7 @@ func (s *AdapterTests) TestIssue469_BadConnection() {
 
 	// This is a new session, ask the MySQL server to disconnect sessions that
 	// remain inactive for more than 1 second.
-	_, err = sess.Exec(`SET SESSION wait_timeout=1`)
+	_, err = sess.SQL().Exec(`SET SESSION wait_timeout=1`)
 	s.NoError(err)
 
 	// Remain inactive for 2 seconds.
@@ -175,7 +175,7 @@ func (s *AdapterTests) TestIssue469_BadConnection() {
 
 	// This is a new session, ask the MySQL server to disconnect sessions that
 	// remain inactive for more than 1 second.
-	_, err = sess.Exec(`SET SESSION wait_timeout=1`)
+	_, err = sess.SQL().Exec(`SET SESSION wait_timeout=1`)
 	s.NoError(err)
 
 	err = sess.Tx(func(sess db.Session) error {
@@ -204,7 +204,7 @@ func (s *AdapterTests) TestIssue469_BadConnection() {
 }
 
 func (s *AdapterTests) TestMySQLTypes() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	type MyType struct {
 		ID int64 `db:"id,omitempty"`
@@ -339,7 +339,7 @@ func (s *AdapterTests) TestMySQLTypes() {
 		}
 
 		for i := range myTypeTests {
-			res, err := sess.InsertInto("my_types").Values(myTypeTests[i]).Exec()
+			res, err := sess.SQL().InsertInto("my_types").Values(myTypeTests[i]).Exec()
 			s.NoError(err)
 
 			id, err := res.LastInsertId()
@@ -356,12 +356,12 @@ func (s *AdapterTests) TestMySQLTypes() {
 			s.Equal(expected, actual)
 
 			var actual2 MyType
-			err = sess.SelectFrom("my_types").Where("id = ?", id).One(&actual2)
+			err = sess.SQL().SelectFrom("my_types").Where("id = ?", id).One(&actual2)
 			s.NoError(err)
 			s.Equal(expected, actual2)
 		}
 
-		inserter := sess.InsertInto("my_types")
+		inserter := sess.SQL().InsertInto("my_types")
 		for i := range myTypeTests {
 			inserter = inserter.Values(myTypeTests[i])
 		}
@@ -371,7 +371,7 @@ func (s *AdapterTests) TestMySQLTypes() {
 		err = sess.Collection("my_types").Truncate()
 		s.NoError(err)
 
-		batch := sess.InsertInto("my_types").Batch(50)
+		batch := sess.SQL().InsertInto("my_types").Batch(50)
 		go func() {
 			defer batch.Done()
 			for i := range myTypeTests {
@@ -383,7 +383,7 @@ func (s *AdapterTests) TestMySQLTypes() {
 		s.NoError(err)
 
 		var values []MyType
-		err = sess.SelectFrom("my_types").All(&values)
+		err = sess.SQL().SelectFrom("my_types").All(&values)
 		s.NoError(err)
 
 		for i := range values {
diff --git a/adapter/mysql/template_test.go b/adapter/mysql/template_test.go
index ca56aef..3495093 100644
--- a/adapter/mysql/template_test.go
+++ b/adapter/mysql/template_test.go
@@ -5,7 +5,7 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	db "github.com/upper/db/v4"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 func TestTemplateSelect(t *testing.T) {
diff --git a/adapter/postgresql/helper_test.go b/adapter/postgresql/helper_test.go
index 44e823b..0fdbb45 100644
--- a/adapter/postgresql/helper_test.go
+++ b/adapter/postgresql/helper_test.go
@@ -29,7 +29,6 @@ import (
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/sqladapter"
 	"github.com/upper/db/v4/internal/testsuite"
-	"github.com/upper/db/v4/sqlbuilder"
 )
 
 var settings = ConnectionURL{
@@ -43,10 +42,10 @@ var settings = ConnectionURL{
 }
 
 type Helper struct {
-	sess sqlbuilder.Session
+	sess db.Session
 }
 
-func cleanUp(sess sqlbuilder.Session) error {
+func cleanUp(sess db.Session) error {
 	if activeStatements := sqladapter.NumActiveStatements(); activeStatements > 128 {
 		return fmt.Errorf("Expecting active statements to be less than 128, got %d", activeStatements)
 	}
@@ -65,7 +64,7 @@ func cleanUp(sess sqlbuilder.Session) error {
 	return nil
 }
 
-func getStats(sess sqlbuilder.Session) (map[string]int, error) {
+func getStats(sess db.Session) (map[string]int, error) {
 	stats := make(map[string]int)
 
 	row := sess.Driver().(*sql.DB).QueryRow(`SELECT count(1) AS value FROM pg_prepared_statements`)
@@ -85,10 +84,6 @@ func (h *Helper) Session() db.Session {
 	return h.sess
 }
 
-func (h *Helper) SQLBuilder() sqlbuilder.Session {
-	return h.sess
-}
-
 func (h *Helper) Adapter() string {
 	return Adapter
 }
diff --git a/adapter/postgresql/postgresql_test.go b/adapter/postgresql/postgresql_test.go
index 56f33ce..a6f82b8 100644
--- a/adapter/postgresql/postgresql_test.go
+++ b/adapter/postgresql/postgresql_test.go
@@ -38,7 +38,6 @@ import (
 	"github.com/stretchr/testify/suite"
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/testsuite"
-	"github.com/upper/db/v4/sqlbuilder"
 )
 
 type customJSONB struct {
@@ -133,11 +132,11 @@ func (s *AdapterTests) SetupSuite() {
 }
 
 func (s *AdapterTests) Test_Issue469_BadConnection() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	// Ask the PostgreSQL server to disconnect sessions that remain inactive for more
 	// than 1 second.
-	_, err := sess.Exec(`SET SESSION idle_in_transaction_session_timeout=1000`)
+	_, err := sess.SQL().Exec(`SET SESSION idle_in_transaction_session_timeout=1000`)
 	s.NoError(err)
 
 	// Remain inactive for 2 seconds.
@@ -149,7 +148,7 @@ func (s *AdapterTests) Test_Issue469_BadConnection() {
 
 	// This is a new session, ask the PostgreSQL server to disconnect sessions that
 	// remain inactive for more than 1 second.
-	_, err = sess.Exec(`SET SESSION idle_in_transaction_session_timeout=1000`)
+	_, err = sess.SQL().Exec(`SET SESSION idle_in_transaction_session_timeout=1000`)
 	s.NoError(err)
 
 	// Remain inactive for 2 seconds.
@@ -170,7 +169,7 @@ func (s *AdapterTests) Test_Issue469_BadConnection() {
 
 	// This is a new session, ask the PostgreSQL server to disconnect sessions that
 	// remain inactive for more than 1 second.
-	_, err = sess.Exec(`SET SESSION idle_in_transaction_session_timeout=1000`)
+	_, err = sess.SQL().Exec(`SET SESSION idle_in_transaction_session_timeout=1000`)
 	s.NoError(err)
 
 	err = sess.Tx(func(sess db.Session) error {
@@ -198,7 +197,7 @@ func (s *AdapterTests) Test_Issue469_BadConnection() {
 	s.Error(err, "Expecting an error (can't recover from this)")
 }
 
-func testPostgreSQLTypes(t *testing.T, sess sqlbuilder.Session) {
+func testPostgreSQLTypes(t *testing.T, sess db.Session) {
 	type PGTypeInline struct {
 		IntegerArrayPtr *Int64Array  `db:"integer_array_ptr,omitempty"`
 		StringArrayPtr  *StringArray `db:"string_array_ptr,omitempty"`
@@ -511,7 +510,7 @@ func testPostgreSQLTypes(t *testing.T, sess sqlbuilder.Session) {
 		}
 
 		for i := range pgTypeTests {
-			row, err := sess.InsertInto("pg_types").Values(pgTypeTests[i]).Returning("id").QueryRow()
+			row, err := sess.SQL().InsertInto("pg_types").Values(pgTypeTests[i]).Returning("id").QueryRow()
 			assert.NoError(t, err)
 
 			var id int64
@@ -528,12 +527,12 @@ func testPostgreSQLTypes(t *testing.T, sess sqlbuilder.Session) {
 			assert.Equal(t, expected, actual)
 
 			var actual2 PGType
-			err = sess.SelectFrom("pg_types").Where("id = ?", id).One(&actual2)
+			err = sess.SQL().SelectFrom("pg_types").Where("id = ?", id).One(&actual2)
 			assert.NoError(t, err)
 			assert.Equal(t, expected, actual2)
 		}
 
-		inserter := sess.InsertInto("pg_types")
+		inserter := sess.SQL().InsertInto("pg_types")
 		for i := range pgTypeTests {
 			inserter = inserter.Values(pgTypeTests[i])
 		}
@@ -543,7 +542,7 @@ func testPostgreSQLTypes(t *testing.T, sess sqlbuilder.Session) {
 		err = sess.Collection("pg_types").Truncate()
 		assert.NoError(t, err)
 
-		batch := sess.InsertInto("pg_types").Batch(50)
+		batch := sess.SQL().InsertInto("pg_types").Batch(50)
 		go func() {
 			defer batch.Done()
 			for i := range pgTypeTests {
@@ -555,7 +554,7 @@ func testPostgreSQLTypes(t *testing.T, sess sqlbuilder.Session) {
 		assert.NoError(t, err)
 
 		var values []PGType
-		err = sess.SelectFrom("pg_types").All(&values)
+		err = sess.SQL().SelectFrom("pg_types").All(&values)
 		assert.NoError(t, err)
 
 		for i := range values {
@@ -567,7 +566,7 @@ func testPostgreSQLTypes(t *testing.T, sess sqlbuilder.Session) {
 }
 
 func (s *AdapterTests) TestOptionTypes() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	optionTypes := sess.Collection("option_types")
 	err := optionTypes.Truncate()
@@ -732,7 +731,7 @@ func (s Settings) Value() (driver.Value, error) {
 }
 
 func (s *AdapterTests) TestOptionTypeJsonbStruct() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	optionTypes := sess.Collection("option_types")
 
@@ -770,7 +769,7 @@ func (s *AdapterTests) TestOptionTypeJsonbStruct() {
 }
 
 func (s *AdapterTests) TestSchemaCollection() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	col := sess.Collection("test_schema.test")
 	_, err := col.Insert(map[string]int{"id": 9})
@@ -784,7 +783,7 @@ func (s *AdapterTests) TestSchemaCollection() {
 }
 
 func (s *AdapterTests) Test_Issue340_MaxOpenConns() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	sess.SetMaxOpenConns(5)
 
@@ -794,7 +793,7 @@ func (s *AdapterTests) Test_Issue340_MaxOpenConns() {
 		go func(i int) {
 			defer wg.Done()
 
-			_, err := sess.Exec(fmt.Sprintf(`SELECT pg_sleep(1.%d)`, i))
+			_, err := sess.SQL().Exec(fmt.Sprintf(`SELECT pg_sleep(1.%d)`, i))
 			if err != nil {
 				s.T().Errorf("%v", err)
 			}
@@ -807,7 +806,7 @@ func (s *AdapterTests) Test_Issue340_MaxOpenConns() {
 }
 
 func (s *AdapterTests) Test_Issue370_InsertUUID() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	{
 		type itemT struct {
@@ -900,7 +899,7 @@ func (s *AdapterTests) Test_Issue370_InsertUUID() {
 }
 
 func (s *AdapterTests) TestInsertVarcharPrimaryKey() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	{
 		type itemT struct {
@@ -933,33 +932,35 @@ func (s *AdapterTests) TestInsertVarcharPrimaryKey() {
 }
 
 func (s *AdapterTests) Test_Issue409_TxOptions() {
-	sess := s.SQLBuilder()
-
-	sess.SetTxOptions(sql.TxOptions{
-		ReadOnly: true,
-	})
+	sess := s.Session()
 
 	{
-		col := sess.Collection("publication")
+		err := sess.TxContext(context.Background(), func(tx db.Session) error {
+			col := tx.Collection("publication")
 
-		row := map[string]interface{}{
-			"title":     "foo",
-			"author_id": 1,
-		}
-		err := col.InsertReturning(&row)
-		s.Error(err)
+			row := map[string]interface{}{
+				"title":     "foo",
+				"author_id": 1,
+			}
+			err := col.InsertReturning(&row)
+			s.Error(err)
 
+			return err
+		}, &sql.TxOptions{
+			ReadOnly: true,
+		})
+		s.Error(err)
 		s.True(strings.Contains(err.Error(), "read-only transaction"))
 	}
 }
 
 func (s *AdapterTests) TestEscapeQuestionMark() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	var val bool
 
 	{
-		res, err := sess.QueryRow(`SELECT '{"mykey":["val1", "val2"]}'::jsonb->'mykey' ?? ?`, "val2")
+		res, err := sess.SQL().QueryRow(`SELECT '{"mykey":["val1", "val2"]}'::jsonb->'mykey' ?? ?`, "val2")
 		s.NoError(err)
 
 		err = res.Scan(&val)
@@ -968,7 +969,7 @@ func (s *AdapterTests) TestEscapeQuestionMark() {
 	}
 
 	{
-		res, err := sess.QueryRow(`SELECT ?::jsonb->'mykey' ?? ?`, `{"mykey":["val1", "val2"]}`, `val2`)
+		res, err := sess.SQL().QueryRow(`SELECT ?::jsonb->'mykey' ?? ?`, `{"mykey":["val1", "val2"]}`, `val2`)
 		s.NoError(err)
 
 		err = res.Scan(&val)
@@ -977,7 +978,7 @@ func (s *AdapterTests) TestEscapeQuestionMark() {
 	}
 
 	{
-		res, err := sess.QueryRow(`SELECT ?::jsonb->? ?? ?`, `{"mykey":["val1", "val2"]}`, `mykey`, `val2`)
+		res, err := sess.SQL().QueryRow(`SELECT ?::jsonb->? ?? ?`, `{"mykey":["val1", "val2"]}`, `mykey`, `val2`)
 		s.NoError(err)
 
 		err = res.Scan(&val)
@@ -987,7 +988,7 @@ func (s *AdapterTests) TestEscapeQuestionMark() {
 }
 
 func (s *AdapterTests) Test_Issue391_TextMode() {
-	testPostgreSQLTypes(s.T(), s.SQLBuilder())
+	testPostgreSQLTypes(s.T(), s.Session())
 }
 
 func (s *AdapterTests) Test_Issue391_BinaryMode() {
@@ -1012,7 +1013,7 @@ func (s *AdapterTests) Test_Issue391_BinaryMode() {
 }
 
 func (s *AdapterTests) TestStringAndInt64Array() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 	driver := sess.Driver().(*sql.DB)
 
 	defer func() {
@@ -1094,17 +1095,18 @@ func (s *AdapterTests) Test_Issue210() {
 		)`,
 	}
 
-	sess := s.SQLBuilder()
-
-	tx, err := sess.NewTx(context.Background())
-	s.NoError(err)
+	sess := s.Session()
 
-	for i := range list {
-		_, err = tx.Exec(list[i])
-		s.NoError(err)
-	}
-
-	err = tx.Commit()
+	err := sess.Tx(func(tx db.Session) error {
+		for i := range list {
+			_, err := tx.SQL().Exec(list[i])
+			s.NoError(err)
+			if err != nil {
+				return err
+			}
+		}
+		return nil
+	})
 	s.NoError(err)
 
 	_, err = sess.Collection("testing123").Find().Count()
@@ -1115,12 +1117,12 @@ func (s *AdapterTests) Test_Issue210() {
 }
 
 func (s *AdapterTests) TestPreparedStatements() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	var val int
 
 	{
-		stmt, err := sess.Prepare(`SELECT 1`)
+		stmt, err := sess.SQL().Prepare(`SELECT 1`)
 		s.NoError(err)
 		s.NotNil(stmt)
 
@@ -1142,35 +1144,34 @@ func (s *AdapterTests) TestPreparedStatements() {
 	}
 
 	{
-		tx, err := sess.NewTx(context.Background())
-		s.NoError(err)
+		err := sess.Tx(func(tx db.Session) error {
+			stmt, err := tx.SQL().Prepare(`SELECT 2`)
+			s.NoError(err)
+			s.NotNil(stmt)
 
-		stmt, err := tx.Prepare(`SELECT 2`)
-		s.NoError(err)
-		s.NotNil(stmt)
+			q, err := stmt.Query()
+			s.NoError(err)
+			s.NotNil(q)
+			s.True(q.Next())
 
-		q, err := stmt.Query()
-		s.NoError(err)
-		s.NotNil(q)
-		s.True(q.Next())
+			err = q.Scan(&val)
+			s.NoError(err)
 
-		err = q.Scan(&val)
-		s.NoError(err)
-
-		err = q.Close()
-		s.NoError(err)
+			err = q.Close()
+			s.NoError(err)
 
-		s.Equal(2, val)
+			s.Equal(2, val)
 
-		err = stmt.Close()
-		s.NoError(err)
+			err = stmt.Close()
+			s.NoError(err)
 
-		err = tx.Commit()
+			return nil
+		})
 		s.NoError(err)
 	}
 
 	{
-		stmt, err := sess.Select(3).Prepare()
+		stmt, err := sess.SQL().Select(3).Prepare()
 		s.NoError(err)
 		s.NotNil(stmt)
 
@@ -1193,7 +1194,7 @@ func (s *AdapterTests) TestPreparedStatements() {
 }
 
 func (s *AdapterTests) TestNonTrivialSubqueries() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	// Creating test data
 	artist := sess.Collection("artist")
@@ -1207,9 +1208,9 @@ func (s *AdapterTests) TestNonTrivialSubqueries() {
 	}
 
 	{
-		q, err := sess.Query(`WITH test AS (?) ?`,
-			sess.Select("id AS foo").From("artist"),
-			sess.Select("foo").From("test").Where("foo > ?", 0),
+		q, err := sess.SQL().Query(`WITH test AS (?) ?`,
+			sess.SQL().Select("id AS foo").From("artist"),
+			sess.SQL().Select("foo").From("test").Where("foo > ?", 0),
 		)
 
 		s.NoError(err)
@@ -1225,9 +1226,10 @@ func (s *AdapterTests) TestNonTrivialSubqueries() {
 	}
 
 	{
-		row, err := sess.QueryRow(`WITH test AS (?) ?`,
-			sess.Select("id AS foo").From("artist"),
-			sess.Select("foo").From("test").Where("foo > ?", 0),
+		builder := sess.SQL()
+		row, err := builder.QueryRow(`WITH test AS (?) ?`,
+			builder.Select("id AS foo").From("artist"),
+			builder.Select("foo").From("test").Where("foo > ?", 0),
 		)
 
 		s.NoError(err)
@@ -1240,9 +1242,9 @@ func (s *AdapterTests) TestNonTrivialSubqueries() {
 	}
 
 	{
-		res, err := sess.Exec(
+		res, err := sess.SQL().Exec(
 			`UPDATE artist a1 SET id = ?`,
-			sess.Select(db.Raw("id + 5")).
+			sess.SQL().Select(db.Raw("id + 5")).
 				From("artist a2").
 				Where("a2.id = a1.id"),
 		)
@@ -1252,9 +1254,11 @@ func (s *AdapterTests) TestNonTrivialSubqueries() {
 	}
 
 	{
-		q, err := sess.Query(db.Raw(`WITH test AS (?) ?`,
-			sess.Select("id AS foo").From("artist"),
-			sess.Select("foo").From("test").Where("foo > ?", 0).OrderBy("foo"),
+		builder := sess.SQL()
+
+		q, err := builder.Query(db.Raw(`WITH test AS (?) ?`,
+			builder.Select("id AS foo").From("artist"),
+			builder.Select("foo").From("test").Where("foo > ?", 0).OrderBy("foo"),
 		))
 
 		s.NoError(err)
@@ -1270,8 +1274,8 @@ func (s *AdapterTests) TestNonTrivialSubqueries() {
 	}
 
 	{
-		res, err := sess.Exec(db.Raw(`UPDATE artist a1 SET id = ?`,
-			sess.Select(db.Raw("id + 7")).From("artist a2").Where("a2.id = a1.id"),
+		res, err := sess.SQL().Exec(db.Raw(`UPDATE artist a1 SET id = ?`,
+			sess.SQL().Select(db.Raw("id + 7")).From("artist a2").Where("a2.id = a1.id"),
 		))
 
 		s.NoError(err)
diff --git a/adapter/postgresql/template_test.go b/adapter/postgresql/template_test.go
index 72876ef..22ddbae 100644
--- a/adapter/postgresql/template_test.go
+++ b/adapter/postgresql/template_test.go
@@ -5,7 +5,7 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	db "github.com/upper/db/v4"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 func TestTemplateSelect(t *testing.T) {
diff --git a/adapter/ql/helper_test.go b/adapter/ql/helper_test.go
index 9811682..37896e6 100644
--- a/adapter/ql/helper_test.go
+++ b/adapter/ql/helper_test.go
@@ -27,7 +27,6 @@ import (
 
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/testsuite"
-	"github.com/upper/db/v4/sqlbuilder"
 )
 
 var settings = ConnectionURL{
@@ -35,17 +34,13 @@ var settings = ConnectionURL{
 }
 
 type Helper struct {
-	sess sqlbuilder.Session
+	sess db.Session
 }
 
 func (h *Helper) Session() db.Session {
 	return h.sess
 }
 
-func (h *Helper) SQLBuilder() sqlbuilder.Session {
-	return h.sess
-}
-
 func (h *Helper) Adapter() string {
 	return "ql"
 }
diff --git a/adapter/ql/template_test.go b/adapter/ql/template_test.go
index 803385c..be28b9b 100644
--- a/adapter/ql/template_test.go
+++ b/adapter/ql/template_test.go
@@ -5,7 +5,7 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	db "github.com/upper/db/v4"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 func TestTemplateSelect(t *testing.T) {
diff --git a/adapter/sqlite/helper_test.go b/adapter/sqlite/helper_test.go
index 7153850..7c3de1a 100644
--- a/adapter/sqlite/helper_test.go
+++ b/adapter/sqlite/helper_test.go
@@ -27,7 +27,6 @@ import (
 
 	db "github.com/upper/db/v4"
 	"github.com/upper/db/v4/internal/testsuite"
-	"github.com/upper/db/v4/sqlbuilder"
 )
 
 var settings = ConnectionURL{
@@ -35,17 +34,13 @@ var settings = ConnectionURL{
 }
 
 type Helper struct {
-	sess sqlbuilder.Session
+	sess db.Session
 }
 
 func (h *Helper) Session() db.Session {
 	return h.sess
 }
 
-func (h *Helper) SQLBuilder() sqlbuilder.Session {
-	return h.sess
-}
-
 func (h *Helper) Adapter() string {
 	return "sqlite"
 }
diff --git a/adapter/sqlite/template_test.go b/adapter/sqlite/template_test.go
index 8711567..d9089fd 100644
--- a/adapter/sqlite/template_test.go
+++ b/adapter/sqlite/template_test.go
@@ -5,7 +5,7 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	db "github.com/upper/db/v4"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 func TestTemplateSelect(t *testing.T) {
diff --git a/internal/testsuite/record_suite.go b/internal/testsuite/record_suite.go
index 5fb3a60..20b2932 100644
--- a/internal/testsuite/record_suite.go
+++ b/internal/testsuite/record_suite.go
@@ -7,7 +7,7 @@ import (
 
 	"github.com/stretchr/testify/suite"
 	"github.com/upper/db/v4"
-	"github.com/upper/db/v4/sqlbuilder"
+	"github.com/upper/db/v4/internal/sqlbuilder"
 )
 
 func Accounts(sess db.Session) db.Collection {
@@ -235,7 +235,7 @@ func (s *RecordTestSuite) TestSelectOnlyIDs() {
 }
 
 func (s *RecordTestSuite) TestTx() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	user := User{Username: "peter"}
 	err := sess.Save(&user)
diff --git a/internal/testsuite/sql_suite.go b/internal/testsuite/sql_suite.go
index 5cc7a37..d0ffc17 100644
--- a/internal/testsuite/sql_suite.go
+++ b/internal/testsuite/sql_suite.go
@@ -1,8 +1,8 @@
 package testsuite
 
 import (
-	"context"
 	"database/sql"
+	"errors"
 	"fmt"
 	"math/rand"
 	"strconv"
@@ -72,7 +72,7 @@ func (s *SQLTestSuite) BeforeTest(suiteName, testName string) {
 	err := s.TearUp()
 	s.NoError(err)
 
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	// Creating test data
 	artist := sess.Collection("artist")
@@ -87,7 +87,7 @@ func (s *SQLTestSuite) BeforeTest(suiteName, testName string) {
 }
 
 func (s *SQLTestSuite) TestPreparedStatementsCache() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	sess.SetPreparedStatementCache(true)
 	defer sess.SetPreparedStatementCache(false)
@@ -172,7 +172,7 @@ func (s *SQLTestSuite) TestPreparedStatementsCache() {
 }
 
 func (s *SQLTestSuite) TestTruncateAllCollections() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	collections, err := sess.Collections()
 	s.NoError(err)
@@ -198,7 +198,7 @@ func (s *SQLTestSuite) TestQueryLogger() {
 		db.Log().SetLevel(logLevel)
 	}()
 
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	_, err := sess.Collection("artist").Find().Count()
 	s.Equal(nil, err)
@@ -208,7 +208,7 @@ func (s *SQLTestSuite) TestQueryLogger() {
 }
 
 func (s *SQLTestSuite) TestExpectCursorError() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	artist := sess.Collection("artist")
 
@@ -227,7 +227,7 @@ func (s *SQLTestSuite) TestInsertDefault() {
 		s.T().Skip("Currently not supported.")
 	}
 
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	artist := sess.Collection("artist")
 
@@ -247,7 +247,7 @@ func (s *SQLTestSuite) TestInsertDefault() {
 }
 
 func (s *SQLTestSuite) TestInsertReturning() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	artist := sess.Collection("artist")
 
@@ -305,78 +305,75 @@ func (s *SQLTestSuite) TestInsertReturning() {
 }
 
 func (s *SQLTestSuite) TestInsertReturningWithinTransaction() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	err := sess.Collection("artist").Truncate()
 	s.NoError(err)
 
-	tx, err := sess.NewTx(context.Background())
-	s.NoError(err)
-	defer tx.Close()
-
-	artist := tx.Collection("artist")
-
-	itemMap := map[string]string{
-		"name": "Ozzie",
-	}
-	s.Zero(itemMap["id"], "Must be zero before inserting")
-	err = artist.InsertReturning(&itemMap)
-	s.NoError(err)
-	s.NotZero(itemMap["id"], "Must not be zero after inserting")
+	err = sess.Tx(func(tx db.Session) error {
+		artist := tx.Collection("artist")
 
-	itemStruct := struct {
-		ID   int    `db:"id,omitempty"`
-		Name string `db:"name"`
-	}{
-		0,
-		"Flea",
-	}
-	s.Zero(itemStruct.ID, "Must be zero before inserting")
-	err = artist.InsertReturning(&itemStruct)
-	s.NoError(err)
-	s.NotZero(itemStruct.ID, "Must not be zero after inserting")
-
-	count, err := artist.Find().Count()
-	s.NoError(err)
-	s.Equal(uint64(2), count, "Expecting 2 elements")
+		itemMap := map[string]string{
+			"name": "Ozzie",
+		}
+		s.Zero(itemMap["id"], "Must be zero before inserting")
+		err = artist.InsertReturning(&itemMap)
+		s.NoError(err)
+		s.NotZero(itemMap["id"], "Must not be zero after inserting")
+
+		itemStruct := struct {
+			ID   int    `db:"id,omitempty"`
+			Name string `db:"name"`
+		}{
+			0,
+			"Flea",
+		}
+		s.Zero(itemStruct.ID, "Must be zero before inserting")
+		err = artist.InsertReturning(&itemStruct)
+		s.NoError(err)
+		s.NotZero(itemStruct.ID, "Must not be zero after inserting")
 
-	itemStruct2 := struct {
-		ID   int    `db:"id,omitempty"`
-		Name string `db:"name"`
-	}{
-		0,
-		"Slash",
-	}
-	s.Zero(itemStruct2.ID, "Must be zero before inserting")
-	err = artist.InsertReturning(itemStruct2)
-	s.Error(err, "Should not happen, using a pointer should be enforced")
-	s.Zero(itemStruct2.ID, "Must still be zero because there was no insertion")
+		count, err := artist.Find().Count()
+		s.NoError(err)
+		s.Equal(uint64(2), count, "Expecting 2 elements")
+
+		itemStruct2 := struct {
+			ID   int    `db:"id,omitempty"`
+			Name string `db:"name"`
+		}{
+			0,
+			"Slash",
+		}
+		s.Zero(itemStruct2.ID, "Must be zero before inserting")
+		err = artist.InsertReturning(itemStruct2)
+		s.Error(err, "Should not happen, using a pointer should be enforced")
+		s.Zero(itemStruct2.ID, "Must still be zero because there was no insertion")
 
-	itemMap2 := map[string]string{
-		"name": "Janus",
-	}
-	s.Zero(itemMap2["id"], "Must be zero before inserting")
-	err = artist.InsertReturning(itemMap2)
-	s.Error(err, "Should not happen, using a pointer should be enforced")
-	s.Zero(itemMap2["id"], "Must still be zero because there was no insertion")
+		itemMap2 := map[string]string{
+			"name": "Janus",
+		}
+		s.Zero(itemMap2["id"], "Must be zero before inserting")
+		err = artist.InsertReturning(itemMap2)
+		s.Error(err, "Should not happen, using a pointer should be enforced")
+		s.Zero(itemMap2["id"], "Must still be zero because there was no insertion")
 
-	// Counting elements, must be exactly 2 elements.
-	count, err = artist.Find().Count()
-	s.NoError(err)
-	s.Equal(uint64(2), count, "Expecting 2 elements")
+		// Counting elements, must be exactly 2 elements.
+		count, err = artist.Find().Count()
+		s.NoError(err)
+		s.Equal(uint64(2), count, "Expecting 2 elements")
 
-	// Rolling back everything
-	err = tx.Rollback()
-	s.NoError(err)
+		return fmt.Errorf("rolling back for no reason")
+	})
+	s.Error(err)
 
 	// Expecting no elements.
-	count, err = sess.Collection("artist").Find().Count()
+	count, err := sess.Collection("artist").Find().Count()
 	s.NoError(err)
 	s.Equal(uint64(0), count, "Expecting 0 elements, everything was rolled back!")
 }
 
 func (s *SQLTestSuite) TestInsertIntoArtistsTable() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	artist := sess.Collection("artist")
 
@@ -461,7 +458,7 @@ func (s *SQLTestSuite) TestInsertIntoArtistsTable() {
 }
 
 func (s *SQLTestSuite) TestQueryNonExistentCollection() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	count, err := sess.Collection("doesnotexist").Find().Count()
 	s.Error(err)
@@ -469,7 +466,7 @@ func (s *SQLTestSuite) TestQueryNonExistentCollection() {
 }
 
 func (s *SQLTestSuite) TestGetOneResult() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	artist := sess.Collection("artist")
 
@@ -501,7 +498,7 @@ func (s *SQLTestSuite) TestGetOneResult() {
 }
 
 func (s *SQLTestSuite) TestGetWithOffset() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	artist := sess.Collection("artist")
 
@@ -514,7 +511,7 @@ func (s *SQLTestSuite) TestGetWithOffset() {
 }
 
 func (s *SQLTestSuite) TestGetResultsOneByOne() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	artist := sess.Collection("artist")
 
@@ -605,7 +602,7 @@ func (s *SQLTestSuite) TestGetResultsOneByOne() {
 }
 
 func (s *SQLTestSuite) TestGetAllResults() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	artist := sess.Collection("artist")
 
@@ -650,7 +647,7 @@ func (s *SQLTestSuite) TestInlineStructs() {
 		Details       reviewTypeDetails `db:",inline"`
 	}
 
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	review := sess.Collection("review")
 
@@ -694,7 +691,7 @@ func (s *SQLTestSuite) TestInlineStructs() {
 }
 
 func (s *SQLTestSuite) TestUpdate() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	artist := sess.Collection("artist")
 
@@ -826,7 +823,7 @@ func (s *SQLTestSuite) TestUpdate() {
 }
 
 func (s *SQLTestSuite) TestFunction() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	rowStruct := struct {
 		ID   int64
@@ -883,7 +880,7 @@ func (s *SQLTestSuite) TestFunction() {
 }
 
 func (s *SQLTestSuite) TestNullableFields() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	type testType struct {
 		ID              int64           `db:"id,omitempty"`
@@ -938,7 +935,7 @@ func (s *SQLTestSuite) TestNullableFields() {
 }
 
 func (s *SQLTestSuite) TestGroup() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	type statsType struct {
 		Numeric int `db:"numeric"`
@@ -973,7 +970,7 @@ func (s *SQLTestSuite) TestGroup() {
 }
 
 func (s *SQLTestSuite) TestInsertAndDelete() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	artist := sess.Collection("artist")
 	res := artist.Find()
@@ -995,7 +992,7 @@ func (s *SQLTestSuite) TestCompositeKeys() {
 		s.T().Skip("Currently not supported.")
 	}
 
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	compositeKeys := sess.Collection("composite_keys")
 
@@ -1042,85 +1039,59 @@ func (s *SQLTestSuite) TestTransactionsAndRollback() {
 		s.T().Skip("Currently not supported.")
 	}
 
-	sess := s.SQLBuilder()
-
-	// Simple transaction that should not fail.
-	tx, err := sess.NewTx(context.Background())
-	s.NoError(err)
-
-	artist := tx.Collection("artist")
-	err = artist.Truncate()
-	s.NoError(err)
-
-	_, err = artist.Insert(artistType{1, "First"})
-	s.NoError(err)
-
-	err = tx.Commit()
-	s.NoError(err)
+	sess := s.Session()
 
-	// An attempt to use the same transaction must fail.
-	err = tx.Commit()
-	s.Error(err)
+	err := sess.Tx(func(tx db.Session) error {
+		artist := tx.Collection("artist")
+		err := artist.Truncate()
+		s.NoError(err)
 
-	err = tx.Close()
-	s.NoError(err)
+		_, err = artist.Insert(artistType{1, "First"})
+		s.NoError(err)
 
-	err = tx.Close()
+		return nil
+	})
 	s.NoError(err)
 
-	// Use another transaction.
-	tx, err = sess.NewTx(context.Background())
-	s.NoError(err)
+	err = sess.Tx(func(tx db.Session) error {
+		artist := tx.Collection("artist")
 
-	artist = tx.Collection("artist")
+		_, err = artist.Insert(artistType{2, "Second"})
+		s.NoError(err)
 
-	_, err = artist.Insert(artistType{2, "Second"})
-	s.NoError(err)
+		// Won't fail.
+		_, err = artist.Insert(artistType{3, "Third"})
+		s.NoError(err)
 
-	// Won't fail.
-	_, err = artist.Insert(artistType{3, "Third"})
-	s.NoError(err)
+		// Will fail.
+		_, err = artist.Insert(artistType{1, "Duplicated"})
+		s.Error(err)
 
-	// Will fail.
-	_, err = artist.Insert(artistType{1, "Duplicated"})
+		return err
+	})
 	s.Error(err)
 
-	err = tx.Rollback()
-	s.NoError(err)
-
-	err = tx.Commit()
-	s.Error(err, "Already rolled back.")
-
 	// Let's verify we still have one element.
-	artist = sess.Collection("artist")
+	artist := sess.Collection("artist")
 
 	count, err := artist.Find().Count()
 	s.NoError(err)
 	s.Equal(uint64(1), count)
 
-	err = tx.Close()
-	s.NoError(err)
+	err = sess.Tx(func(tx db.Session) error {
+		artist := tx.Collection("artist")
 
-	// Attempt to add some rows.
-	tx, err = sess.NewTx(context.Background())
-	s.NoError(err)
-
-	artist = tx.Collection("artist")
-
-	// Won't fail.
-	_, err = artist.Insert(artistType{2, "Second"})
-	s.NoError(err)
-
-	// Won't fail.
-	_, err = artist.Insert(artistType{3, "Third"})
-	s.NoError(err)
+		// Won't fail.
+		_, err = artist.Insert(artistType{2, "Second"})
+		s.NoError(err)
 
-	// Then rollback for no reason.
-	err = tx.Rollback()
-	s.NoError(err)
+		// Won't fail.
+		_, err = artist.Insert(artistType{3, "Third"})
+		s.NoError(err)
 
-	err = tx.Commit()
-	s.Error(err, "Already rolled back.")
+		return fmt.Errorf("rollback for no reason")
+	})
+	s.Error(err)
 
 	// Let's verify we still have one element.
 	artist = sess.Collection("artist")
@@ -1129,29 +1100,22 @@ func (s *SQLTestSuite) TestTransactionsAndRollback() {
 	s.NoError(err)
 	s.Equal(uint64(1), count)
 
-	err = tx.Close()
-	s.NoError(err)
-
 	// Attempt to add some rows.
-	tx, err = sess.NewTx(context.Background())
-	s.NoError(err)
-
-	artist = tx.Collection("artist")
+	err = sess.Tx(func(tx db.Session) error {
+		artist = tx.Collection("artist")
 
-	// Won't fail.
-	_, err = artist.Insert(artistType{2, "Second"})
-	s.NoError(err)
+		// Won't fail.
+		_, err = artist.Insert(artistType{2, "Second"})
+		s.NoError(err)
 
-	// Won't fail.
-	_, err = artist.Insert(artistType{3, "Third"})
-	s.NoError(err)
+		// Won't fail.
+		_, err = artist.Insert(artistType{3, "Third"})
+		s.NoError(err)
 
-	err = tx.Commit()
+		return nil
+	})
 	s.NoError(err)
 
-	err = tx.Rollback()
-	s.Error(err, "Already committed")
-
 	// Let's verify we have 3 rows.
 	artist = sess.Collection("artist")
 
@@ -1192,7 +1156,7 @@ func (s *SQLTestSuite) TestDataTypes() {
 		Time  int64      `db:"_time"`
 	}
 
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	// Getting a pointer to the "data_types" collection.
 	dataTypes := sess.Collection("data_types")
@@ -1265,7 +1229,7 @@ func (s *SQLTestSuite) TestDataTypes() {
 }
 
 func (s *SQLTestSuite) TestUpdateWithNullColumn() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	artist := sess.Collection("artist")
 	err := artist.Truncate()
@@ -1298,13 +1262,13 @@ func (s *SQLTestSuite) TestUpdateWithNullColumn() {
 }
 
 func (s *SQLTestSuite) TestBatchInsert() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	for batchSize := 0; batchSize < 17; batchSize++ {
 		err := sess.Collection("artist").Truncate()
 		s.NoError(err)
 
-		q := sess.InsertInto("artist").Columns("name")
+		q := sess.SQL().InsertInto("artist").Columns("name")
 
 		switch s.Adapter() {
 		case "postgresql", "cockroachdb":
@@ -1341,13 +1305,13 @@ func (s *SQLTestSuite) TestBatchInsert() {
 }
 
 func (s *SQLTestSuite) TestBatchInsertNoColumns() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	for batchSize := 0; batchSize < 17; batchSize++ {
 		err := sess.Collection("artist").Truncate()
 		s.NoError(err)
 
-		batch := sess.InsertInto("artist").Batch(batchSize)
+		batch := sess.SQL().InsertInto("artist").Batch(batchSize)
 
 		totalItems := int(rand.Int31n(21))
 
@@ -1386,14 +1350,14 @@ func (s *SQLTestSuite) TestBatchInsertReturningKeys() {
 		return
 	}
 
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	err := sess.Collection("artist").Truncate()
 	s.NoError(err)
 
 	batchSize, totalItems := 7, 12
 
-	batch := sess.InsertInto("artist").Columns("name").Returning("id").Batch(batchSize)
+	batch := sess.SQL().InsertInto("artist").Columns("name").Returning("id").Batch(batchSize)
 
 	go func() {
 		defer batch.Done()
@@ -1430,12 +1394,12 @@ func (s *SQLTestSuite) TestBatchInsertReturningKeys() {
 }
 
 func (s *SQLTestSuite) TestPaginator() {
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	err := sess.Collection("artist").Truncate()
 	s.NoError(err)
 
-	batch := sess.InsertInto("artist").Batch(100)
+	batch := sess.SQL().InsertInto("artist").Batch(100)
 
 	go func() {
 		defer batch.Done()
@@ -1451,9 +1415,9 @@ func (s *SQLTestSuite) TestPaginator() {
 	s.NoError(err)
 	s.NoError(batch.Err())
 
-	q := sess.SelectFrom("artist")
+	q := sess.SQL().SelectFrom("artist")
 	if s.Adapter() == "ql" {
-		q = sess.SelectFrom(sess.Select("id() AS id", "name").From("artist"))
+		q = sess.SQL().SelectFrom(sess.SQL().Select("id() AS id", "name").From("artist"))
 	}
 
 	const pageSize = 13
@@ -1642,21 +1606,21 @@ func (s *SQLTestSuite) TestPaginator() {
 	}
 }
 
-func (s *SQLTestSuite) TestSQLBuilder() {
-	sess := s.SQLBuilder()
+func (s *SQLTestSuite) TestSession() {
+	sess := s.Session()
 
 	var all []map[string]interface{}
 
 	err := sess.Collection("artist").Truncate()
 	s.NoError(err)
 
-	_, err = sess.InsertInto("artist").Values(struct {
+	_, err = sess.SQL().InsertInto("artist").Values(struct {
 		Name string `db:"name"`
 	}{"Rinko Kikuchi"}).Exec()
 	s.NoError(err)
 
 	// Using explicit iterator.
-	iter := sess.SelectFrom("artist").Iterator()
+	iter := sess.SQL().SelectFrom("artist").Iterator()
 	err = iter.All(&all)
 
 	s.NoError(err)
@@ -1664,14 +1628,14 @@ func (s *SQLTestSuite) TestSQLBuilder() {
 
 	// Using explicit iterator to fetch one item.
 	var item map[string]interface{}
-	iter = sess.SelectFrom("artist").Iterator()
+	iter = sess.SQL().SelectFrom("artist").Iterator()
 	err = iter.One(&item)
 
 	s.NoError(err)
 	s.NotZero(item)
 
 	// Using explicit iterator and NextScan.
-	iter = sess.SelectFrom("artist").Iterator()
+	iter = sess.SQL().SelectFrom("artist").Iterator()
 	var id int
 	var name string
 
@@ -1691,7 +1655,7 @@ func (s *SQLTestSuite) TestSQLBuilder() {
 	s.Error(err)
 
 	// Using explicit iterator and ScanOne.
-	iter = sess.SelectFrom("artist").Iterator()
+	iter = sess.SQL().SelectFrom("artist").Iterator()
 	id, name = 0, ""
 	if s.Adapter() == "ql" {
 		err = iter.ScanOne(&name)
@@ -1708,7 +1672,7 @@ func (s *SQLTestSuite) TestSQLBuilder() {
 	s.Error(err)
 
 	// Using explicit iterator and Next.
-	iter = sess.SelectFrom("artist").Iterator()
+	iter = sess.SQL().SelectFrom("artist").Iterator()
 
 	var artist map[string]interface{}
 	for iter.Next(&artist) {
@@ -1727,25 +1691,24 @@ func (s *SQLTestSuite) TestSQLBuilder() {
 	}
 
 	// Using implicit iterator.
-	q := sess.SelectFrom("artist")
+	q := sess.SQL().SelectFrom("artist")
 	err = q.All(&all)
 
 	s.NoError(err)
 	s.NotZero(all)
 
-	tx, err := sess.NewTx(context.Background())
-	s.NoError(err)
-	s.NotZero(tx)
-	defer tx.Close()
+	err = sess.Tx(func(tx db.Session) error {
+		q := tx.SQL().SelectFrom("artist")
+		s.NotZero(iter)
 
-	q = tx.SelectFrom("artist")
-	s.NotZero(iter)
+		err = q.All(&all)
+		s.NoError(err)
+		s.NotZero(all)
 
-	err = q.All(&all)
-	s.NoError(err)
-	s.NotZero(all)
+		return nil
+	})
 
-	s.NoError(tx.Commit())
+	s.NoError(err)
 }
 
 func (s *SQLTestSuite) TestExhaustConnectionPool() {
@@ -1754,27 +1717,12 @@ func (s *SQLTestSuite) TestExhaustConnectionPool() {
 		return
 	}
 
-	var tMu sync.Mutex
-
-	tFatal := func(err error) {
-		tMu.Lock()
-		defer tMu.Unlock()
-
-		s.T().Errorf("%v", err)
-	}
-
-	tLogf := func(format string, args ...interface{}) {
-		tMu.Lock()
-		defer tMu.Unlock()
-
-		s.T().Logf(format, args...)
-	}
-
-	sess := s.SQLBuilder()
+	sess := s.Session()
+	errRolledBack := errors.New("rolled back")
 
 	var wg sync.WaitGroup
 	for i := 0; i < 100; i++ {
-		tLogf("Tx %d: Pending", i)
+		s.T().Logf("Tx %d: Pending", i)
 
 		wg.Add(1)
 		go func(wg *sync.WaitGroup, i int) {
@@ -1782,76 +1730,37 @@ func (s *SQLTestSuite) TestExhaustConnectionPool() {
 
 			// Requesting a new transaction session.
 			start := time.Now()
-			tLogf("Tx: %d: NewTx", i)
-			tx, err := sess.NewTx(context.Background())
-			if err != nil {
-				tFatal(err)
-			}
-			tLogf("Tx %d: OK (time to connect: %v)", i, time.Since(start))
+			s.T().Logf("Tx: %d: NewTx", i)
 
-			// Let's suppose that we do a bunch of complex stuff and that the
-			// transaction lasts 3 seconds.
-			time.Sleep(time.Second * 3)
+			expectError := false
+			if i%2 == 1 {
+				expectError = true
+			}
 
-			switch i % 7 {
-			case 0:
-				var account map[string]interface{}
-				if err := tx.Collection("artist").Find().One(&account); err != nil {
-					tFatal(err)
-				}
-				if err := tx.Commit(); err != nil {
-					tFatal(err)
-				}
-				tLogf("Tx %d: Committed", i)
-			case 1:
-				if _, err := tx.DeleteFrom("artist").Exec(); err != nil {
-					tFatal(err)
+			err := sess.Tx(func(tx db.Session) error {
+				s.T().Logf("Tx %d: OK (time to connect: %v)", i, time.Since(start))
+				// Let's suppose that we do a bunch of complex stuff and that the
+				// transaction lasts 3 seconds.
+				time.Sleep(time.Second * 3)
+
+				if expectError {
+					if _, err := tx.SQL().DeleteFrom("artist").Exec(); err != nil {
+						return err
+					}
+					return errRolledBack
 				}
-				if err := tx.Rollback(); err != nil {
-					tFatal(err)
-				}
-				tLogf("Tx %d: Rolled back", i)
-			case 2:
-				if err := tx.Close(); err != nil {
-					tFatal(err)
-				}
-				tLogf("Tx %d: Closed", i)
-			case 3:
+
 				var account map[string]interface{}
 				if err := tx.Collection("artist").Find().One(&account); err != nil {
-					tFatal(err)
-				}
-				if err := tx.Commit(); err != nil {
-					tFatal(err)
+					return err
 				}
-				if err := tx.Close(); err != nil {
-					tFatal(err)
-				}
-				tLogf("Tx %d: Committed and closed", i)
-			case 4:
-				if err := tx.Rollback(); err != nil {
-					tFatal(err)
-				}
-				if err := tx.Close(); err != nil {
-					tFatal(err)
-				}
-				tLogf("Tx %d: Rolled back and closed", i)
-			case 5:
-				if err := tx.Close(); err != nil {
-					tFatal(err)
-				}
-				if err := tx.Commit(); err == nil {
-					tFatal(fmt.Errorf("Error expected"))
-				}
-				tLogf("Tx %d: Closed and committed", i)
-			case 6:
-				if err := tx.Close(); err != nil {
-					tFatal(err)
-				}
-				if err := tx.Rollback(); err == nil {
-					tFatal(fmt.Errorf("Error expected"))
-				}
-				tLogf("Tx %d: Closed and rolled back", i)
+				return nil
+			})
+			if expectError {
+				s.Error(err)
+				s.True(errors.Is(err, errRolledBack))
+			} else {
+				s.NoError(err)
 			}
 		}(&wg, i)
 	}
@@ -1861,7 +1770,7 @@ func (s *SQLTestSuite) TestExhaustConnectionPool() {
 
 func (s *SQLTestSuite) TestCustomType() {
 	// See https://github.com/upper/db/issues/332
-	sess := s.SQLBuilder()
+	sess := s.Session()
 
 	artist := sess.Collection("artist")
 
diff --git a/internal/testsuite/suite.go b/internal/testsuite/suite.go
index df8a2f2..32cc6e9 100644
--- a/internal/testsuite/suite.go
+++ b/internal/testsuite/suite.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/stretchr/testify/suite"
 	db "github.com/upper/db/v4"
-	"github.com/upper/db/v4/sqlbuilder"
 )
 
 const TimeZone = "Canada/Eastern"
@@ -13,7 +12,6 @@ const TimeZone = "Canada/Eastern"
 var TimeLocation, _ = time.LoadLocation(TimeZone)
 
 type Helper interface {
-	SQLBuilder() sqlbuilder.Session
 	Session() db.Session
 
 	Adapter() string