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 <[email protected]> Authored: Thu Sep 29 16:36:03 2016 +1000 Committer: Julian Hyde <[email protected]> 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, }, })
