Add support for configuring the transaction isolation level using the DSN.

Project: http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/commit/88a23514
Tree: http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/tree/88a23514
Diff: http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/diff/88a23514

Branch: refs/heads/master
Commit: 88a23514205a4d88d8ef2e2c801f8ced1d199367
Parents: 6eed044
Author: Francis Chuang <francis.chu...@boostport.com>
Authored: Thu Sep 29 16:36:03 2016 +1000
Committer: Julian Hyde <jh...@apache.org>
Committed: Thu Aug 10 18:47:09 2017 -0700

----------------------------------------------------------------------
 README.md      | 18 ++++++++++++++++++
 connection.go  |  2 +-
 driver_test.go | 18 ++++++++++++------
 dsn.go         | 33 +++++++++++++++++++++++++--------
 dsn_test.go    | 39 ++++++++++++++++++++++++++++++++++++++-
 transaction.go |  2 +-
 6 files changed, 95 insertions(+), 17 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/88a23514/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index f8bfe30..17c0992 100644
--- a/README.md
+++ b/README.md
@@ -66,6 +66,24 @@ additional frames are then fetched on a as-needed basis. 
`frameMaxSize` allows y
 in each frame to suit your application's performance profile. By default this 
is set to `-1`, so that there is no limit
 on the number of rows in a frame.
 
+#### transactionIsolation
+
+Setting `transactionIsolation` allows you to set the isolation level for 
transactions using the connection. The value
+should be a positive integer analogous to the transaction levels defined by 
the JDBC specification. The default value
+is `0`, which means transactions are not supported. This is to deal with the 
fact that Calcite/Avatica works with
+many types of backends, with some backends having no transaction support. If 
you are using Apache Phoenix 4.7 onwards,
+we recommend setting it to `4`, which is the maximum isolation level supported.
+
+The supported values for `transactionIsolation` are:
+
+| Value | JDBC Constant                  | Description                         
                                             |
+| ----- | ------------------------------ | 
--------------------------------------------------------------------------------
 |
+| 0     | none                           | Transactions are not supported      
                                             |
+| 1     | `TRANSACTION_READ_UNCOMMITTED` | Dirty reads, non-repeatable reads 
and phantom reads may occur.                   |
+| 2     | `TRANSACTION_READ_COMMITTED`   | Dirty reads are prevented, but 
non-repeatable reads and phantom reads may occur. |
+| 4     | `TRANSACTION_REPEATABLE_READ`  | Dirty reads and non-repeatable 
reads are prevented, but phantom reads may occur. |
+| 8     | `TRANSACTION_SERIALIZABLE`     | Dirty reads, non-repeatable reads, 
and phantom reads are all prevented.          |
+
 ### time.Time support
 
 The following Phoenix/Avatica datatypes are automatically converted to and 
from `time.Time`:

http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/88a23514/connection.go
----------------------------------------------------------------------
diff --git a/connection.go b/connection.go
index 24cad3b..1a3c9df 100644
--- a/connection.go
+++ b/connection.go
@@ -74,7 +74,7 @@ func (c *conn) Begin() (driver.Tx, error) {
                ConnProps: &message.ConnectionProperties{
                        AutoCommit:           false,
                        HasAutoCommit:        true,
-                       TransactionIsolation: 4,
+                       TransactionIsolation: c.config.transactionIsolation,
                },
        })
 

