http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/2968def4/driver_phoenix_test.go ---------------------------------------------------------------------- diff --git a/driver_phoenix_test.go b/driver_phoenix_test.go new file mode 100644 index 0000000..b33830b --- /dev/null +++ b/driver_phoenix_test.go @@ -0,0 +1,1044 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package avatica + +import ( + "bytes" + "crypto/sha256" + "database/sql" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + "time" + + "github.com/apache/calcite-avatica-go/errors" +) + +func skipTestIfNotPhoenix(t *testing.T) { + + val := os.Getenv("AVATICA_FLAVOR") + + if val != "PHOENIX" { + t.Skip("Skipping Apache Phoenix test") + } +} + +func TestPhoenixConnectionMustBeOpenedWithAutoCommitTrue(t *testing.T) { + + skipTestIfNotPhoenix(t) + + runTests(t, dsn, func(dbt *DBTest) { + + // Create and seed table + dbt.mustExec("CREATE TABLE " + dbt.tableName + " (id BIGINT PRIMARY KEY, val VARCHAR) TRANSACTIONAL=false") + + dbt.mustExec("UPSERT INTO " + dbt.tableName + " VALUES (1,'A')") + + dbt.mustExec("UPSERT INTO " + dbt.tableName + " VALUES (2,'B')") + + rows := dbt.mustQuery("SELECT COUNT(*) FROM " + dbt.tableName) + defer rows.Close() + + for rows.Next() { + + var count int + + err := rows.Scan(&count) + + if err != nil { + dbt.Fatal(err) + } + + if count != 2 { + dbt.Fatalf("There should be 2 rows, got %d", count) + } + } + + }) +} + +func TestPhoenixZeroValues(t *testing.T) { + + skipTestIfNotPhoenix(t) + + runTests(t, dsn, func(dbt *DBTest) { + + // Create and seed table + dbt.mustExec("CREATE TABLE " + dbt.tableName + " (int INTEGER PRIMARY KEY, flt FLOAT, bool BOOLEAN, str VARCHAR) TRANSACTIONAL=false") + + dbt.mustExec("UPSERT INTO " + dbt.tableName + " VALUES (0, 0.0, false, '')") + + rows := dbt.mustQuery("SELECT * FROM " + dbt.tableName) + defer rows.Close() + + for rows.Next() { + + var i int + var flt float64 + var b bool + var s string + + err := rows.Scan(&i, &flt, &b, &s) + + if err != nil { + dbt.Fatal(err) + } + + if i != 0 { + dbt.Fatalf("Integer should be 0, got %v", i) + } + + if flt != 0.0 { + dbt.Fatalf("Float should be 0.0, got %v", flt) + } + + if b != false { + dbt.Fatalf("Boolean should be false, got %v", b) + } + + if s != "" { + dbt.Fatalf("String should be \"\", got %v", s) + } + } + + }) +} + +func TestPhoenixDataTypes(t *testing.T) { + + skipTestIfNotPhoenix(t) + + runTests(t, dsn, func(dbt *DBTest) { + + // Create and seed table + dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( + int INTEGER PRIMARY KEY, + uint UNSIGNED_INT, + bint BIGINT, + ulong UNSIGNED_LONG, + tint TINYINT, + utint UNSIGNED_TINYINT, + sint SMALLINT, + usint UNSIGNED_SMALLINT, + flt FLOAT, + uflt UNSIGNED_FLOAT, + dbl DOUBLE, + udbl UNSIGNED_DOUBLE, + dec DECIMAL, + bool BOOLEAN, + tm TIME, + dt DATE, + tmstmp TIMESTAMP, + utm UNSIGNED_TIME, + udt UNSIGNED_DATE, + utmstmp UNSIGNED_TIMESTAMP, + var VARCHAR, + ch CHAR(3), + bin BINARY(20), + varbin VARBINARY + ) TRANSACTIONAL=false`) + + var ( + integerValue int = -20 + uintegerValue int = 5 + bintValue int = -9223372036854775807 + ulongValue int = 9223372036854775807 + tintValue int = -128 + utintValue int = 126 + sintValue int = -32768 + usintValue int = 32767 + fltValue float64 = -3.555 + ufltValue float64 = 3.555 + dblValue float64 = -9.555 + udblValue float64 = 9.555 + decValue string = "1.333" + booleanValue bool = true + tmValue time.Time = time.Date(0, 1, 1, 21, 21, 21, 222000000, time.UTC) + dtValue time.Time = time.Date(2100, 2, 1, 0, 0, 0, 0, time.UTC) + tmstmpValue time.Time = time.Date(2100, 2, 1, 21, 21, 21, 222000000, time.UTC) + utmValue time.Time = time.Date(0, 1, 1, 21, 21, 21, 222000000, time.UTC) + udtValue time.Time = time.Date(2100, 2, 1, 0, 0, 0, 0, time.UTC) + utmstmpValue time.Time = time.Date(2100, 2, 1, 21, 21, 21, 222000000, time.UTC) + varcharValue string = "test string" + chValue string = "a" + binValue []byte = make([]byte, 20, 20) + varbinValue []byte = []byte("testtesttest") + ) + + copy(binValue[:], []byte("test")) + + dbt.mustExec(`UPSERT INTO `+dbt.tableName+` VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + integerValue, + uintegerValue, + bintValue, + ulongValue, + tintValue, + utintValue, + sintValue, + usintValue, + fltValue, + ufltValue, + dblValue, + udblValue, + decValue, + booleanValue, + tmValue, + dtValue, + tmstmpValue, + utmValue, + udtValue, + utmstmpValue, + varcharValue, + chValue, + binValue, + varbinValue, + ) + + rows := dbt.mustQuery("SELECT * FROM " + dbt.tableName) + defer rows.Close() + + var ( + integer int + uinteger int + bint int + ulong int + tint int + utint int + sint int + usint int + flt float64 + uflt float64 + dbl float64 + udbl float64 + dec string + boolean bool + tm time.Time + dt time.Time + tmstmp time.Time + utm time.Time + udt time.Time + utmstmp time.Time + varchar string + ch string + bin []byte + varbin []byte + ) + + for rows.Next() { + + err := rows.Scan(&integer, &uinteger, &bint, &ulong, &tint, &utint, &sint, &usint, &flt, &uflt, &dbl, &udbl, &dec, &boolean, &tm, &dt, &tmstmp, &utm, &udt, &utmstmp, &varchar, &ch, &bin, &varbin) + + if err != nil { + dbt.Fatal(err) + } + } + + comparisons := []struct { + result interface{} + expected interface{} + }{ + {integer, integerValue}, + {uinteger, uintegerValue}, + {bint, bintValue}, + {ulong, ulongValue}, + {tint, tintValue}, + {utint, utintValue}, + {sint, sintValue}, + {usint, usintValue}, + {flt, fltValue}, + {uflt, ufltValue}, + {dbl, dblValue}, + {udbl, udblValue}, + {dec, decValue}, + {boolean, booleanValue}, + {tm, tmValue}, + {dt, dtValue}, + {tmstmp, tmstmpValue}, + {utm, utmValue}, + {udt, udtValue}, + {utmstmp, utmstmpValue}, + {varchar, varcharValue}, + {ch, chValue}, + {bin, binValue}, + {varbin, varbinValue}, + } + + for _, tt := range comparisons { + + if v, ok := tt.expected.(time.Time); ok { + + if !v.Equal(tt.result.(time.Time)) { + dbt.Fatalf("Expected %v, got %v.", tt.expected, tt.result) + } + + } else if v, ok := tt.expected.([]byte); ok { + + if !bytes.Equal(v, tt.result.([]byte)) { + dbt.Fatalf("Expected %v, got %v.", tt.expected, tt.result) + } + + } else if tt.expected != tt.result { + dbt.Errorf("Expected %v, got %v.", tt.expected, tt.result) + } + } + }) +} + +func TestPhoenixLocations(t *testing.T) { + + skipTestIfNotPhoenix(t) + + query := "?location=Australia/Melbourne" + + runTests(t, dsn+query, func(dbt *DBTest) { + + // Create and seed table + dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( + tm TIME PRIMARY KEY, + dt DATE, + tmstmp TIMESTAMP + ) TRANSACTIONAL=false`) + + loc, err := time.LoadLocation("Australia/Melbourne") + + if err != nil { + dbt.Fatalf("Unexpected error: %s", err) + } + + var ( + tmValue time.Time = time.Date(0, 1, 1, 21, 21, 21, 222000000, loc) + dtValue time.Time = time.Date(2100, 2, 1, 0, 0, 0, 0, loc) + tmstmpValue time.Time = time.Date(2100, 2, 1, 21, 21, 21, 222000000, loc) + ) + + dbt.mustExec(`UPSERT INTO `+dbt.tableName+` VALUES (?, ?, ?)`, + tmValue, + dtValue, + tmstmpValue, + ) + + rows := dbt.mustQuery("SELECT * FROM " + dbt.tableName) + defer rows.Close() + + var ( + tm time.Time + dt time.Time + tmstmp time.Time + ) + + for rows.Next() { + + err := rows.Scan(&tm, &dt, &tmstmp) + + if err != nil { + dbt.Fatal(err) + } + } + + comparisons := []struct { + result time.Time + expected time.Time + }{ + {tm, tmValue}, + {dt, dtValue}, + {tmstmp, tmstmpValue}, + } + + for _, tt := range comparisons { + + if !tt.result.Equal(tt.expected) { + dbt.Fatalf("Expected %v, got %v.", tt.expected, tt.result) + } + } + }) +} + +func TestPhoenixDateAndTimestampsBefore1970(t *testing.T) { + + skipTestIfNotPhoenix(t) + + runTests(t, dsn, func(dbt *DBTest) { + + // Create and seed table + dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( + int INTEGER PRIMARY KEY, + dt DATE, + tmstmp TIMESTAMP + ) TRANSACTIONAL=false`) + + var ( + integerValue int = 1 + dtValue time.Time = time.Date(1945, 5, 20, 0, 0, 0, 0, time.UTC) + tmstmpValue time.Time = time.Date(1911, 5, 20, 21, 21, 21, 222000000, time.UTC) + ) + + dbt.mustExec(`UPSERT INTO `+dbt.tableName+` VALUES (?, ?, ?)`, + integerValue, + dtValue, + tmstmpValue, + ) + + rows := dbt.mustQuery("SELECT dt, tmstmp FROM " + dbt.tableName) + defer rows.Close() + + var ( + dt time.Time + tmstmp time.Time + ) + + for rows.Next() { + err := rows.Scan(&dt, &tmstmp) + + if err != nil { + dbt.Fatal(err) + } + } + + comparisons := []struct { + result time.Time + expected time.Time + }{ + {dt, dtValue}, + {tmstmp, tmstmpValue}, + } + + for _, tt := range comparisons { + if !tt.expected.Equal(tt.result) { + dbt.Fatalf("Expected %v, got %v.", tt.expected, tt.result) + } + } + }) +} + +func TestPhoenixStoreAndRetrieveBinaryData(t *testing.T) { + + skipTestIfNotPhoenix(t) + + runTests(t, dsn, func(dbt *DBTest) { + + // Create and seed table + dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( + int INTEGER PRIMARY KEY, + bin VARBINARY + ) TRANSACTIONAL=false`) + + filePath := filepath.Join("test-fixtures", "gopher.png") + + file, err := ioutil.ReadFile(filePath) + + if err != nil { + t.Fatalf("Unable to read text-fixture: %s", filePath) + } + + hash := sha256.Sum256(file) + + dbt.mustExec(`UPSERT INTO `+dbt.tableName+` VALUES (?, ?)`, + 1, + file, + ) + + rows := dbt.mustQuery("SELECT bin FROM " + dbt.tableName) + defer rows.Close() + + var receivedFile []byte + + for rows.Next() { + + err := rows.Scan(&receivedFile) + + if err != nil { + dbt.Fatal(err) + } + } + + ioutil.WriteFile("test-fixtures/gopher.png", receivedFile, os.ModePerm) + + receivedHash := sha256.Sum256(receivedFile) + + if !bytes.Equal(hash[:], receivedHash[:]) { + t.Fatalf("Hash of stored file (%x) does not equal hash of retrieved file (%x).", hash[:], receivedHash[:]) + } + }) +} + +func TestPhoenixCommittingTransactions(t *testing.T) { + + skipTestIfNotPhoenix(t) + + query := "?transactionIsolation=4" + + runTests(t, dsn+query, func(dbt *DBTest) { + + // Create and seed table + dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( + int INTEGER PRIMARY KEY + ) TRANSACTIONAL=true`) + + tx, err := dbt.db.Begin() + + if err != nil { + t.Fatalf("Unable to create transaction: %s", err) + } + + stmt, err := tx.Prepare(`UPSERT INTO ` + dbt.tableName + ` VALUES(?)`) + + if err != nil { + t.Fatalf("Could not prepare statement: %s", err) + } + + totalRows := 6 + + for i := 1; i <= totalRows; i++ { + _, err := stmt.Exec(i) + + if err != nil { + dbt.Fatal(err) + } + } + + r := tx.QueryRow("SELECT COUNT(*) FROM " + dbt.tableName) + + var count int + + err = r.Scan(&count) + + if err != nil { + t.Fatalf("Unable to scan row result: %s", err) + } + + if count != totalRows { + t.Fatalf("Expected %d rows, got %d", totalRows, count) + } + + // Commit the transaction + tx.Commit() + + rows := dbt.mustQuery("SELECT COUNT(*) FROM " + dbt.tableName) + + var countAfterRollback int + + for rows.Next() { + err := rows.Scan(&countAfterRollback) + + if err != nil { + dbt.Fatal(err) + } + } + + if countAfterRollback != totalRows { + t.Fatalf("Expected %d rows, got %d", totalRows, countAfterRollback) + } + }) +} + +func TestPhoenixRollingBackTransactions(t *testing.T) { + + skipTestIfNotPhoenix(t) + + query := "?transactionIsolation=4" + + runTests(t, dsn+query, func(dbt *DBTest) { + + // Create and seed table + dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( + int INTEGER PRIMARY KEY + ) TRANSACTIONAL=true`) + + tx, err := dbt.db.Begin() + + if err != nil { + t.Fatalf("Unable to create transaction: %s", err) + } + + stmt, err := tx.Prepare(`UPSERT INTO ` + dbt.tableName + ` VALUES(?)`) + + if err != nil { + t.Fatalf("Could not prepare statement: %s", err) + } + + totalRows := 6 + + for i := 1; i <= totalRows; i++ { + _, err := stmt.Exec(i) + + if err != nil { + dbt.Fatal(err) + } + } + + r := tx.QueryRow(`SELECT COUNT(*) FROM ` + dbt.tableName) + + var count int + + err = r.Scan(&count) + + if err != nil { + t.Fatalf("Unable to scan row result: %s", err) + } + + if count != totalRows { + t.Fatalf("Expected %d rows, got %d", totalRows, count) + } + + // Rollback the transaction + tx.Rollback() + + rows := dbt.mustQuery(`SELECT COUNT(*) FROM ` + dbt.tableName) + + var countAfterRollback int + + for rows.Next() { + err := rows.Scan(&countAfterRollback) + + if err != nil { + dbt.Fatal(err) + } + } + + if countAfterRollback != 0 { + t.Fatalf("Expected %d rows, got %d", 0, countAfterRollback) + } + }) +} + +func TestPhoenixPreparedStatements(t *testing.T) { + + skipTestIfNotPhoenix(t) + + runTests(t, dsn, func(dbt *DBTest) { + + // Create and seed table + dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( + int INTEGER PRIMARY KEY + ) TRANSACTIONAL=false`) + + stmt, err := dbt.db.Prepare(`UPSERT INTO ` + dbt.tableName + ` VALUES(?)`) + + if err != nil { + dbt.Fatal(err) + } + + totalRows := 6 + + for i := 1; i <= totalRows; i++ { + _, err := stmt.Exec(i) + + if err != nil { + dbt.Fatal(err) + } + } + + queryStmt, err := dbt.db.Prepare(`SELECT * FROM ` + dbt.tableName + ` WHERE int = ?`) + + if err != nil { + dbt.Fatal(err) + } + + var res int + + for i := 1; i <= totalRows; i++ { + + err := queryStmt.QueryRow(i).Scan(&res) + + if err != nil { + dbt.Fatal(err) + } + + if res != i { + dbt.Fatalf("Unexpected query result. Expected %d, got %d.", i, res) + } + } + }) +} + +func TestPhoenixFetchingMoreRows(t *testing.T) { + + skipTestIfNotPhoenix(t) + + query := "?maxRowsTotal=-1&frameMaxSize=1" + + runTests(t, dsn+query, func(dbt *DBTest) { + + // Create and seed table + dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( + int INTEGER PRIMARY KEY + ) TRANSACTIONAL=false`) + + stmt, err := dbt.db.Prepare(`UPSERT INTO ` + dbt.tableName + ` VALUES(?)`) + + if err != nil { + dbt.Fatal(err) + } + + totalRows := 6 + + for i := 1; i <= totalRows; i++ { + _, err := stmt.Exec(i) + + if err != nil { + dbt.Fatal(err) + } + } + + rows := dbt.mustQuery(`SELECT * FROM ` + dbt.tableName) + defer rows.Close() + + count := 0 + + for rows.Next() { + count++ + } + + if count != totalRows { + dbt.Fatalf("Expected %d rows to be retrieved, retrieved %d", totalRows, count) + } + }) +} + +func TestPhoenixExecuteShortcut(t *testing.T) { + + skipTestIfNotPhoenix(t) + + runTests(t, dsn, func(dbt *DBTest) { + + // Create and seed table + dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( + int INTEGER PRIMARY KEY + ) TRANSACTIONAL=false`) + + res, err := dbt.db.Exec(`UPSERT INTO ` + dbt.tableName + ` VALUES(1)`) + + if err != nil { + dbt.Fatal(err) + } + + affected, err := res.RowsAffected() + + if err != nil { + dbt.Fatal(err) + } + + if affected != 1 { + dbt.Fatalf("Expected 1 row to be affected, %d affected", affected) + } + }) +} + +func TestPhoenixQueryShortcut(t *testing.T) { + + skipTestIfNotPhoenix(t) + + query := "?maxRowsTotal=-1&frameMaxSize=1" + + runTests(t, dsn+query, func(dbt *DBTest) { + + // Create and seed table + dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( + int INTEGER PRIMARY KEY + ) TRANSACTIONAL=false`) + + stmt, err := dbt.db.Prepare(`UPSERT INTO ` + dbt.tableName + ` VALUES(?)`) + + if err != nil { + dbt.Fatal(err) + } + + totalRows := 6 + + for i := 1; i <= totalRows; i++ { + _, err := stmt.Exec(i) + + if err != nil { + dbt.Fatal(err) + } + } + + rows := dbt.mustQuery(`SELECT * FROM ` + dbt.tableName) + defer rows.Close() + + count := 0 + + for rows.Next() { + count++ + } + + if count != totalRows { + dbt.Fatalf("Expected %d rows to be retrieved, retrieved %d", totalRows, count) + } + }) +} + +func TestPhoenixOptimisticConcurrency(t *testing.T) { + + skipTestIfNotPhoenix(t) + + query := "?transactionIsolation=4" + + runTests(t, dsn+query, func(dbt *DBTest) { + + // Create and seed table + dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( + id INTEGER PRIMARY KEY, + msg VARCHAR, + version INTEGER + ) TRANSACTIONAL=true`) + + stmt, err := dbt.db.Prepare(`UPSERT INTO ` + dbt.tableName + ` VALUES(?, ?, ?)`) + + if err != nil { + dbt.Fatal(err) + } + + totalRows := 6 + + for i := 1; i <= totalRows; i++ { + _, err := stmt.Exec(i, fmt.Sprintf("message version %d", i), i) + + if err != nil { + dbt.Fatal(err) + } + } + + // Start the transactions + tx1, err := dbt.db.Begin() + + if err != nil { + dbt.Fatal(err) + } + + tx2, err := dbt.db.Begin() + + if err != nil { + dbt.Fatal(err) + } + + // Select from first transaction + _ = tx1.QueryRow(`SELECT MAX(version) FROM ` + dbt.tableName) + + // Modify using second transaction + _, err = tx2.Exec(`UPSERT INTO `+dbt.tableName+` VALUES(?, ?, ?)`, 7, "message value 7", 7) + + if err != nil { + dbt.Fatal(err) + } + + err = tx2.Commit() + + if err != nil { + dbt.Fatal(err) + } + + // Modify using tx1 + _, err = tx1.Exec(`UPSERT INTO `+dbt.tableName+` VALUES(?, ?, ?)`, 7, "message value 7", 7) + + if err != nil { + dbt.Fatal(err) + } + + err = tx1.Commit() + + if err == nil { + dbt.Fatal("Expected an error, but did not receive any.") + } + + errName := err.(errors.ResponseError).Name + + if errName != "transaction_conflict_exception" { + dbt.Fatal("Expected transaction_conflict") + } + }) +} + +func TestPhoenixLastInsertIDShouldReturnError(t *testing.T) { + + skipTestIfNotPhoenix(t) + + runTests(t, dsn, func(dbt *DBTest) { + + dbt.mustExec(`DROP SEQUENCE IF EXISTS test_sequence`) + + dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( + id INTEGER PRIMARY KEY, + msg VARCHAR, + version INTEGER + ) TRANSACTIONAL=false`) + + dbt.mustExec(`CREATE SEQUENCE test_sequence`) + + res, err := dbt.db.Exec(`UPSERT INTO ` + dbt.tableName + ` VALUES(NEXT VALUE FOR test_sequence, 'abc', 1)`) + + dbt.mustExec(`DROP SEQUENCE test_sequence`) + + if err != nil { + dbt.Fatal(err) + } + + _, err = res.LastInsertId() + + if err == nil { + dbt.Fatal("Expected an error as Avatica does not support LastInsertId(), but there was no error.") + } + }) +} + +func TestPhoenixSchemaSupport(t *testing.T) { + + skipTestIfNotPhoenix(t) + + db, err := sql.Open("avatica", dsn) + + if err != nil { + t.Fatalf("error connecting: %s", err.Error()) + } + + defer db.Close() + + _, err = db.Exec("CREATE SCHEMA IF NOT EXISTS avaticatest") + + if err != nil { + t.Fatalf("error creating schema: %s", err) + } + + defer db.Exec("DROP SCHEMA IF EXISTS avaticatest") + + path := "/avaticatest" + + runTests(t, dsn+path, func(dbt *DBTest) { + + // Create and seed table + dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( + int INTEGER PRIMARY KEY + ) TRANSACTIONAL=false`) + + defer dbt.mustExec(`DROP TABLE IF EXISTS ` + dbt.tableName) + + _, err := dbt.db.Exec(`UPSERT INTO ` + dbt.tableName + ` VALUES(1)`) + + if err != nil { + dbt.Fatal(err) + } + + rows := dbt.mustQuery(`SELECT * FROM avaticatest.` + dbt.tableName) + defer rows.Close() + + count := 0 + + for rows.Next() { + count++ + } + + if count != 1 { + dbt.Errorf("Expected 1 row, got %d rows back,", count) + } + }) +} + +func TestPhoenixMultipleSchemaSupport(t *testing.T) { + + skipTestIfNotPhoenix(t) + + db, err := sql.Open("avatica", dsn) + + if err != nil { + t.Fatalf("error connecting: %s", err.Error()) + } + + defer db.Close() + + _, err = db.Exec("CREATE SCHEMA IF NOT EXISTS avaticatest1") + + if err != nil { + t.Fatalf("error creating schema: %s", err) + } + + defer db.Exec("DROP SCHEMA IF EXISTS avaticatest1") + + _, err = db.Exec("CREATE SCHEMA IF NOT EXISTS avaticatest2") + + if err != nil { + t.Fatalf("error creating schema: %s", err) + } + + defer db.Exec("DROP SCHEMA IF EXISTS avaticatest2") + + path := "/avaticatest1" + + runTests(t, dsn+path, func(dbt *DBTest) { + + // Create and seed table + dbt.mustExec(`CREATE TABLE avaticatest2.` + dbt.tableName + ` ( + int INTEGER PRIMARY KEY + ) TRANSACTIONAL=false`) + + defer dbt.mustExec(`DROP TABLE IF EXISTS avaticatest2.` + dbt.tableName) + + _, err := dbt.db.Exec(`UPSERT INTO avaticatest2.` + dbt.tableName + ` VALUES(1)`) + + if err != nil { + dbt.Fatal(err) + } + + rows := dbt.mustQuery(`SELECT * FROM avaticatest2.` + dbt.tableName) + defer rows.Close() + + count := 0 + + for rows.Next() { + count++ + } + + if count != 1 { + dbt.Errorf("Expected 1 row, got %d rows back,", count) + } + }) +} + +func TestPhoenixErrorCodeParsing(t *testing.T) { + + skipTestIfNotPhoenix(t) + + db, err := sql.Open("avatica", dsn) + + if err != nil { + t.Fatalf("error connecting: %s", err.Error()) + } + + defer db.Close() + + _, err = db.Query("SELECT * FROM table_that_does_not_exist") + + if err == nil { + t.Error("Expected error due to selecting from non-existent table, but there was no error.") + } + + resErr, ok := err.(errors.ResponseError) + + if !ok { + t.Fatalf("Error type was not ResponseError") + } + + if resErr.ErrorCode != 1012 { + t.Errorf("Expected error code to be %d, got %d.", 1012, resErr.ErrorCode) + } + + if resErr.SqlState != "42M03" { + t.Errorf("Expected SQL state to be %s, got %s.", "42M03", resErr.SqlState) + } +}
http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/2968def4/driver_test.go ---------------------------------------------------------------------- diff --git a/driver_test.go b/driver_test.go index 2d0f8b0..39f648a 100644 --- a/driver_test.go +++ b/driver_test.go @@ -18,16 +18,12 @@ package avatica import ( - "bytes" "context" - "crypto/sha256" "database/sql" "fmt" - "io/ioutil" "math/rand" "net/http" "os" - "path/filepath" "testing" "time" ) @@ -46,9 +42,19 @@ func init() { return defaultValue } - dsn = env("AVATICA_HOST", "http://phoenix:8765") + var serverAddr string - // Wait for the phoenix server to be ready + if val := os.Getenv("AVATICA_FLAVOR"); val == "PHOENIX" { + serverAddr = env("PHOENIX_HOST", "http://phoenix:8765") + } else if val == "HSQLDB" { + serverAddr = env("HSQLDB_HOST", "http://hsqldb:8765") + } else { + panic("The AVATICA_FLAVOR environment variable should be either PHOENIX or HSQLDB") + } + + dsn = serverAddr + + // Wait for the avatica server to be ready ctx, _ := context.WithTimeout(context.Background(), 5*time.Minute) ticker := time.NewTicker(2 * time.Second) @@ -56,10 +62,10 @@ func init() { select { case <-ctx.Done(): - panic("Timed out while waiting for the phoenix server to be ready after 5 minutes.") + panic("Timed out while waiting for the avatica server to be ready after 5 minutes.") case <-ticker.C: - resp, err := http.Get(dsn) + resp, err := http.Get(serverAddr) if err == nil { resp.Body.Close() @@ -131,837 +137,6 @@ func runTests(t *testing.T, dsn string, tests ...func(dbt *DBTest)) { } } -func TestConnectionMustBeOpenedWithAutoCommitTrue(t *testing.T) { - - runTests(t, dsn, func(dbt *DBTest) { - - // Create and seed table - dbt.mustExec("CREATE TABLE " + dbt.tableName + " (id BIGINT PRIMARY KEY, val VARCHAR) TRANSACTIONAL=false") - - dbt.mustExec("UPSERT INTO " + dbt.tableName + " VALUES (1,'A')") - - dbt.mustExec("UPSERT INTO " + dbt.tableName + " VALUES (2,'B')") - - rows := dbt.mustQuery("SELECT COUNT(*) FROM " + dbt.tableName) - defer rows.Close() - - for rows.Next() { - - var count int - - err := rows.Scan(&count) - - if err != nil { - dbt.Fatal(err) - } - - if count != 2 { - dbt.Fatalf("There should be 2 rows, got %d", count) - } - } - - }) -} - -func TestZeroValues(t *testing.T) { - - runTests(t, dsn, func(dbt *DBTest) { - - // Create and seed table - dbt.mustExec("CREATE TABLE " + dbt.tableName + " (int INTEGER PRIMARY KEY, flt FLOAT, bool BOOLEAN, str VARCHAR) TRANSACTIONAL=false") - - dbt.mustExec("UPSERT INTO " + dbt.tableName + " VALUES (0, 0.0, false, '')") - - rows := dbt.mustQuery("SELECT * FROM " + dbt.tableName) - defer rows.Close() - - for rows.Next() { - - var i int - var flt float64 - var b bool - var s string - - err := rows.Scan(&i, &flt, &b, &s) - - if err != nil { - dbt.Fatal(err) - } - - if i != 0 { - dbt.Fatalf("Integer should be 0, got %v", i) - } - - if flt != 0.0 { - dbt.Fatalf("Float should be 0.0, got %v", flt) - } - - if b != false { - dbt.Fatalf("Boolean should be false, got %v", b) - } - - if s != "" { - dbt.Fatalf("String should be \"\", got %v", s) - } - } - - }) -} - -func TestDataTypes(t *testing.T) { - - runTests(t, dsn, func(dbt *DBTest) { - - // Create and seed table - dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( - int INTEGER PRIMARY KEY, - uint UNSIGNED_INT, - bint BIGINT, - ulong UNSIGNED_LONG, - tint TINYINT, - utint UNSIGNED_TINYINT, - sint SMALLINT, - usint UNSIGNED_SMALLINT, - flt FLOAT, - uflt UNSIGNED_FLOAT, - dbl DOUBLE, - udbl UNSIGNED_DOUBLE, - dec DECIMAL, - bool BOOLEAN, - tm TIME, - dt DATE, - tmstmp TIMESTAMP, - utm UNSIGNED_TIME, - udt UNSIGNED_DATE, - utmstmp UNSIGNED_TIMESTAMP, - var VARCHAR, - ch CHAR(3), - bin BINARY(20), - varbin VARBINARY - ) TRANSACTIONAL=false`) - - var ( - integerValue int = -20 - uintegerValue int = 5 - bintValue int = -9223372036854775807 - ulongValue int = 9223372036854775807 - tintValue int = -128 - utintValue int = 126 - sintValue int = -32768 - usintValue int = 32767 - fltValue float64 = -3.555 - ufltValue float64 = 3.555 - dblValue float64 = -9.555 - udblValue float64 = 9.555 - decValue string = "1.333" - booleanValue bool = true - tmValue time.Time = time.Date(0, 1, 1, 21, 21, 21, 222000000, time.UTC) - dtValue time.Time = time.Date(2100, 2, 1, 0, 0, 0, 0, time.UTC) - tmstmpValue time.Time = time.Date(2100, 2, 1, 21, 21, 21, 222000000, time.UTC) - utmValue time.Time = time.Date(0, 1, 1, 21, 21, 21, 222000000, time.UTC) - udtValue time.Time = time.Date(2100, 2, 1, 0, 0, 0, 0, time.UTC) - utmstmpValue time.Time = time.Date(2100, 2, 1, 21, 21, 21, 222000000, time.UTC) - varcharValue string = "test string" - chValue string = "a" - binValue []byte = make([]byte, 20, 20) - varbinValue []byte = []byte("testtesttest") - ) - - copy(binValue[:], []byte("test")) - - dbt.mustExec(`UPSERT INTO `+dbt.tableName+` VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - integerValue, - uintegerValue, - bintValue, - ulongValue, - tintValue, - utintValue, - sintValue, - usintValue, - fltValue, - ufltValue, - dblValue, - udblValue, - decValue, - booleanValue, - tmValue, - dtValue, - tmstmpValue, - utmValue, - udtValue, - utmstmpValue, - varcharValue, - chValue, - binValue, - varbinValue, - ) - - rows := dbt.mustQuery("SELECT * FROM " + dbt.tableName) - defer rows.Close() - - var ( - integer int - uinteger int - bint int - ulong int - tint int - utint int - sint int - usint int - flt float64 - uflt float64 - dbl float64 - udbl float64 - dec string - boolean bool - tm time.Time - dt time.Time - tmstmp time.Time - utm time.Time - udt time.Time - utmstmp time.Time - varchar string - ch string - bin []byte - varbin []byte - ) - - for rows.Next() { - - err := rows.Scan(&integer, &uinteger, &bint, &ulong, &tint, &utint, &sint, &usint, &flt, &uflt, &dbl, &udbl, &dec, &boolean, &tm, &dt, &tmstmp, &utm, &udt, &utmstmp, &varchar, &ch, &bin, &varbin) - - if err != nil { - dbt.Fatal(err) - } - } - - comparisons := []struct { - result interface{} - expected interface{} - }{ - {integer, integerValue}, - {uinteger, uintegerValue}, - {bint, bintValue}, - {ulong, ulongValue}, - {tint, tintValue}, - {utint, utintValue}, - {sint, sintValue}, - {usint, usintValue}, - {flt, fltValue}, - {uflt, ufltValue}, - {dbl, dblValue}, - {udbl, udblValue}, - {dec, decValue}, - {boolean, booleanValue}, - {tm, tmValue}, - {dt, dtValue}, - {tmstmp, tmstmpValue}, - {utm, utmValue}, - {udt, udtValue}, - {utmstmp, utmstmpValue}, - {varchar, varcharValue}, - {ch, chValue}, - {bin, binValue}, - {varbin, varbinValue}, - } - - for _, tt := range comparisons { - - if v, ok := tt.expected.(time.Time); ok { - - if !v.Equal(tt.result.(time.Time)) { - dbt.Fatalf("Expected %v, got %v.", tt.expected, tt.result) - } - - } else if v, ok := tt.expected.([]byte); ok { - - if !bytes.Equal(v, tt.result.([]byte)) { - dbt.Fatalf("Expected %v, got %v.", tt.expected, tt.result) - } - - } else if tt.expected != tt.result { - - dbt.Fatalf("Expected %v, got %v.", tt.expected, tt.result) - } - } - }) -} - -func TestLocations(t *testing.T) { - - query := "?location=Australia/Melbourne" - - runTests(t, dsn+query, func(dbt *DBTest) { - - // Create and seed table - dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( - tm TIME PRIMARY KEY, - dt DATE, - tmstmp TIMESTAMP - ) TRANSACTIONAL=false`) - - loc, err := time.LoadLocation("Australia/Melbourne") - - if err != nil { - dbt.Fatalf("Unexpected error: %s", err) - } - - var ( - tmValue time.Time = time.Date(0, 1, 1, 21, 21, 21, 222000000, loc) - dtValue time.Time = time.Date(2100, 2, 1, 0, 0, 0, 0, loc) - tmstmpValue time.Time = time.Date(2100, 2, 1, 21, 21, 21, 222000000, loc) - ) - - dbt.mustExec(`UPSERT INTO `+dbt.tableName+` VALUES (?, ?, ?)`, - tmValue, - dtValue, - tmstmpValue, - ) - - rows := dbt.mustQuery("SELECT * FROM " + dbt.tableName) - defer rows.Close() - - var ( - tm time.Time - dt time.Time - tmstmp time.Time - ) - - for rows.Next() { - - err := rows.Scan(&tm, &dt, &tmstmp) - - if err != nil { - dbt.Fatal(err) - } - } - - comparisons := []struct { - result time.Time - expected time.Time - }{ - {tm, tmValue}, - {dt, dtValue}, - {tmstmp, tmstmpValue}, - } - - for _, tt := range comparisons { - - if !tt.result.Equal(tt.expected) { - dbt.Fatalf("Expected %v, got %v.", tt.expected, tt.result) - } - } - }) -} - -func TestDateAndTimestampsBefore1970(t *testing.T) { - - runTests(t, dsn, func(dbt *DBTest) { - - // Create and seed table - dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( - int INTEGER PRIMARY KEY, - dt DATE, - tmstmp TIMESTAMP - ) TRANSACTIONAL=false`) - - var ( - integerValue int = 1 - dtValue time.Time = time.Date(1945, 5, 20, 0, 0, 0, 0, time.UTC) - tmstmpValue time.Time = time.Date(1911, 5, 20, 21, 21, 21, 222000000, time.UTC) - ) - - dbt.mustExec(`UPSERT INTO `+dbt.tableName+` VALUES (?, ?, ?)`, - integerValue, - dtValue, - tmstmpValue, - ) - - rows := dbt.mustQuery("SELECT dt, tmstmp FROM " + dbt.tableName) - defer rows.Close() - - var ( - dt time.Time - tmstmp time.Time - ) - - for rows.Next() { - err := rows.Scan(&dt, &tmstmp) - - if err != nil { - dbt.Fatal(err) - } - } - - comparisons := []struct { - result time.Time - expected time.Time - }{ - {dt, dtValue}, - {tmstmp, tmstmpValue}, - } - - for _, tt := range comparisons { - if !tt.expected.Equal(tt.result) { - dbt.Fatalf("Expected %v, got %v.", tt.expected, tt.result) - } - } - }) -} - -func TestStoreAndRetrieveBinaryData(t *testing.T) { - - runTests(t, dsn, func(dbt *DBTest) { - - // Create and seed table - dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( - int INTEGER PRIMARY KEY, - bin VARBINARY - ) TRANSACTIONAL=false`) - - filePath := filepath.Join("test-fixtures", "gopher.png") - - file, err := ioutil.ReadFile(filePath) - - if err != nil { - t.Fatalf("Unable to read text-fixture: %s", filePath) - } - - hash := sha256.Sum256(file) - - dbt.mustExec(`UPSERT INTO `+dbt.tableName+` VALUES (?, ?)`, - 1, - file, - ) - - rows := dbt.mustQuery("SELECT bin FROM " + dbt.tableName) - defer rows.Close() - - var receivedFile []byte - - for rows.Next() { - - err := rows.Scan(&receivedFile) - - if err != nil { - dbt.Fatal(err) - } - } - - ioutil.WriteFile("test-fixtures/gopher.png", receivedFile, os.ModePerm) - - receivedHash := sha256.Sum256(receivedFile) - - if !bytes.Equal(hash[:], receivedHash[:]) { - t.Fatalf("Hash of stored file (%x) does not equal hash of retrieved file (%x).", hash[:], receivedHash[:]) - } - }) -} - -func TestCommittingTransactions(t *testing.T) { - - query := "?transactionIsolation=4" - - runTests(t, dsn+query, func(dbt *DBTest) { - - // Create and seed table - dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( - int INTEGER PRIMARY KEY - ) TRANSACTIONAL=true`) - - tx, err := dbt.db.Begin() - - if err != nil { - t.Fatalf("Unable to create transaction: %s", err) - } - - stmt, err := tx.Prepare(`UPSERT INTO ` + dbt.tableName + ` VALUES(?)`) - - if err != nil { - t.Fatalf("Could not prepare statement: %s", err) - } - - totalRows := 6 - - for i := 1; i <= totalRows; i++ { - _, err := stmt.Exec(i) - - if err != nil { - dbt.Fatal(err) - } - } - - r := tx.QueryRow("SELECT COUNT(*) FROM " + dbt.tableName) - - var count int - - err = r.Scan(&count) - - if err != nil { - t.Fatalf("Unable to scan row result: %s", err) - } - - if count != totalRows { - t.Fatalf("Expected %d rows, got %d", totalRows, count) - } - - // Commit the transaction - tx.Commit() - - rows := dbt.mustQuery("SELECT COUNT(*) FROM " + dbt.tableName) - - var countAfterRollback int - - for rows.Next() { - err := rows.Scan(&countAfterRollback) - - if err != nil { - dbt.Fatal(err) - } - } - - if countAfterRollback != totalRows { - t.Fatalf("Expected %d rows, got %d", totalRows, countAfterRollback) - } - }) -} - -func TestRollingBackTransactions(t *testing.T) { - - query := "?transactionIsolation=4" - - runTests(t, dsn+query, func(dbt *DBTest) { - - // Create and seed table - dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( - int INTEGER PRIMARY KEY - ) TRANSACTIONAL=true`) - - tx, err := dbt.db.Begin() - - if err != nil { - t.Fatalf("Unable to create transaction: %s", err) - } - - stmt, err := tx.Prepare(`UPSERT INTO ` + dbt.tableName + ` VALUES(?)`) - - if err != nil { - t.Fatalf("Could not prepare statement: %s", err) - } - - totalRows := 6 - - for i := 1; i <= totalRows; i++ { - _, err := stmt.Exec(i) - - if err != nil { - dbt.Fatal(err) - } - } - - r := tx.QueryRow(`SELECT COUNT(*) FROM ` + dbt.tableName) - - var count int - - err = r.Scan(&count) - - if err != nil { - t.Fatalf("Unable to scan row result: %s", err) - } - - if count != totalRows { - t.Fatalf("Expected %d rows, got %d", totalRows, count) - } - - // Rollback the transaction - tx.Rollback() - - rows := dbt.mustQuery(`SELECT COUNT(*) FROM ` + dbt.tableName) - - var countAfterRollback int - - for rows.Next() { - err := rows.Scan(&countAfterRollback) - - if err != nil { - dbt.Fatal(err) - } - } - - if countAfterRollback != 0 { - t.Fatalf("Expected %d rows, got %d", 0, countAfterRollback) - } - }) -} - -func TestPreparedStatements(t *testing.T) { - - runTests(t, dsn, func(dbt *DBTest) { - - // Create and seed table - dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( - int INTEGER PRIMARY KEY - ) TRANSACTIONAL=false`) - - stmt, err := dbt.db.Prepare(`UPSERT INTO ` + dbt.tableName + ` VALUES(?)`) - - if err != nil { - dbt.Fatal(err) - } - - totalRows := 6 - - for i := 1; i <= totalRows; i++ { - _, err := stmt.Exec(i) - - if err != nil { - dbt.Fatal(err) - } - } - - queryStmt, err := dbt.db.Prepare(`SELECT * FROM ` + dbt.tableName + ` WHERE int = ?`) - - if err != nil { - dbt.Fatal(err) - } - - var res int - - for i := 1; i <= totalRows; i++ { - - err := queryStmt.QueryRow(i).Scan(&res) - - if err != nil { - dbt.Fatal(err) - } - - if res != i { - dbt.Fatalf("Unexpected query result. Expected %d, got %d.", i, res) - } - } - }) -} - -func TestFetchingMoreRows(t *testing.T) { - - query := "?maxRowsTotal=-1&frameMaxSize=1" - - runTests(t, dsn+query, func(dbt *DBTest) { - - // Create and seed table - dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( - int INTEGER PRIMARY KEY - ) TRANSACTIONAL=false`) - - stmt, err := dbt.db.Prepare(`UPSERT INTO ` + dbt.tableName + ` VALUES(?)`) - - if err != nil { - dbt.Fatal(err) - } - - totalRows := 6 - - for i := 1; i <= totalRows; i++ { - _, err := stmt.Exec(i) - - if err != nil { - dbt.Fatal(err) - } - } - - rows := dbt.mustQuery(`SELECT * FROM ` + dbt.tableName) - defer rows.Close() - - count := 0 - - for rows.Next() { - count++ - } - - if count != totalRows { - dbt.Fatalf("Expected %d rows to be retrieved, retrieved %d", totalRows, count) - } - }) -} - -func TestExecuteShortcut(t *testing.T) { - - runTests(t, dsn, func(dbt *DBTest) { - - // Create and seed table - dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( - int INTEGER PRIMARY KEY - ) TRANSACTIONAL=false`) - - res, err := dbt.db.Exec(`UPSERT INTO ` + dbt.tableName + ` VALUES(1)`) - - if err != nil { - dbt.Fatal(err) - } - - affected, err := res.RowsAffected() - - if err != nil { - dbt.Fatal(err) - } - - if affected != 1 { - dbt.Fatalf("Expected 1 row to be affected, %d affected", affected) - } - }) -} - -func TestQueryShortcut(t *testing.T) { - - query := "?maxRowsTotal=-1&frameMaxSize=1" - - runTests(t, dsn+query, func(dbt *DBTest) { - - // Create and seed table - dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( - int INTEGER PRIMARY KEY - ) TRANSACTIONAL=false`) - - stmt, err := dbt.db.Prepare(`UPSERT INTO ` + dbt.tableName + ` VALUES(?)`) - - if err != nil { - dbt.Fatal(err) - } - - totalRows := 6 - - for i := 1; i <= totalRows; i++ { - _, err := stmt.Exec(i) - - if err != nil { - dbt.Fatal(err) - } - } - - rows := dbt.mustQuery(`SELECT * FROM ` + dbt.tableName) - defer rows.Close() - - count := 0 - - for rows.Next() { - count++ - } - - if count != totalRows { - dbt.Fatalf("Expected %d rows to be retrieved, retrieved %d", totalRows, count) - } - }) -} - -func TestOptimisticConcurrency(t *testing.T) { - - query := "?transactionIsolation=4" - - runTests(t, dsn+query, func(dbt *DBTest) { - - // Create and seed table - dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( - id INTEGER PRIMARY KEY, - msg VARCHAR, - version INTEGER - ) TRANSACTIONAL=true`) - - stmt, err := dbt.db.Prepare(`UPSERT INTO ` + dbt.tableName + ` VALUES(?, ?, ?)`) - - if err != nil { - dbt.Fatal(err) - } - - totalRows := 6 - - for i := 1; i <= totalRows; i++ { - _, err := stmt.Exec(i, fmt.Sprintf("message version %d", i), i) - - if err != nil { - dbt.Fatal(err) - } - } - - // Start the transactions - tx1, err := dbt.db.Begin() - - if err != nil { - dbt.Fatal(err) - } - - tx2, err := dbt.db.Begin() - - if err != nil { - dbt.Fatal(err) - } - - // Select from first transaction - _ = tx1.QueryRow(`SELECT MAX(version) FROM ` + dbt.tableName) - - // Modify using second transaction - _, err = tx2.Exec(`UPSERT INTO `+dbt.tableName+` VALUES(?, ?, ?)`, 7, "message value 7", 7) - - if err != nil { - dbt.Fatal(err) - } - - err = tx2.Commit() - - if err != nil { - dbt.Fatal(err) - } - - // Modify using tx1 - _, err = tx1.Exec(`UPSERT INTO `+dbt.tableName+` VALUES(?, ?, ?)`, 7, "message value 7", 7) - - if err != nil { - dbt.Fatal(err) - } - - err = tx1.Commit() - - if err == nil { - dbt.Fatal("Expected an error, but did not receive any.") - } - - errName := err.(ResponseError).Name() - - if errName != "transaction_conflict_exception" { - dbt.Fatal("Expected transaction_conflict") - } - }) -} - -func TestLastInsertIDShouldReturnError(t *testing.T) { - - runTests(t, dsn, func(dbt *DBTest) { - - dbt.mustExec(`DROP SEQUENCE IF EXISTS test_sequence`) - - dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( - id INTEGER PRIMARY KEY, - msg VARCHAR, - version INTEGER - ) TRANSACTIONAL=false`) - - dbt.mustExec(`CREATE SEQUENCE test_sequence`) - - res, err := dbt.db.Exec(`UPSERT INTO ` + dbt.tableName + ` VALUES(NEXT VALUE FOR test_sequence, 'abc', 1)`) - - dbt.mustExec(`DROP SEQUENCE test_sequence`) - - if err != nil { - dbt.Fatal(err) - } - - _, err = res.LastInsertId() - - if err == nil { - dbt.Fatal("Expected an error as Avatica does not support LastInsertId(), but there was no error.") - } - }) -} - func TestConnectionToInvalidServerShouldReturnError(t *testing.T) { runTests(t, "http://invalid-server:8765", func(dbt *DBTest) { @@ -976,127 +151,3 @@ func TestConnectionToInvalidServerShouldReturnError(t *testing.T) { } }) } - -func TestSchemaSupport(t *testing.T) { - - db, err := sql.Open("avatica", dsn) - - if err != nil { - t.Fatalf("error connecting: %s", err.Error()) - } - - defer db.Close() - - db.Exec("CREATE SCHEMA IF NOT EXISTS avaticatest") - defer db.Exec("DROP SCHEMA IF EXISTS avaticatest") - - path := "/avaticatest" - - runTests(t, dsn+path, func(dbt *DBTest) { - - // Create and seed table - dbt.mustExec(`CREATE TABLE ` + dbt.tableName + ` ( - int INTEGER PRIMARY KEY - ) TRANSACTIONAL=false`) - - defer dbt.mustExec(`DROP TABLE IF EXISTS ` + dbt.tableName) - - _, err := dbt.db.Exec(`UPSERT INTO ` + dbt.tableName + ` VALUES(1)`) - - if err != nil { - dbt.Fatal(err) - } - - rows := dbt.mustQuery(`SELECT * FROM avaticatest.` + dbt.tableName) - defer rows.Close() - - count := 0 - - for rows.Next() { - count++ - } - - if count != 1 { - dbt.Errorf("Expected 1 row, got %d rows back,", count) - } - }) -} - -func TestMultipleSchemaSupport(t *testing.T) { - - db, err := sql.Open("avatica", dsn) - - if err != nil { - t.Fatalf("error connecting: %s", err.Error()) - } - - defer db.Close() - - db.Exec("CREATE SCHEMA IF NOT EXISTS avaticatest1") - defer db.Exec("DROP SCHEMA IF EXISTS avaticatest1") - - db.Exec("CREATE SCHEMA IF NOT EXISTS avaticatest2") - defer db.Exec("DROP SCHEMA IF EXISTS avaticatest2") - - path := "/avaticatest1" - - runTests(t, dsn+path, func(dbt *DBTest) { - - // Create and seed table - dbt.mustExec(`CREATE TABLE avaticatest2.` + dbt.tableName + ` ( - int INTEGER PRIMARY KEY - ) TRANSACTIONAL=false`) - - defer dbt.mustExec(`DROP TABLE IF EXISTS avaticatest2.` + dbt.tableName) - - _, err := dbt.db.Exec(`UPSERT INTO avaticatest2.` + dbt.tableName + ` VALUES(1)`) - - if err != nil { - dbt.Fatal(err) - } - - rows := dbt.mustQuery(`SELECT * FROM avaticatest2.` + dbt.tableName) - defer rows.Close() - - count := 0 - - for rows.Next() { - count++ - } - - if count != 1 { - dbt.Errorf("Expected 1 row, got %d rows back,", count) - } - }) -} - -func TestErrorCodeParsing(t *testing.T) { - - db, err := sql.Open("avatica", dsn) - - if err != nil { - t.Fatalf("error connecting: %s", err.Error()) - } - - defer db.Close() - - _, err = db.Query("SELECT * FROM table_that_does_not_exist") - - if err == nil { - t.Error("Expected error due to selecting from non-existent table, but there was no error.") - } - - resErr, ok := err.(ResponseError) - - if !ok { - t.Fatalf("Error type was not ResponseError") - } - - if resErr.ErrorCode != 1012 { - t.Errorf("Expected error code to be %d, got %d.", 1012, resErr.ErrorCode) - } - - if resErr.SqlState != "42M03" { - t.Errorf("Expected SQL state to be %s, got %s.", "42M03", resErr.SqlState) - } -} http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/2968def4/errors.go ---------------------------------------------------------------------- diff --git a/errors.go b/errors.go deleted file mode 100644 index 16a842a..0000000 --- a/errors.go +++ /dev/null @@ -1,324 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package avatica - -import ( - "regexp" - "strconv" - - "github.com/apache/calcite-avatica-go/message" -) - -// Error severity codes -const ( - Eunknown int8 = iota - Efatal - Eerror - Ewarning -) - -// RPCMetadata contains metadata about the call that caused the error. -type RPCMetadata struct { - ServerAddress string -} - -// ErrorCode represents the error code returned by the avatica server -type ErrorCode uint32 - -// SQLState represents the SQL code returned by the avatica server -type SQLState string - -// ResponseError is an error type that contains detailed information on -// what caused the server to return an error. -type ResponseError struct { - Exceptions []string - HasExceptions bool - ErrorMessage string - Severity int8 - ErrorCode ErrorCode - SqlState SQLState - Metadata *RPCMetadata -} - -func (r ResponseError) Error() string { - - msg := "An error was encountered while processing your request" - - if r.ErrorMessage != "" { - msg += ": " + r.ErrorMessage - } else if len(r.Exceptions) > 0 { - msg += ":\n" + r.Exceptions[0] - } - - return msg -} - -// Name returns the name of the error encountered by the server. -func (r ResponseError) Name() string { - return errorCodeNames[r.ErrorCode] -} - -// errorResponseToReponseError converts an error protocol buffer response -// to a native golang error. -func errorResponseToResponseError(message *message.ErrorResponse) ResponseError { - - var ( - errorCode int - sqlState string - ) - - re := regexp.MustCompile(`ERROR (\d+) \(([0-9a-zA-Z]+)\)`) - codes := re.FindStringSubmatch(message.ErrorMessage) - - if len(codes) > 1 { - errorCode, _ = strconv.Atoi(codes[1]) - } - - if len(codes) > 2 { - sqlState = codes[2] - } - - err := ResponseError{ - Exceptions: message.Exceptions, - ErrorMessage: message.ErrorMessage, - Severity: int8(message.Severity), - ErrorCode: ErrorCode(errorCode), - SqlState: SQLState(sqlState), - Metadata: &RPCMetadata{ - ServerAddress: message.GetMetadata().ServerAddress, - }, - } - - return err -} - -var errorCodeNames = map[ErrorCode]string{ - // Connection exceptions (errorcode 01, sqlstate 08) - 101: "io_exception", - 102: "malformed_connection_url", - 103: "cannot_establish_connection", - - // Data exceptions (errorcode 02, sqlstate 22) - 201: "illegal_data", - 202: "divide_by_zero", - 203: "type_mismatch", - 204: "value_in_upsert_not_constant", - 205: "malformed_url", - 206: "data_exceeds_max_capacity", - 207: "missing_max_length", - 208: "nonpositive_max_length", - 209: "decimal_precision_out_of_range", - 212: "server_arithmetic_error", - 213: "value_outside_range", - 214: "value_in_list_not_constant", - 215: "single_row_subquery_returns_multiple_rows", - 216: "subquery_returns_different_number_of_fields", - 217: "ambiguous_join_condition", - 218: "constraint_violation", - - 301: "concurrent_table_mutation", - 302: "cannot_index_column_on_type", - - // Invalid cursor state (errorcode 04, sqlstate 24) - 401: "cursor_before_first_row", - 402: "cursor_past_last_row", - - // Syntax error or access rule violation (errorcode 05, sqlstate 42) - 501: "ambiguous_table", - 502: "ambiguous_column", - 503: "index_missing_pk_columns", - 504: "column_not_found", - 505: "read_only_table", - 506: "cannot_drop_pk", - 509: "primary_key_missing", - 510: "primary_key_already_exists", - 511: "order_by_not_in_select_distinct", - 512: "invalid_primary_key_constraint", - 513: "array_not_allowed_in_primary_key", - 514: "column_exist_in_def", - 515: "order_by_array_not_supported", - 516: "non_equality_array_comparison", - 517: "invalid_not_null_constraint", - - // Invalid transaction state (errorcode 05, sqlstate 25) - 518: "read_only_connection", - 519: "varbinary_array_not_supported", - - // Expression index exceptions - 520: "aggregate_expression_not_allowed_in_index", - 521: "non_deterministic_expression_not_allowed_in_index", - 522: "stateless_expression_not_allowed_in_index", - - // Transaction exceptions - 523: "transaction_conflict_exception", - 524: "transaction_exception", - - // Union all related errors - 525: "select_column_num_in_unionall_diffs", - 526: "select_column_type_in_unionall_diffs", - - // Row timestamp column related errors - 527: "rowtimestamp_one_pk_col_only", - 528: "rowtimestamp_pk_col_only", - 529: "rowtimestamp_create_only", - 530: "rowtimestamp_col_invalid_type", - 531: "rowtimestamp_not_allowed_on_view", - 532: "invalid_scn", - - // Column family related exceptions - 1000: "single_pk_may_not_be_null", - 1001: "column_family_not_found", - 1002: "properties_for_family", - 1003: "primary_key_with_family_name", - 1004: "primary_key_out_of_order", - 1005: "varbinary_in_row_key", - 1006: "not_nullable_column_in_row_key", - 1015: "varbinary_last_pk", - 1023: "nullable_fixed_width_last_pk", - 1036: "cannot_modify_view_pk", - 1037: "base_table_column", - - // Key/value column related errors - 1007: "key_value_not_null", - - // View related errors - 1008: "view_with_table_config", - 1009: "view_with_properties", - - // Table related errors that are not in standard code - 1010: "cannot_mutate_table", - 1011: "unexpected_mutation_code", - 1012: "table_undefined", - 1013: "table_already_exist", - - // Syntax error - 1014: "type_not_supported_for_operator", - 1016: "aggregate_in_group_by", - 1017: "aggregate_in_where", - 1018: "aggregate_with_not_group_by_column", - 1019: "only_aggregate_in_having_clause", - 1020: "upsert_column_numbers_mismatch", - - // Table properties exception - 1021: "invalid_bucket_num", - 1022: "no_splits_on_salted_table", - 1024: "salt_only_on_create_table", - 1025: "set_unsupported_prop_on_alter_table", - 1038: "cannot_add_not_nullable_column", - 1026: "no_mutable_indexes", - 1028: "invalid_index_state_transition", - 1029: "invalid_mutable_index_config", - 1030: "cannot_create_tenant_specific_table", - 1034: "default_column_family_only_on_create_table", - 1040: "insufficient_multi_tenant_columns", - 1041: "tenantid_is_of_wrong_type", - 1045: "view_where_is_constant", - 1046: "cannot_update_view_column", - 1047: "too_many_indexes", - 1048: "no_local_index_on_table_with_immutable_rows", - 1049: "column_family_not_allowed_table_property", - 1050: "column_family_not_allowed_for_ttl", - 1051: "cannot_alter_property", - 1052: "cannot_set_property_for_column_not_added", - 1053: "cannot_set_table_property_add_column", - 1054: "no_local_indexes", - 1055: "unallowed_local_indexes", - 1056: "desc_varbinary_not_supported", - 1057: "no_table_specified_for_wildcard_select", - 1058: "unsupported_group_by_expressions", - 1069: "default_column_family_on_shared_table", - 1070: "only_table_may_be_declared_transactional", - 1071: "tx_may_not_switch_to_non_tx", - 1072: "store_nulls_must_be_true_for_transactional", - 1073: "cannot_start_transaction_with_scn_set", - 1074: "tx_max_versions_must_be_greater_than_one", - 1075: "cannot_specify_scn_for_txn_table", - 1076: "null_transaction_context", - 1077: "transaction_failed", - 1078: "cannot_create_txn_table_if_txns_disabled", - 1079: "cannot_alter_to_be_txn_if_txns_disabled", - 1080: "cannot_create_txn_table_with_row_timestamp", - 1081: "cannot_alter_to_be_txn_with_row_timestamp", - 1082: "tx_must_be_enabled_to_set_tx_context", - 1083: "tx_must_be_enabled_to_set_auto_flush", - 1084: "tx_must_be_enabled_to_set_isolation_level", - 1085: "tx_unable_to_get_write_fence", - 1086: "sequence_not_castable_to_auto_partition_id_column", - 1087: "cannot_coerce_auto_partition_id", - - // Sequence related - 1200: "sequence_already_exist", - 1201: "sequence_undefined", - 1202: "start_with_must_be_constant", - 1203: "increment_by_must_be_constant", - 1204: "cache_must_be_non_negative_constant", - 1205: "invalid_use_of_next_value_for", - 1206: "cannot_call_current_before_next_value", - 1207: "empty_sequence_cache", - 1208: "minvalue_must_be_constant", - 1209: "maxvalue_must_be_constant", - 1210: "minvalue_must_be_less_than_or_equal_to_maxvalue", - 1211: "starts_with_must_be_between_min_max_value", - 1212: "sequence_val_reached_max_value", - 1213: "sequence_val_reached_min_value", - 1214: "increment_by_must_not_be_zero", - 1215: "num_seq_to_allocate_must_be_constant", - 1216: "num_seq_to_allocate_not_supported", - 1217: "auto_partition_sequence_undefined", - - // Parser error. (errorcode 06, sqlstate 42p) - 601: "parser_error", - 602: "missing_token", - 603: "unwanted_token", - 604: "mismatched_token", - 605: "unknown_function", - - // Implementation defined class. execution exceptions (errorcode 11, sqlstate xcl) - 1101: "resultset_closed", - 1102: "get_table_regions_fail", - 1103: "execute_query_not_applicable", - 1104: "execute_update_not_applicable", - 1105: "split_point_not_constant", - 1106: "batch_exception", - 1107: "execute_update_with_non_empty_batch", - 1108: "stale_region_boundary_cache", - 1109: "cannot_split_local_index", - 1110: "cannot_salt_local_index", - 1120: "index_failure_block_write", - 1130: "update_cache_frequency_invalid", - 1131: "cannot_drop_col_append_only_schema", - 1132: "view_append_only_schema", - - // Implementation defined class. phoenix internal error. (errorcode 20, sqlstate int) - 2001: "cannot_call_method_on_type", - 2002: "class_not_unwrappable", - 2003: "param_index_out_of_bound", - 2004: "param_value_unbound", - 2005: "interrupted_exception", - 2006: "incompatible_client_server_jar", - 2007: "outdated_jars", - 2008: "index_metadata_not_found", - 2009: "unknown_error_code", - 6000: "operation_timed_out", - 6001: "function_undefined", - 6002: "function_already_exist", - 6003: "unallowed_user_defined_functions", - 721: "schema_already_exists", - 722: "schema_not_found", - 723: "cannot_mutate_schema", -} http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/2968def4/errors/errors.go ---------------------------------------------------------------------- diff --git a/errors/errors.go b/errors/errors.go new file mode 100644 index 0000000..ee79af9 --- /dev/null +++ b/errors/errors.go @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package errors + +// Error severity codes +const ( + Eunknown int8 = iota + Efatal + Eerror + Ewarning +) + +// RPCMetadata contains metadata about the call that caused the error. +type RPCMetadata struct { + ServerAddress string +} + +// ErrorCode represents the error code returned by the avatica server +type ErrorCode uint32 + +// SQLState represents the SQL code returned by the avatica server +type SQLState string + +// ResponseError is an error type that contains detailed information on +// what caused the server to return an error. +type ResponseError struct { + Exceptions []string + HasExceptions bool + ErrorMessage string + Severity int8 + ErrorCode ErrorCode + SqlState SQLState + Metadata *RPCMetadata + Name string +} + +func (r ResponseError) Error() string { + + msg := "An error was encountered while processing your request" + + if r.ErrorMessage != "" { + msg += ": " + r.ErrorMessage + } else if len(r.Exceptions) > 0 { + msg += ":\n" + r.Exceptions[0] + } + + return msg +} http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/2968def4/gen-protobuf.bat ---------------------------------------------------------------------- diff --git a/gen-protobuf.bat b/gen-protobuf.bat index 143b6dc..83c51cc 100644 --- a/gen-protobuf.bat +++ b/gen-protobuf.bat @@ -14,7 +14,7 @@ rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. rem See the License for the specific language governing permissions and rem limitations under the License. -SET AVATICA_VER=rel/avatica-1.10.0 +SET AVATICA_VER=rel/avatica-1.11.0 IF EXIST message\ rmdir /Q /S message IF EXIST avatica-tmp\ rmdir /Q /S avatica-tmp http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/2968def4/gen-protobuf.sh ---------------------------------------------------------------------- diff --git a/gen-protobuf.sh b/gen-protobuf.sh index 73d9793..55b236f 100644 --- a/gen-protobuf.sh +++ b/gen-protobuf.sh @@ -18,7 +18,7 @@ set -e rm -rf message avatica-tmp -export AVATICA_VER="rel/avatica-1.10.0" +export AVATICA_VER="rel/avatica-1.11.0" mkdir -p avatica-tmp pushd avatica-tmp &> /dev/null http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/2968def4/generic/generic.go ---------------------------------------------------------------------- diff --git a/generic/generic.go b/generic/generic.go new file mode 100644 index 0000000..8629375 --- /dev/null +++ b/generic/generic.go @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package generic + +import ( + "fmt" + "math" + "reflect" + "time" + + "github.com/apache/calcite-avatica-go/errors" + "github.com/apache/calcite-avatica-go/internal" + "github.com/apache/calcite-avatica-go/message" +) + +type Adapter struct { +} + +func (a Adapter) GetPingStatement() string { + return "SELECT 1" +} + +func (a Adapter) GetColumnTypeDefinition(col *message.ColumnMetaData) *internal.Column { + + column := &internal.Column{ + Name: col.ColumnName, + TypeName: col.Type.Name, + Nullable: col.Nullable != 0, + } + + // Handle precision and length + switch col.Type.Name { + case "DECIMAL": + + precision := int64(col.Precision) + + if precision == 0 { + precision = math.MaxInt64 + } + + scale := int64(col.Scale) + + if scale == 0 { + scale = math.MaxInt64 + } + + column.PrecisionScale = &internal.PrecisionScale{ + Precision: precision, + Scale: scale, + } + case "VARCHAR", "CHARACTER VARYING", "CHAR", "CHARACTER", "BINARY", "VARBINARY", "BINARY VARYING": + column.Length = int64(col.Precision) + } + + // Handle scan types + switch col.Type.Name { + case "INTEGER", "BIGINT", "TINYINT", "SMALLINT": + column.ScanType = reflect.TypeOf(int64(0)) + + case "REAL", "FLOAT", "DOUBLE": + column.ScanType = reflect.TypeOf(float64(0)) + + case "DECIMAL", "NUMERIC", "VARCHAR", "CHAR", "CHARACTER", "CHARACTER VARYING": + column.ScanType = reflect.TypeOf("") + + case "BOOLEAN": + column.ScanType = reflect.TypeOf(false) + + case "TIME", "DATE", "TIMESTAMP", "TIMESTAMP WITH LOCAL TIME ZONE", "TIMESTAMP WITH TIME ZONE": + column.ScanType = reflect.TypeOf(time.Time{}) + + case "BINARY", "VARBINARY", "BINARY VARYING": + column.ScanType = reflect.TypeOf([]byte{}) + + default: + panic(fmt.Sprintf("scantype for %s is not implemented", col.Type.Name)) + } + + // Handle rep type special cases for decimals, floats, date, time and timestamp + switch col.Type.Name { + case "DECIMAL", "NUMERIC": + column.Rep = message.Rep_BIG_DECIMAL + case "FLOAT", "REAL": + column.Rep = message.Rep_FLOAT + case "TIME": + column.Rep = message.Rep_JAVA_SQL_TIME + case "DATE": + column.Rep = message.Rep_JAVA_SQL_DATE + case "TIMESTAMP", "TIMESTAMP WITH LOCAL TIME ZONE", "TIMESTAMP WITH TIME ZONE": + column.Rep = message.Rep_JAVA_SQL_TIMESTAMP + default: + column.Rep = col.Type.Rep + } + + return column +} + +func (a Adapter) ErrorResponseToResponseError(message *message.ErrorResponse) errors.ResponseError { + + return errors.ResponseError{ + Exceptions: message.Exceptions, + ErrorMessage: message.ErrorMessage, + Severity: int8(message.Severity), + ErrorCode: errors.ErrorCode(message.ErrorCode), + SqlState: errors.SQLState(message.SqlState), + Metadata: &errors.RPCMetadata{ + ServerAddress: message.GetMetadata().ServerAddress, + }, + } +} http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/2968def4/hsqldb/hsqldb.go ---------------------------------------------------------------------- diff --git a/hsqldb/hsqldb.go b/hsqldb/hsqldb.go new file mode 100644 index 0000000..808449f --- /dev/null +++ b/hsqldb/hsqldb.go @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package hsqldb + +import ( + "fmt" + "math" + "reflect" + "time" + + "github.com/apache/calcite-avatica-go/errors" + "github.com/apache/calcite-avatica-go/internal" + "github.com/apache/calcite-avatica-go/message" +) + +type Adapter struct { +} + +func (a Adapter) GetPingStatement() string { + return "SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS" +} + +func (a Adapter) GetColumnTypeDefinition(col *message.ColumnMetaData) *internal.Column { + + column := &internal.Column{ + Name: col.ColumnName, + TypeName: col.Type.Name, + Nullable: col.Nullable != 0, + } + + // Handle precision and length + switch col.Type.Name { + case "DECIMAL", "NUMERIC": + + precision := int64(col.Precision) + + if precision == 0 { + precision = math.MaxInt64 + } + + scale := int64(col.Scale) + + if scale == 0 { + scale = math.MaxInt64 + } + + column.PrecisionScale = &internal.PrecisionScale{ + Precision: precision, + Scale: scale, + } + case "VARCHAR", "CHAR", "CHARACTER", "BINARY", "VARBINARY", "BIT", "BITVARYING": + column.Length = int64(col.Precision) + } + + // Handle scan types + switch col.Type.Name { + case "INTEGER", "BIGINT", "TINYINT", "SMALLINT": + column.ScanType = reflect.TypeOf(int64(0)) + + case "FLOAT", "DOUBLE": + column.ScanType = reflect.TypeOf(float64(0)) + + case "DECIMAL", "NUMERIC", "VARCHAR", "CHAR", "CHARACTER": + column.ScanType = reflect.TypeOf("") + + case "BOOLEAN": + column.ScanType = reflect.TypeOf(false) + + case "TIME", "DATE", "TIMESTAMP": + column.ScanType = reflect.TypeOf(time.Time{}) + + case "BINARY", "VARBINARY": + column.ScanType = reflect.TypeOf([]byte{}) + + default: + panic(fmt.Sprintf("scantype for %s is not implemented", col.Type.Name)) + } + + // Handle rep type special cases for decimals, floats, date, time and timestamp + switch col.Type.Name { + case "DECIMAL", "NUMERIC": + column.Rep = message.Rep_BIG_DECIMAL + case "FLOAT": + column.Rep = message.Rep_FLOAT + case "TIME": + column.Rep = message.Rep_JAVA_SQL_TIME + case "DATE": + column.Rep = message.Rep_JAVA_SQL_DATE + case "TIMESTAMP": + column.Rep = message.Rep_JAVA_SQL_TIMESTAMP + default: + column.Rep = col.Type.Rep + } + + return column +} + +func (a Adapter) ErrorResponseToResponseError(message *message.ErrorResponse) errors.ResponseError { + + return errors.ResponseError{ + Exceptions: message.Exceptions, + ErrorMessage: message.ErrorMessage, + Severity: int8(message.Severity), + ErrorCode: errors.ErrorCode(message.ErrorCode), + SqlState: errors.SQLState(message.SqlState), + Metadata: &errors.RPCMetadata{ + ServerAddress: message.GetMetadata().ServerAddress, + }, + } +} http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/2968def4/http_client.go ---------------------------------------------------------------------- diff --git a/http_client.go b/http_client.go index dff7a02..b58a62a 100644 --- a/http_client.go +++ b/http_client.go @@ -55,12 +55,18 @@ type httpClientAuthConfig struct { // httpClient wraps the default http.Client to communicate with the Avatica server. type httpClient struct { - host string - authConfig httpClientAuthConfig + host string + authConfig httpClientAuthConfig + httpClient *http.Client + kerberosClient client.Client +} - httpClient *http.Client +type avaticaError struct { + message *avaticaMessage.ErrorResponse +} - kerberosClient client.Client +func (e avaticaError) Error() string { + return fmt.Sprintf("avatica encountered an error: %s", e.message.ErrorMessage) } // NewHTTPClient creates a new httpClient from a host. @@ -202,7 +208,7 @@ func (c *httpClient) post(ctx context.Context, message proto.Message) (proto.Mes } } - return nil, errorResponseToResponseError(v) + return nil, avaticaError{v} } return inner, nil http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/2968def4/internal/column.go ---------------------------------------------------------------------- diff --git a/internal/column.go b/internal/column.go new file mode 100644 index 0000000..81fee3f --- /dev/null +++ b/internal/column.go @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package internal + +import ( + "reflect" + + "github.com/apache/calcite-avatica-go/message" +) + +type Column struct { + Name string + TypeName string + Rep message.Rep + Length int64 + Nullable bool + PrecisionScale *PrecisionScale + ScanType reflect.Type +} + +type PrecisionScale struct { + Precision int64 + Scale int64 +} http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/2968def4/moby.yml ---------------------------------------------------------------------- diff --git a/moby.yml b/moby.yml index 07cc28a..d1ceefc 100644 --- a/moby.yml +++ b/moby.yml @@ -16,13 +16,18 @@ services: - id: phoenix image: boostport/hbase-phoenix-all-in-one:1.3-4.13 + + - id: hsqldb + image: f21global/calcite-avatica:1.11.0-hypersql + command: -u jdbc:hsqldb:mem:public ports: - "8765" dev: image: golang:1.10-alpine env: - AVATICA_HOST: http://phoenix:8765 + PHOENIX_HOST: http://phoenix:8765 + HSQLDB_HOST: http://hsqldb:8765 steps: - type: script name: Set up workspace @@ -34,4 +39,4 @@ dev: name: Run tests cwd: $GOPATH/src/github.com/apache/calcite-avatica-go options: - command: go test -v $(go list ./... | grep -v /vendor/) \ No newline at end of file + command: export AVATICA_FLAVOR=HSQLDB && go test -v ./... && export AVATICA_FLAVOR=PHOENIX && go test -v ./... \ No newline at end of file http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/2968def4/phoenix/phoenix.go ---------------------------------------------------------------------- diff --git a/phoenix/phoenix.go b/phoenix/phoenix.go new file mode 100644 index 0000000..08ad4f9 --- /dev/null +++ b/phoenix/phoenix.go @@ -0,0 +1,365 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package phoenix + +import ( + "fmt" + "math" + "reflect" + "regexp" + "strconv" + "time" + + "github.com/apache/calcite-avatica-go/errors" + "github.com/apache/calcite-avatica-go/internal" + "github.com/apache/calcite-avatica-go/message" +) + +type Adapter struct { +} + +func (a Adapter) GetPingStatement() string { + return "SELECT 1" +} + +func (a Adapter) GetColumnTypeDefinition(col *message.ColumnMetaData) *internal.Column { + + column := &internal.Column{ + Name: col.ColumnName, + TypeName: col.Type.Name, + Nullable: col.Nullable != 0, + } + + // Handle precision and length + switch col.Type.Name { + case "DECIMAL": + + precision := int64(col.Precision) + + if precision == 0 { + precision = math.MaxInt64 + } + + scale := int64(col.Scale) + + if scale == 0 { + scale = math.MaxInt64 + } + + column.PrecisionScale = &internal.PrecisionScale{ + Precision: precision, + Scale: scale, + } + case "VARCHAR", "CHAR", "BINARY": + column.Length = int64(col.Precision) + case "VARBINARY": + column.Length = math.MaxInt64 + } + + // Handle scan types + switch col.Type.Name { + case "INTEGER", "UNSIGNED_INT", "BIGINT", "UNSIGNED_LONG", "TINYINT", "UNSIGNED_TINYINT", "SMALLINT", "UNSIGNED_SMALLINT": + column.ScanType = reflect.TypeOf(int64(0)) + + case "FLOAT", "UNSIGNED_FLOAT", "DOUBLE", "UNSIGNED_DOUBLE": + column.ScanType = reflect.TypeOf(float64(0)) + + case "DECIMAL", "VARCHAR", "CHAR": + column.ScanType = reflect.TypeOf("") + + case "BOOLEAN": + column.ScanType = reflect.TypeOf(false) + + case "TIME", "DATE", "TIMESTAMP", "UNSIGNED_TIME", "UNSIGNED_DATE", "UNSIGNED_TIMESTAMP": + column.ScanType = reflect.TypeOf(time.Time{}) + + case "BINARY", "VARBINARY": + column.ScanType = reflect.TypeOf([]byte{}) + + default: + panic(fmt.Sprintf("scantype for %s is not implemented", col.Type.Name)) + } + + // Handle rep type special cases for decimals, floats, date, time and timestamp + switch col.Type.Name { + case "DECIMAL": + column.Rep = message.Rep_BIG_DECIMAL + case "FLOAT": + column.Rep = message.Rep_FLOAT + case "UNSIGNED_FLOAT": + column.Rep = message.Rep_FLOAT + case "TIME", "UNSIGNED_TIME": + column.Rep = message.Rep_JAVA_SQL_TIME + case "DATE", "UNSIGNED_DATE": + column.Rep = message.Rep_JAVA_SQL_DATE + case "TIMESTAMP", "UNSIGNED_TIMESTAMP": + column.Rep = message.Rep_JAVA_SQL_TIMESTAMP + default: + column.Rep = col.Type.Rep + } + + return column +} + +func (a Adapter) ErrorResponseToResponseError(message *message.ErrorResponse) errors.ResponseError { + var ( + errorCode int + sqlState string + ) + + re := regexp.MustCompile(`ERROR (\d+) \(([0-9a-zA-Z]+)\)`) + codes := re.FindStringSubmatch(message.ErrorMessage) + + if len(codes) > 1 { + errorCode, _ = strconv.Atoi(codes[1]) + } + + if len(codes) > 2 { + sqlState = codes[2] + } + + err := errors.ResponseError{ + Exceptions: message.Exceptions, + ErrorMessage: message.ErrorMessage, + Severity: int8(message.Severity), + ErrorCode: errors.ErrorCode(errorCode), + SqlState: errors.SQLState(sqlState), + Metadata: &errors.RPCMetadata{ + ServerAddress: message.GetMetadata().ServerAddress, + }, + Name: errorCodeNames[uint32(errorCode)], + } + + return err +} + +var errorCodeNames = map[uint32]string{ + // Connection exceptions (errorcode 01, sqlstate 08) + 101: "io_exception", + 102: "malformed_connection_url", + 103: "cannot_establish_connection", + + // Data exceptions (errorcode 02, sqlstate 22) + 201: "illegal_data", + 202: "divide_by_zero", + 203: "type_mismatch", + 204: "value_in_upsert_not_constant", + 205: "malformed_url", + 206: "data_exceeds_max_capacity", + 207: "missing_max_length", + 208: "nonpositive_max_length", + 209: "decimal_precision_out_of_range", + 212: "server_arithmetic_error", + 213: "value_outside_range", + 214: "value_in_list_not_constant", + 215: "single_row_subquery_returns_multiple_rows", + 216: "subquery_returns_different_number_of_fields", + 217: "ambiguous_join_condition", + 218: "constraint_violation", + + 301: "concurrent_table_mutation", + 302: "cannot_index_column_on_type", + + // Invalid cursor state (errorcode 04, sqlstate 24) + 401: "cursor_before_first_row", + 402: "cursor_past_last_row", + + // Syntax error or access rule violation (errorcode 05, sqlstate 42) + 501: "ambiguous_table", + 502: "ambiguous_column", + 503: "index_missing_pk_columns", + 504: "column_not_found", + 505: "read_only_table", + 506: "cannot_drop_pk", + 509: "primary_key_missing", + 510: "primary_key_already_exists", + 511: "order_by_not_in_select_distinct", + 512: "invalid_primary_key_constraint", + 513: "array_not_allowed_in_primary_key", + 514: "column_exist_in_def", + 515: "order_by_array_not_supported", + 516: "non_equality_array_comparison", + 517: "invalid_not_null_constraint", + + // Invalid transaction state (errorcode 05, sqlstate 25) + 518: "read_only_connection", + 519: "varbinary_array_not_supported", + + // Expression index exceptions + 520: "aggregate_expression_not_allowed_in_index", + 521: "non_deterministic_expression_not_allowed_in_index", + 522: "stateless_expression_not_allowed_in_index", + + // Transaction exceptions + 523: "transaction_conflict_exception", + 524: "transaction_exception", + + // Union all related errors + 525: "select_column_num_in_unionall_diffs", + 526: "select_column_type_in_unionall_diffs", + + // Row timestamp column related errors + 527: "rowtimestamp_one_pk_col_only", + 528: "rowtimestamp_pk_col_only", + 529: "rowtimestamp_create_only", + 530: "rowtimestamp_col_invalid_type", + 531: "rowtimestamp_not_allowed_on_view", + 532: "invalid_scn", + + // Column family related exceptions + 1000: "single_pk_may_not_be_null", + 1001: "column_family_not_found", + 1002: "properties_for_family", + 1003: "primary_key_with_family_name", + 1004: "primary_key_out_of_order", + 1005: "varbinary_in_row_key", + 1006: "not_nullable_column_in_row_key", + 1015: "varbinary_last_pk", + 1023: "nullable_fixed_width_last_pk", + 1036: "cannot_modify_view_pk", + 1037: "base_table_column", + + // Key/value column related errors + 1007: "key_value_not_null", + + // View related errors + 1008: "view_with_table_config", + 1009: "view_with_properties", + + // Table related errors that are not in standard code + 1010: "cannot_mutate_table", + 1011: "unexpected_mutation_code", + 1012: "table_undefined", + 1013: "table_already_exist", + + // Syntax error + 1014: "type_not_supported_for_operator", + 1016: "aggregate_in_group_by", + 1017: "aggregate_in_where", + 1018: "aggregate_with_not_group_by_column", + 1019: "only_aggregate_in_having_clause", + 1020: "upsert_column_numbers_mismatch", + + // Table properties exception + 1021: "invalid_bucket_num", + 1022: "no_splits_on_salted_table", + 1024: "salt_only_on_create_table", + 1025: "set_unsupported_prop_on_alter_table", + 1038: "cannot_add_not_nullable_column", + 1026: "no_mutable_indexes", + 1028: "invalid_index_state_transition", + 1029: "invalid_mutable_index_config", + 1030: "cannot_create_tenant_specific_table", + 1034: "default_column_family_only_on_create_table", + 1040: "insufficient_multi_tenant_columns", + 1041: "tenantid_is_of_wrong_type", + 1045: "view_where_is_constant", + 1046: "cannot_update_view_column", + 1047: "too_many_indexes", + 1048: "no_local_index_on_table_with_immutable_rows", + 1049: "column_family_not_allowed_table_property", + 1050: "column_family_not_allowed_for_ttl", + 1051: "cannot_alter_property", + 1052: "cannot_set_property_for_column_not_added", + 1053: "cannot_set_table_property_add_column", + 1054: "no_local_indexes", + 1055: "unallowed_local_indexes", + 1056: "desc_varbinary_not_supported", + 1057: "no_table_specified_for_wildcard_select", + 1058: "unsupported_group_by_expressions", + 1069: "default_column_family_on_shared_table", + 1070: "only_table_may_be_declared_transactional", + 1071: "tx_may_not_switch_to_non_tx", + 1072: "store_nulls_must_be_true_for_transactional", + 1073: "cannot_start_transaction_with_scn_set", + 1074: "tx_max_versions_must_be_greater_than_one", + 1075: "cannot_specify_scn_for_txn_table", + 1076: "null_transaction_context", + 1077: "transaction_failed", + 1078: "cannot_create_txn_table_if_txns_disabled", + 1079: "cannot_alter_to_be_txn_if_txns_disabled", + 1080: "cannot_create_txn_table_with_row_timestamp", + 1081: "cannot_alter_to_be_txn_with_row_timestamp", + 1082: "tx_must_be_enabled_to_set_tx_context", + 1083: "tx_must_be_enabled_to_set_auto_flush", + 1084: "tx_must_be_enabled_to_set_isolation_level", + 1085: "tx_unable_to_get_write_fence", + 1086: "sequence_not_castable_to_auto_partition_id_column", + 1087: "cannot_coerce_auto_partition_id", + + // Sequence related + 1200: "sequence_already_exist", + 1201: "sequence_undefined", + 1202: "start_with_must_be_constant", + 1203: "increment_by_must_be_constant", + 1204: "cache_must_be_non_negative_constant", + 1205: "invalid_use_of_next_value_for", + 1206: "cannot_call_current_before_next_value", + 1207: "empty_sequence_cache", + 1208: "minvalue_must_be_constant", + 1209: "maxvalue_must_be_constant", + 1210: "minvalue_must_be_less_than_or_equal_to_maxvalue", + 1211: "starts_with_must_be_between_min_max_value", + 1212: "sequence_val_reached_max_value", + 1213: "sequence_val_reached_min_value", + 1214: "increment_by_must_not_be_zero", + 1215: "num_seq_to_allocate_must_be_constant", + 1216: "num_seq_to_allocate_not_supported", + 1217: "auto_partition_sequence_undefined", + + // Parser error. (errorcode 06, sqlstate 42p) + 601: "parser_error", + 602: "missing_token", + 603: "unwanted_token", + 604: "mismatched_token", + 605: "unknown_function", + + // Implementation defined class. execution exceptions (errorcode 11, sqlstate xcl) + 1101: "resultset_closed", + 1102: "get_table_regions_fail", + 1103: "execute_query_not_applicable", + 1104: "execute_update_not_applicable", + 1105: "split_point_not_constant", + 1106: "batch_exception", + 1107: "execute_update_with_non_empty_batch", + 1108: "stale_region_boundary_cache", + 1109: "cannot_split_local_index", + 1110: "cannot_salt_local_index", + 1120: "index_failure_block_write", + 1130: "update_cache_frequency_invalid", + 1131: "cannot_drop_col_append_only_schema", + 1132: "view_append_only_schema", + + // Implementation defined class. phoenix internal error. (errorcode 20, sqlstate int) + 2001: "cannot_call_method_on_type", + 2002: "class_not_unwrappable", + 2003: "param_index_out_of_bound", + 2004: "param_value_unbound", + 2005: "interrupted_exception", + 2006: "incompatible_client_server_jar", + 2007: "outdated_jars", + 2008: "index_metadata_not_found", + 2009: "unknown_error_code", + 6000: "operation_timed_out", + 6001: "function_undefined", + 6002: "function_already_exist", + 6003: "unallowed_user_defined_functions", + 721: "schema_already_exists", + 722: "schema_not_found", + 723: "cannot_mutate_schema", +} http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/2968def4/rows.go ---------------------------------------------------------------------- diff --git a/rows.go b/rows.go index b8fd54c..e205e19 100644 --- a/rows.go +++ b/rows.go @@ -19,33 +19,16 @@ package avatica import ( "database/sql/driver" - "fmt" "io" - "math" - "reflect" "time" + "github.com/apache/calcite-avatica-go/internal" "github.com/apache/calcite-avatica-go/message" "golang.org/x/net/context" ) -type precisionScale struct { - precision int64 - scale int64 -} - -type column struct { - name string - typeName string - rep message.Rep - length int64 - nullable bool - precisionScale *precisionScale - scanType reflect.Type -} - type resultSet struct { - columns []*column + columns []*internal.Column done bool offset uint64 data [][]*message.TypedValue @@ -68,7 +51,7 @@ func (r *rows) Columns() []string { var cols []string for _, column := range r.resultSets[r.currentResultSet].columns { - cols = append(cols, column.name) + cols = append(cols, column.Name) } return cols @@ -110,7 +93,7 @@ func (r *rows) Next(dest []driver.Value) error { }) if err != nil { - return err + return r.conn.avaticaErrorToResponseErrorOrError(err) } frame := res.(*message.FetchResponse).Frame @@ -140,7 +123,7 @@ func (r *rows) Next(dest []driver.Value) error { } for i, val := range resultSet.data[resultSet.currentRow] { - dest[i] = typedValueToNative(resultSet.columns[i].rep, val, r.conn.config) + dest[i] = typedValueToNative(resultSet.columns[i].Rep, val, r.conn.config) } resultSet.currentRow++ @@ -158,84 +141,10 @@ func newRows(conn *conn, statementID uint32, resultSets []*message.ResultSetResp break } - var columns []*column + var columns []*internal.Column for _, col := range result.Signature.Columns { - - column := &column{ - name: col.ColumnName, - typeName: col.Type.Name, - nullable: col.Nullable != 0, - } - - // Handle precision and length - switch col.Type.Name { - case "DECIMAL": - - precision := int64(col.Precision) - - if precision == 0 { - precision = math.MaxInt64 - } - - scale := int64(col.Scale) - - if scale == 0 { - scale = math.MaxInt64 - } - - column.precisionScale = &precisionScale{ - precision: precision, - scale: scale, - } - case "VARCHAR", "CHAR", "BINARY": - column.length = int64(col.Precision) - case "VARBINARY": - column.length = math.MaxInt64 - } - - // Handle scan types - switch col.Type.Name { - case "INTEGER", "UNSIGNED_INT", "BIGINT", "UNSIGNED_LONG", "TINYINT", "UNSIGNED_TINYINT", "SMALLINT", "UNSIGNED_SMALLINT": - column.scanType = reflect.TypeOf(int64(0)) - - case "FLOAT", "UNSIGNED_FLOAT", "DOUBLE", "UNSIGNED_DOUBLE": - column.scanType = reflect.TypeOf(float64(0)) - - case "DECIMAL", "VARCHAR", "CHAR": - column.scanType = reflect.TypeOf("") - - case "BOOLEAN": - column.scanType = reflect.TypeOf(false) - - case "TIME", "DATE", "TIMESTAMP", "UNSIGNED_TIME", "UNSIGNED_DATE", "UNSIGNED_TIMESTAMP": - column.scanType = reflect.TypeOf(time.Time{}) - - case "BINARY", "VARBINARY": - column.scanType = reflect.TypeOf([]byte{}) - - default: - panic(fmt.Sprintf("scantype for %s is not implemented", col.Type.Name)) - } - - // Handle rep type special cases for decimals, floats, date, time and timestamp - switch col.Type.Name { - case "DECIMAL": - column.rep = message.Rep_BIG_DECIMAL - case "FLOAT": - column.rep = message.Rep_FLOAT - case "UNSIGNED_FLOAT": - column.rep = message.Rep_FLOAT - case "TIME", "UNSIGNED_TIME": - column.rep = message.Rep_JAVA_SQL_TIME - case "DATE", "UNSIGNED_DATE": - column.rep = message.Rep_JAVA_SQL_DATE - case "TIMESTAMP", "UNSIGNED_TIMESTAMP": - column.rep = message.Rep_JAVA_SQL_TIMESTAMP - default: - column.rep = col.Type.Rep - } - + column := conn.adapter.GetColumnTypeDefinition(col) columns = append(columns, column) } @@ -271,7 +180,6 @@ func newRows(conn *conn, statementID uint32, resultSets []*message.ResultSetResp // typedValueToNative converts values from avatica's types to Go's native types func typedValueToNative(rep message.Rep, v *message.TypedValue, config *Config) interface{} { - switch rep { case message.Rep_BOOLEAN, message.Rep_PRIMITIVE_BOOLEAN: return v.BoolValue http://git-wip-us.apache.org/repos/asf/calcite-avatica-go/blob/2968def4/rows_go18.go ---------------------------------------------------------------------- diff --git a/rows_go18.go b/rows_go18.go index 3dcf95c..792c211 100644 --- a/rows_go18.go +++ b/rows_go18.go @@ -44,11 +44,11 @@ func (r *rows) NextResultSet() error { func (r *rows) ColumnTypeDatabaseTypeName(index int) string { - return r.resultSets[r.currentResultSet].columns[index].typeName + return r.resultSets[r.currentResultSet].columns[index].TypeName } func (r *rows) ColumnTypeLength(index int) (length int64, ok bool) { - l := r.resultSets[r.currentResultSet].columns[index].length + l := r.resultSets[r.currentResultSet].columns[index].Length if l == 0 { return 0, false @@ -58,20 +58,20 @@ func (r *rows) ColumnTypeLength(index int) (length int64, ok bool) { } func (r *rows) ColumnTypeNullable(index int) (nullable, ok bool) { - return r.resultSets[r.currentResultSet].columns[index].nullable, true + return r.resultSets[r.currentResultSet].columns[index].Nullable, true } func (r *rows) ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool) { - ps := r.resultSets[r.currentResultSet].columns[index].precisionScale + ps := r.resultSets[r.currentResultSet].columns[index].PrecisionScale if ps != nil { - return ps.precision, ps.scale, true + return ps.Precision, ps.Scale, true } return 0, 0, false } func (r *rows) ColumnTypeScanType(index int) reflect.Type { - return r.resultSets[r.currentResultSet].columns[index].scanType + return r.resultSets[r.currentResultSet].columns[index].ScanType }
