diff --git a/.travis.yml b/.travis.yml index 7add991..3c725d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,8 @@ services: language: bash before_install: - - docker pull wnameless/oracle-xe-11g - - docker run -d wnameless/oracle-xe-11g + - docker pull wnameless/oracle-xe-11g-r2 + - docker run -d wnameless/oracle-xe-11g-r2 script: - - docker run --rm -i -e TESTDIR=$(pwd) -v $(pwd):$(pwd) wnameless/oracle-xe-11g /bin/bash < test.sh + - docker run --rm -i -e TESTDIR=$(pwd) -v $(pwd):$(pwd) wnameless/oracle-xe-11g-r2 /bin/bash < test.sh diff --git a/c_helpers.go b/c_helpers.go index 9836405..16cfc93 100644 --- a/c_helpers.go +++ b/c_helpers.go @@ -72,20 +72,24 @@ func cGoStringN(s *C.OraText, size int) string { // freeDefines frees defines func freeDefines(defines []oci8Define) { - for _, define := range defines { - if define.pbuf != nil { - freeBuffer(define.pbuf, define.dataType) - define.pbuf = nil + for i := 0; i < len(defines); i++ { + if len(defines[i].subDefines) > 0 { + freeDefines(defines[i].subDefines) } - if define.length != nil { - C.free(unsafe.Pointer(define.length)) - define.length = nil + defines[i].subDefines = nil + if defines[i].pbuf != nil { + freeBuffer(defines[i].pbuf, defines[i].dataType) + defines[i].pbuf = nil } - if define.indicator != nil { - C.free(unsafe.Pointer(define.indicator)) - define.indicator = nil + if defines[i].length != nil { + C.free(unsafe.Pointer(defines[i].length)) + defines[i].length = nil } - define.defineHandle = nil // should be freed by oci statement close + if defines[i].indicator != nil { + C.free(unsafe.Pointer(defines[i].indicator)) + defines[i].indicator = nil + } + defines[i].defineHandle = nil // should be freed by oci statement close } } @@ -124,6 +128,8 @@ func freeBuffer(buffer unsafe.Pointer, dataType C.ub2) { C.OCIDescriptorFree(*(*unsafe.Pointer)(buffer), C.OCI_DTYPE_INTERVAL_DS) case C.SQLT_INTERVAL_YM: C.OCIDescriptorFree(*(*unsafe.Pointer)(buffer), C.OCI_DTYPE_INTERVAL_YM) + case C.SQLT_RSET: + C.OCIDescriptorFree(*(*unsafe.Pointer)(buffer), C.OCI_HTYPE_STMT) default: C.free(buffer) } diff --git a/globals.go b/globals.go index e370065..c8810b8 100644 --- a/globals.go +++ b/globals.go @@ -102,6 +102,7 @@ type ( length *C.ub2 indicator *C.sb2 defineHandle *C.OCIDefine + subDefines []oci8Define } oci8Bind struct { @@ -118,7 +119,6 @@ type ( OCI8Rows struct { stmt *OCI8Stmt defines []oci8Define - e bool closed bool ctx context.Context } diff --git a/oci8_sql_number_go112_test.go b/oci8_sql_number_go112_test.go new file mode 100644 index 0000000..05bd5da --- /dev/null +++ b/oci8_sql_number_go112_test.go @@ -0,0 +1,149 @@ +// +build go1.12 + +package oci8 + +import ( + "context" + "database/sql" + "testing" +) + +// TestDestructiveNumberCursor checks select cursor +func TestDestructiveNumberCursor(t *testing.T) { + if TestDisableDatabase || TestDisableDestructive { + t.SkipNow() + } + + tableName := "number_cursor_" + TestTimeString + err := testExec(t, "create table "+tableName+" ( A INTEGER )", nil) + if err != nil { + t.Fatal("create table error:", err) + } + + defer testDropTable(t, tableName) + + testDestructiveNumberCursorInsert(t, tableName) + + testDestructiveNumberCursorSelect(t, tableName) +} + +func testDestructiveNumberCursorInsert(t *testing.T, tableName string) { + ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout) + stmt, err := TestDB.PrepareContext(ctx, "insert into "+tableName+" ( A ) values (:1)") + cancel() + if err != nil { + t.Fatal("prepare error:", err) + } + + defer func() { + err = stmt.Close() + if err != nil { + if t.Failed() { + t.Logf("stmt close error: %v", err) + } else { + t.Fatal("stmt close error:", err) + } + } + }() + + ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout) + _, err = stmt.ExecContext(ctx, 1) + cancel() + if err != nil { + t.Fatal("exec error:", err) + } + + ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout) + _, err = stmt.ExecContext(ctx, 2) + cancel() + if err != nil { + stmt.Close() + t.Fatal("exec error:", err) + } +} + +func testDestructiveNumberCursorSelect(t *testing.T, tableName string) { + ctx, cancel := context.WithTimeout(context.Background(), TestContextTimeout) + stmt, err := TestDB.PrepareContext(ctx, "select 1.5, cursor(select A from "+tableName+" order by A) from dual") + cancel() + if err != nil { + t.Fatal("prepare error:", err) + } + + defer func() { + err = stmt.Close() + if err != nil { + if t.Failed() { + t.Logf("stmt close err: %v", err) + } else { + t.Fatal("stmt close error:", err) + } + } + }() + + var rows *sql.Rows + ctx, cancel = context.WithTimeout(context.Background(), TestContextTimeout) + rows, err = stmt.QueryContext(ctx) + if err != nil { + cancel() + t.Fatal("query error:", err) + } + + defer func() { + cancel() + err = rows.Close() + if err != nil { + if t.Failed() { + t.Logf("rows close error: %v", err) + } else { + t.Fatal("rows close error:", err) + } + } + }() + + if !rows.Next() { + t.Fatal("expected row") + } + + var float float64 + var subRows *sql.Rows + err = rows.Scan(&float, &subRows) + if err != nil { + t.Fatal("scan error:", err) + } + + if float != 1.5 { + t.Fatal("float != 1.5") + } + + if subRows == nil { + t.Fatal("subRows is nil") + } + + if !subRows.Next() { + t.Fatal("expected row") + } + + var aInt int64 + err = subRows.Scan(&aInt) + if err != nil { + t.Fatal("scan error:", err) + } + + if aInt != 1 { + t.Fatal("aInt != 1") + } + + if !subRows.Next() { + t.Fatal("expected row") + } + + err = subRows.Scan(&aInt) + if err != nil { + t.Fatal("scan error:", err) + } + + if aInt != 2 { + t.Fatal("aInt != 2") + } +} diff --git a/rows.go b/rows.go index a8649e1..18f78b0 100644 --- a/rows.go +++ b/rows.go @@ -30,8 +30,8 @@ func (rows *OCI8Rows) Close() error { // Columns returns column names func (rows *OCI8Rows) Columns() []string { names := make([]string, len(rows.defines)) - for i, define := range rows.defines { - names[i] = define.name + for i := 0; i < len(rows.defines); i++ { + names[i] = rows.defines[i].name } return names } @@ -199,6 +199,24 @@ func (rows *OCI8Rows) Next(dest []driver.Value) error { } dest[i] = (int64(years) * 12) + int64(months) + // SQLT_RSET - ref cursor + case C.SQLT_RSET: + stmtP := (**C.OCIStmt)(rows.defines[i].pbuf) + subStmt := &OCI8Stmt{conn: rows.stmt.conn, stmt: *stmtP} + if rows.defines[i].subDefines == nil { + var err error + rows.defines[i].subDefines, err = subStmt.makeDefines(rows.ctx) + if err != nil { + return err + } + } + subRows := &OCI8Rows{ + stmt: subStmt, + defines: rows.defines[i].subDefines, + ctx: rows.ctx, + } + dest[i] = subRows + // default default: return fmt.Errorf("Unhandled column type: %d", rows.defines[i].dataType) diff --git a/statement.go b/statement.go index a977444..afce7e0 100644 --- a/statement.go +++ b/statement.go @@ -424,8 +424,29 @@ func (stmt *OCI8Stmt) query(ctx context.Context, binds []oci8Bind) (driver.Rows, return nil, err } + var defines []oci8Define + defines, err = stmt.makeDefines(ctx) + if err != nil { + return nil, err + } + + if ctx.Err() != nil { + freeDefines(defines) + return nil, ctx.Err() + } + + rows := &OCI8Rows{ + stmt: stmt, + defines: defines, + ctx: ctx, + } + + return rows, nil +} + +func (stmt *OCI8Stmt) makeDefines(ctx context.Context) ([]oci8Define, error) { var paramCountUb4 C.ub4 // number of columns in the select-list - _, err = stmt.ociAttrGet(unsafe.Pointer(¶mCountUb4), C.OCI_ATTR_PARAM_COUNT) + _, err := stmt.ociAttrGet(unsafe.Pointer(¶mCountUb4), C.OCI_ATTR_PARAM_COUNT) if err != nil { return nil, err } @@ -597,6 +618,17 @@ func (stmt *OCI8Stmt) query(ctx context.Context, binds []oci8Bind) (driver.Rows, defines[i].maxSize = 40 defines[i].pbuf = C.malloc(C.size_t(defines[i].maxSize)) + case C.SQLT_RSET: // ref cursor + defines[i].dataType = dataType + defines[i].maxSize = C.sb4(sizeOfNilPointer) + var stmtP *unsafe.Pointer + stmtP, _, err = stmt.conn.ociHandleAlloc(C.OCI_HTYPE_STMT, 0) + if err != nil { + freeDefines(defines) + return nil, err + } + defines[i].pbuf = unsafe.Pointer(stmtP) + default: defines[i].dataType = C.SQLT_AFC defines[i].maxSize = C.sb4(maxSize) @@ -622,17 +654,7 @@ func (stmt *OCI8Stmt) query(ctx context.Context, binds []oci8Bind) (driver.Rows, } } - if ctx.Err() != nil { - return nil, ctx.Err() - } - - rows := &OCI8Rows{ - stmt: stmt, - defines: defines, - ctx: ctx, - } - - return rows, nil + return defines, nil } // getRowid returns the rowid diff --git a/test.sh b/test.sh index cdf960b..a49f601 100644 --- a/test.sh +++ b/test.sh @@ -8,12 +8,22 @@ apt-get -qq -y install git pkg-config gcc wget 2>&1 > /dev/null echo "installing go" cd /tmp/ -wget -nv https://dl.google.com/go/go1.11.5.linux-amd64.tar.gz +wget -nv https://dl.google.com/go/go1.13.8.linux-amd64.tar.gz +wget -nv https://dl.google.com/go/go1.12.17.linux-amd64.tar.gz +wget -nv https://dl.google.com/go/go1.11.13.linux-amd64.tar.gz wget -nv https://dl.google.com/go/go1.10.8.linux-amd64.tar.gz wget -nv https://dl.google.com/go/go1.9.7.linux-amd64.tar.gz +mkdir -p /usr/local/goFiles1.13.x +tar -xf /tmp/go1.13.8.linux-amd64.tar.gz +mv /tmp/go /usr/local/go1.13.x + +mkdir -p /usr/local/goFiles1.12.x +tar -xf /tmp/go1.12.17.linux-amd64.tar.gz +mv /tmp/go /usr/local/go1.12.x + mkdir -p /usr/local/goFiles1.11.x -tar -xf /tmp/go1.11.5.linux-amd64.tar.gz +tar -xf /tmp/go1.11.13.linux-amd64.tar.gz mv /tmp/go /usr/local/go1.11.x mkdir -p /usr/local/goFiles1.10.x @@ -64,6 +74,26 @@ PKGCONFIG export PATH_SAVE=${PATH} +echo "testing go-oci8 Go 1.13.x" +export PATH=/usr/local/go1.13.x/bin:${PATH_SAVE} +export GOROOT=/usr/local/go1.13.x +export GOPATH=/usr/local/goFiles1.13.x +mkdir -p ${GOPATH}/src/github.com/mattn/go-oci8 +cp -r ${TESTDIR}/* ${GOPATH}/src/github.com/mattn/go-oci8/ + +go test -v github.com/mattn/go-oci8 -args -disableDatabase=false -hostValid ${DOCKER_IP} -username scott -password tiger + + +echo "testing go-oci8 Go 1.12.x" +export PATH=/usr/local/go1.12.x/bin:${PATH_SAVE} +export GOROOT=/usr/local/go1.12.x +export GOPATH=/usr/local/goFiles1.12.x +mkdir -p ${GOPATH}/src/github.com/mattn/go-oci8 +cp -r ${TESTDIR}/* ${GOPATH}/src/github.com/mattn/go-oci8/ + +go test -v github.com/mattn/go-oci8 -args -disableDatabase=false -hostValid ${DOCKER_IP} -username scott -password tiger + + echo "testing go-oci8 Go 1.11.x" export PATH=/usr/local/go1.11.x/bin:${PATH_SAVE} export GOROOT=/usr/local/go1.11.x