http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/88a23514/driver_test.go
----------------------------------------------------------------------
diff --git a/driver_test.go b/driver_test.go
index b69e747..928b610 100644
--- a/driver_test.go
+++ b/driver_test.go
@@ -501,7 +501,9 @@ func TestStoreAndRetrieveBinaryData(t *testing.T) {
 
 func TestCommittingTransactions(t *testing.T) {
 
-       runTests(t, dsn, func(dbt *DBTest) {
+       query := "?transactionIsolation=4"
+
+       runTests(t, dsn+query, func(dbt *DBTest) {
 
                // Create and seed table
                dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` (
@@ -567,7 +569,9 @@ func TestCommittingTransactions(t *testing.T) {
 
 func TestRollingBackTransactions(t *testing.T) {
 
-       runTests(t, dsn, func(dbt *DBTest) {
+       query := "?transactionIsolation=4"
+
+       runTests(t, dsn+query, func(dbt *DBTest) {
 
                // Create and seed table
                dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` (
@@ -638,7 +642,7 @@ func TestPreparedStatements(t *testing.T) {
                // Create and seed table
                dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` (
                                int INTEGER PRIMARY KEY
-                           ) TRANSACTIONAL=true`)
+                           ) TRANSACTIONAL=false`)
 
                stmt, err := dbt.db.Prepare(`UPSERT INTO ` + dbt.tableName + ` 
VALUES(?)`)
 
@@ -788,7 +792,9 @@ func TestQueryShortcut(t *testing.T) {
 
 func TestOptimisticConcurrency(t *testing.T) {
 
-       runTests(t, dsn, func(dbt *DBTest) {
+       query := "?transactionIsolation=4"
+
+       runTests(t, dsn+query, func(dbt *DBTest) {
 
                // Create and seed table
                dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` (
@@ -873,7 +879,7 @@ func TestLastInsertIDShouldReturnError(t *testing.T) {
                                id INTEGER PRIMARY KEY,
                                msg VARCHAR,
                                version INTEGER
-                           ) TRANSACTIONAL=true`)
+                           ) TRANSACTIONAL=false`)
 
                dbt.mustExec(`CREATE SEQUENCE test_sequence`)
 
@@ -900,7 +906,7 @@ func TestConnectionToInvalidServerShouldReturnError(t 
*testing.T) {
                _, err := dbt.db.Exec(`CREATE TABLE ` + dbt.tableName + ` (
                                        id INTEGER PRIMARY KEY,
                                        msg VARCHAR,
-                                     ) TRANSACTIONAL=true`)
+                                     ) TRANSACTIONAL=false`)
 
                if err == nil {
                        dbt.Fatal("Expected an error due to connection to 
invalid server, but got nothing.")

http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/88a23514/dsn.go
----------------------------------------------------------------------
diff --git a/dsn.go b/dsn.go
index eb8270e..2292bf3 100644
--- a/dsn.go
+++ b/dsn.go
@@ -9,20 +9,22 @@ import (
 
 // Config is a configuration parsed from a DSN string
 type Config struct {
-       endpoint     string
-       maxRowsTotal int64
-       frameMaxSize int32
-       location     *time.Location
-       schema       string
+       endpoint             string
+       maxRowsTotal         int64
+       frameMaxSize         int32
+       location             *time.Location
+       schema               string
+       transactionIsolation uint32
 }
 
 // ParseDSN parses a DSN string to a Config
 func ParseDSN(dsn string) (*Config, error) {
 
        conf := &Config{
-               maxRowsTotal: -1,
-               frameMaxSize: -1,
-               location:     time.UTC,
+               maxRowsTotal:         -1,
+               frameMaxSize:         -1,
+               location:             time.UTC,
+               transactionIsolation: 0,
        }
 
        parsed, err := url.ParseRequestURI(dsn)
@@ -70,6 +72,21 @@ func ParseDSN(dsn string) (*Config, error) {
                conf.schema = v
        }
 
+       if v := queries.Get("transactionIsolation"); v != "" {
+
+               isolation, err := strconv.Atoi(v)
+
+               if err != nil {
+                       return nil, fmt.Errorf("Invalid value for 
transactionIsolation: %s", err)
+               }
+
+               if isolation < 0 || isolation > 8 || isolation&(isolation-1) != 
0 {
+                       return nil, fmt.Errorf("transactionIsolation must be 0, 
1, 2, 4 or 8, %d given", isolation)
+               }
+
+               conf.transactionIsolation = uint32(isolation)
+       }
+
        parsed.RawQuery = ""
        parsed.Fragment = ""
 

http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/88a23514/dsn_test.go
----------------------------------------------------------------------
diff --git a/dsn_test.go b/dsn_test.go
index 74a35b6..7c43786 100644
--- a/dsn_test.go
+++ b/dsn_test.go
@@ -1,13 +1,14 @@
 package avatica
 
 import (
+       "strconv"
        "testing"
        "time"
 )
 
 func TestParseDSN(t *testing.T) {
 
-       config, err := 
ParseDSN("http://localhost:8765?maxRowsTotal=1&frameMaxSize=1&location=Australia/Melbourne&schema=myschema";)
+       config, err := 
ParseDSN("http://localhost:8765?maxRowsTotal=1&frameMaxSize=1&location=Australia/Melbourne&schema=myschema&transactionIsolation=8";)
 
        if err != nil {
                t.Fatalf("Unexpected error: %s", err)
@@ -32,6 +33,10 @@ func TestParseDSN(t *testing.T) {
        if config.schema != "myschema" {
                t.Errorf("Expected schema to be %s, got %s", "myschema", 
config.schema)
        }
+
+       if config.transactionIsolation != 8 {
+               t.Errorf("Expected transactionIsolation to be %d, got %d", 8, 
config.transactionIsolation)
+       }
 }
 
 func TestParseEmptyDSN(t *testing.T) {
@@ -66,6 +71,10 @@ func TestDSNDefaults(t *testing.T) {
        if config.schema != "" {
                t.Errorf("Unexpected schema set: %s", config.schema)
        }
+
+       if config.transactionIsolation != 0 {
+               t.Errorf("Default transaction level should be %d, got %d.", 0, 
config.transactionIsolation)
+       }
 }
 
 func TestLocallocation(t *testing.T) {
@@ -101,3 +110,31 @@ func TestBadInput(t *testing.T) {
                t.Fatal("Expected error due to invalid frameMaxSize, but did 
not receive any.")
        }
 }
+
+func TestInvalidTransactionIsolation(t *testing.T) {
+
+       badIsolationLevels := []int{-1, 3, 5, 6, 7, 9, 10, 11, 100}
+
+       for _, isolationLevel := range badIsolationLevels {
+
+               _, err := 
ParseDSN("http://localhost:8765?transactionIsolation="; + 
strconv.Itoa(isolationLevel))
+
+               if err == nil {
+                       t.Fatal("Expected error due to invalid 
transactionIsolation, but did not receive any.")
+               }
+       }
+}
+
+func TestValidTransactionIsolation(t *testing.T) {
+
+       validIsolationLevels := []int{0, 1, 2, 4, 8}
+
+       for _, isolationLevel := range validIsolationLevels {
+
+               _, err := 
ParseDSN("http://localhost:8765?transactionIsolation="; + 
strconv.Itoa(isolationLevel))
+
+               if err != nil {
+                       t.Fatalf("Unexpected error when %d is set as the 
isolation level: %s", isolationLevel, err)
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/88a23514/transaction.go
----------------------------------------------------------------------
diff --git a/transaction.go b/transaction.go
index ca968ec..e1a012e 100644
--- a/transaction.go
+++ b/transaction.go
@@ -41,7 +41,7 @@ func (t *tx) enableAutoCommit() error {
                ConnProps: &message.ConnectionProperties{
                        AutoCommit:           true,
                        HasAutoCommit:        true,
-                       TransactionIsolation: 4,
+                       TransactionIsolation: 
t.conn.config.transactionIsolation,
                },
        })
 

Reply via email to