diff --git a/README.rst b/README.rst index d760872b..70c42ba3 100644 --- a/README.rst +++ b/README.rst @@ -20,8 +20,9 @@ Installation $ go get github.com/cznic/mathutil $ go get github.com/kardianos/osext - $ go get github.com/nyarla/go-crypt + $ go get github.com/shopspring/decimal $ go get github.com/nakagami/firebirdsql + $ go get gitlab.com/nyarla/go-crypt Example @@ -71,6 +72,11 @@ Optional param1, param2... are -- role: Role name. -- auth_plugin_name: Authentication plugin name for FB3. Srp or Legacy_Auth are available. Default is Srp. -- wire_crypt: Enable wire data encryption or not. It is for FB3 server. Default is true. +.. csv-table:: + :header: Name,Description,Default,Note + + auth_plugin_name,Authentication plugin name.,Srp,Srp256/Srp/Legacy_Auth are available. + column_name_to_lower,Force column name to lower,false,For "github.com/jmoiron/sqlx" + role,Role name, + tzname, Time Zone name, For Firebird 4.0+ + wire_crypt,Enable wire data encryption or not.,true,For Firebird 3.0+ diff --git a/attic/errmsgs.c b/attic/errmsgs.c index 903f8b74..f0542cde 100644 --- a/attic/errmsgs.c +++ b/attic/errmsgs.c @@ -48,7 +48,7 @@ Software distributed under the License is distributed on an\n\ \"AS IS\" basis, WITHOUT WARRANTY OF ANY KIND, either express\n\ or implied. See the License for the specific language governing\n\ rights and limitations under the License.\n\n\ -*****************************************************************************/\n"); +*****************************************************************************/\n\n"); fprintf(fp, "package firebirdsql\n\nvar errmsgs = map[int]string{\n"); for (i = 0; messages[i].code_text; i++) { fprintf(fp, "\t%ld: \"%s\\n\",\n", messages[i].code_number, messages[i].code_text); diff --git a/connection.go b/connection.go index 2a04506d..72d2ea89 100644 --- a/connection.go +++ b/connection.go @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2013-2016 Hajime Nakagami +Copyright (c) 2013-2019 Hajime Nakagami Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -24,22 +24,23 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package firebirdsql import ( + "context" "database/sql/driver" "math/big" - - "context" ) type firebirdsqlConn struct { - wp *wireProtocol - tx *firebirdsqlTx - addr string - dbName string - user string - password string - isAutocommit bool - clientPublic *big.Int - clientSecret *big.Int + wp *wireProtocol + tx *firebirdsqlTx + addr string + dbName string + user string + password string + columnNameToLower bool + isAutocommit bool + clientPublic *big.Int + clientSecret *big.Int + transactionSet map[*firebirdsqlTx]struct{} } func (fc *firebirdsqlConn) begin(isolationLevel int) (driver.Tx, error) { @@ -53,6 +54,10 @@ func (fc *firebirdsqlConn) Begin() (driver.Tx, error) { } func (fc *firebirdsqlConn) Close() (err error) { + for tx, _ := range fc.transactionSet { + tx.Rollback() + } + fc.wp.opDetach() _, _, _, err = fc.wp.opResponse() fc.wp.conn.Close() @@ -68,16 +73,25 @@ func (fc *firebirdsqlConn) Prepare(query string) (driver.Stmt, error) { } func (fc *firebirdsqlConn) exec(ctx context.Context, query string, args []driver.Value) (result driver.Result, err error) { + + if fc.tx.needBegin { + err = fc.tx.begin() + if err != nil { + return + } + } + stmt, err := fc.prepare(ctx, query) if err != nil { return } + result, err = stmt.(*firebirdsqlStmt).exec(ctx, args) if err != nil { return } if fc.isAutocommit && fc.tx.isAutocommit { - fc.tx.Commit() + fc.tx.commitRetainging() } stmt.Close() return @@ -101,69 +115,83 @@ func (fc *firebirdsqlConn) Query(query string, args []driver.Value) (rows driver } func newFirebirdsqlConn(dsn string) (fc *firebirdsqlConn, err error) { - addr, dbName, user, password, role, authPluginName, wireCrypt, err := parseDSN(dsn) - wp, err := newWireProtocol(addr) + addr, dbName, user, password, options, err := parseDSN(dsn) + + wp, err := newWireProtocol(addr, options["timezone"]) if err != nil { return } + + column_name_to_lower := convertToBool(options["column_name_to_lower"], false) + clientPublic, clientSecret := getClientSeed() - wp.opConnect(dbName, user, password, authPluginName, wireCrypt, clientPublic) - err = wp.opAccept(user, password, authPluginName, wireCrypt, clientPublic, clientSecret) + wp.opConnect(dbName, user, password, options, clientPublic) + err = wp._parse_connect_response(user, password, options, clientPublic, clientSecret) if err != nil { return } - wp.opAttach(dbName, user, password, role) + wp.opAttach(dbName, user, password, options["role"]) wp.dbHandle, _, _, err = wp.opResponse() if err != nil { return } fc = new(firebirdsqlConn) + fc.transactionSet = make(map[*firebirdsqlTx]struct{}) fc.wp = wp fc.addr = addr fc.dbName = dbName fc.user = user fc.password = password + fc.columnNameToLower = column_name_to_lower fc.isAutocommit = true fc.tx, err = newFirebirdsqlTx(fc, ISOLATION_LEVEL_READ_COMMITED, fc.isAutocommit) fc.clientPublic = clientPublic fc.clientSecret = clientSecret + fc.loadTimeZoneId() + return fc, err } func createFirebirdsqlConn(dsn string) (fc *firebirdsqlConn, err error) { // Create Database - addr, dbName, user, password, role, authPluginName, wireCrypt, err := parseDSN(dsn) - wp, err := newWireProtocol(addr) + addr, dbName, user, password, options, err := parseDSN(dsn) + + wp, err := newWireProtocol(addr, options["timezone"]) if err != nil { return } + column_name_to_lower := convertToBool(options["column_name_to_lower"], false) clientPublic, clientSecret := getClientSeed() - wp.opConnect(dbName, user, password, authPluginName, wireCrypt, clientPublic) - err = wp.opAccept(user, password, authPluginName, wireCrypt, clientPublic, clientSecret) + wp.opConnect(dbName, user, password, options, clientPublic) + err = wp._parse_connect_response(user, password, options, clientPublic, clientSecret) if err != nil { return } - wp.opCreate(dbName, user, password, role) + wp.opCreate(dbName, user, password, options["role"]) wp.dbHandle, _, _, err = wp.opResponse() if err != nil { return } fc = new(firebirdsqlConn) + fc.transactionSet = make(map[*firebirdsqlTx]struct{}) fc.wp = wp fc.addr = addr fc.dbName = dbName fc.user = user fc.password = password + fc.columnNameToLower = column_name_to_lower fc.isAutocommit = true fc.tx, err = newFirebirdsqlTx(fc, ISOLATION_LEVEL_READ_COMMITED, fc.isAutocommit) fc.clientPublic = clientPublic fc.clientSecret = clientSecret + fc.loadTimeZoneId() + return fc, err } diff --git a/consts.go b/consts.go index a7b6f4f1..2481cec8 100644 --- a/consts.go +++ b/consts.go @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2013-2016 Hajime Nakagami +Copyright (c) 2013-2019 Hajime Nakagami Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -217,6 +217,13 @@ const ( isc_dpb_process_name = 74 isc_dpb_utf8_filename = 77 isc_dpb_specific_auth_data = 84 + isc_dpb_auth_plugin_list = 85 + isc_dpb_auth_plugin_name = 86 + isc_dpb_config = 87 + isc_dpb_nolinger = 88 + isc_dpb_reset_icu = 89 + isc_dpb_map_attach = 90 + isc_dpb_session_time_zone = 91 // backup isc_spb_bkp_file = 5 diff --git a/decfloat.go b/decfloat.go new file mode 100644 index 00000000..7b8b71fa --- /dev/null +++ b/decfloat.go @@ -0,0 +1,258 @@ +/******************************************************************************* +The MIT License (MIT) + +Copyright (c) 2019 Hajime Nakagami + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*******************************************************************************/ + +package firebirdsql + +import ( + "github.com/shopspring/decimal" + "math" + "math/big" +) + +func dpdBitToInt(dpd uint, mask uint) int { + if (dpd & mask) != 0 { + return 1 + } else { + return 0 + } +} + +func dpdToInt(dpd uint) int64 { + // Convert DPD encodined value to int (0-999) + // dpd: DPD encoded value. 10bit unsigned int + + b := make([]int, 10) + b[9] = dpdBitToInt(dpd, 0x0200) + b[8] = dpdBitToInt(dpd, 0x0100) + b[7] = dpdBitToInt(dpd, 0x0080) + b[6] = dpdBitToInt(dpd, 0x0040) + b[5] = dpdBitToInt(dpd, 0x0020) + b[4] = dpdBitToInt(dpd, 0x0010) + b[3] = dpdBitToInt(dpd, 0x0008) + b[2] = dpdBitToInt(dpd, 0x0004) + b[1] = dpdBitToInt(dpd, 0x0002) + b[0] = dpdBitToInt(dpd, 0x0001) + + d := make([]int, 3) + if b[3] == 0 { + d[2] = b[9]*4 + b[8]*2 + b[7] + d[1] = b[6]*4 + b[5]*2 + b[4] + d[0] = b[2]*4 + b[1]*2 + b[0] + } else if b[3] == 1 && b[2] == 0 && b[1] == 0 { + d[2] = b[9]*4 + b[8]*2 + b[7] + d[1] = b[6]*4 + b[5]*2 + b[4] + d[0] = 8 + b[0] + } else if b[3] == 1 && b[2] == 0 && b[1] == 1 { + d[2] = b[9]*4 + b[8]*2 + b[7] + d[1] = 8 + b[4] + d[0] = b[6]*4 + b[5]*2 + b[0] + } else if b[3] == 1 && b[2] == 1 && b[1] == 0 { + d[2] = 8 + b[7] + d[1] = b[6]*4 + b[5]*2 + b[4] + d[0] = b[9]*4 + b[8]*2 + b[0] + } else if b[6] == 0 && b[5] == 0 && b[3] == 1 && b[2] == 1 && b[1] == 1 { + d[2] = 8 + b[7] + d[1] = 8 + b[4] + d[0] = b[9]*4 + b[8]*2 + b[0] + } else if b[6] == 0 && b[5] == 1 && b[3] == 1 && b[2] == 1 && b[1] == 1 { + d[2] = 8 + b[7] + d[1] = b[9]*4 + b[8]*2 + b[4] + d[0] = 8 + b[0] + } else if b[6] == 1 && b[5] == 0 && b[3] == 1 && b[2] == 1 && b[1] == 1 { + d[2] = b[9]*4 + b[8]*2 + b[7] + d[1] = 8 + b[4] + d[0] = 8 + b[0] + } else if b[6] == 1 && b[5] == 1 && b[3] == 1 && b[2] == 1 && b[1] == 1 { + d[2] = 8 + b[7] + d[1] = 8 + b[4] + d[0] = 8 + b[0] + } else { + panic("Invalid DPD encoding") + } + + return int64(d[2])*100 + int64(d[1])*10 + int64(d[0]) +} + +func calcSignificand(prefix int64, dpdBits *big.Int, numBits int) *big.Int { + // prefix: High bits integer value + // dpdBits: dpd encoded bits + // numBits: bit length of dpd_bits + // https://en.wikipedia.org/wiki/Decimal128_floating-point_format#Densely_packed_decimal_significand_field + numSegments := numBits / 10 + segments := make([]uint, numSegments) + bi1024 := big.NewInt(1024) + + for i := 0; i < numSegments; i++ { + var work big.Int + work = *dpdBits + segments[numSegments-i-1] = uint(work.Mod(&work, bi1024).Int64()) + dpdBits.Rsh(dpdBits, 10) + } + + v := big.NewInt(prefix) + bi1000 := big.NewInt(1000) + for _, dpd := range segments { + v.Mul(v, bi1000) + v.Add(v, big.NewInt(dpdToInt(dpd))) + } + + return v +} + +func decimal128ToSignDigitsExponent(b []byte) (v *decimal.Decimal, sign int, digits *big.Int, exponent int32) { + // https://en.wikipedia.org/wiki/Decimal128_floating-point_format + + var prefix int64 + if (b[0] & 0x80) == 0x80 { + sign = 1 + } + cf := (uint32(b[0]&0x7f) << 10) + uint32(b[1]<<2) + uint32(b[2]>>6) + if (cf & 0x1F000) == 0x1F000 { + var d decimal.Decimal + if sign == 1 { + // Is there -NaN ? + d = decimal.NewFromFloat(math.NaN()) + } else { + d = decimal.NewFromFloat(math.NaN()) + } + v = &d + return + } else if (cf & 0x1F000) == 0x1E000 { + var d decimal.Decimal + if sign == 1 { + d = decimal.NewFromFloat(math.Inf(-1)) + } else { + d = decimal.NewFromFloat(math.Inf(1)) + } + v = &d + return + } else if (cf & 0x18000) == 0x00000 { + exponent = int32(0x0000 + (cf & 0x00fff)) + prefix = int64((cf >> 12) & 0x07) + } else if (cf & 0x18000) == 0x08000 { + exponent = int32(0x1000 + (cf & 0x00fff)) + prefix = int64((cf >> 12) & 0x07) + } else if (cf & 0x18000) == 0x10000 { + exponent = int32(0x2000 + (cf & 0x00fff)) + prefix = int64((cf >> 12) & 0x07) + } else if (cf & 0x1e000) == 0x18000 { + exponent = int32(0x0000 + (cf & 0x00fff)) + prefix = int64(8 + (cf>>12)&0x01) + } else if (cf & 0x1e000) == 0x1a000 { + exponent = int32(0x1000 + (cf & 0x00fff)) + prefix = int64(8 + (cf>>12)&0x01) + } else if (cf & 0x1e000) == 0x1c000 { + exponent = int32(0x2000 + (cf & 0x00fff)) + prefix = int64(8 + (cf>>12)&0x01) + } else { + panic("decimal128 value error") + } + exponent -= 6176 + + dpdBits := bytesToBig(b) + mask := bigFromHexString("3fffffffffffffffffffffffffff") + dpdBits.And(dpdBits, mask) + digits = calcSignificand(prefix, dpdBits, 110) + + return +} + +func decimalFixedToDecimal(b []byte, scale int32) decimal.Decimal { + v, sign, digits, _ := decimal128ToSignDigitsExponent(b) + if v != nil { + return *v + } + if sign != 0 { + digits.Mul(digits, big.NewInt(-1)) + } + return decimal.NewFromBigInt(digits, scale) +} + +func decimal64ToDecimal(b []byte) decimal.Decimal { + // https://en.wikipedia.org/wiki/Decimal64_floating-point_format + var prefix int64 + var sign int + if (b[0] & 0x80) == 0x80 { + sign = 1 + } + cf := (uint32(b[0]) >> 2) & 0x1f + exponent := ((int32(b[0]) & 3) << 6) + ((int32(b[1]) >> 2) & 0x3f) + + dpdBits := bytesToBig(b) + mask := bigFromHexString("3ffffffffffff") + dpdBits.And(dpdBits, mask) + + if cf == 0x1f { + if sign == 1 { + // Is there -NaN ? + return decimal.NewFromFloat(math.NaN()) + } else { + return decimal.NewFromFloat(math.NaN()) + } + } else if cf == 0x1e { + if sign == 1 { + return decimal.NewFromFloat(math.Inf(-1)) + } else { + return decimal.NewFromFloat(math.Inf(1)) + } + } else if (cf & 0x18) == 0x00 { + exponent = 0x000 + exponent + prefix = int64(cf & 0x07) + } else if (cf & 0x18) == 0x08 { + exponent = 0x100 + exponent + prefix = int64(cf & 0x07) + } else if (cf & 0x18) == 0x10 { + exponent = 0x200 + exponent + prefix = int64(cf & 0x07) + } else if (cf & 0x1e) == 0x18 { + exponent = 0x000 + exponent + prefix = int64(8 + cf&1) + } else if (cf & 0x1e) == 0x1a { + exponent = 0x100 + exponent + prefix = int64(8 + cf&1) + } else if (cf & 0x1e) == 0x1c { + exponent = 0x200 + exponent + prefix = int64(8 + cf&1) + } else { + panic("decimal64 value error") + } + digits := calcSignificand(prefix, dpdBits, 50) + exponent -= 398 + + if sign != 0 { + digits.Mul(digits, big.NewInt(-1)) + } + return decimal.NewFromBigInt(digits, exponent) +} + +func decimal128ToDecimal(b []byte) decimal.Decimal { + // https://en.wikipedia.org/wiki/Decimal64_floating-point_format + v, sign, digits, exponent := decimal128ToSignDigitsExponent(b) + if v != nil { + return *v + } + if sign != 0 { + digits.Mul(digits, big.NewInt(-1)) + } + return decimal.NewFromBigInt(digits, exponent) +} diff --git a/decode_speed_test.go b/decode_speed_test.go new file mode 100644 index 00000000..1c46721f --- /dev/null +++ b/decode_speed_test.go @@ -0,0 +1,75 @@ +package firebirdsql + +import ( + "database/sql" + "log" + "testing" +) + +func BenchmarkRead(b *testing.B) { + b.StopTimer() + temppath := TempFileName("test_basic_") + conn, err := sql.Open("firebirdsql_createdb", "sysdba:masterkey@localhost:3050"+temppath) + query := ` + CREATE TABLE PERFTEST ( + A SMALLINT, + B INT, + C BIGINT, + D VARCHAR(255), + B1 INT, + B2 INT, + B3 INT, + B4 INT, + B5 INT, + B6 INT, + B7 INT, + B8 INT, + B9 INT, + B11 INT, + B12 INT, + B13 INT, + B14 INT, + B15 INT, + B16 INT, + B17 INT, + B18 INT, + B19 INT + ); + ` + _, err = conn.Exec(query) + if err != nil { + b.Error(err) + log.Fatal(err) + } + + tx, err := conn.Begin() + if err != nil { + b.Error(err) + } + + stmt, err := tx.Prepare("INSERT INTO PERFTEST (A,B,C,D,B1,B2,B3,B4,B5,B6,B7,B8,B9,B11,B12,B13,B14,B15,B16,B17,B18,B19) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)") + if err != nil { + b.Error(err) + log.Fatal(err) + } + + for i := 0; i < b.N; i++ { + stmt.Exec(i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i) + } + tx.Commit() + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := conn.Query("SELECT * FROM PERFTEST") + if err != nil { + b.Error(err) + } + + var vals []interface{} + + for rows.Next() { + rows.Scan(vals...) + } + rows.Close() + } +} diff --git a/driver.go b/driver.go index 145f4d19..322c88d6 100644 --- a/driver.go +++ b/driver.go @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2013-2016 Hajime Nakagami +Copyright (c) 2013-2019 Hajime Nakagami Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -21,6 +21,7 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *******************************************************************************/ +// Package firebird provides database/sql driver for Firebird RDBMS. package firebirdsql import ( diff --git a/driver_go18.go b/driver_go18.go index 00a60e96..9a2a441b 100644 --- a/driver_go18.go +++ b/driver_go18.go @@ -3,7 +3,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2016 Hajime Nakagami +Copyright (c) 2016-2019 Hajime Nakagami Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/driver_go18_test.go b/driver_go18_test.go index e59504b2..72a8e8d6 100644 --- a/driver_go18_test.go +++ b/driver_go18_test.go @@ -3,7 +3,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2016 Hajime Nakagami +Copyright (c) 2016-2019 Hajime Nakagami Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/driver_test.go b/driver_test.go index ac078ea6..24d36e3a 100644 --- a/driver_test.go +++ b/driver_test.go @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2013-2016 Hajime Nakagami +Copyright (c) 2013-2019 Hajime Nakagami Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -120,11 +120,12 @@ func TestBasic(t *testing.T) { for rows.Next() { rows.Scan(&a, &b, &c, &d, &e, &f, &g, &h, &i, &j) + // fmt.Println(a, b, c, d, e, f, g, h, i, j) } stmt, _ := conn.Prepare("select count(*) from foo where a=? and b=? and d=? and e=? and f=? and g=?") - ep := time.Date(1967, 8, 11, 0, 0, 0, 0, time.UTC) - fp := time.Date(1967, 8, 11, 23, 45, 1, 0, time.UTC) + ep := time.Date(1967, 8, 11, 0, 0, 0, 0, time.Local) + fp := time.Date(1967, 8, 11, 23, 45, 1, 0, time.Local) gp, err := time.Parse("15:04:05", "23:45:01") err = stmt.QueryRow(1, "a", -0.123, ep, fp, gp).Scan(&n) if err != nil { @@ -275,7 +276,7 @@ func TestInsertTimestamp(t *testing.T) { t.Fatalf("Error creating table: %v", err) } - dt1 := time.Date(2015, 2, 9, 19, 25, 50, 740500000, time.UTC) + dt1 := time.Date(2015, 2, 9, 19, 25, 50, 740500000, time.Local) dt2 := "2015/2/9 19:25:50.7405" dt3 := "2015-2-9 19:25:50.7405" @@ -307,7 +308,9 @@ func TestInsertTimestamp(t *testing.T) { /* func TestBoolean(t *testing.T) { - conn, err := sql.Open("firebirdsql_createdb", "sysdba:masterkey@localhost:3050/tmp/go_test_fb3.fdb") + temppath := TempFileName("test_boolean_") + + conn, err := sql.Open("firebirdsql_createdb", "sysdba:masterkey@localhost:3050"+temppath) if err != nil { t.Fatalf("Error connecting: %v", err) } @@ -358,6 +361,49 @@ func TestBoolean(t *testing.T) { conn.Close() } + +func TestDecFloat(t *testing.T) { + temppath := TempFileName("test_decfloat_") + + conn, err := sql.Open("firebirdsql_createdb", "sysdba:masterkey@localhost:3050"+temppath) + if err != nil { + t.Fatalf("Error connecting: %v", err) + } + + sql := ` + CREATE TABLE test_decfloat ( + i integer, + d DECIMAL(20, 2), + df64 DECFLOAT(16), + df128 DECFLOAT(34) + ) + ` + conn.Exec(sql) + conn.Exec("insert into test_decfloat(i, d, df64, df128) values (1, 0.0, 0.0, 0.0)") + conn.Exec("insert into test_decfloat(i, d, df64, df128) values (2, 1.0, 1.0, 1.0)") + conn.Exec("insert into test_decfloat(i, d, df64, df128) values (3, 20.0, 20.0, 20.0)") + conn.Exec("insert into test_decfloat(i, d, df64, df128) values (4, -1.0, -1.0, -1.0)") + conn.Exec("insert into test_decfloat(i, d, df64, df128) values (5, -20.0, -20.0, -20.0)") + + var n int + err = conn.QueryRow("select count(*) cnt from test_decfloat").Scan(&n) + if err != nil { + t.Fatalf("Error QueryRow: %v", err) + } + if n != 5 { + t.Fatalf("Error bad record count: %v", n) + } + + rows, err := conn.Query("select d, df64, df128 from test_decfloat order by i") + + var d, df64, df128 decimal.Decimal + for rows.Next() { + rows.Scan(&d, &df64, &df128) + fmt.Println(d, df64, df128) + } + + conn.Close() +} */ func TestLegacyAuthWireCrypt(t *testing.T) { diff --git a/errmsgs.go b/errmsgs.go index 8b0f6659..bc3c60b1 100644 --- a/errmsgs.go +++ b/errmsgs.go @@ -10,6 +10,7 @@ or implied. See the License for the specific language governing rights and limitations under the License. *****************************************************************************/ + package firebirdsql var errmsgs = map[int]string{ diff --git a/result.go b/result.go index 2cf76443..d784c4e7 100644 --- a/result.go +++ b/result.go @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2014-2016 Hajime Nakagami +Copyright (c) 2014-2019 Hajime Nakagami Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/rows.go b/rows.go index a1284823..8681917b 100644 --- a/rows.go +++ b/rows.go @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2013-2016 Hajime Nakagami +Copyright (c) 2013-2019 Hajime Nakagami Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -29,6 +29,7 @@ import ( "database/sql/driver" "io" "reflect" + "strings" ) type firebirdsqlRows struct { @@ -52,6 +53,9 @@ func (rows *firebirdsqlRows) Columns() []string { columns := make([]string, len(rows.stmt.xsqlda)) for i, x := range rows.stmt.xsqlda { columns[i] = x.aliasname + if rows.stmt.tx.fc.columnNameToLower { + columns[i] = strings.ToLower(columns[i]) + } } return columns } diff --git a/srp.go b/srp.go index d2bf9036..b038668f 100644 --- a/srp.go +++ b/srp.go @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2014 Hajime Nakagami +Copyright (c) 2014-2019 Hajime Nakagami Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -26,7 +26,9 @@ package firebirdsql import ( "bytes" "crypto/sha1" + "crypto/sha256" "github.com/cznic/mathutil" + "hash" "math/big" "math/rand" "time" @@ -39,18 +41,6 @@ const ( DEBUG_SRP = false ) -func bigFromHexString(s string) *big.Int { - ret := new(big.Int) - ret.SetString(s, 16) - return ret -} - -func bigFromString(s string) *big.Int { - ret := new(big.Int) - ret.SetString(s, 10) - return ret -} - func bigToSha1(n *big.Int) []byte { sha1 := sha1.New() sha1.Write(n.Bytes()) @@ -82,30 +72,6 @@ func pad(v *big.Int) []byte { return buf[i:] } -func bigToBytes(v *big.Int) []byte { - buf := pad(v) - for i, _ := range buf { - if buf[i] != 0 { - return buf[i:] - } - } - - return buf[:1] // 0 -} - -func bytesToBig(v []byte) (r *big.Int) { - m := new(big.Int) - m.SetInt64(256) - a := new(big.Int) - r = new(big.Int) - r.SetInt64(0) - for _, b := range v { - r = r.Mul(r, m) - r = r.Add(r, a.SetInt64(int64(b))) - } - return r -} - func getPrime() (prime *big.Int, g *big.Int, k *big.Int) { prime = bigFromHexString("E67D2E994B2F900C3F41F08F5BB2627ED0D49EE1FE767A52EFCD565CD6E768812C3E1E9CE8F0A8BEA6CB13CD29DDEBF7A96D4A93B55D488DF099A15C89DCB0640738EB2CBDD9A8F7BAB561AB1B0DC1C6CDABF303264A08D1BCA932D1F1EE428B619D970F342ABA9A65793B8B2F041AE5364350C16F735F56ECBCA87BD57B29E7") g = big.NewInt(2) @@ -202,7 +168,7 @@ func getServerSession(user string, password string, salt []byte, keyA *big.Int, return bigToSha1(sessionSecret) } -func getClientProof(user string, password string, salt []byte, keyA *big.Int, keyB *big.Int, keya *big.Int) (keyM []byte, keyK []byte) { +func getClientProof(user string, password string, salt []byte, keyA *big.Int, keyB *big.Int, keya *big.Int, pluginName string) (keyM []byte, keyK []byte) { // M = H(H(N) xor H(g), H(I), s, A, B, K) prime, g, _ := getPrime() keyK = getClientSession(user, password, salt, keyA, keyB, keya) @@ -211,14 +177,22 @@ func getClientProof(user string, password string, salt []byte, keyA *big.Int, ke n2 := bytesToBig(bigToSha1(g)) n3 := mathutil.ModPowBigInt(n1, n2, prime) n4 := getStringHash(user) - sha1 := sha1.New() - sha1.Write(n3.Bytes()) - sha1.Write(n4.Bytes()) - sha1.Write(salt) - sha1.Write(keyA.Bytes()) - sha1.Write(keyB.Bytes()) - sha1.Write(keyK) - keyM = sha1.Sum(nil) + + var digest hash.Hash + if pluginName == "Srp" { + digest = sha1.New() + } else if pluginName == "Srp256" { + digest = sha256.New() + } else { + panic("srp protocol error") + } + digest.Write(n3.Bytes()) + digest.Write(n4.Bytes()) + digest.Write(salt) + digest.Write(keyA.Bytes()) + digest.Write(keyB.Bytes()) + digest.Write(keyK) + keyM = digest.Sum(nil) return keyM, keyK } diff --git a/srp_test.go b/srp_test.go index 2fa68139..48b083fb 100644 --- a/srp_test.go +++ b/srp_test.go @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2014 Hajime Nakagami +Copyright (c) 2014-2019 Hajime Nakagami Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -36,10 +36,17 @@ func TestSrp(t *testing.T) { v := getVerifier(user, password, salt) keyB, keyb := getServerSeed(v) serverKey := getServerSession(user, password, salt, keyA, keyB, keyb) - _, clientKey := getClientProof(user, password, salt, keyA, keyB, keya) + _, clientKey := getClientProof(user, password, salt, keyA, keyB, keya, "Srp") for i, _ := range clientKey { if clientKey[i] != serverKey[i] { t.Fatalf("Error srp key exchange") } } + + _, clientKey = getClientProof(user, password, salt, keyA, keyB, keya, "Srp256") + for i, _ := range clientKey { + if clientKey[i] != serverKey[i] { + t.Fatalf("Error srp256 key exchange") + } + } } diff --git a/statement.go b/statement.go index 68408fb2..41bd321f 100644 --- a/statement.go +++ b/statement.go @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2013-2016 Hajime Nakagami +Copyright (c) 2013-2019 Hajime Nakagami Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -86,10 +86,14 @@ func (stmt *firebirdsqlStmt) Exec(args []driver.Value) (result driver.Result, er return stmt.exec(context.Background(), args) } -func (stmt *firebirdsqlStmt) query(ctx context.Context, args []driver.Value) (rows driver.Rows, err error) { +func (stmt *firebirdsqlStmt) query(ctx context.Context, args []driver.Value) (driver.Rows, error) { + var rows driver.Rows + var err error + var result []driver.Value + if stmt.stmtType == isc_info_sql_stmt_exec_procedure { stmt.wp.opExecute2(stmt.stmtHandle, stmt.tx.transHandle, args, stmt.blr) - result, _ := stmt.wp.opSqlResponse(stmt.xsqlda) + result, err = stmt.wp.opSqlResponse(stmt.xsqlda) rows = newFirebirdsqlRows(stmt, result) _, _, _, err = stmt.wp.opResponse() } else { @@ -97,7 +101,7 @@ func (stmt *firebirdsqlStmt) query(ctx context.Context, args []driver.Value) (ro _, _, _, err = stmt.wp.opResponse() rows = newFirebirdsqlRows(stmt, nil) } - return + return rows, err } func (stmt *firebirdsqlStmt) Query(args []driver.Value) (rows driver.Rows, err error) { diff --git a/timezonemap.go b/timezonemap.go new file mode 100644 index 00000000..48d9f1a9 --- /dev/null +++ b/timezonemap.go @@ -0,0 +1,667 @@ +/******************************************************************************* +The MIT License (MIT) + +Copyright (c) 2019 Hajime Nakagami + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*******************************************************************************/ + +package firebirdsql + +func (fc *firebirdsqlConn) loadTimeZoneId() { + fc.wp.tzNameById = map[int]string{ + 65535: "GMT", + 65534: "ACT", + 65533: "AET", + 65532: "AGT", + 65531: "ART", + 65530: "AST", + 65529: "Africa/Abidjan", + 65528: "Africa/Accra", + 65527: "Africa/Addis_Ababa", + 65526: "Africa/Algiers", + 65525: "Africa/Asmara", + 65524: "Africa/Asmera", + 65523: "Africa/Bamako", + 65522: "Africa/Bangui", + 65521: "Africa/Banjul", + 65520: "Africa/Bissau", + 65519: "Africa/Blantyre", + 65518: "Africa/Brazzaville", + 65517: "Africa/Bujumbura", + 65516: "Africa/Cairo", + 65515: "Africa/Casablanca", + 65514: "Africa/Ceuta", + 65513: "Africa/Conakry", + 65512: "Africa/Dakar", + 65511: "Africa/Dar_es_Salaam", + 65510: "Africa/Djibouti", + 65509: "Africa/Douala", + 65508: "Africa/El_Aaiun", + 65507: "Africa/Freetown", + 65506: "Africa/Gaborone", + 65505: "Africa/Harare", + 65504: "Africa/Johannesburg", + 65503: "Africa/Juba", + 65502: "Africa/Kampala", + 65501: "Africa/Khartoum", + 65500: "Africa/Kigali", + 65499: "Africa/Kinshasa", + 65498: "Africa/Lagos", + 65497: "Africa/Libreville", + 65496: "Africa/Lome", + 65495: "Africa/Luanda", + 65494: "Africa/Lubumbashi", + 65493: "Africa/Lusaka", + 65492: "Africa/Malabo", + 65491: "Africa/Maputo", + 65490: "Africa/Maseru", + 65489: "Africa/Mbabane", + 65488: "Africa/Mogadishu", + 65487: "Africa/Monrovia", + 65486: "Africa/Nairobi", + 65485: "Africa/Ndjamena", + 65484: "Africa/Niamey", + 65483: "Africa/Nouakchott", + 65482: "Africa/Ouagadougou", + 65481: "Africa/Porto-Novo", + 65480: "Africa/Sao_Tome", + 65479: "Africa/Timbuktu", + 65478: "Africa/Tripoli", + 65477: "Africa/Tunis", + 65476: "Africa/Windhoek", + 65475: "America/Adak", + 65474: "America/Anchorage", + 65473: "America/Anguilla", + 65472: "America/Antigua", + 65471: "America/Araguaina", + 65470: "America/Argentina/Buenos_Aires", + 65469: "America/Argentina/Catamarca", + 65468: "America/Argentina/ComodRivadavia", + 65467: "America/Argentina/Cordoba", + 65466: "America/Argentina/Jujuy", + 65465: "America/Argentina/La_Rioja", + 65464: "America/Argentina/Mendoza", + 65463: "America/Argentina/Rio_Gallegos", + 65462: "America/Argentina/Salta", + 65461: "America/Argentina/San_Juan", + 65460: "America/Argentina/San_Luis", + 65459: "America/Argentina/Tucuman", + 65458: "America/Argentina/Ushuaia", + 65457: "America/Aruba", + 65456: "America/Asuncion", + 65455: "America/Atikokan", + 65454: "America/Atka", + 65453: "America/Bahia", + 65452: "America/Bahia_Banderas", + 65451: "America/Barbados", + 65450: "America/Belem", + 65449: "America/Belize", + 65448: "America/Blanc-Sablon", + 65447: "America/Boa_Vista", + 65446: "America/Bogota", + 65445: "America/Boise", + 65444: "America/Buenos_Aires", + 65443: "America/Cambridge_Bay", + 65442: "America/Campo_Grande", + 65441: "America/Cancun", + 65440: "America/Caracas", + 65439: "America/Catamarca", + 65438: "America/Cayenne", + 65437: "America/Cayman", + 65436: "America/Chicago", + 65435: "America/Chihuahua", + 65434: "America/Coral_Harbour", + 65433: "America/Cordoba", + 65432: "America/Costa_Rica", + 65431: "America/Creston", + 65430: "America/Cuiaba", + 65429: "America/Curacao", + 65428: "America/Danmarkshavn", + 65427: "America/Dawson", + 65426: "America/Dawson_Creek", + 65425: "America/Denver", + 65424: "America/Detroit", + 65423: "America/Dominica", + 65422: "America/Edmonton", + 65421: "America/Eirunepe", + 65420: "America/El_Salvador", + 65419: "America/Ensenada", + 65418: "America/Fort_Nelson", + 65417: "America/Fort_Wayne", + 65416: "America/Fortaleza", + 65415: "America/Glace_Bay", + 65414: "America/Godthab", + 65413: "America/Goose_Bay", + 65412: "America/Grand_Turk", + 65411: "America/Grenada", + 65410: "America/Guadeloupe", + 65409: "America/Guatemala", + 65408: "America/Guayaquil", + 65407: "America/Guyana", + 65406: "America/Halifax", + 65405: "America/Havana", + 65404: "America/Hermosillo", + 65403: "America/Indiana/Indianapolis", + 65402: "America/Indiana/Knox", + 65401: "America/Indiana/Marengo", + 65400: "America/Indiana/Petersburg", + 65399: "America/Indiana/Tell_City", + 65398: "America/Indiana/Vevay", + 65397: "America/Indiana/Vincennes", + 65396: "America/Indiana/Winamac", + 65395: "America/Indianapolis", + 65394: "America/Inuvik", + 65393: "America/Iqaluit", + 65392: "America/Jamaica", + 65391: "America/Jujuy", + 65390: "America/Juneau", + 65389: "America/Kentucky/Louisville", + 65388: "America/Kentucky/Monticello", + 65387: "America/Knox_IN", + 65386: "America/Kralendijk", + 65385: "America/La_Paz", + 65384: "America/Lima", + 65383: "America/Los_Angeles", + 65382: "America/Louisville", + 65381: "America/Lower_Princes", + 65380: "America/Maceio", + 65379: "America/Managua", + 65378: "America/Manaus", + 65377: "America/Marigot", + 65376: "America/Martinique", + 65375: "America/Matamoros", + 65374: "America/Mazatlan", + 65373: "America/Mendoza", + 65372: "America/Menominee", + 65371: "America/Merida", + 65370: "America/Metlakatla", + 65369: "America/Mexico_City", + 65368: "America/Miquelon", + 65367: "America/Moncton", + 65366: "America/Monterrey", + 65365: "America/Montevideo", + 65364: "America/Montreal", + 65363: "America/Montserrat", + 65362: "America/Nassau", + 65361: "America/New_York", + 65360: "America/Nipigon", + 65359: "America/Nome", + 65358: "America/Noronha", + 65357: "America/North_Dakota/Beulah", + 65356: "America/North_Dakota/Center", + 65355: "America/North_Dakota/New_Salem", + 65354: "America/Ojinaga", + 65353: "America/Panama", + 65352: "America/Pangnirtung", + 65351: "America/Paramaribo", + 65350: "America/Phoenix", + 65349: "America/Port-au-Prince", + 65348: "America/Port_of_Spain", + 65347: "America/Porto_Acre", + 65346: "America/Porto_Velho", + 65345: "America/Puerto_Rico", + 65344: "America/Punta_Arenas", + 65343: "America/Rainy_River", + 65342: "America/Rankin_Inlet", + 65341: "America/Recife", + 65340: "America/Regina", + 65339: "America/Resolute", + 65338: "America/Rio_Branco", + 65337: "America/Rosario", + 65336: "America/Santa_Isabel", + 65335: "America/Santarem", + 65334: "America/Santiago", + 65333: "America/Santo_Domingo", + 65332: "America/Sao_Paulo", + 65331: "America/Scoresbysund", + 65330: "America/Shiprock", + 65329: "America/Sitka", + 65328: "America/St_Barthelemy", + 65327: "America/St_Johns", + 65326: "America/St_Kitts", + 65325: "America/St_Lucia", + 65324: "America/St_Thomas", + 65323: "America/St_Vincent", + 65322: "America/Swift_Current", + 65321: "America/Tegucigalpa", + 65320: "America/Thule", + 65319: "America/Thunder_Bay", + 65318: "America/Tijuana", + 65317: "America/Toronto", + 65316: "America/Tortola", + 65315: "America/Vancouver", + 65314: "America/Virgin", + 65313: "America/Whitehorse", + 65312: "America/Winnipeg", + 65311: "America/Yakutat", + 65310: "America/Yellowknife", + 65309: "Antarctica/Casey", + 65308: "Antarctica/Davis", + 65307: "Antarctica/DumontDUrville", + 65306: "Antarctica/Macquarie", + 65305: "Antarctica/Mawson", + 65304: "Antarctica/McMurdo", + 65303: "Antarctica/Palmer", + 65302: "Antarctica/Rothera", + 65301: "Antarctica/South_Pole", + 65300: "Antarctica/Syowa", + 65299: "Antarctica/Troll", + 65298: "Antarctica/Vostok", + 65297: "Arctic/Longyearbyen", + 65296: "Asia/Aden", + 65295: "Asia/Almaty", + 65294: "Asia/Amman", + 65293: "Asia/Anadyr", + 65292: "Asia/Aqtau", + 65291: "Asia/Aqtobe", + 65290: "Asia/Ashgabat", + 65289: "Asia/Ashkhabad", + 65288: "Asia/Atyrau", + 65287: "Asia/Baghdad", + 65286: "Asia/Bahrain", + 65285: "Asia/Baku", + 65284: "Asia/Bangkok", + 65283: "Asia/Barnaul", + 65282: "Asia/Beirut", + 65281: "Asia/Bishkek", + 65280: "Asia/Brunei", + 65279: "Asia/Calcutta", + 65278: "Asia/Chita", + 65277: "Asia/Choibalsan", + 65276: "Asia/Chongqing", + 65275: "Asia/Chungking", + 65274: "Asia/Colombo", + 65273: "Asia/Dacca", + 65272: "Asia/Damascus", + 65271: "Asia/Dhaka", + 65270: "Asia/Dili", + 65269: "Asia/Dubai", + 65268: "Asia/Dushanbe", + 65267: "Asia/Famagusta", + 65266: "Asia/Gaza", + 65265: "Asia/Harbin", + 65264: "Asia/Hebron", + 65263: "Asia/Ho_Chi_Minh", + 65262: "Asia/Hong_Kong", + 65261: "Asia/Hovd", + 65260: "Asia/Irkutsk", + 65259: "Asia/Istanbul", + 65258: "Asia/Jakarta", + 65257: "Asia/Jayapura", + 65256: "Asia/Jerusalem", + 65255: "Asia/Kabul", + 65254: "Asia/Kamchatka", + 65253: "Asia/Karachi", + 65252: "Asia/Kashgar", + 65251: "Asia/Kathmandu", + 65250: "Asia/Katmandu", + 65249: "Asia/Khandyga", + 65248: "Asia/Kolkata", + 65247: "Asia/Krasnoyarsk", + 65246: "Asia/Kuala_Lumpur", + 65245: "Asia/Kuching", + 65244: "Asia/Kuwait", + 65243: "Asia/Macao", + 65242: "Asia/Macau", + 65241: "Asia/Magadan", + 65240: "Asia/Makassar", + 65239: "Asia/Manila", + 65238: "Asia/Muscat", + 65237: "Asia/Nicosia", + 65236: "Asia/Novokuznetsk", + 65235: "Asia/Novosibirsk", + 65234: "Asia/Omsk", + 65233: "Asia/Oral", + 65232: "Asia/Phnom_Penh", + 65231: "Asia/Pontianak", + 65230: "Asia/Pyongyang", + 65229: "Asia/Qatar", + 65228: "Asia/Qyzylorda", + 65227: "Asia/Rangoon", + 65226: "Asia/Riyadh", + 65225: "Asia/Saigon", + 65224: "Asia/Sakhalin", + 65223: "Asia/Samarkand", + 65222: "Asia/Seoul", + 65221: "Asia/Shanghai", + 65220: "Asia/Singapore", + 65219: "Asia/Srednekolymsk", + 65218: "Asia/Taipei", + 65217: "Asia/Tashkent", + 65216: "Asia/Tbilisi", + 65215: "Asia/Tehran", + 65214: "Asia/Tel_Aviv", + 65213: "Asia/Thimbu", + 65212: "Asia/Thimphu", + 65211: "Asia/Tokyo", + 65210: "Asia/Tomsk", + 65209: "Asia/Ujung_Pandang", + 65208: "Asia/Ulaanbaatar", + 65207: "Asia/Ulan_Bator", + 65206: "Asia/Urumqi", + 65205: "Asia/Ust-Nera", + 65204: "Asia/Vientiane", + 65203: "Asia/Vladivostok", + 65202: "Asia/Yakutsk", + 65201: "Asia/Yangon", + 65200: "Asia/Yekaterinburg", + 65199: "Asia/Yerevan", + 65198: "Atlantic/Azores", + 65197: "Atlantic/Bermuda", + 65196: "Atlantic/Canary", + 65195: "Atlantic/Cape_Verde", + 65194: "Atlantic/Faeroe", + 65193: "Atlantic/Faroe", + 65192: "Atlantic/Jan_Mayen", + 65191: "Atlantic/Madeira", + 65190: "Atlantic/Reykjavik", + 65189: "Atlantic/South_Georgia", + 65188: "Atlantic/St_Helena", + 65187: "Atlantic/Stanley", + 65186: "Australia/ACT", + 65185: "Australia/Adelaide", + 65184: "Australia/Brisbane", + 65183: "Australia/Broken_Hill", + 65182: "Australia/Canberra", + 65181: "Australia/Currie", + 65180: "Australia/Darwin", + 65179: "Australia/Eucla", + 65178: "Australia/Hobart", + 65177: "Australia/LHI", + 65176: "Australia/Lindeman", + 65175: "Australia/Lord_Howe", + 65174: "Australia/Melbourne", + 65173: "Australia/NSW", + 65172: "Australia/North", + 65171: "Australia/Perth", + 65170: "Australia/Queensland", + 65169: "Australia/South", + 65168: "Australia/Sydney", + 65167: "Australia/Tasmania", + 65166: "Australia/Victoria", + 65165: "Australia/West", + 65164: "Australia/Yancowinna", + 65163: "BET", + 65162: "BST", + 65161: "Brazil/Acre", + 65160: "Brazil/DeNoronha", + 65159: "Brazil/East", + 65158: "Brazil/West", + 65157: "CAT", + 65156: "CET", + 65155: "CNT", + 65154: "CST", + 65153: "CST6CDT", + 65152: "CTT", + 65151: "Canada/Atlantic", + 65150: "Canada/Central", + 65149: "Canada/East-Saskatchewan", + 65148: "Canada/Eastern", + 65147: "Canada/Mountain", + 65146: "Canada/Newfoundland", + 65145: "Canada/Pacific", + 65144: "Canada/Saskatchewan", + 65143: "Canada/Yukon", + 65142: "Chile/Continental", + 65141: "Chile/EasterIsland", + 65140: "Cuba", + 65139: "EAT", + 65138: "ECT", + 65137: "EET", + 65136: "EST", + 65135: "EST5EDT", + 65134: "Egypt", + 65133: "Eire", + 65132: "Etc/GMT", + 65131: "Etc/GMT+0", + 65130: "Etc/GMT+1", + 65129: "Etc/GMT+10", + 65128: "Etc/GMT+11", + 65127: "Etc/GMT+12", + 65126: "Etc/GMT+2", + 65125: "Etc/GMT+3", + 65124: "Etc/GMT+4", + 65123: "Etc/GMT+5", + 65122: "Etc/GMT+6", + 65121: "Etc/GMT+7", + 65120: "Etc/GMT+8", + 65119: "Etc/GMT+9", + 65118: "Etc/GMT-0", + 65117: "Etc/GMT-1", + 65116: "Etc/GMT-10", + 65115: "Etc/GMT-11", + 65114: "Etc/GMT-12", + 65113: "Etc/GMT-13", + 65112: "Etc/GMT-14", + 65111: "Etc/GMT-2", + 65110: "Etc/GMT-3", + 65109: "Etc/GMT-4", + 65108: "Etc/GMT-5", + 65107: "Etc/GMT-6", + 65106: "Etc/GMT-7", + 65105: "Etc/GMT-8", + 65104: "Etc/GMT-9", + 65103: "Etc/GMT0", + 65102: "Etc/Greenwich", + 65101: "Etc/UCT", + 65100: "Etc/UTC", + 65099: "Etc/Universal", + 65098: "Etc/Zulu", + 65097: "Europe/Amsterdam", + 65096: "Europe/Andorra", + 65095: "Europe/Astrakhan", + 65094: "Europe/Athens", + 65093: "Europe/Belfast", + 65092: "Europe/Belgrade", + 65091: "Europe/Berlin", + 65090: "Europe/Bratislava", + 65089: "Europe/Brussels", + 65088: "Europe/Bucharest", + 65087: "Europe/Budapest", + 65086: "Europe/Busingen", + 65085: "Europe/Chisinau", + 65084: "Europe/Copenhagen", + 65083: "Europe/Dublin", + 65082: "Europe/Gibraltar", + 65081: "Europe/Guernsey", + 65080: "Europe/Helsinki", + 65079: "Europe/Isle_of_Man", + 65078: "Europe/Istanbul", + 65077: "Europe/Jersey", + 65076: "Europe/Kaliningrad", + 65075: "Europe/Kiev", + 65074: "Europe/Kirov", + 65073: "Europe/Lisbon", + 65072: "Europe/Ljubljana", + 65071: "Europe/London", + 65070: "Europe/Luxembourg", + 65069: "Europe/Madrid", + 65068: "Europe/Malta", + 65067: "Europe/Mariehamn", + 65066: "Europe/Minsk", + 65065: "Europe/Monaco", + 65064: "Europe/Moscow", + 65063: "Europe/Nicosia", + 65062: "Europe/Oslo", + 65061: "Europe/Paris", + 65060: "Europe/Podgorica", + 65059: "Europe/Prague", + 65058: "Europe/Riga", + 65057: "Europe/Rome", + 65056: "Europe/Samara", + 65055: "Europe/San_Marino", + 65054: "Europe/Sarajevo", + 65053: "Europe/Saratov", + 65052: "Europe/Simferopol", + 65051: "Europe/Skopje", + 65050: "Europe/Sofia", + 65049: "Europe/Stockholm", + 65048: "Europe/Tallinn", + 65047: "Europe/Tirane", + 65046: "Europe/Tiraspol", + 65045: "Europe/Ulyanovsk", + 65044: "Europe/Uzhgorod", + 65043: "Europe/Vaduz", + 65042: "Europe/Vatican", + 65041: "Europe/Vienna", + 65040: "Europe/Vilnius", + 65039: "Europe/Volgograd", + 65038: "Europe/Warsaw", + 65037: "Europe/Zagreb", + 65036: "Europe/Zaporozhye", + 65035: "Europe/Zurich", + 65034: "Factory", + 65033: "GB", + 65032: "GB-Eire", + 65031: "GMT+0", + 65030: "GMT-0", + 65029: "GMT0", + 65028: "Greenwich", + 65027: "HST", + 65026: "Hongkong", + 65025: "IET", + 65024: "IST", + 65023: "Iceland", + 65022: "Indian/Antananarivo", + 65021: "Indian/Chagos", + 65020: "Indian/Christmas", + 65019: "Indian/Cocos", + 65018: "Indian/Comoro", + 65017: "Indian/Kerguelen", + 65016: "Indian/Mahe", + 65015: "Indian/Maldives", + 65014: "Indian/Mauritius", + 65013: "Indian/Mayotte", + 65012: "Indian/Reunion", + 65011: "Iran", + 65010: "Israel", + 65009: "JST", + 65008: "Jamaica", + 65007: "Japan", + 65006: "Kwajalein", + 65005: "Libya", + 65004: "MET", + 65003: "MIT", + 65002: "MST", + 65001: "MST7MDT", + 65000: "Mexico/BajaNorte", + 64999: "Mexico/BajaSur", + 64998: "Mexico/General", + 64997: "NET", + 64996: "NST", + 64995: "NZ", + 64994: "NZ-CHAT", + 64993: "Navajo", + 64992: "PLT", + 64991: "PNT", + 64990: "PRC", + 64989: "PRT", + 64988: "PST", + 64987: "PST8PDT", + 64986: "Pacific/Apia", + 64985: "Pacific/Auckland", + 64984: "Pacific/Bougainville", + 64983: "Pacific/Chatham", + 64982: "Pacific/Chuuk", + 64981: "Pacific/Easter", + 64980: "Pacific/Efate", + 64979: "Pacific/Enderbury", + 64978: "Pacific/Fakaofo", + 64977: "Pacific/Fiji", + 64976: "Pacific/Funafuti", + 64975: "Pacific/Galapagos", + 64974: "Pacific/Gambier", + 64973: "Pacific/Guadalcanal", + 64972: "Pacific/Guam", + 64971: "Pacific/Honolulu", + 64970: "Pacific/Johnston", + 64969: "Pacific/Kiritimati", + 64968: "Pacific/Kosrae", + 64967: "Pacific/Kwajalein", + 64966: "Pacific/Majuro", + 64965: "Pacific/Marquesas", + 64964: "Pacific/Midway", + 64963: "Pacific/Nauru", + 64962: "Pacific/Niue", + 64961: "Pacific/Norfolk", + 64960: "Pacific/Noumea", + 64959: "Pacific/Pago_Pago", + 64958: "Pacific/Palau", + 64957: "Pacific/Pitcairn", + 64956: "Pacific/Pohnpei", + 64955: "Pacific/Ponape", + 64954: "Pacific/Port_Moresby", + 64953: "Pacific/Rarotonga", + 64952: "Pacific/Saipan", + 64951: "Pacific/Samoa", + 64950: "Pacific/Tahiti", + 64949: "Pacific/Tarawa", + 64948: "Pacific/Tongatapu", + 64947: "Pacific/Truk", + 64946: "Pacific/Wake", + 64945: "Pacific/Wallis", + 64944: "Pacific/Yap", + 64943: "Poland", + 64942: "Portugal", + 64941: "ROC", + 64940: "ROK", + 64939: "SST", + 64938: "Singapore", + 64937: "SystemV/AST4", + 64936: "SystemV/AST4ADT", + 64935: "SystemV/CST6", + 64934: "SystemV/CST6CDT", + 64933: "SystemV/EST5", + 64932: "SystemV/EST5EDT", + 64931: "SystemV/HST10", + 64930: "SystemV/MST7", + 64929: "SystemV/MST7MDT", + 64928: "SystemV/PST8", + 64927: "SystemV/PST8PDT", + 64926: "SystemV/YST9", + 64925: "SystemV/YST9YDT", + 64924: "Turkey", + 64923: "UCT", + 64922: "US/Alaska", + 64921: "US/Aleutian", + 64920: "US/Arizona", + 64919: "US/Central", + 64918: "US/East-Indiana", + 64917: "US/Eastern", + 64916: "US/Hawaii", + 64915: "US/Indiana-Starke", + 64914: "US/Michigan", + 64913: "US/Mountain", + 64912: "US/Pacific", + 64911: "US/Pacific-New", + 64910: "US/Samoa", + 64909: "UTC", + 64908: "Universal", + 64907: "VST", + 64906: "W-SU", + 64905: "WET", + 64904: "Zulu", + } + + fc.wp.tzIdByName = make(map[string]int) + + for k, v := range fc.wp.tzNameById { + fc.wp.tzIdByName[v] = k + } +} diff --git a/transaction.go b/transaction.go index ebe332e7..bf61d9e7 100644 --- a/transaction.go +++ b/transaction.go @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2013-2016 Hajime Nakagami +Copyright (c) 2013-2019 Hajime Nakagami Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -28,6 +28,7 @@ type firebirdsqlTx struct { isolationLevel int isAutocommit bool transHandle int32 + needBegin bool } func (tx *firebirdsqlTx) begin() (err error) { @@ -74,20 +75,31 @@ func (tx *firebirdsqlTx) begin() (err error) { } tx.fc.wp.opTransaction(tpb) tx.transHandle, _, _, err = tx.fc.wp.opResponse() + tx.needBegin = false + tx.fc.transactionSet[tx] = struct{}{} return } -func (tx *firebirdsqlTx) Commit() (err error) { +func (tx *firebirdsqlTx) commitRetainging() (err error) { tx.fc.wp.opCommitRetaining(tx.transHandle) _, _, _, err = tx.fc.wp.opResponse() tx.isAutocommit = tx.fc.isAutocommit return } +func (tx *firebirdsqlTx) Commit() (err error) { + tx.fc.wp.opCommit(tx.transHandle) + _, _, _, err = tx.fc.wp.opResponse() + tx.isAutocommit = tx.fc.isAutocommit + tx.needBegin = true + return +} + func (tx *firebirdsqlTx) Rollback() (err error) { - tx.fc.wp.opRollbackRetaining(tx.transHandle) + tx.fc.wp.opRollback(tx.transHandle) _, _, _, err = tx.fc.wp.opResponse() tx.isAutocommit = tx.fc.isAutocommit + tx.needBegin = true return } diff --git a/transaction_test.go b/transaction_test.go index d85dc697..22686788 100644 --- a/transaction_test.go +++ b/transaction_test.go @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2016 Hajime Nakagami +Copyright (c) 2016-2019 Hajime Nakagami Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -264,3 +264,26 @@ func TestIssue39(t *testing.T) { conn.Close() } + +func TestIssue67(t *testing.T) { + temppath := TempFileName("test_issue67_") + conn, _ := sql.Open("firebirdsql_createdb", "sysdba:masterkey@localhost:3050"+temppath) + var n int + conn.QueryRow("SELECT Count(*) FROM rdb$relations").Scan(&n) + err := conn.Close() + if err != nil { + t.Fatalf("Error Close: %v", err) + } + + conn, _ = sql.Open("firebirdsql", "sysdba:masterkey@localhost:3050"+temppath) + tx, _ := conn.Begin() + tx.QueryRow("SELECT Count(*) FROM rdb$relations").Scan(&n) + + tx.Commit() + + err = conn.Close() + if err != nil { + t.Fatalf("Error Close: %v", err) + } + +} diff --git a/utils.go b/utils.go index 9d43843c..11b53cc2 100644 --- a/utils.go +++ b/utils.go @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2013-2016 Hajime Nakagami +Copyright (c) 2013-2019 Hajime Nakagami Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -28,6 +28,7 @@ import ( "container/list" "encoding/binary" "errors" + "math/big" "net/url" "strconv" "strings" @@ -35,7 +36,7 @@ import ( ) func str_to_bytes(s string) []byte { - return bytes.NewBufferString(s).Bytes() + return []byte(s) } func int32_to_bytes(i32 int32) []byte { @@ -66,73 +67,67 @@ func int16_to_bytes(i16 int16) []byte { return bs } func bytes_to_str(b []byte) string { - return bytes.NewBuffer(b).String() + return string(b) } func bytes_to_bint32(b []byte) int32 { - var i32 int32 - buffer := bytes.NewBuffer(b) - binary.Read(buffer, binary.BigEndian, &i32) - return i32 + return int32(binary.BigEndian.Uint32(b)) } func bytes_to_int32(b []byte) int32 { - var i32 int32 - buffer := bytes.NewBuffer(b) - binary.Read(buffer, binary.LittleEndian, &i32) - return i32 + return int32(binary.LittleEndian.Uint32(b)) } func bytes_to_bint16(b []byte) int16 { - var i int16 - buffer := bytes.NewBuffer(b) - binary.Read(buffer, binary.BigEndian, &i) - return i + return int16(binary.BigEndian.Uint16(b)) } func bytes_to_int16(b []byte) int16 { - var i int16 - buffer := bytes.NewBuffer(b) - binary.Read(buffer, binary.LittleEndian, &i) - return i + return int16(binary.LittleEndian.Uint16(b)) } func bytes_to_bint64(b []byte) int64 { - var i int64 - buffer := bytes.NewBuffer(b) - binary.Read(buffer, binary.BigEndian, &i) - return i + return int64(binary.BigEndian.Uint64(b)) } func bytes_to_int64(b []byte) int64 { - var i int64 - buffer := bytes.NewBuffer(b) - binary.Read(buffer, binary.LittleEndian, &i) - return i + return int64(binary.LittleEndian.Uint64(b)) } -func xdrBytes(bs []byte) []byte { - // XDR encoding bytes - n := len(bs) - padding := 0 - if n%4 != 0 { - padding = 4 - n%4 - } - buf := make([]byte, 4+n+padding) - buf[0] = byte(n >> 24 & 0xFF) - buf[1] = byte(n >> 16 & 0xFF) - buf[2] = byte(n >> 8 & 0xFF) - buf[3] = byte(n & 0xFF) - for i, b := range bs { - buf[4+i] = b +func bigFromHexString(s string) *big.Int { + ret := new(big.Int) + ret.SetString(s, 16) + return ret +} + +func bigFromString(s string) *big.Int { + ret := new(big.Int) + ret.SetString(s, 10) + return ret +} + +func bigToBytes(v *big.Int) []byte { + buf := pad(v) + for i, _ := range buf { + if buf[i] != 0 { + return buf[i:] + } } - return buf + + return buf[:1] // 0 } -func xdrString(s string) []byte { - // XDR encoding string - bs := bytes.NewBufferString(s).Bytes() - return xdrBytes(bs) +func bytesToBig(v []byte) (r *big.Int) { + m := new(big.Int) + m.SetInt64(256) + a := new(big.Int) + r = new(big.Int) + r.SetInt64(0) + for _, b := range v { + r = r.Mul(r, m) + r = r.Add(r, a.SetInt64(int64(b))) + } + return r } func flattenBytes(l *list.List) []byte { @@ -154,6 +149,30 @@ func flattenBytes(l *list.List) []byte { return bs } +func xdrBytes(bs []byte) []byte { + // XDR encoding bytes + n := len(bs) + padding := 0 + if n%4 != 0 { + padding = 4 - n%4 + } + buf := make([]byte, 4+n+padding) + buf[0] = byte(n >> 24 & 0xFF) + buf[1] = byte(n >> 16 & 0xFF) + buf[2] = byte(n >> 8 & 0xFF) + buf[3] = byte(n & 0xFF) + for i, b := range bs { + buf[4+i] = b + } + return buf +} + +func xdrString(s string) []byte { + // XDR encoding string + bs := bytes.NewBufferString(s).Bytes() + return xdrBytes(bs) +} + func _int32ToBlr(i32 int32) ([]byte, []byte) { v := bytes.Join([][]byte{ bint32_to_bytes(i32), @@ -227,7 +246,8 @@ func split1(src string, delm string) (string, string) { return src, "" } -func parseDSN(dsn string) (addr string, dbName string, user string, passwd string, role string, authPluginName string, wireCrypt bool, err error) { +func parseDSN(dsn string) (addr string, dbName string, user string, passwd string, options map[string]string, err error) { + options = make(map[string]string) if !strings.HasPrefix(dsn, "firebird://") { dsn = "firebird://" + dsn } @@ -257,30 +277,34 @@ func parseDSN(dsn string) (addr string, dbName string, user string, passwd strin m, _ := url.ParseQuery(u.RawQuery) - values, ok := m["role"] - if ok { - role = values[0] - } else { - role = "" - } - - values, ok = m["auth_plugin_name"] - if ok { - authPluginName = values[0] - } else { - authPluginName = "Srp" + var default_options = map[string]string{ + "auth_plugin_name": "Srp", + "column_name_to_lower": "false", + "role": "", + "timezone": "", + "wire_crypt": "true", } - values, ok = m["wire_crypt"] - if ok { - wireCrypt, _ = strconv.ParseBool(values[0]) - } else { - wireCrypt = true + for k, v := range default_options { + values, ok := m[k] + if ok { + options[k] = values[0] + } else { + options[k] = v + } } return } +func convertToBool(s string, defaultValue bool) bool { + v, err := strconv.ParseBool(s) + if err != nil { + v = defaultValue + } + return v +} + func calcBlr(xsqlda []xSQLVAR) []byte { // Calculate BLR from XSQLVAR array. ln := len(xsqlda) * 2 @@ -325,13 +349,9 @@ func calcBlr(xsqlda []xSQLVAR) []byte { blr[n] = 9 blr[n+1] = byte(sqlscale) n += 2 - case SQL_TYPE_BLOB: - blr[n] = 9 - blr[n+1] = 0 - n += 2 - case SQL_TYPE_ARRAY: - blr[n] = 9 - blr[n+1] = 0 + case SQL_TYPE_DEC_FIXED: + blr[n] = 26 + blr[n+1] = byte(sqlscale) n += 2 case SQL_TYPE_DOUBLE: blr[n] = 27 @@ -351,9 +371,29 @@ func calcBlr(xsqlda []xSQLVAR) []byte { case SQL_TYPE_TIMESTAMP: blr[n] = 35 n += 1 + case SQL_TYPE_BLOB: + blr[n] = 9 + blr[n+1] = 0 + n += 2 + case SQL_TYPE_ARRAY: + blr[n] = 9 + blr[n+1] = 0 + n += 2 case SQL_TYPE_BOOLEAN: blr[n] = 23 n += 1 + case SQL_TYPE_DEC64: + blr[n] = 24 + n += 1 + case SQL_TYPE_DEC128: + blr[n] = 25 + n += 1 + case SQL_TYPE_TIME_TZ: + blr[n] = 28 + n += 1 + case SQL_TYPE_TIMESTAMP_TZ: + blr[n] = 29 + n += 1 } // [blr_short, 0] blr[n] = 7 diff --git a/utils_test.go b/utils_test.go index 81f66cde..4073cd95 100644 --- a/utils_test.go +++ b/utils_test.go @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2013-2016 Hajime Nakagami +Copyright (c) 2013-2019 Hajime Nakagami Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -36,20 +36,21 @@ func TestDSNParse(t *testing.T) { passwd string role string authPluginName string - wireCrypt bool + wireCrypt string }{ - {"user:password@localhost:3000/dbname", "localhost:3000", "dbname", "user", "password", "", "Srp", true}, - {"user:password@localhost/dbname", "localhost:3050", "dbname", "user", "password", "", "Srp", true}, - {"user:password@localhost/dir/dbname", "localhost:3050", "/dir/dbname", "user", "password", "", "Srp", true}, - {"user:password@localhost/c:\\fbdata\\database.fdb", "localhost:3050", "c:\\fbdata\\database.fdb", "user", "password", "", "Srp", true}, - {"user:password@localhost/c:/fbdata/database.fdb", "localhost:3050", "c:/fbdata/database.fdb", "user", "password", "", "Srp", true}, - {"user:password@localhost/dbname?role=role", "localhost:3050", "dbname", "user", "password", "role", "Srp", true}, - {"user:password@localhost:3000/c:/fbdata/database.fdb?role=role&wire_crypt=false", "localhost:3000", "c:/fbdata/database.fdb", "user", "password", "role", "Srp", false}, - {"firebird://user:password@localhost:3000/dbname", "localhost:3000", "dbname", "user", "password", "", "Srp", true}, + {"user:password@localhost:3000/dbname", "localhost:3000", "dbname", "user", "password", "", "Srp", "true"}, + {"user:password@localhost/dbname", "localhost:3050", "dbname", "user", "password", "", "Srp", "true"}, + {"user:password@localhost/dir/dbname", "localhost:3050", "/dir/dbname", "user", "password", "", "Srp", "true"}, + {"user:password@localhost/c:\\fbdata\\database.fdb", "localhost:3050", "c:\\fbdata\\database.fdb", "user", "password", "", "Srp", "true"}, + {"user:password@localhost/c:/fbdata/database.fdb", "localhost:3050", "c:/fbdata/database.fdb", "user", "password", "", "Srp", "true"}, + {"user:password@localhost/dbname?role=role", "localhost:3050", "dbname", "user", "password", "role", "Srp", "true"}, + {"user:password@localhost:3000/c:/fbdata/database.fdb?role=role&wire_crypt=false", "localhost:3000", "c:/fbdata/database.fdb", "user", "password", "role", "Srp", "false"}, + {"firebird://user:password@localhost:3000/dbname", "localhost:3000", "dbname", "user", "password", "", "Srp", "true"}, + {"firebird://user:%21p%40ssword%3F@localhost:3050/dbname", "localhost:3050", "dbname", "user", "!p@ssword?", "", "Srp", "true"}, } for _, d := range testDSNs { - addr, dbName, user, passwd, role, authPluginName, wireCrypt, err := parseDSN(d.dsn) + addr, dbName, user, passwd, options, err := parseDSN(d.dsn) if err != nil { t.Fatal(err) } @@ -65,22 +66,22 @@ func TestDSNParse(t *testing.T) { if passwd != d.passwd { t.Errorf("parse DSN fail:%s(%s != %s)", d.dsn, passwd, d.passwd) } - if role != d.role { - t.Errorf("parse DSN fail:%s(%s != %s)", d.dsn, role, d.role) + if options["role"] != d.role { + t.Errorf("parse DSN fail:%s(%s != %s)", d.dsn, options["role"], d.role) } - if authPluginName != d.authPluginName { - t.Errorf("parse DSN fail:%s(%s != %s)", d.dsn, authPluginName, d.authPluginName) + if options["auth_plugin_name"] != d.authPluginName { + t.Errorf("parse DSN fail:%s(%s != %s)", d.dsn, options["auth_plugin_name"], d.authPluginName) } - if wireCrypt != d.wireCrypt { - t.Errorf("parse DSN fail:%s(%v != %v)", d.dsn, wireCrypt, d.wireCrypt) + if options["wire_crypt"] != d.wireCrypt { + t.Errorf("parse DSN fail:%s(%v != %v)", d.dsn, options["wire_crypt"], d.wireCrypt) } } - _, _, _, _, _, _, _, err := parseDSN("something wrong") + _, _, _, _, _, err := parseDSN("something wrong") if err == nil { t.Fatalf("Error Not occured") } - _, _, _, _, _, _, _, err = parseDSN("SomethingWrongConnectionString") + _, _, _, _, _, err = parseDSN("SomethingWrongConnectionString") if err == nil { t.Fatalf("Error Not occured") } diff --git a/wireprotocol.go b/wireprotocol.go index a6ca619d..128e998c 100644 --- a/wireprotocol.go +++ b/wireprotocol.go @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2013-2016 Hajime Nakagami +Copyright (c) 2013-2019 Hajime Nakagami Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -40,12 +40,12 @@ import ( "time" "github.com/kardianos/osext" - "github.com/nyarla/go-crypt" + "gitlab.com/nyarla/go-crypt" //"unsafe" ) const ( - PLUGIN_LIST = "Srp,Legacy_Auth" + PLUGIN_LIST = "Srp256,Srp,Legacy_Auth" BUFFER_LEN = 1024 MAX_CHAR_LENGTH = 32767 BLOB_SEGMENT_SIZE = 32000 @@ -146,9 +146,14 @@ type wireProtocol struct { user string password string authData []byte + + // Time Zone + timezone string + tzNameById map[int]string + tzIdByName map[string]int } -func newWireProtocol(addr string) (*wireProtocol, error) { +func newWireProtocol(addr string, timezone string) (*wireProtocol, error) { p := new(wireProtocol) p.buf = make([]byte, 0, BUFFER_LEN) @@ -159,6 +164,7 @@ func newWireProtocol(addr string) (*wireProtocol, error) { } p.conn, err = newWireChannel(conn) + p.timezone = timezone return p, err } @@ -215,7 +221,7 @@ func (p *wireProtocol) uid(user string, password string, authPluginName string, } var specific_data []byte - if authPluginName == "Srp" { + if authPluginName == "Srp" || authPluginName == "Srp256" { specific_data = getSrpClientPublicBytes(clientPublic) } else if authPluginName == "Legacy_Auth" { b := bytes.NewBufferString(crypt.Crypt(password, "9z")[2:]).Bytes() @@ -359,6 +365,110 @@ func (p *wireProtocol) _parse_op_response() (int32, []byte, []byte, error) { return h, oid, buf, err } +func (p *wireProtocol) _parse_connect_response(user string, password string, options map[string]string, clientPublic *big.Int, clientSecret *big.Int) (err error) { + p.debugPrint("_parse_connect_response") + wire_crypt := true + wire_crypt, _ = strconv.ParseBool(options["wire_crypt"]) + + b, err := p.recvPackets(4) + opcode := bytes_to_bint32(b) + + for opcode == op_dummy { + b, _ = p.recvPackets(4) + opcode = bytes_to_bint32(b) + } + + if opcode == op_reject { + err = errors.New("_parse_connect_response() op_reject") + return + } + if opcode == op_response { + _, _, _, err = p._parse_op_response() // error occured + return + } + + b, _ = p.recvPackets(12) + p.protocolVersion = int32(b[3]) + p.acceptArchitecture = bytes_to_bint32(b[4:8]) + p.acceptType = bytes_to_bint32(b[8:12]) + + if opcode == op_cond_accept || opcode == op_accept_data { + var readLength, ln int + + b, _ := p.recvPackets(4) + ln = int(bytes_to_bint32(b)) + data, _ := p.recvPacketsAlignment(ln) + + b, _ = p.recvPackets(4) + ln = int(bytes_to_bint32(b)) + pluginName, _ := p.recvPacketsAlignment(ln) + p.pluginName = bytes_to_str(pluginName) + + b, _ = p.recvPackets(4) + isAuthenticated := bytes_to_bint32(b) + readLength += 4 + + b, _ = p.recvPackets(4) + ln = int(bytes_to_bint32(b)) + _, _ = p.recvPacketsAlignment(ln) // keys + + if isAuthenticated == 0 { + var authData []byte + var sessionKey []byte + if (p.pluginName == "Srp" || p.pluginName == "Srp256") && len(data) > 2 { + ln = int(bytes_to_int16(data[:2])) + serverSalt := data[2 : ln+2] + serverPublic := bigFromHexString(bytes_to_str(data[4+ln:])) + authData, sessionKey = getClientProof(strings.ToUpper(user), password, serverSalt, clientPublic, serverPublic, clientSecret, p.pluginName) + if DEBUG_SRP { + fmt.Printf("pluginName=%s\nserverSalt=%s\nserverPublic(bin)=%s\nserverPublic=%s\nauthData=%v,sessionKey=%v\n", + p.pluginName, serverSalt, data[4+ln:], serverPublic, authData, sessionKey) + } + } else if p.pluginName == "Legacy_Auth" { + authData = bytes.NewBufferString(crypt.Crypt(password, "9z")[2:]).Bytes() + } else { + err = errors.New("_parse_connect_response() Unauthorized") + return + } + if wire_crypt { + // Send op_cont_auth + p.packInt(op_cont_auth) + p.packString(hex.EncodeToString(authData)) + p.packString(options["auth_plugin_name"]) + p.packString(PLUGIN_LIST) + p.packString("") + p.sendPackets() + _, _, _, err = p.opResponse() + if err != nil { + return + } + + // Send op_crypt + p.packInt(op_crypt) + p.packString("Arc4") + p.packString("Symmetric") + p.sendPackets() + p.conn.setAuthKey(sessionKey) + + _, _, _, err = p.opResponse() + if err != nil { + return + } + } else { + p.authData = authData // use later opAttach and opCreate + } + + } + } else { + if opcode != op_accept { + err = errors.New("_parse_connect_response() protocol error") + return + } + } + + return +} + func (p *wireProtocol) _parse_select_items(buf []byte, xsqlda []xSQLVAR) (int, error) { var err error var ln int @@ -472,6 +582,10 @@ func (p *wireProtocol) parse_xsqlda(buf []byte, stmtHandle int32) (int32, []xSQL break } } + + for i, _ := range xsqlda { + xsqlda[i].wp = p + } return stmt_type, xsqlda, err } @@ -510,8 +624,10 @@ func (p *wireProtocol) getBlobSegments(blobId []byte, transHandle int32) ([]byte return blob, err } -func (p *wireProtocol) opConnect(dbName string, user string, password string, authPluginName string, wireCrypt bool, clientPublic *big.Int) { +func (p *wireProtocol) opConnect(dbName string, user string, password string, options map[string]string, clientPublic *big.Int) { p.debugPrint("opConnect") + wire_crypt := true + wire_crypt, _ = strconv.ParseBool(options["wire_crypt"]) protocols := []string{ // PROTOCOL_VERSION, Arch type (Generic=1), min, max, weight "0000000a00000001000000000000000500000002", // 10, 1, 0, 5, 2 @@ -525,7 +641,7 @@ func (p *wireProtocol) opConnect(dbName string, user string, password string, au p.packInt(1) // Arch type(GENERIC) p.packString(dbName) p.packInt(int32(len(protocols))) - p.packBytes(p.uid(strings.ToUpper(user), password, authPluginName, wireCrypt, clientPublic)) + p.packBytes(p.uid(strings.ToUpper(user), password, options["auth_plugin_name"], wire_crypt, clientPublic)) buf, _ := hex.DecodeString(strings.Join(protocols, "")) p.appendBytes(buf) p.sendPackets() @@ -559,6 +675,12 @@ func (p *wireProtocol) opCreate(dbName string, user string, password string, rol dpb, []byte{isc_dpb_specific_auth_data, byte(len(specificAuthData))}, specificAuthData}, nil) } + if p.timezone != "" { + tznameBytes := []byte(p.timezone) + dpb = bytes.Join([][]byte{ + dpb, + []byte{isc_dpb_session_time_zone, byte(len(tznameBytes))}, tznameBytes}, nil) + } p.packInt(op_create) p.packInt(0) // Database Object ID @@ -567,108 +689,6 @@ func (p *wireProtocol) opCreate(dbName string, user string, password string, rol p.sendPackets() } -func (p *wireProtocol) opAccept(user string, password string, authPluginName string, wireCrypt bool, clientPublic *big.Int, clientSecret *big.Int) (err error) { - p.debugPrint("opAccept") - - b, err := p.recvPackets(4) - opcode := bytes_to_bint32(b) - - for opcode == op_dummy { - b, _ = p.recvPackets(4) - opcode = bytes_to_bint32(b) - } - - if opcode == op_reject { - err = errors.New("opAccept() op_reject") - return - } - if opcode == op_response { - _, _, _, err = p._parse_op_response() // error occured - return - } - - b, _ = p.recvPackets(12) - p.protocolVersion = int32(b[3]) - p.acceptArchitecture = bytes_to_bint32(b[4:8]) - p.acceptType = bytes_to_bint32(b[8:12]) - - if opcode == op_cond_accept || opcode == op_accept_data { - var readLength, ln int - - b, _ := p.recvPackets(4) - ln = int(bytes_to_bint32(b)) - data, _ := p.recvPacketsAlignment(ln) - - b, _ = p.recvPackets(4) - ln = int(bytes_to_bint32(b)) - pluginName, _ := p.recvPacketsAlignment(ln) - p.pluginName = bytes_to_str(pluginName) - - b, _ = p.recvPackets(4) - isAuthenticated := bytes_to_bint32(b) - readLength += 4 - - b, _ = p.recvPackets(4) - ln = int(bytes_to_bint32(b)) - _, _ = p.recvPacketsAlignment(ln) // keys - - if isAuthenticated == 0 { - var authData []byte - var sessionKey []byte - if p.pluginName == "Srp" && len(data) > 2 { - ln = int(bytes_to_int16(data[:2])) - serverSalt := data[2 : ln+2] - serverPublic := bigFromHexString(bytes_to_str(data[4+ln:])) - authData, sessionKey = getClientProof(strings.ToUpper(user), password, serverSalt, clientPublic, serverPublic, clientSecret) - if DEBUG_SRP { - fmt.Printf("serverSalt=%s\nserverPublic(bin)=%s\nserverPublic=%s\nauthData=%v,sessionKey=%v\n", - serverSalt, data[4+ln:], serverPublic, authData, sessionKey) - } - } else if p.pluginName == "Legacy_Auth" { - authData = bytes.NewBufferString(crypt.Crypt(password, "9z")[2:]).Bytes() - } else { - err = errors.New("opAccept() Unauthorized") - return - } - if wireCrypt { - // Send op_cont_auth - p.packInt(op_cont_auth) - p.packString(hex.EncodeToString(authData)) - p.packString(authPluginName) - p.packString(PLUGIN_LIST) - p.packString("") - p.sendPackets() - _, _, _, err = p.opResponse() - if err != nil { - return - } - - // Send op_crypt - p.packInt(op_crypt) - p.packString("Arc4") - p.packString("Symmetric") - p.sendPackets() - p.conn.setAuthKey(sessionKey) - - _, _, _, err = p.opResponse() - if err != nil { - return - } - } else { - p.authData = authData // use later opAttach and opCreate - } - - } - } else { - if opcode != op_accept { - err = errors.New("opAccept() protocol error") - return - } - } - - return -} - func (p *wireProtocol) opAttach(dbName string, user string, password string, role string) { p.debugPrint("opAttach") encode := bytes.NewBufferString("UTF8").Bytes() @@ -705,6 +725,13 @@ func (p *wireProtocol) opAttach(dbName string, user string, password string, rol dpb, []byte{isc_dpb_specific_auth_data, byte(len(specificAuthData))}, specificAuthData}, nil) } + if p.timezone != "" { + tznameBytes := []byte(p.timezone) + dpb = bytes.Join([][]byte{ + dpb, + []byte{isc_dpb_session_time_zone, byte(len(tznameBytes))}, tznameBytes}, nil) + } + p.packInt(op_attach) p.packInt(0) // Database Object ID p.packString(dbName) @@ -1057,7 +1084,10 @@ func (p *wireProtocol) opSqlResponse(xsqlda []xSQLVAR) ([]driver.Value, error) { } b, err = p.recvPackets(4) - // count := int(bytes_to_bint32(b)) + count := int(bytes_to_bint32(b)) + if count == 0 { + return nil, nil + } r := make([]driver.Value, len(xsqlda)) var ln int diff --git a/xsqlvar.go b/xsqlvar.go index cc968477..bef1bd5b 100644 --- a/xsqlvar.go +++ b/xsqlvar.go @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2013-2016 Hajime Nakagami +Copyright (c) 2013-2019 Hajime Nakagami Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -26,80 +26,102 @@ package firebirdsql import ( "bytes" "encoding/binary" + "github.com/shopspring/decimal" "math" - // "math/big" "reflect" "time" ) const ( - SQL_TYPE_TEXT = 452 - SQL_TYPE_VARYING = 448 - SQL_TYPE_SHORT = 500 - SQL_TYPE_LONG = 496 - SQL_TYPE_FLOAT = 482 - SQL_TYPE_DOUBLE = 480 - SQL_TYPE_D_FLOAT = 530 - SQL_TYPE_TIMESTAMP = 510 - SQL_TYPE_BLOB = 520 - SQL_TYPE_ARRAY = 540 - SQL_TYPE_QUAD = 550 - SQL_TYPE_TIME = 560 - SQL_TYPE_DATE = 570 - SQL_TYPE_INT64 = 580 - SQL_TYPE_BOOLEAN = 32764 - SQL_TYPE_NULL = 32766 + SQL_TYPE_TEXT = 452 + SQL_TYPE_VARYING = 448 + SQL_TYPE_SHORT = 500 + SQL_TYPE_LONG = 496 + SQL_TYPE_FLOAT = 482 + SQL_TYPE_DOUBLE = 480 + SQL_TYPE_D_FLOAT = 530 + SQL_TYPE_TIMESTAMP = 510 + SQL_TYPE_BLOB = 520 + SQL_TYPE_ARRAY = 540 + SQL_TYPE_QUAD = 550 + SQL_TYPE_TIME = 560 + SQL_TYPE_DATE = 570 + SQL_TYPE_INT64 = 580 + SQL_TYPE_TIMESTAMP_TZ = 32754 + SQL_TYPE_TIME_TZ = 32756 + SQL_TYPE_DEC_FIXED = 32758 + SQL_TYPE_DEC64 = 32760 + SQL_TYPE_DEC128 = 32762 + SQL_TYPE_BOOLEAN = 32764 + SQL_TYPE_NULL = 32766 ) var xsqlvarTypeLength = map[int]int{ - SQL_TYPE_VARYING: -1, - SQL_TYPE_SHORT: 4, - SQL_TYPE_LONG: 4, - SQL_TYPE_FLOAT: 4, - SQL_TYPE_TIME: 4, - SQL_TYPE_DATE: 4, - SQL_TYPE_DOUBLE: 8, - SQL_TYPE_TIMESTAMP: 8, - SQL_TYPE_BLOB: 8, - SQL_TYPE_ARRAY: 8, - SQL_TYPE_QUAD: 8, - SQL_TYPE_INT64: 8, - SQL_TYPE_BOOLEAN: 1, + SQL_TYPE_VARYING: -1, + SQL_TYPE_SHORT: 4, + SQL_TYPE_LONG: 4, + SQL_TYPE_FLOAT: 4, + SQL_TYPE_TIME: 4, + SQL_TYPE_DATE: 4, + SQL_TYPE_DOUBLE: 8, + SQL_TYPE_TIMESTAMP: 8, + SQL_TYPE_BLOB: 8, + SQL_TYPE_ARRAY: 8, + SQL_TYPE_QUAD: 8, + SQL_TYPE_INT64: 8, + SQL_TYPE_TIMESTAMP_TZ: 10, + SQL_TYPE_TIME_TZ: 6, + SQL_TYPE_DEC64: 8, + SQL_TYPE_DEC128: 16, + SQL_TYPE_DEC_FIXED: 16, + SQL_TYPE_BOOLEAN: 1, } var xsqlvarTypeDisplayLength = map[int]int{ - SQL_TYPE_VARYING: -1, - SQL_TYPE_SHORT: 6, - SQL_TYPE_LONG: 11, - SQL_TYPE_FLOAT: 17, - SQL_TYPE_TIME: 11, - SQL_TYPE_DATE: 10, - SQL_TYPE_DOUBLE: 17, - SQL_TYPE_TIMESTAMP: 22, - SQL_TYPE_BLOB: 0, - SQL_TYPE_ARRAY: -1, - SQL_TYPE_QUAD: 20, - SQL_TYPE_INT64: 20, - SQL_TYPE_BOOLEAN: 5, + SQL_TYPE_VARYING: -1, + SQL_TYPE_SHORT: 6, + SQL_TYPE_LONG: 11, + SQL_TYPE_FLOAT: 17, + SQL_TYPE_TIME: 11, + SQL_TYPE_DATE: 10, + SQL_TYPE_DOUBLE: 17, + SQL_TYPE_TIMESTAMP: 22, + SQL_TYPE_BLOB: 0, + SQL_TYPE_ARRAY: -1, + SQL_TYPE_QUAD: 20, + SQL_TYPE_INT64: 20, + SQL_TYPE_TIMESTAMP_TZ: 28, + SQL_TYPE_TIME_TZ: 17, + SQL_TYPE_DEC64: 16, + SQL_TYPE_DEC128: 34, + SQL_TYPE_DEC_FIXED: 34, + + SQL_TYPE_BOOLEAN: 5, } var xsqlvarTypeName = map[int]string{ - SQL_TYPE_VARYING: "VARYING", - SQL_TYPE_SHORT: "SHORT", - SQL_TYPE_LONG: "LONG", - SQL_TYPE_FLOAT: "FLOAT", - SQL_TYPE_TIME: "TIME", - SQL_TYPE_DATE: "DATE", - SQL_TYPE_DOUBLE: "DOUBLE", - SQL_TYPE_TIMESTAMP: "TIMESTAMP", - SQL_TYPE_BLOB: "BLOB", - SQL_TYPE_ARRAY: "ARRAY", - SQL_TYPE_QUAD: "QUAD", - SQL_TYPE_INT64: "INT64", - SQL_TYPE_BOOLEAN: "BOOLEAN", + SQL_TYPE_VARYING: "VARYING", + SQL_TYPE_SHORT: "SHORT", + SQL_TYPE_LONG: "LONG", + SQL_TYPE_FLOAT: "FLOAT", + SQL_TYPE_TIME: "TIME", + SQL_TYPE_DATE: "DATE", + SQL_TYPE_DOUBLE: "DOUBLE", + SQL_TYPE_TIMESTAMP: "TIMESTAMP", + SQL_TYPE_BLOB: "BLOB", + SQL_TYPE_ARRAY: "ARRAY", + SQL_TYPE_QUAD: "QUAD", + SQL_TYPE_INT64: "INT64", + SQL_TYPE_TIMESTAMP_TZ: "TIMESTAMP WITH TIMEZONE", + SQL_TYPE_TIME_TZ: "TIME WITH TIMEZONE", + SQL_TYPE_DEC64: "DECFLOAT(16)", + SQL_TYPE_DEC128: "DECFLOAT(34)", + SQL_TYPE_DEC_FIXED: "DECFIXED", + SQL_TYPE_BOOLEAN: "BOOLEAN", } type xSQLVAR struct { + wp *wireProtocol sqltype int sqlscale int sqlsubtype int @@ -136,7 +158,7 @@ func (x *xSQLVAR) scale() int { } func (x *xSQLVAR) hasPrecisionScale() bool { - return (x.sqltype == SQL_TYPE_SHORT || x.sqltype == SQL_TYPE_LONG || x.sqltype == SQL_TYPE_QUAD || x.sqltype == SQL_TYPE_INT64) && x.sqlscale != 0 + return (x.sqltype == SQL_TYPE_SHORT || x.sqltype == SQL_TYPE_LONG || x.sqltype == SQL_TYPE_QUAD || x.sqltype == SQL_TYPE_INT64 || x.sqltype == SQL_TYPE_DEC64 || x.sqltype == SQL_TYPE_DEC128 || x.sqltype == SQL_TYPE_DEC_FIXED) && x.sqlscale != 0 } func (x *xSQLVAR) typename() string { @@ -151,20 +173,17 @@ func (x *xSQLVAR) scantype() reflect.Type { return reflect.TypeOf("") case SQL_TYPE_SHORT: if x.sqlscale != 0 { - //return reflect.TypeOf(big.NewRat(0, 1)) - return reflect.TypeOf(float64(0)) + return reflect.TypeOf(decimal.Decimal{}) } return reflect.TypeOf(int16(0)) case SQL_TYPE_LONG: if x.sqlscale != 0 { - //return reflect.TypeOf(big.NewRat(0, 1)) - return reflect.TypeOf(float64(0)) + return reflect.TypeOf(decimal.Decimal{}) } return reflect.TypeOf(int32(0)) case SQL_TYPE_INT64: if x.sqlscale != 0 { - //return reflect.TypeOf(big.NewRat(0, 1)) - return reflect.TypeOf(float64(0)) + return reflect.TypeOf(decimal.Decimal{}) } return reflect.TypeOf(int64(0)) case SQL_TYPE_DATE: @@ -181,10 +200,26 @@ func (x *xSQLVAR) scantype() reflect.Type { return reflect.TypeOf(false) case SQL_TYPE_BLOB: return reflect.TypeOf([]byte{}) + case SQL_TYPE_TIMESTAMP_TZ: + return reflect.TypeOf(time.Time{}) + case SQL_TYPE_TIME_TZ: + return reflect.TypeOf(time.Time{}) + case SQL_TYPE_DEC64: + return reflect.TypeOf(decimal.Decimal{}) + case SQL_TYPE_DEC128: + return reflect.TypeOf(decimal.Decimal{}) + case SQL_TYPE_DEC_FIXED: + return reflect.TypeOf(decimal.Decimal{}) } return reflect.TypeOf(nil) } +func (x *xSQLVAR) _parseTimezone(raw_value []byte) *time.Location { + timezone := x.wp.tzNameById[int(bytes_to_bint32(raw_value))] + tz, _ := time.LoadLocation(timezone) + return tz +} + func (x *xSQLVAR) _parseDate(raw_value []byte) (int, int, int) { nday := int(bytes_to_bint32(raw_value)) + 678882 century := (4*nday - 1) / 146097 @@ -219,19 +254,45 @@ func (x *xSQLVAR) _parseTime(raw_value []byte) (int, int, int, int) { } func (x *xSQLVAR) parseDate(raw_value []byte) time.Time { + tz := time.Local + if x.wp.timezone != "" { + tz, _ = time.LoadLocation(x.wp.timezone) + } year, month, day := x._parseDate(raw_value) - return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) + return time.Date(year, time.Month(month), day, 0, 0, 0, 0, tz) } func (x *xSQLVAR) parseTime(raw_value []byte) time.Time { + tz := time.Local + if x.wp.timezone != "" { + tz, _ = time.LoadLocation(x.wp.timezone) + } h, m, s, n := x._parseTime(raw_value) - return time.Date(0, time.Month(1), 1, h, m, s, n, time.UTC) + return time.Date(0, time.Month(1), 1, h, m, s, n, tz) } func (x *xSQLVAR) parseTimestamp(raw_value []byte) time.Time { + tz := time.Local + if x.wp.timezone != "" { + tz, _ = time.LoadLocation(x.wp.timezone) + } + year, month, day := x._parseDate(raw_value[:4]) h, m, s, n := x._parseTime(raw_value[4:]) - return time.Date(year, time.Month(month), day, h, m, s, n, time.UTC) + return time.Date(year, time.Month(month), day, h, m, s, n, tz) +} + +func (x *xSQLVAR) parseTimeTz(raw_value []byte) time.Time { + h, m, s, n := x._parseTime(raw_value[:4]) + tz := x._parseTimezone(raw_value[4:]) + return time.Date(0, time.Month(1), 1, h, m, s, n, tz) +} + +func (x *xSQLVAR) parseTimestampTz(raw_value []byte) time.Time { + year, month, day := x._parseDate(raw_value[:4]) + h, m, s, n := x._parseTime(raw_value[4:8]) + tz := x._parseTimezone(raw_value[8:]) + return time.Date(year, time.Month(month), day, h, m, s, n, tz) } func (x *xSQLVAR) value(raw_value []byte) (v interface{}, err error) { @@ -253,8 +314,7 @@ func (x *xSQLVAR) value(raw_value []byte) (v interface{}, err error) { if x.sqlscale > 0 { v = int64(i16) * int64(math.Pow10(x.sqlscale)) } else if x.sqlscale < 0 { - //v = big.NewRat(int64(i16), int64(math.Pow10(x.sqlscale*-1))) - v = float64(i16) / float64(math.Pow10(x.sqlscale*-1)) + v = decimal.New(int64(i16), int32(x.sqlscale)) } else { v = i16 } @@ -263,8 +323,7 @@ func (x *xSQLVAR) value(raw_value []byte) (v interface{}, err error) { if x.sqlscale > 0 { v = int64(i32) * int64(math.Pow10(x.sqlscale)) } else if x.sqlscale < 0 { - //v = big.NewRat(int64(i32), int64(math.Pow10(x.sqlscale*-1))) - v = float64(i32) / float64(math.Pow10(x.sqlscale*-1)) + v = decimal.New(int64(i32), int32(x.sqlscale)) } else { v = i32 } @@ -273,8 +332,7 @@ func (x *xSQLVAR) value(raw_value []byte) (v interface{}, err error) { if x.sqlscale > 0 { v = i64 * int64(math.Pow10(x.sqlscale)) } else if x.sqlscale < 0 { - //v = big.NewRat(i64, int64(math.Pow10(x.sqlscale*-1))) - v = float64(i64) / float64(math.Pow10(x.sqlscale*-1)) + v = decimal.New(int64(i64), int32(x.sqlscale)) } else { v = i64 } @@ -284,6 +342,10 @@ func (x *xSQLVAR) value(raw_value []byte) (v interface{}, err error) { v = x.parseTime(raw_value) case SQL_TYPE_TIMESTAMP: v = x.parseTimestamp(raw_value) + case SQL_TYPE_TIME_TZ: + v = x.parseTimeTz(raw_value) + case SQL_TYPE_TIMESTAMP_TZ: + v = x.parseTimestampTz(raw_value) case SQL_TYPE_FLOAT: var f32 float32 b := bytes.NewReader(raw_value) @@ -298,6 +360,12 @@ func (x *xSQLVAR) value(raw_value []byte) (v interface{}, err error) { v = raw_value[0] != 0 case SQL_TYPE_BLOB: v = raw_value + case SQL_TYPE_DEC_FIXED: + v = decimalFixedToDecimal(raw_value, int32(x.sqlscale)) + case SQL_TYPE_DEC64: + v = decimal64ToDecimal(raw_value) + case SQL_TYPE_DEC128: + v = decimal128ToDecimal(raw_value) } return }