You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3530 lines
91 KiB
3530 lines
91 KiB
// Copyright 2017, 2020 The Godror Authors
|
|
//
|
|
//
|
|
// SPDX-License-Identifier: UPL-1.0 OR Apache-2.0
|
|
|
|
package godror
|
|
|
|
/*
|
|
#include <stdlib.h>
|
|
#include "dpiImpl.h"
|
|
|
|
const int sizeof_dpiData = sizeof(void);
|
|
|
|
void godror_setFromString(dpiVar *dv, uint32_t pos, const _GoString_ value) {
|
|
uint32_t length;
|
|
length = _GoStringLen(value);
|
|
if( length == 0 ) {
|
|
return;
|
|
}
|
|
dpiVar_setFromBytes(dv, pos, _GoStringPtr(value), length);
|
|
}
|
|
*/
|
|
import "C"
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"database/sql"
|
|
"database/sql/driver"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"github.com/godror/knownpb/timestamppb"
|
|
)
|
|
|
|
// NullTime is an alias for sql.NullTime
|
|
type NullTime = sql.NullTime
|
|
|
|
var nullTime interface{} = nil
|
|
|
|
type stmtOptions struct {
|
|
boolString boolString
|
|
fetchArraySize int // zero means DefaultFetchArraySize
|
|
prefetchCount int // zero means DefaultPrefetchCount, -1 is zero.
|
|
arraySize int
|
|
callTimeout time.Duration
|
|
execMode C.dpiExecMode
|
|
plSQLArrays bool
|
|
lobAsReader bool
|
|
nullDateAsZeroTime bool
|
|
deleteFromCache bool
|
|
numberAsString bool
|
|
}
|
|
|
|
type boolString struct {
|
|
True, False string
|
|
}
|
|
|
|
func (bs boolString) IsZero() bool { return bs.True == "" && bs.False == "" }
|
|
func (bs boolString) MaxLen() int {
|
|
n := len(bs.True)
|
|
if m := len(bs.False); m > n {
|
|
return m
|
|
}
|
|
return n
|
|
}
|
|
func (bs boolString) ToString(b bool) string {
|
|
if b {
|
|
return bs.True
|
|
}
|
|
return bs.False
|
|
}
|
|
func (bs boolString) FromString(s string) bool {
|
|
return s == bs.True && bs.True != "" || bs.True == "" && s != bs.False
|
|
}
|
|
|
|
func (o stmtOptions) BoolString() boolString { return o.boolString }
|
|
|
|
func (o stmtOptions) ExecMode() C.dpiExecMode {
|
|
if o.execMode == 0 {
|
|
return C.DPI_MODE_EXEC_DEFAULT
|
|
}
|
|
return o.execMode
|
|
}
|
|
|
|
func (o stmtOptions) ArraySize() int {
|
|
if o.arraySize <= 0 {
|
|
return DefaultArraySize
|
|
} else if o.arraySize > 1<<16 {
|
|
return 1 << 16
|
|
}
|
|
return o.arraySize
|
|
}
|
|
func (o stmtOptions) PrefetchCount() int {
|
|
switch n := o.prefetchCount; {
|
|
case n == 0:
|
|
return DefaultPrefetchCount
|
|
case n < 0:
|
|
return 0
|
|
default:
|
|
return n
|
|
}
|
|
}
|
|
func (o stmtOptions) FetchArraySize() int {
|
|
n := o.fetchArraySize
|
|
if n <= 0 {
|
|
return DefaultFetchArraySize
|
|
}
|
|
return n
|
|
}
|
|
func (o stmtOptions) PlSQLArrays() bool { return o.plSQLArrays }
|
|
|
|
func (o stmtOptions) ClobAsString() bool { return !o.lobAsReader }
|
|
func (o stmtOptions) LobAsReader() bool { return o.lobAsReader }
|
|
func (o stmtOptions) NullDate() interface{} {
|
|
if o.nullDateAsZeroTime {
|
|
return time.Time{}
|
|
}
|
|
return nullTime
|
|
}
|
|
func (o stmtOptions) DeleteFromCache() bool { return o.deleteFromCache }
|
|
func (o stmtOptions) NumberAsString() bool { return o.numberAsString }
|
|
|
|
// Option holds statement options.
|
|
//
|
|
// Use it "naked", without sql.Named!
|
|
type Option func(*stmtOptions)
|
|
|
|
// BoolToString is an option that governs convertsion from bool to string in the database.
|
|
// This is for converting from bool to string, from outside of the database
|
|
// (which does not have a BOOL(EAN) column (SQL) type, only a BOOLEAN PL/SQL type).
|
|
//
|
|
// This will be used only with DML statements and when the PlSQLArrays Option is not used.
|
|
//
|
|
// For the other way around, use an sql.Scanner that converts from string to bool. For example:
|
|
//
|
|
// type Booler bool
|
|
// var _ sql.Scanner = Booler{}
|
|
// func (b Booler) Scan(src interface{}) error {
|
|
// switch src := src.(type) {
|
|
// case int: *b = x == 1
|
|
// case string: *b = x == "Y" || x == "T" // or any string your database model treats as truth value
|
|
// default: return fmt.Errorf("unknown scanner source %T", src)
|
|
// }
|
|
// return nil
|
|
// }
|
|
//
|
|
// Such a type cannot be included in this package till we can inject the truth strings into the scanner method.
|
|
func BoolToString(trueVal, falseVal string) Option {
|
|
return func(o *stmtOptions) { o.boolString = boolString{True: trueVal, False: falseVal} }
|
|
}
|
|
|
|
// PlSQLArrays is to signal that the slices given in arguments of Exec to
|
|
// be left as is - the default is to treat them as arguments for ExecMany.
|
|
//
|
|
// Use it "naked", without sql.Named!
|
|
var PlSQLArrays Option = func(o *stmtOptions) { o.plSQLArrays = true }
|
|
|
|
// FetchRowCount is DEPRECATED, use FetchArraySize.
|
|
//
|
|
// It returns an option to set the rows to be fetched, overriding DefaultFetchRowCount.
|
|
//
|
|
// Use it "naked", without sql.Named!
|
|
func FetchRowCount(rowCount int) Option { return FetchArraySize(rowCount) }
|
|
|
|
// FetchArraySize returns an option to set the rows to be fetched, overriding DefaultFetchRowCount.
|
|
//
|
|
// For choosing FetchArraySize and PrefetchCount, see https://cx-oracle.readthedocs.io/en/latest/user_guide/tuning.html#choosing-values-for-arraysize-and-prefetchrows
|
|
//
|
|
// Use it "naked", without sql.Named!
|
|
func FetchArraySize(rowCount int) Option {
|
|
return func(o *stmtOptions) {
|
|
if rowCount > 0 {
|
|
o.fetchArraySize = rowCount
|
|
} else {
|
|
o.fetchArraySize = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
// PrefetchCount returns an option to set the rows to be fetched, overriding DefaultPrefetchCount.
|
|
//
|
|
// For choosing FetchArraySize and PrefetchCount, see https://cx-oracle.readthedocs.io/en/latest/user_guide/tuning.html#choosing-values-for-arraysize-and-prefetchrows
|
|
//
|
|
// WARNING: If you will take a REF CURSOR, the driver will start prefetching, so if you give that cursor to a stored procedure, that won't see the prefetched rows!
|
|
//
|
|
// Use it "naked", without sql.Named!
|
|
func PrefetchCount(rowCount int) Option {
|
|
return func(o *stmtOptions) {
|
|
if rowCount > 0 {
|
|
o.prefetchCount = rowCount
|
|
} else {
|
|
o.prefetchCount = -1
|
|
}
|
|
}
|
|
}
|
|
|
|
// ArraySize returns an option to set the array size to be used, overriding DefaultArraySize.
|
|
//
|
|
// Use it "naked", without sql.Named!
|
|
func ArraySize(arraySize int) Option {
|
|
if arraySize <= 0 {
|
|
return nil
|
|
}
|
|
return func(o *stmtOptions) { o.arraySize = arraySize }
|
|
}
|
|
func parseOnly(o *stmtOptions) { o.execMode = C.DPI_MODE_EXEC_PARSE_ONLY }
|
|
|
|
// ParseOnly returns an option to set the ExecMode to only Parse.
|
|
//
|
|
// Use it "naked", without sql.Named!
|
|
func ParseOnly() Option {
|
|
return parseOnly
|
|
}
|
|
|
|
func describeOnly(o *stmtOptions) { o.execMode = C.DPI_MODE_EXEC_DESCRIBE_ONLY }
|
|
|
|
// ClobAsString returns an option to force fetching CLOB columns as strings.
|
|
//
|
|
// Deprecated: CLOBs are returned as string by default - for CLOB, use LobAsReader.
|
|
// EXCEPT for Object attributes, those are returned as-is - as lobReader.
|
|
//
|
|
// Use it "naked", without sql.Named!
|
|
func ClobAsString() Option { return func(o *stmtOptions) { o.lobAsReader = false } }
|
|
|
|
// LobAsReader is an option to set query columns of CLOB/BLOB to be returned as a Lob.
|
|
//
|
|
// LOB as a reader and writer is not the most performant at all. Yes, OCI
|
|
// and ODPI-C provide a way to retrieve this data directly. Effectively,
|
|
// all you need to do is tell ODPI-C that you want a "long string" or "long
|
|
// raw" returned. You can do that by telling ODPI-C you want a variable
|
|
// with oracleTypeNum=DPI_ORACLE_TYPE_LONG_VARCHAR or
|
|
// DPI_ORACLE_TYPE_LONG_RAW and nativeTypeNum=DPI_NATIVE_TYPE_BYTES. ODPI-C
|
|
// will handle all of the dynamic fetching and allocation that is required.
|
|
// :-) You can also use DPI_ORACLE_TYPE_VARCHAR and DPI_ORACLE_TYPE_RAW as
|
|
// long as you set the size > 32767 -- whichever way you wish to use.
|
|
//
|
|
// With the use of LOBs, there is one round-trip to get the LOB locators,
|
|
// then a round-trip for each read() that is performed. If you request the
|
|
// length there is another round-trip required. So if you fetch 100 rows
|
|
// with 2 CLOB columns, that means you get 401 round-trips. Using
|
|
// string/[]bytes directly means only one round trip. So you can see that
|
|
// if your database is remote with high latency you can have a significant
|
|
// performance penalty!
|
|
//
|
|
// EXCEPT for Object attributes, those are returned as-is - as lobReader.
|
|
//
|
|
// Use it "naked", without sql.Named!
|
|
func LobAsReader() Option { return func(o *stmtOptions) { o.lobAsReader = true } }
|
|
|
|
// CallTimeout sets the round-trip timeout (OCI_ATTR_CALL_TIMEOUT).
|
|
//
|
|
// See https://docs.oracle.com/en/database/oracle/oracle-database/18/lnoci/handle-and-descriptor-attributes.html#GUID-D8EE68EB-7E38-4068-B06E-DF5686379E5E
|
|
func CallTimeout(d time.Duration) Option {
|
|
return func(o *stmtOptions) { o.callTimeout = d }
|
|
}
|
|
|
|
// NullDateAsZeroTime is an option to return NULL DATE columns as time.Time{} instead of nil.
|
|
// If you must Scan into time.Time (cannot use sql.NullTime), this may help.
|
|
func NullDateAsZeroTime() Option { return func(o *stmtOptions) { o.nullDateAsZeroTime = true } }
|
|
|
|
// DeleteFromCache is an option to delete the statement from the statement cache.
|
|
func DeleteFromCache() Option { return func(o *stmtOptions) { o.deleteFromCache = true } }
|
|
|
|
// NumberAsString is an option to return numbers a string, not Number.
|
|
func NumberAsString() Option { return func(o *stmtOptions) { o.numberAsString = true } }
|
|
|
|
const minChunkSize = 1 << 16
|
|
|
|
var _ driver.Stmt = (*statement)(nil)
|
|
var _ driver.StmtQueryContext = (*statement)(nil)
|
|
var _ driver.StmtExecContext = (*statement)(nil)
|
|
var _ driver.NamedValueChecker = (*statement)(nil)
|
|
|
|
type statement struct {
|
|
ctx context.Context
|
|
*conn
|
|
dpiStmt *C.dpiStmt
|
|
query string
|
|
columns []Column
|
|
isSlice []bool
|
|
gets []dataGetter
|
|
dests []interface{}
|
|
data [][]C.dpiData
|
|
vars []*C.dpiVar
|
|
varInfos []varInfo
|
|
stmtOptions
|
|
arrLen int
|
|
dpiStmtInfo C.dpiStmtInfo
|
|
sync.Mutex
|
|
}
|
|
type dataGetter func(v interface{}, data []C.dpiData) error
|
|
|
|
// Close closes the statement.
|
|
//
|
|
// As of Go 1.1, a Stmt will not be closed if it's in use
|
|
// by any queries.
|
|
func (st *statement) Close() error {
|
|
if st == nil {
|
|
return nil
|
|
}
|
|
st.Lock()
|
|
defer st.Unlock()
|
|
|
|
return st.closeNotLocking()
|
|
}
|
|
func (st *statement) closeNotLocking() error {
|
|
if st == nil || st.dpiStmt == nil {
|
|
return nil
|
|
}
|
|
|
|
c, dpiStmt, vars := st.conn, st.dpiStmt, st.vars
|
|
st.vars = nil
|
|
st.isSlice = nil
|
|
st.query = ""
|
|
st.data = nil
|
|
st.varInfos = nil
|
|
st.gets = nil
|
|
st.dests = nil
|
|
st.columns = nil
|
|
st.dpiStmt = nil
|
|
st.conn = nil
|
|
st.dpiStmtInfo = C.dpiStmtInfo{}
|
|
st.ctx = nil
|
|
|
|
if logger := getLogger(); logger != nil {
|
|
logger.Log("msg", "statement.closeNotLocking", "st", fmt.Sprintf("%p", st), "refCount", dpiStmt.refCount)
|
|
if false {
|
|
var a [4096]byte
|
|
stack := a[:runtime.Stack(a[:], false)]
|
|
logger.Log("msg", "closeNotLocking", "stack", string(stack))
|
|
}
|
|
}
|
|
for _, v := range vars[:cap(vars)] {
|
|
if v != nil {
|
|
C.dpiVar_release(v)
|
|
}
|
|
}
|
|
if dpiStmt.refCount > 0 {
|
|
C.dpiStmt_release(dpiStmt)
|
|
}
|
|
if c == nil {
|
|
return driver.ErrBadConn
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Exec executes a query that doesn't return rows, such
|
|
// as an INSERT or UPDATE.
|
|
//
|
|
// Deprecated: Drivers should implement StmtExecContext instead (or additionally).
|
|
func (st *statement) Exec(args []driver.Value) (driver.Result, error) {
|
|
nargs := make([]driver.NamedValue, len(args))
|
|
for i, arg := range args {
|
|
nargs[i].Ordinal = i + 1
|
|
nargs[i].Value = arg
|
|
}
|
|
return st.ExecContext(context.Background(), nargs)
|
|
}
|
|
|
|
// Query executes a query that may return rows, such as a
|
|
// SELECT.
|
|
//
|
|
// Deprecated: Drivers should implement StmtQueryContext instead (or additionally).
|
|
func (st *statement) Query(args []driver.Value) (driver.Rows, error) {
|
|
nargs := make([]driver.NamedValue, len(args))
|
|
for i, arg := range args {
|
|
nargs[i].Ordinal = i + 1
|
|
nargs[i].Value = arg
|
|
}
|
|
return st.QueryContext(context.Background(), nargs)
|
|
}
|
|
|
|
func newDoneCh() (<-chan struct{}, func()) {
|
|
done := make(chan struct{})
|
|
var once sync.Once
|
|
return done, func() { once.Do(func() { close(done) }) }
|
|
}
|
|
|
|
// ExecContext executes a query that doesn't return rows, such as an INSERT or UPDATE.
|
|
//
|
|
// ExecContext must honor the context timeout and return when it is canceled.
|
|
//
|
|
// Cancelation/timeout is honored, execution is broken, but you may have to disable out-of-bound execution - see https://github.com/oracle/odpi/issues/116 for details.
|
|
func (st *statement) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
logger := ctxGetLog(ctx)
|
|
if logger != nil {
|
|
logger.Log("msg", "ExecContext", "stmt", fmt.Sprintf("%p", st), "args", args)
|
|
}
|
|
|
|
st.Lock()
|
|
defer st.Unlock()
|
|
if st.conn == nil {
|
|
return nil, driver.ErrBadConn
|
|
}
|
|
st.ctx = ctx
|
|
|
|
if st.dpiStmt == nil && st.query == getConnection {
|
|
*(args[0].Value.(sql.Out).Dest.(*interface{})) = st.conn
|
|
return driver.ResultNoRows, nil
|
|
}
|
|
|
|
st.conn.mu.RLock()
|
|
defer st.conn.mu.RUnlock()
|
|
|
|
// HandleDeadline for all ODPI calls called below
|
|
done, closeDone := newDoneCh()
|
|
defer closeDone()
|
|
if err := st.handleDeadline(ctx, done); err != nil {
|
|
return nil, err
|
|
}
|
|
var err error
|
|
closeIfBadConn := func(err error) error {
|
|
closeDone()
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
c := st.conn
|
|
st.closeNotLocking()
|
|
return maybeBadConn(err, c)
|
|
}
|
|
|
|
// bind variables
|
|
if err = st.bindVars(args, logger); err != nil {
|
|
return nil, closeIfBadConn(err)
|
|
}
|
|
|
|
mode := st.ExecMode()
|
|
//fmt.Printf("%p.%p: inTran? %t\n%s\n", st.conn, st, st.inTransaction, st.query)
|
|
if !st.inTransaction {
|
|
mode |= C.DPI_MODE_EXEC_COMMIT_ON_SUCCESS
|
|
}
|
|
if st.DeleteFromCache() {
|
|
C.dpiStmt_deleteFromCache(st.dpiStmt)
|
|
}
|
|
// execute
|
|
var f func() C.int
|
|
many := !st.PlSQLArrays() && st.arrLen > 0
|
|
if many {
|
|
f = func() C.int { return C.dpiStmt_executeMany(st.dpiStmt, mode, C.uint32_t(st.arrLen)) }
|
|
} else {
|
|
f = func() C.int { return C.dpiStmt_execute(st.dpiStmt, mode, nil) }
|
|
}
|
|
for i := 0; i < 3; i++ {
|
|
if logger != nil {
|
|
logger.Log("C", "dpiStmt_execute", "st", fmt.Sprintf("%p", st.dpiStmt), "many", many, "mode", mode, "len", st.arrLen)
|
|
}
|
|
if err = st.checkExec(f); err == nil {
|
|
break
|
|
}
|
|
if ctxErr := ctx.Err(); ctxErr != nil {
|
|
return nil, ctxErr
|
|
}
|
|
if !isInvalidErr(err) {
|
|
break
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, closeIfBadConn(err) //fmt.Errorf("dpiStmt_execute(mode=%d arrLen=%d): %w", mode, arrLen, err))
|
|
}
|
|
|
|
if logger != nil {
|
|
logger.Log("gets", st.gets, "dests", st.dests)
|
|
}
|
|
for i, get := range st.gets {
|
|
if get == nil {
|
|
continue
|
|
}
|
|
i := i
|
|
if st.dpiStmtInfo.isReturning == 1 {
|
|
var n C.uint32_t
|
|
data := &st.data[i][0]
|
|
if err := st.checkExec(func() C.int { return C.dpiVar_getReturnedData(st.vars[i], 0, &n, &data) }); err != nil {
|
|
return nil, closeIfBadConn(fmt.Errorf("%d.getReturnedData: %w", i, err))
|
|
}
|
|
if n == 0 {
|
|
st.data[i] = st.data[i][:0]
|
|
} else {
|
|
st.data[i] = unsafe.Slice(data, n)
|
|
}
|
|
}
|
|
dest := st.dests[i]
|
|
if !st.isSlice[i] {
|
|
if err := get(dest, st.data[i]); err != nil {
|
|
if logger != nil {
|
|
logger.Log("get", i, "error", err)
|
|
}
|
|
return nil, closeIfBadConn(fmt.Errorf("%d. get[%d]: %w", i, 0, err))
|
|
}
|
|
if logger != nil {
|
|
logger.Log("msg", "get-not-slice", "i", i, "dest", dest)
|
|
}
|
|
continue
|
|
}
|
|
var n C.uint32_t = 1
|
|
if err := st.checkExec(func() C.int { return C.dpiVar_getNumElementsInArray(st.vars[i], &n) }); err != nil {
|
|
if logger != nil {
|
|
logger.Log("msg", "getNumElementsInArray", "i", i, "error", err)
|
|
}
|
|
return nil, closeIfBadConn(fmt.Errorf("%d.getNumElementsInArray: %w", i, err))
|
|
}
|
|
//fmt.Printf("i=%d dest=%T %#v\n", i, dest, dest)
|
|
if err := get(dest, st.data[i][:n]); err != nil {
|
|
if logger != nil {
|
|
logger.Log("msg", "get", "i", i, "n", n, "error", err)
|
|
}
|
|
return nil, closeIfBadConn(fmt.Errorf("%d. get: %w", i, err))
|
|
}
|
|
if false && logger != nil {
|
|
logger.Log("msg", "get-slice", "i", i, "dest", dest)
|
|
}
|
|
}
|
|
var count C.uint64_t
|
|
if st.checkExec(func() C.int { return C.dpiStmt_getRowCount(st.dpiStmt, &count) }) != nil {
|
|
return nil, nil
|
|
}
|
|
return driver.RowsAffected(count), nil
|
|
}
|
|
|
|
// QueryContext executes a query that may return rows, such as a SELECT.
|
|
//
|
|
// QueryContext must honor the context timeout and return when it is canceled.
|
|
//
|
|
// Cancelation/timeout is honored, execution is broken, but you may have to disable out-of-bound execution - see https://github.com/oracle/odpi/issues/116 for details.
|
|
func (st *statement) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
st.Lock()
|
|
defer st.Unlock()
|
|
if st.conn == nil {
|
|
return nil, driver.ErrBadConn
|
|
}
|
|
st.conn.mu.RLock()
|
|
defer st.conn.mu.RUnlock()
|
|
return st.queryContextNotLocked(ctx, args)
|
|
}
|
|
|
|
func (st *statement) queryContextNotLocked(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
st.ctx = ctx
|
|
|
|
logger := ctxGetLog(ctx)
|
|
switch st.query {
|
|
case getConnection:
|
|
if logger != nil {
|
|
logger.Log("msg", "QueryContext", "args", args)
|
|
}
|
|
return &directRow{conn: st.conn, query: st.query, result: []interface{}{st.conn}}, nil
|
|
|
|
case wrapResultset:
|
|
if logger != nil {
|
|
logger.Log("msg", "QueryContext", "args", args)
|
|
}
|
|
return args[0].Value.(driver.Rows), nil
|
|
}
|
|
|
|
done, closeDone := newDoneCh()
|
|
defer closeDone()
|
|
if err := st.handleDeadline(ctx, done); err != nil {
|
|
return nil, err
|
|
}
|
|
var err error
|
|
closeIfBadConn := func(err error) error {
|
|
closeDone()
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
c := st.conn
|
|
st.closeNotLocking()
|
|
return maybeBadConn(err, c)
|
|
}
|
|
// HandleDeadline for all ODPI calls called below
|
|
|
|
//fmt.Printf("QueryContext(%+v)\n", args)
|
|
// bind variables
|
|
if err = st.bindVars(args, logger); err != nil {
|
|
return nil, closeIfBadConn(err)
|
|
}
|
|
|
|
mode := st.ExecMode()
|
|
//fmt.Printf("%p.%p: inTran? %t\n%s\n", st.conn, st, st.inTransaction, st.query)
|
|
if !st.inTransaction {
|
|
mode |= C.DPI_MODE_EXEC_COMMIT_ON_SUCCESS
|
|
}
|
|
// set Prefetch Parameters before execute
|
|
C.dpiStmt_setFetchArraySize(st.dpiStmt, C.uint32_t(st.FetchArraySize()))
|
|
C.dpiStmt_setPrefetchRows(st.dpiStmt, C.uint32_t(st.PrefetchCount()))
|
|
|
|
// execute
|
|
var colCount C.uint32_t
|
|
f := func() C.int { return C.dpiStmt_execute(st.dpiStmt, mode, &colCount) }
|
|
for i := 0; i < 3; i++ {
|
|
if err = st.checkExec(f); err == nil {
|
|
break
|
|
}
|
|
if ctxErr := ctx.Err(); ctxErr != nil {
|
|
return nil, ctxErr
|
|
}
|
|
if !isInvalidErr(err) {
|
|
break
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, closeIfBadConn(fmt.Errorf("dpiStmt_execute: %w", err))
|
|
}
|
|
|
|
rows, err := st.openRows(int(colCount))
|
|
return rows, closeIfBadConn(err)
|
|
}
|
|
|
|
// NumInput returns the number of placeholder parameters.
|
|
//
|
|
// If NumInput returns >= 0, the sql package will sanity check
|
|
// argument counts from callers and return errors to the caller
|
|
// before the statement's Exec or Query methods are called.
|
|
//
|
|
// NumInput may also return -1, if the driver doesn't know
|
|
// its number of placeholders. In that case, the sql package
|
|
// will not sanity check Exec or Query argument counts.
|
|
func (st *statement) NumInput() int {
|
|
logger := getLogger()
|
|
if logger != nil {
|
|
logger.Log("msg", "NumInput", "stmt", fmt.Sprintf("%p", st), "dpiStmt", fmt.Sprintf("%p", st.dpiStmt), "query", st.query)
|
|
}
|
|
if st.query == wrapResultset {
|
|
return 1
|
|
}
|
|
if st.dpiStmt == nil {
|
|
switch st.query {
|
|
case getConnection, wrapResultset:
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
st.Lock()
|
|
defer st.Unlock()
|
|
var cnt C.uint32_t
|
|
if err := st.checkExec(func() C.int { return C.dpiStmt_getBindCount(st.dpiStmt, &cnt) }); err != nil {
|
|
if logger != nil {
|
|
logger.Log("msg", "getBindCount", "error", err)
|
|
}
|
|
if st.conn == nil {
|
|
panic(driver.ErrBadConn)
|
|
}
|
|
panic(err)
|
|
}
|
|
if cnt < 2 { // 1 can't decrease...
|
|
if logger != nil {
|
|
logger.Log("msg", "NumInput", "count", cnt, "stmt", fmt.Sprintf("%p", st))
|
|
}
|
|
return int(cnt)
|
|
}
|
|
|
|
var prevColon bool
|
|
var mayHaveBoundName bool
|
|
for _, r := range st.query {
|
|
if r == ':' {
|
|
prevColon = true
|
|
continue
|
|
}
|
|
if prevColon {
|
|
prevColon = false
|
|
if 'a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' {
|
|
mayHaveBoundName = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !mayHaveBoundName {
|
|
return int(cnt)
|
|
}
|
|
|
|
names := make([]*C.char, int(cnt))
|
|
lengths := make([]C.uint32_t, int(cnt))
|
|
if err := st.checkExec(func() C.int { return C.dpiStmt_getBindNames(st.dpiStmt, &cnt, &names[0], &lengths[0]) }); err != nil {
|
|
if logger != nil {
|
|
logger.Log("msg", "getBindNames", "error", err)
|
|
}
|
|
if st.conn == nil {
|
|
panic(driver.ErrBadConn)
|
|
}
|
|
panic(err)
|
|
}
|
|
if logger != nil {
|
|
logger.Log("msg", "NumInput", "count", cnt, "stmt", fmt.Sprintf("%p", st))
|
|
}
|
|
|
|
// return the number of *unique* arguments
|
|
return int(cnt)
|
|
}
|
|
|
|
type argInfo struct {
|
|
objType *C.dpiObjectType
|
|
set dataSetter
|
|
bufSize int
|
|
typ C.dpiOracleTypeNum
|
|
natTyp C.dpiNativeTypeNum
|
|
isIn, isOut bool
|
|
}
|
|
|
|
// bindVars binds the given args into new variables.
|
|
func (st *statement) bindVars(args []driver.NamedValue, logger Logger) error {
|
|
if logger != nil {
|
|
logger.Log("enter", "bindVars", "st", fmt.Sprintf("%p", st), "args", args)
|
|
}
|
|
var named bool
|
|
if cap(st.vars) < len(args) {
|
|
st.vars = make([]*C.dpiVar, len(args))
|
|
} else {
|
|
st.vars = st.vars[:len(args)]
|
|
}
|
|
if cap(st.varInfos) < len(args) {
|
|
st.varInfos = make([]varInfo, len(args))
|
|
} else {
|
|
st.varInfos = st.varInfos[:len(args)]
|
|
}
|
|
if cap(st.data) < len(args) {
|
|
st.data = make([][]C.dpiData, len(args))
|
|
} else {
|
|
st.data = st.data[:len(args)]
|
|
}
|
|
if cap(st.gets) < len(args) {
|
|
st.gets = make([]dataGetter, len(args))
|
|
} else {
|
|
st.gets = st.gets[:len(args)]
|
|
}
|
|
if cap(st.dests) < len(args) {
|
|
st.dests = make([]interface{}, len(args))
|
|
} else {
|
|
st.dests = st.dests[:len(args)]
|
|
}
|
|
if cap(st.isSlice) < len(args) {
|
|
st.isSlice = make([]bool, len(args))
|
|
} else {
|
|
st.isSlice = st.isSlice[:len(args)]
|
|
}
|
|
|
|
rArgs := make([]reflect.Value, len(args))
|
|
minArrLen, maxArrLen := -1, -1
|
|
|
|
st.arrLen = minArrLen
|
|
maxArraySize := st.ArraySize()
|
|
|
|
infos := make([]argInfo, len(args))
|
|
//fmt.Printf("bindVars %d\n", len(args))
|
|
for i, a := range args {
|
|
st.gets[i] = nil
|
|
st.dests[i] = nil
|
|
if !named {
|
|
named = a.Name != ""
|
|
}
|
|
info := &(infos[i])
|
|
info.isIn = true
|
|
value := a.Value
|
|
if out, ok := value.(sql.Out); ok {
|
|
if !st.PlSQLArrays() && st.arrLen > 1 {
|
|
st.arrLen = maxArraySize
|
|
}
|
|
info.isIn, info.isOut = out.In, true
|
|
value = out.Dest
|
|
}
|
|
st.dests[i] = value
|
|
rv := reflect.ValueOf(value)
|
|
if info.isOut {
|
|
if false && rv.IsNil() {
|
|
fmt.Printf("%d. v=%T %#v kind=%s\n", i, value, value, reflect.ValueOf(value).Kind())
|
|
}
|
|
if rv.Kind() == reflect.Ptr {
|
|
rv = rv.Elem()
|
|
value = rv.Interface()
|
|
}
|
|
}
|
|
st.isSlice[i] = false
|
|
rArgs[i] = rv
|
|
if rv.Kind() == reflect.Ptr {
|
|
// deref in rArgs, but NOT value!
|
|
rArgs[i] = rv.Elem()
|
|
}
|
|
if _, isByteSlice := value.([]byte); !isByteSlice {
|
|
st.isSlice[i] = rArgs[i].Kind() == reflect.Slice
|
|
if !st.PlSQLArrays() && st.isSlice[i] {
|
|
n := rArgs[i].Len()
|
|
if minArrLen == -1 || n < minArrLen {
|
|
minArrLen = n
|
|
}
|
|
if maxArrLen == -1 || n > maxArrLen {
|
|
maxArrLen = n
|
|
}
|
|
}
|
|
}
|
|
|
|
if logger != nil {
|
|
logger.Log("msg", "bindVars", "i", i, "in", info.isIn, "out", info.isOut, "value", fmt.Sprintf("%[1]p %[1]T %#[1]v", st.dests[i]))
|
|
}
|
|
}
|
|
|
|
if maxArrLen > maxArraySize {
|
|
if st.arrLen == maxArraySize {
|
|
st.arrLen = maxArrLen
|
|
}
|
|
maxArraySize = maxArrLen
|
|
}
|
|
doManyCount := 1
|
|
doExecMany := !st.PlSQLArrays()
|
|
if doExecMany {
|
|
if minArrLen != -1 && minArrLen != maxArrLen {
|
|
return fmt.Errorf("PlSQLArrays is not set, but has different lengthed slices (min=%d < %d=max)", minArrLen, maxArrLen)
|
|
}
|
|
st.arrLen = minArrLen
|
|
if doExecMany = st.arrLen > 1; doExecMany {
|
|
doManyCount = st.arrLen
|
|
}
|
|
}
|
|
if logger != nil {
|
|
logger.Log("doManyCount", doManyCount, "arrLen", st.arrLen, "doExecMany", doExecMany, "minArrLen", "maxArrLen")
|
|
}
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
for i := range args {
|
|
info := &(infos[i])
|
|
value := st.dests[i]
|
|
|
|
var err error
|
|
if value, err = st.bindVarTypeSwitch(info, &(st.gets[i]), value); err != nil {
|
|
return fmt.Errorf("%d. arg: %w", i+1, err)
|
|
}
|
|
|
|
var rv reflect.Value
|
|
if st.isSlice[i] {
|
|
rv = reflect.ValueOf(value)
|
|
}
|
|
|
|
n := doManyCount
|
|
if st.PlSQLArrays() && st.isSlice[i] {
|
|
n = rv.Len()
|
|
if info.isOut {
|
|
n = rv.Cap()
|
|
}
|
|
}
|
|
if logger != nil {
|
|
logger.Log("msg", "newVar", "i", i, "plSQLArrays", st.PlSQLArrays(), "typ", int(info.typ), "natTyp", int(info.natTyp), "sliceLen", n, "bufSize", info.bufSize, "isSlice", st.isSlice[i])
|
|
}
|
|
//i, st.PlSQLArrays(), info.typ, info.natTyp dataSliceLen, info.bufSize)
|
|
vi := varInfo{
|
|
IsPLSArray: st.PlSQLArrays() && st.isSlice[i],
|
|
Typ: info.typ, NatTyp: info.natTyp,
|
|
SliceLen: n, BufSize: info.bufSize,
|
|
ObjectType: info.objType,
|
|
}
|
|
if vi.IsPLSArray && vi.SliceLen > maxArraySize {
|
|
return fmt.Errorf("maximum array size allowed is %d", maxArraySize)
|
|
}
|
|
mustAllocate := st.vars[i] == nil || st.data[i] == nil
|
|
if !mustAllocate && st.varInfos[i] != vi {
|
|
C.dpiVar_release(st.vars[i])
|
|
mustAllocate = true
|
|
}
|
|
if mustAllocate {
|
|
if st.vars[i], st.data[i], err = st.newVar(vi); err != nil {
|
|
return fmt.Errorf("%d: %w", i, err)
|
|
}
|
|
st.varInfos[i] = vi
|
|
}
|
|
|
|
// Have to setNumElementsInArray for the actual lengths for PL/SQL arrays
|
|
dv, data := st.vars[i], st.data[i]
|
|
if !info.isIn {
|
|
if st.PlSQLArrays() {
|
|
if logger != nil {
|
|
logger.Log("C", "dpiVar_setNumElementsInArray", "i", i, "n", 0)
|
|
}
|
|
if err := st.checkExecNoLOT(func() C.int { return C.dpiVar_setNumElementsInArray(dv, C.uint32_t(0)) }); err != nil {
|
|
return fmt.Errorf("setNumElementsInArray[%d](%d): %w", i, 0, err)
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
|
|
if !st.isSlice[i] {
|
|
if logger != nil {
|
|
logger.Log("msg", "set", "i", i, "value", fmt.Sprintf("%T=%#v", value, value))
|
|
}
|
|
if err := info.set(dv, data[:1], value); err != nil {
|
|
return fmt.Errorf("set(data[%d][%d], %#v (%T)): %w", i, 0, value, value, err)
|
|
}
|
|
continue
|
|
}
|
|
|
|
if st.PlSQLArrays() {
|
|
n = rv.Len()
|
|
|
|
if logger != nil {
|
|
logger.Log("C", "dpiVar_setNumElementsInArray", "i", i, "n", n)
|
|
}
|
|
if err := st.checkExecNoLOT(func() C.int { return C.dpiVar_setNumElementsInArray(dv, C.uint32_t(n)) }); err != nil {
|
|
return fmt.Errorf("%+v.setNumElementsInArray[%d](%d): %w", dv, i, n, err)
|
|
}
|
|
}
|
|
//fmt.Println("n:", len(st.data[i]))
|
|
if err := info.set(dv, data, value); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if !named {
|
|
for i, v := range st.vars {
|
|
i, v := i, v
|
|
if err := st.checkExecNoLOT(func() C.int { return C.dpiStmt_bindByPos(st.dpiStmt, C.uint32_t(i+1), v) }); err != nil {
|
|
return fmt.Errorf("bindByPos[%d]: %w", i, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
for i, a := range args {
|
|
name := a.Name
|
|
if name == "" {
|
|
name = strconv.Itoa(a.Ordinal)
|
|
}
|
|
//fmt.Printf("bindByName(%q)\n", name)
|
|
cName := C.CString(name)
|
|
err := st.checkExecNoLOT(func() C.int { return C.dpiStmt_bindByName(st.dpiStmt, cName, C.uint32_t(len(name)), st.vars[i]) })
|
|
C.free(unsafe.Pointer(cName))
|
|
if err != nil {
|
|
return fmt.Errorf("bindByName[%q]: %w", name, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (st *statement) bindVarTypeSwitch(info *argInfo, get *dataGetter, value interface{}) (interface{}, error) {
|
|
nilPtr := false
|
|
logger := getLogger()
|
|
if logger != nil {
|
|
defer func() {
|
|
logger.Log("msg", "bindVarTypeSwitch", "info", info, "value", fmt.Sprintf("[%T]%v", value, value))
|
|
}()
|
|
}
|
|
vlr, isValuer := value.(driver.Valuer)
|
|
|
|
switch value.(type) {
|
|
case *driver.Rows, *Object, *timestamppb.Timestamp:
|
|
default:
|
|
if rv := reflect.ValueOf(value); rv.Kind() == reflect.Ptr {
|
|
if nilPtr = rv.IsNil(); nilPtr {
|
|
info.set = dataSetNull
|
|
value = reflect.Zero(rv.Type().Elem()).Interface()
|
|
} else {
|
|
value = rv.Elem().Interface()
|
|
if !isValuer {
|
|
vlr, isValuer = value.(driver.Valuer)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch v := value.(type) {
|
|
case Lob, []Lob:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_BLOB, C.DPI_NATIVE_TYPE_LOB
|
|
var isClob bool
|
|
switch v := v.(type) {
|
|
case Lob:
|
|
isClob = v.IsClob
|
|
case []Lob:
|
|
isClob = len(v) > 0 && v[0].IsClob
|
|
}
|
|
if isClob {
|
|
info.typ = C.DPI_ORACLE_TYPE_CLOB
|
|
}
|
|
info.set = st.dataSetLOB
|
|
if info.isOut {
|
|
*get = st.dataGetLOB
|
|
}
|
|
case *driver.Rows:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_STMT, C.DPI_NATIVE_TYPE_STMT
|
|
info.set = dataSetNull
|
|
if info.isOut {
|
|
*get = st.dataGetStmt
|
|
}
|
|
case int, []int:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_NUMBER, C.DPI_NATIVE_TYPE_INT64
|
|
if !nilPtr {
|
|
info.set = dataSetNumber
|
|
if info.isOut {
|
|
*get = dataGetNumber
|
|
}
|
|
}
|
|
case int8, []int8:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_NATIVE_INT, C.DPI_NATIVE_TYPE_INT64
|
|
if !nilPtr {
|
|
info.set = dataSetNumber
|
|
if info.isOut {
|
|
*get = dataGetNumber
|
|
}
|
|
}
|
|
case int16, []int16:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_NATIVE_INT, C.DPI_NATIVE_TYPE_INT64
|
|
if !nilPtr {
|
|
info.set = dataSetNumber
|
|
if info.isOut {
|
|
*get = dataGetNumber
|
|
}
|
|
}
|
|
case int32, []int32:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_NATIVE_INT, C.DPI_NATIVE_TYPE_INT64
|
|
if !nilPtr {
|
|
info.set = dataSetNumber
|
|
if info.isOut {
|
|
*get = dataGetNumber
|
|
}
|
|
}
|
|
case int64, []int64, sql.NullInt64, []sql.NullInt64:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_NUMBER, C.DPI_NATIVE_TYPE_INT64
|
|
if !nilPtr {
|
|
info.set = dataSetNumber
|
|
if info.isOut {
|
|
*get = dataGetNumber
|
|
}
|
|
}
|
|
case uint, []uint:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_NUMBER, C.DPI_NATIVE_TYPE_UINT64
|
|
if !nilPtr {
|
|
info.set = dataSetNumber
|
|
if info.isOut {
|
|
*get = dataGetNumber
|
|
}
|
|
}
|
|
case uint8:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_NATIVE_UINT, C.DPI_NATIVE_TYPE_UINT64
|
|
if !nilPtr {
|
|
info.set = dataSetNumber
|
|
if info.isOut {
|
|
*get = dataGetNumber
|
|
}
|
|
}
|
|
case uint16, []uint16:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_NATIVE_UINT, C.DPI_NATIVE_TYPE_UINT64
|
|
if !nilPtr {
|
|
info.set = dataSetNumber
|
|
if info.isOut {
|
|
*get = dataGetNumber
|
|
}
|
|
}
|
|
case uint32, []uint32:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_NATIVE_UINT, C.DPI_NATIVE_TYPE_UINT64
|
|
if !nilPtr {
|
|
info.set = dataSetNumber
|
|
if info.isOut {
|
|
*get = dataGetNumber
|
|
}
|
|
}
|
|
case uint64, []uint64:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_NUMBER, C.DPI_NATIVE_TYPE_UINT64
|
|
if !nilPtr {
|
|
info.set = dataSetNumber
|
|
if info.isOut {
|
|
*get = dataGetNumber
|
|
}
|
|
}
|
|
case float32, []float32:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_NATIVE_FLOAT, C.DPI_NATIVE_TYPE_FLOAT
|
|
if !nilPtr {
|
|
info.set = dataSetNumber
|
|
if info.isOut {
|
|
*get = dataGetNumber
|
|
}
|
|
}
|
|
case float64, []float64:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_NATIVE_DOUBLE, C.DPI_NATIVE_TYPE_DOUBLE
|
|
if !nilPtr {
|
|
info.set = dataSetNumber
|
|
if info.isOut {
|
|
*get = dataGetNumber
|
|
}
|
|
}
|
|
case sql.NullFloat64, []sql.NullFloat64:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_NATIVE_DOUBLE, C.DPI_NATIVE_TYPE_DOUBLE
|
|
info.set = dataSetNumber
|
|
if info.isOut {
|
|
*get = dataGetNumber
|
|
}
|
|
case bool, []bool:
|
|
if st.dpiStmtInfo.isPLSQL == 1 || st.stmtOptions.boolString.IsZero() || st.PlSQLArrays() {
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_BOOLEAN, C.DPI_NATIVE_TYPE_BOOLEAN
|
|
info.set = dataSetBool
|
|
if info.isOut {
|
|
*get = dataGetBool
|
|
}
|
|
} else {
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_VARCHAR, C.DPI_NATIVE_TYPE_BYTES
|
|
info.bufSize = st.stmtOptions.boolString.MaxLen()
|
|
info.set = st.dataSetBoolBytes
|
|
if info.isOut {
|
|
*get = st.dataGetBoolBytes
|
|
}
|
|
}
|
|
|
|
case []byte, [][]byte:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_RAW, C.DPI_NATIVE_TYPE_BYTES
|
|
info.set = dataSetBytes
|
|
if info.isOut {
|
|
info.bufSize = 32767
|
|
*get = dataGetBytes
|
|
} else {
|
|
switch v := v.(type) {
|
|
case []byte:
|
|
info.bufSize = len(v)
|
|
case [][]byte:
|
|
for _, b := range v {
|
|
if n := len(b); n > info.bufSize {
|
|
info.bufSize = n
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
case Number, []Number:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_NUMBER, C.DPI_NATIVE_TYPE_BYTES
|
|
info.set = dataSetBytes
|
|
if info.isOut {
|
|
info.bufSize = 32767
|
|
*get = dataGetBytes
|
|
} else {
|
|
switch v := v.(type) {
|
|
case Number:
|
|
info.bufSize = len(v)
|
|
case []Number:
|
|
for _, s := range v {
|
|
if n := len(s); n > info.bufSize {
|
|
info.bufSize = n
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
case string, []string, nil:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_VARCHAR, C.DPI_NATIVE_TYPE_BYTES
|
|
info.set = dataSetBytes
|
|
if info.isOut {
|
|
info.bufSize = 32767
|
|
*get = dataGetBytes
|
|
} else {
|
|
switch v := v.(type) {
|
|
case string:
|
|
info.bufSize = 4 * len(v)
|
|
case []string:
|
|
for _, s := range v {
|
|
if n := 4 * len(s); n > info.bufSize {
|
|
info.bufSize = n
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
case time.Time, NullTime, *timestamppb.Timestamp:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_TIMESTAMP_TZ, C.DPI_NATIVE_TYPE_TIMESTAMP
|
|
info.set = st.conn.dataSetTime
|
|
if info.isOut {
|
|
*get = st.conn.dataGetTime
|
|
}
|
|
|
|
case []time.Time, []NullTime, []*timestamppb.Timestamp:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_TIMESTAMP_TZ, C.DPI_NATIVE_TYPE_TIMESTAMP
|
|
if st.plSQLArrays {
|
|
info.typ = C.DPI_ORACLE_TYPE_DATE
|
|
}
|
|
info.set = st.conn.dataSetTime
|
|
if info.isOut {
|
|
*get = st.conn.dataGetTime
|
|
}
|
|
|
|
case time.Duration, []time.Duration:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_INTERVAL_DS, C.DPI_NATIVE_TYPE_INTERVAL_DS
|
|
info.set = st.conn.dataSetIntervalDS
|
|
if info.isOut {
|
|
*get = st.conn.dataGetIntervalDS
|
|
}
|
|
|
|
case Object:
|
|
info.objType = v.ObjectType.dpiObjectType
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_OBJECT, C.DPI_NATIVE_TYPE_OBJECT
|
|
info.set = st.dataSetObject
|
|
if info.isOut {
|
|
*get = st.dataGetObject
|
|
}
|
|
|
|
case *Object:
|
|
if !nilPtr && v != nil {
|
|
info.objType = v.ObjectType.dpiObjectType
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_OBJECT, C.DPI_NATIVE_TYPE_OBJECT
|
|
}
|
|
info.set = st.dataSetObject
|
|
if info.isOut {
|
|
*get = st.dataGetObject
|
|
}
|
|
|
|
case userType:
|
|
info.objType = v.ObjectRef().ObjectType.dpiObjectType
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_OBJECT, C.DPI_NATIVE_TYPE_OBJECT
|
|
info.set = st.dataSetObject
|
|
if info.isOut {
|
|
*get = st.dataGetObject
|
|
}
|
|
case JSON:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_JSON, C.DPI_NATIVE_TYPE_JSON
|
|
info.set = st.conn.dataSetJSON
|
|
if info.isOut {
|
|
*get = st.conn.dataGetJSON
|
|
}
|
|
case JSONString, []JSONString:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_JSON, C.DPI_NATIVE_TYPE_JSON
|
|
info.set = st.dataSetJSONString
|
|
if info.isOut {
|
|
*get = st.dataGetJSONString
|
|
}
|
|
case JSONValue, []JSONValue:
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_JSON, C.DPI_NATIVE_TYPE_JSON
|
|
info.set = st.conn.dataSetJSONValue
|
|
if info.isOut {
|
|
*get = st.conn.dataGetJSONValue
|
|
}
|
|
|
|
default:
|
|
if logger != nil {
|
|
logger.Log("msg", "bindVarTypeSwitch default", "value", value)
|
|
}
|
|
if !isValuer {
|
|
if ot, err := st.conn.getStructObjectType(value, ""); err != nil {
|
|
if logger != nil {
|
|
logger.Log("msg", "getStructObjectType", "value", fmt.Sprintf("%T", value), "error", err)
|
|
}
|
|
if !errors.Is(err, errUnknownType) {
|
|
return value, err
|
|
}
|
|
} else {
|
|
info.objType = ot.dpiObjectType
|
|
info.typ, info.natTyp = C.DPI_ORACLE_TYPE_OBJECT, C.DPI_NATIVE_TYPE_OBJECT
|
|
info.set = func(dv *C.dpiVar, data []C.dpiData, vv interface{}) error {
|
|
return st.dataSetObjectStruct(ot, dv, &data[0], vv)
|
|
}
|
|
if info.isOut {
|
|
*get = func(v interface{}, data []C.dpiData) error {
|
|
return st.dataGetObjectStruct(ot, v, data)
|
|
}
|
|
}
|
|
return value, nil
|
|
}
|
|
return value, fmt.Errorf("bindVarTypeSwitch(%T): %w", value, errUnknownType)
|
|
}
|
|
oval := value
|
|
var err error
|
|
if value, err = vlr.Value(); err != nil {
|
|
return value, fmt.Errorf("arg.Value(): %w", err)
|
|
}
|
|
if logger != nil {
|
|
logger.Log("msg", "valuer", "old", fmt.Sprintf("[%T]%#v.Value()", oval, oval), "new", fmt.Sprintf("[%T]%#v", value, value))
|
|
}
|
|
return st.bindVarTypeSwitch(info, get, value)
|
|
}
|
|
|
|
return value, nil
|
|
}
|
|
|
|
type dataSetter func(dv *C.dpiVar, data []C.dpiData, vv interface{}) error
|
|
|
|
func dataSetNull(dv *C.dpiVar, data []C.dpiData, vv interface{}) error {
|
|
for i := range data {
|
|
data[i].isNull = 1
|
|
}
|
|
return nil
|
|
}
|
|
func dataGetBool(v interface{}, data []C.dpiData) error {
|
|
if b, ok := v.(*bool); ok {
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*b = false
|
|
return nil
|
|
}
|
|
//*b = C.dpiData_getBool(&data[0]) == 1
|
|
*b = *((*C.int)(unsafe.Pointer(&data[0].value))) == 1
|
|
return nil
|
|
}
|
|
slice := v.(*[]bool)
|
|
if cap(*slice) >= len(data) {
|
|
*slice = (*slice)[:len(data)]
|
|
} else {
|
|
*slice = make([]bool, len(data))
|
|
}
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
(*slice)[i] = false
|
|
continue
|
|
}
|
|
//(*slice)[i] = C.dpiData_getBool(&data[i]) == 1
|
|
(*slice)[i] = *((*C.int)(unsafe.Pointer(&data[i].value))) == 1
|
|
}
|
|
return nil
|
|
}
|
|
func dataSetBool(dv *C.dpiVar, data []C.dpiData, vv interface{}) error {
|
|
if vv == nil {
|
|
return dataSetNull(dv, data, nil)
|
|
}
|
|
b := C.int(0)
|
|
if v, ok := vv.(bool); ok {
|
|
if v {
|
|
b = 1
|
|
}
|
|
C.dpiData_setBool(&data[0], b)
|
|
return nil
|
|
}
|
|
if bb, ok := vv.([]bool); ok {
|
|
for i, v := range bb {
|
|
if v {
|
|
b = 1
|
|
}
|
|
C.dpiData_setBool(&data[i], b)
|
|
}
|
|
return nil
|
|
}
|
|
for i := range data {
|
|
data[i].isNull = 1
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var _ = sql.Scanner((*NullTime)(nil))
|
|
|
|
func (c *conn) dataGetTime(v interface{}, data []C.dpiData) error {
|
|
switch x := v.(type) {
|
|
case *time.Time:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*x = time.Time{}
|
|
return nil
|
|
}
|
|
c.dataGetTimeC(x, &data[0])
|
|
case *timestamppb.Timestamp:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
x.Reset()
|
|
return nil
|
|
}
|
|
var t time.Time
|
|
c.dataGetTimeC(&t, &data[0])
|
|
if t.IsZero() {
|
|
x.Reset()
|
|
} else {
|
|
*x = *timestamppb.New(t)
|
|
}
|
|
|
|
case *NullTime:
|
|
if x.Valid = !(len(data) == 0 || data[0].isNull == 1); x.Valid {
|
|
c.dataGetTimeC(&x.Time, &data[0])
|
|
}
|
|
|
|
case *[]time.Time:
|
|
n := len(data)
|
|
if cap(*x) >= n {
|
|
*x = (*x)[:n]
|
|
} else {
|
|
*x = make([]time.Time, n)
|
|
}
|
|
for i := range data {
|
|
c.dataGetTimeC(&((*x)[i]), &data[i])
|
|
}
|
|
case *[]*timestamppb.Timestamp:
|
|
n := len(data)
|
|
if cap(*x) >= n {
|
|
*x = (*x)[:n]
|
|
} else {
|
|
*x = make([]*timestamppb.Timestamp, n)
|
|
}
|
|
for i := range data {
|
|
var t time.Time
|
|
c.dataGetTimeC(&t, &data[i])
|
|
if t.IsZero() {
|
|
(*x)[i].Reset()
|
|
} else {
|
|
if (*x)[i] == nil {
|
|
(*x)[i] = timestamppb.New(t)
|
|
} else {
|
|
*((*x)[i]) = *timestamppb.New(t)
|
|
}
|
|
}
|
|
}
|
|
case *[]NullTime:
|
|
n := len(data)
|
|
if cap(*x) >= n {
|
|
*x = (*x)[:n]
|
|
} else {
|
|
*x = make([]NullTime, n)
|
|
}
|
|
for i := range data {
|
|
if (*x)[i].Valid = !(data[i].isNull == 1); (*x)[i].Valid {
|
|
c.dataGetTimeC(&((*x)[i].Time), &data[i])
|
|
}
|
|
}
|
|
|
|
default:
|
|
return fmt.Errorf("dataGetTime(%T): %w", v, errUnknownType)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var errUnknownType = errors.New("unknown type")
|
|
|
|
func (c *conn) dataGetTimeC(t *time.Time, data *C.dpiData) {
|
|
if data.isNull == 1 {
|
|
*t = time.Time{}
|
|
return
|
|
}
|
|
//ts := C.dpiData_getTimestamp(data)
|
|
ts := *((*C.dpiTimestamp)(unsafe.Pointer(&data.value)))
|
|
*t = time.Date(
|
|
int(ts.year), time.Month(ts.month), int(ts.day),
|
|
int(ts.hour), int(ts.minute), int(ts.second), int(ts.fsecond),
|
|
timeZoneFor(ts.tzHourOffset, ts.tzMinuteOffset, c.Timezone()),
|
|
)
|
|
}
|
|
|
|
var date8192begin, date8192end = time.Date(0, time.December, 31, 0, 0, 0, 0, time.UTC), time.Date(1, time.January, 2, 0, 0, 0, 0, time.UTC)
|
|
|
|
func (c *conn) dataSetTime(dv *C.dpiVar, data []C.dpiData, vv interface{}) error {
|
|
if vv == nil {
|
|
return dataSetNull(dv, data, nil)
|
|
}
|
|
var logger Logger
|
|
times := []time.Time{{}}
|
|
switch x := vv.(type) {
|
|
case time.Time:
|
|
times[0] = x
|
|
data[0].isNull = C.int(b2i(x.IsZero()))
|
|
case NullTime:
|
|
if data[0].isNull = C.int(b2i(!x.Valid)); x.Valid {
|
|
times[0] = x.Time
|
|
}
|
|
case *timestamppb.Timestamp:
|
|
times[0] = x.AsTime()
|
|
data[0].isNull = C.int(b2i(times[0].IsZero()))
|
|
|
|
case []time.Time:
|
|
times = x
|
|
for i, t := range times {
|
|
data[i].isNull = C.int(b2i(t.IsZero()))
|
|
}
|
|
case []NullTime:
|
|
if cap(times) < len(x) {
|
|
times = make([]time.Time, len(x))
|
|
} else {
|
|
times = times[:len(x)]
|
|
}
|
|
for i, n := range x {
|
|
if data[i].isNull = C.int(b2i(!n.Valid)); n.Valid {
|
|
times[i] = x[i].Time
|
|
}
|
|
}
|
|
case []*timestamppb.Timestamp:
|
|
if cap(times) < len(x) {
|
|
times = make([]time.Time, len(x))
|
|
} else {
|
|
times = times[:len(x)]
|
|
}
|
|
for i, n := range x {
|
|
times[i] = n.AsTime()
|
|
data[i].isNull = C.int(b2i(times[i].IsZero()))
|
|
}
|
|
|
|
default:
|
|
return fmt.Errorf("dataSetTime(%T): %w", vv, errUnknownType)
|
|
}
|
|
|
|
//tzHour, tzMin := C.int8_t(c.tzOffSecs/3600), C.int8_t((c.tzOffSecs%3600)/60)
|
|
if logger == nil {
|
|
logger = getLogger()
|
|
}
|
|
tz := c.Timezone()
|
|
for i, t := range times {
|
|
if data[i].isNull == 1 {
|
|
continue
|
|
}
|
|
tz, tzOff := tz, 0
|
|
if tz != time.UTC && // Against ORA-08192
|
|
date8192begin.Before(t) && date8192end.After(t) {
|
|
tz = time.UTC
|
|
}
|
|
if t.Location() != tz {
|
|
t = t.In(tz)
|
|
}
|
|
if tz != time.UTC {
|
|
_, tzOff = t.Zone()
|
|
}
|
|
Y, M, D := t.Date()
|
|
if Y <= 0 { // Oracle skips year 0, 0001-01-01 follows -0001-12-31 !
|
|
Y--
|
|
}
|
|
if -4713 > Y || Y == 0 || 9999 < Y { // Against ORA-01841
|
|
return fmt.Errorf("%v: %w", t, ErrBadDate)
|
|
}
|
|
h, m, s := t.Clock()
|
|
if logger != nil {
|
|
logger.Log("msg", "setTimestamp", "time", t.Format(time.RFC3339), "utc", t.UTC(), "tz", tzOff,
|
|
"Y", Y, "M", M, "D", D, "h", h, "m", m, "s", s, "t", t.Nanosecond(), "tzHour", tzOff/3600, "tzMin", (tzOff%3600)/60)
|
|
}
|
|
C.dpiData_setTimestamp(&data[i],
|
|
C.int16_t(Y), C.uint8_t(M), C.uint8_t(D),
|
|
C.uint8_t(h), C.uint8_t(m), C.uint8_t(s), C.uint32_t(t.Nanosecond()),
|
|
C.int8_t(tzOff/3600), C.int8_t((tzOff%3600)/60),
|
|
)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *conn) dataGetIntervalDS(v interface{}, data []C.dpiData) error {
|
|
logger := getLogger()
|
|
if logger != nil {
|
|
logger.Log("msg", "dataGetIntervalDS", "data", data, "v", v)
|
|
}
|
|
switch x := v.(type) {
|
|
case *time.Duration:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*x = 0
|
|
return nil
|
|
}
|
|
dataGetIntervalDS(x, &data[0])
|
|
|
|
case *[]time.Duration:
|
|
n := len(data)
|
|
if cap(*x) >= n {
|
|
*x = (*x)[:n]
|
|
} else {
|
|
*x = make([]time.Duration, n)
|
|
}
|
|
for i := range data {
|
|
dataGetIntervalDS(&((*x)[i]), &data[i])
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func dataGetIntervalDS(t *time.Duration, d *C.dpiData) {
|
|
//ds := C.dpiData_getIntervalDS(d)
|
|
ds := *((*C.dpiIntervalDS)(unsafe.Pointer(&d.value)))
|
|
*t = time.Duration(ds.days)*24*time.Hour +
|
|
time.Duration(ds.hours)*time.Hour +
|
|
time.Duration(ds.minutes)*time.Minute +
|
|
time.Duration(ds.seconds)*time.Second +
|
|
time.Duration(ds.fseconds)
|
|
if logger := getLogger(); logger != nil {
|
|
logger.Log("msg", "dataGetIntervalDS", "d", *d, "t", *t)
|
|
}
|
|
}
|
|
|
|
func (c *conn) dataSetIntervalDS(dv *C.dpiVar, data []C.dpiData, vv interface{}) error {
|
|
if vv == nil {
|
|
return dataSetNull(dv, data, nil)
|
|
}
|
|
times := []time.Duration{0}
|
|
switch x := vv.(type) {
|
|
case time.Duration:
|
|
times[0] = x
|
|
data[0].isNull = C.int(b2i(x == 0))
|
|
|
|
case []time.Duration:
|
|
times = x
|
|
for i, t := range times {
|
|
data[i].isNull = C.int(b2i(t == 0))
|
|
}
|
|
|
|
default:
|
|
for i := range data {
|
|
data[i].isNull = 1
|
|
}
|
|
return nil
|
|
}
|
|
logger := getLogger()
|
|
if logger != nil {
|
|
logger.Log("msg", "dataSetIntervalDS", "data", data, "times", times)
|
|
}
|
|
|
|
for i, t := range times {
|
|
if data[i].isNull == 1 {
|
|
continue
|
|
}
|
|
rem := t % (24 * time.Hour)
|
|
d := C.int32_t(t / (24 * time.Hour))
|
|
t, rem = rem, t%(time.Hour)
|
|
h := C.int32_t(t / time.Hour)
|
|
t, rem = rem, t%(time.Minute)
|
|
m := C.int32_t(t / time.Minute)
|
|
t, rem = rem, t%time.Second
|
|
s := C.int32_t(t / time.Second)
|
|
fs := C.int32_t(rem)
|
|
if logger != nil {
|
|
logger.Log("i", i, "t", t, "day", d, "hour", h, "minute", m, "second", s, "fsecond", fs)
|
|
}
|
|
C.dpiData_setIntervalDS(&data[i], d, h, m, s, fs)
|
|
if logger != nil {
|
|
logger.Log("i", i, "t", t, "data", data[i])
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func dataGetNumber(v interface{}, data []C.dpiData) error {
|
|
switch x := v.(type) {
|
|
case *int:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*x = 0
|
|
} else {
|
|
//*x = int(C.dpiData_getInt64(&data[0]))
|
|
*x = int(*((*int64)(unsafe.Pointer(&data[0].value))))
|
|
}
|
|
case *[]int:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, 0)
|
|
} else {
|
|
//*x = append(*x, int(C.dpiData_getInt64(&data[i])))
|
|
*x = append(*x, int(*((*int64)(unsafe.Pointer(&data[i].value)))))
|
|
}
|
|
}
|
|
case *int8:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*x = 0
|
|
} else {
|
|
//*x = int8(C.dpiData_getInt64(&data[0]))
|
|
*x = *((*int8)(unsafe.Pointer(&data[0].value)))
|
|
}
|
|
case *[]int8:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, 0)
|
|
} else {
|
|
//*x = append(*x, int8(C.dpiData_getInt64(&data[i])))
|
|
*x = append(*x, *((*int8)(unsafe.Pointer(&data[i].value))))
|
|
}
|
|
}
|
|
case *int16:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*x = 0
|
|
} else {
|
|
//*x = int16(C.dpiData_getInt64(&data[0]))
|
|
*x = *((*int16)(unsafe.Pointer(&data[0].value)))
|
|
}
|
|
case *[]int16:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, 0)
|
|
} else {
|
|
//*x = append(*x, int16(C.dpiData_getInt64(&data[i])))
|
|
*x = append(*x, *((*int16)(unsafe.Pointer(&data[i].value))))
|
|
}
|
|
}
|
|
case *int32:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*x = 0
|
|
} else {
|
|
//*x = int32(C.dpiData_getInt64(&data[0]))
|
|
*x = *((*int32)(unsafe.Pointer(&data[0].value)))
|
|
}
|
|
case *[]int32:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, 0)
|
|
} else {
|
|
//*x = append(*x, int32(C.dpiData_getInt64(&data[i])))
|
|
*x = append(*x, *((*int32)(unsafe.Pointer(&data[i].value))))
|
|
}
|
|
}
|
|
case *int64:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*x = 0
|
|
} else {
|
|
//*x = int64(C.dpiData_getInt64(&data[0]))
|
|
*x = *((*int64)(unsafe.Pointer(&data[0].value)))
|
|
}
|
|
case *[]int64:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, 0)
|
|
} else {
|
|
//*x = append(*x, int64(C.dpiData_getInt64(&data[i])))
|
|
*x = append(*x, *((*int64)(unsafe.Pointer(&data[i].value))))
|
|
}
|
|
}
|
|
case *sql.NullInt32:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
x.Valid = false
|
|
} else {
|
|
//x.Valid, x.Int32 = true, int32(C.dpiData_getInt64(&data[0]))
|
|
x.Valid, x.Int32 = true, *((*int32)(unsafe.Pointer(&data[0].value)))
|
|
}
|
|
case *[]sql.NullInt32:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, sql.NullInt32{Valid: false})
|
|
} else {
|
|
*x = append(*x, sql.NullInt32{Valid: true,
|
|
//Int32: int32(C.dpiData_getInt64(&data[i]))})
|
|
Int32: *((*int32)(unsafe.Pointer(&data[i].value)))})
|
|
}
|
|
}
|
|
case *sql.NullInt64:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
x.Valid = false
|
|
} else {
|
|
//x.Valid, x.Int64 = true, int64(C.dpiData_getInt64(&data[0]))
|
|
x.Valid, x.Int64 = true, *((*int64)(unsafe.Pointer(&data[0].value)))
|
|
}
|
|
case *[]sql.NullInt64:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, sql.NullInt64{Valid: false})
|
|
} else {
|
|
*x = append(*x, sql.NullInt64{Valid: true,
|
|
//Int64: int64(C.dpiData_getInt64(&data[i]))})
|
|
Int64: *((*int64)(unsafe.Pointer(&data[i].value)))})
|
|
}
|
|
}
|
|
case *sql.NullFloat64:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
x.Valid = false
|
|
} else {
|
|
//x.Valid, x.Float64 = true, float64(C.dpiData_getDouble(&data[0]))
|
|
x.Valid, x.Float64 = true, *((*float64)(unsafe.Pointer(&data[0].value)))
|
|
}
|
|
case *[]sql.NullFloat64:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, sql.NullFloat64{Valid: false})
|
|
} else {
|
|
*x = append(*x, sql.NullFloat64{Valid: true,
|
|
//Float64: float64(C.dpiData_getDouble(&data[i]))})
|
|
Float64: *((*float64)(unsafe.Pointer(&data[i].value)))})
|
|
}
|
|
}
|
|
|
|
case *uint:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*x = 0
|
|
} else {
|
|
//*x = uint(C.dpiData_getUint64(&data[0]))
|
|
*x = uint(*((*uint64)(unsafe.Pointer(&data[0].value))))
|
|
}
|
|
case *[]uint:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, 0)
|
|
} else {
|
|
//*x = append(*x, uint(C.dpiData_getUint64(&data[i])))
|
|
*x = append(*x, uint(*((*uint64)(unsafe.Pointer(&data[i].value)))))
|
|
}
|
|
}
|
|
case *[]uint8:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, 0)
|
|
} else {
|
|
//*x = append(*x, uint8(C.dpiData_getUint64(&data[i])))
|
|
*x = append(*x, *((*uint8)(unsafe.Pointer(&data[i].value))))
|
|
}
|
|
}
|
|
case *uint8:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*x = 0
|
|
} else {
|
|
//*x = uint8(C.dpiData_getUint64(&data[0]))
|
|
*x = *((*uint8)(unsafe.Pointer(&data[0].value)))
|
|
}
|
|
case *[]uint16:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, 0)
|
|
} else {
|
|
//*x = append(*x, uint16(C.dpiData_getUint64(&data[i])))
|
|
*x = append(*x, *((*uint16)(unsafe.Pointer(&data[i].value))))
|
|
}
|
|
}
|
|
case *uint16:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*x = 0
|
|
} else {
|
|
//*x = uint16(C.dpiData_getUint64(&data[0]))
|
|
*x = *((*uint16)(unsafe.Pointer(&data[0].value)))
|
|
}
|
|
case *uint32:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*x = 0
|
|
} else {
|
|
//*x = uint32(C.dpiData_getUint64(&data[0]))
|
|
*x = *((*uint32)(unsafe.Pointer(&data[0].value)))
|
|
}
|
|
case *[]uint32:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, 0)
|
|
} else {
|
|
//*x = append(*x, uint32(C.dpiData_getUint64(&data[i])))
|
|
*x = append(*x, *((*uint32)(unsafe.Pointer(&data[i].value))))
|
|
}
|
|
}
|
|
case *uint64:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*x = 0
|
|
} else {
|
|
//*x = uint64(C.dpiData_getUint64(&data[0]))
|
|
*x = *((*uint64)(unsafe.Pointer(&data[0].value)))
|
|
}
|
|
case *[]uint64:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, 0)
|
|
} else {
|
|
//*x = append(*x, uint64(C.dpiData_getUint64(&data[i])))
|
|
*x = append(*x, *((*uint64)(unsafe.Pointer(&data[i].value))))
|
|
}
|
|
}
|
|
|
|
case *float32:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*x = 0
|
|
} else {
|
|
//*x = float32(C.dpiData_getFloat(&data[0]))
|
|
*x = *((*float32)(unsafe.Pointer(&data[0].value)))
|
|
}
|
|
case *[]float32:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, 0)
|
|
} else {
|
|
//*x = append(*x, float32(C.dpiData_getFloat(&data[i])))
|
|
*x = append(*x, *((*float32)(unsafe.Pointer(&data[i].value))))
|
|
}
|
|
}
|
|
case *float64:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*x = 0
|
|
} else {
|
|
//*x = float64(C.dpiData_getDouble(&data[0]))
|
|
*x = *((*float64)(unsafe.Pointer(&data[0].value)))
|
|
}
|
|
case *[]float64:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, 0)
|
|
} else {
|
|
//*x = append(*x, float64(C.dpiData_getDouble(&data[i])))
|
|
*x = append(*x, *((*float64)(unsafe.Pointer(&data[i].value))))
|
|
}
|
|
}
|
|
|
|
case *Number, *[]Number, decimalCompose, *[]decimalCompose:
|
|
return dataGetBytes(x, data)
|
|
|
|
default:
|
|
return fmt.Errorf("unknown number [%T] %#v", v, v)
|
|
}
|
|
|
|
//fmt.Printf("setInt64(%#v, %#v)\n", data, C.int64_t(int64(v.(int))))
|
|
return nil
|
|
}
|
|
|
|
func dataSetNumber(dv *C.dpiVar, data []C.dpiData, vv interface{}) error {
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
if vv == nil {
|
|
return dataSetNull(dv, data, nil)
|
|
}
|
|
switch slice := vv.(type) {
|
|
case int:
|
|
i, x := 0, slice
|
|
C.dpiData_setInt64(&data[i], C.int64_t(x))
|
|
case []int:
|
|
for i, x := range slice {
|
|
C.dpiData_setInt64(&data[i], C.int64_t(x))
|
|
}
|
|
case int8:
|
|
i, x := 0, slice
|
|
C.dpiData_setInt64(&data[i], C.int64_t(x))
|
|
case []int8:
|
|
for i, x := range slice {
|
|
C.dpiData_setInt64(&data[i], C.int64_t(x))
|
|
}
|
|
case int16:
|
|
i, x := 0, slice
|
|
C.dpiData_setInt64(&data[i], C.int64_t(x))
|
|
case []int16:
|
|
for i, x := range slice {
|
|
C.dpiData_setInt64(&data[i], C.int64_t(x))
|
|
}
|
|
case int32:
|
|
i, x := 0, slice
|
|
C.dpiData_setInt64(&data[i], C.int64_t(x))
|
|
case []int32:
|
|
for i, x := range slice {
|
|
C.dpiData_setInt64(&data[i], C.int64_t(x))
|
|
}
|
|
case int64:
|
|
i, x := 0, slice
|
|
C.dpiData_setInt64(&data[i], C.int64_t(x))
|
|
case []int64:
|
|
for i, x := range slice {
|
|
C.dpiData_setInt64(&data[i], C.int64_t(x))
|
|
}
|
|
case sql.NullInt32:
|
|
i, x := 0, slice
|
|
if x.Valid {
|
|
data[i].isNull = 0
|
|
C.dpiData_setInt64(&data[i], C.int64_t(x.Int32))
|
|
} else {
|
|
data[i].isNull = 1
|
|
}
|
|
case []sql.NullInt32:
|
|
for i, x := range slice {
|
|
if x.Valid {
|
|
data[i].isNull = 0
|
|
C.dpiData_setInt64(&data[i], C.int64_t(x.Int32))
|
|
} else {
|
|
data[i].isNull = 1
|
|
}
|
|
}
|
|
case sql.NullInt64:
|
|
i, x := 0, slice
|
|
if x.Valid {
|
|
data[i].isNull = 0
|
|
C.dpiData_setInt64(&data[i], C.int64_t(x.Int64))
|
|
} else {
|
|
data[i].isNull = 1
|
|
}
|
|
case []sql.NullInt64:
|
|
for i, x := range slice {
|
|
if x.Valid {
|
|
data[i].isNull = 0
|
|
C.dpiData_setInt64(&data[i], C.int64_t(x.Int64))
|
|
} else {
|
|
data[i].isNull = 1
|
|
}
|
|
}
|
|
case sql.NullFloat64:
|
|
i, x := 0, slice
|
|
if x.Valid {
|
|
data[i].isNull = 0
|
|
C.dpiData_setDouble(&data[i], C.double(x.Float64))
|
|
} else {
|
|
data[i].isNull = 1
|
|
}
|
|
case []sql.NullFloat64:
|
|
for i, x := range slice {
|
|
if x.Valid {
|
|
data[i].isNull = 0
|
|
C.dpiData_setDouble(&data[i], C.double(x.Float64))
|
|
} else {
|
|
data[i].isNull = 1
|
|
}
|
|
}
|
|
|
|
case uint:
|
|
i, x := 0, slice
|
|
C.dpiData_setUint64(&data[i], C.uint64_t(x))
|
|
case []uint:
|
|
for i, x := range slice {
|
|
C.dpiData_setUint64(&data[i], C.uint64_t(x))
|
|
}
|
|
case uint8:
|
|
i, x := 0, slice
|
|
C.dpiData_setUint64(&data[i], C.uint64_t(x))
|
|
case []uint8:
|
|
for i, x := range slice {
|
|
C.dpiData_setUint64(&data[i], C.uint64_t(x))
|
|
}
|
|
case uint16:
|
|
i, x := 0, slice
|
|
C.dpiData_setUint64(&data[i], C.uint64_t(x))
|
|
case []uint16:
|
|
for i, x := range slice {
|
|
C.dpiData_setUint64(&data[i], C.uint64_t(x))
|
|
}
|
|
case uint32:
|
|
i, x := 0, slice
|
|
C.dpiData_setUint64(&data[i], C.uint64_t(x))
|
|
case []uint32:
|
|
for i, x := range slice {
|
|
C.dpiData_setUint64(&data[i], C.uint64_t(x))
|
|
}
|
|
case uint64:
|
|
i, x := 0, slice
|
|
C.dpiData_setUint64(&data[i], C.uint64_t(x))
|
|
case []uint64:
|
|
for i, x := range slice {
|
|
C.dpiData_setUint64(&data[i], C.uint64_t(x))
|
|
}
|
|
|
|
case float32:
|
|
i, x := 0, slice
|
|
C.dpiData_setFloat(&data[i], C.float(x))
|
|
case []float32:
|
|
for i, x := range slice {
|
|
C.dpiData_setFloat(&data[i], C.float(x))
|
|
}
|
|
case float64:
|
|
i, x := 0, slice
|
|
C.dpiData_setDouble(&data[i], C.double(x))
|
|
case []float64:
|
|
for i, x := range slice {
|
|
C.dpiData_setDouble(&data[i], C.double(x))
|
|
}
|
|
|
|
case Number, []Number, decimalDecompose, []decimalDecompose, string, []string:
|
|
return dataSetBytes(dv, data, vv)
|
|
|
|
default:
|
|
return fmt.Errorf("unknown number slice [%T] %#v", vv, vv)
|
|
}
|
|
|
|
//fmt.Printf("setInt64(%#v, %#v)\n", data, C.int64_t(int64(v.(int))))
|
|
return nil
|
|
}
|
|
|
|
func dataGetBytes(v interface{}, data []C.dpiData) error {
|
|
switch x := v.(type) {
|
|
case *[]byte:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*x = nil
|
|
return nil
|
|
}
|
|
// must be copied
|
|
*x = append((*x)[:0], dpiData_getBytes(&data[0])...)
|
|
|
|
case *[][]byte:
|
|
maX := (*x)[:cap(*x)]
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, nil)
|
|
continue
|
|
}
|
|
b := dpiData_getBytes(&data[i])
|
|
// b must be copied
|
|
if i < len(maX) {
|
|
*x = append(*x, append(maX[i][:0], b...))
|
|
} else {
|
|
*x = append(*x, append(make([]byte, 0, len(b)), b...))
|
|
}
|
|
}
|
|
|
|
case *Number:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*x = ""
|
|
return nil
|
|
}
|
|
*x = Number(dpiData_getBytes(&data[0]))
|
|
case *[]Number:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, "")
|
|
continue
|
|
}
|
|
*x = append(*x, Number(dpiData_getBytes(&data[i])))
|
|
}
|
|
case decimalCompose:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
x = nil
|
|
return nil
|
|
}
|
|
return x.Compose(Number(dpiData_getBytes(&data[0])).Decompose(nil))
|
|
case *[]decimalCompose:
|
|
*x = (*x)[:0]
|
|
et := reflect.TypeOf(*x).Elem()
|
|
var a [22]byte
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, nil)
|
|
continue
|
|
}
|
|
z := reflect.Zero(et).Interface().(decimalCompose)
|
|
if err := z.Compose(Number(dpiData_getBytes(&data[i])).Decompose(a[:0])); err != nil {
|
|
return err
|
|
}
|
|
*x = append(*x, z)
|
|
}
|
|
|
|
case *string:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*x = ""
|
|
return nil
|
|
}
|
|
*x = string(dpiData_getBytes(&data[0]))
|
|
case *[]string:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, "")
|
|
continue
|
|
}
|
|
*x = append(*x, string(dpiData_getBytes(&data[i])))
|
|
}
|
|
|
|
case *sql.NullInt32:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
x.Int32, x.Valid = 0, false
|
|
return nil
|
|
}
|
|
v, err := strconv.ParseInt(string(dpiData_getBytes(&data[0])), 10, 32)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
x.Int32, x.Valid = int32(v), true
|
|
return err
|
|
case *[]sql.NullInt32:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, sql.NullInt32{})
|
|
continue
|
|
}
|
|
v, err := strconv.ParseInt(string(dpiData_getBytes(&data[i])), 10, 32)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*x = append(*x, sql.NullInt32{Valid: true, Int32: int32(v)})
|
|
}
|
|
case *sql.NullInt64:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
x.Int64, x.Valid = 0, false
|
|
return nil
|
|
}
|
|
v, err := strconv.ParseInt(string(dpiData_getBytes(&data[0])), 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
x.Int64, x.Valid = v, true
|
|
return nil
|
|
case *[]sql.NullInt64:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, sql.NullInt64{})
|
|
continue
|
|
}
|
|
v, err := strconv.ParseInt(string(dpiData_getBytes(&data[i])), 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*x = append(*x, sql.NullInt64{Valid: true, Int64: v})
|
|
}
|
|
|
|
case *interface{}:
|
|
switch y := (*x).(type) {
|
|
case []byte:
|
|
err := dataGetBytes(&y, data[:1])
|
|
*x = y
|
|
return err
|
|
case [][]byte:
|
|
err := dataGetBytes(&y, data)
|
|
*x = y
|
|
return err
|
|
|
|
case Number:
|
|
err := dataGetBytes(&y, data[:1])
|
|
*x = y
|
|
return err
|
|
case []Number:
|
|
err := dataGetBytes(&y, data)
|
|
*x = y
|
|
return err
|
|
|
|
case string:
|
|
err := dataGetBytes(&y, data[:1])
|
|
*x = y
|
|
return err
|
|
case []string:
|
|
err := dataGetBytes(&y, data)
|
|
*x = y
|
|
return err
|
|
|
|
case sql.NullInt32:
|
|
err := dataGetBytes(&y, data[:1])
|
|
*x = y
|
|
return err
|
|
case []sql.NullInt32:
|
|
err := dataGetBytes(&y, data)
|
|
*x = y
|
|
return err
|
|
case sql.NullInt64:
|
|
err := dataGetBytes(&y, data[:1])
|
|
*x = y
|
|
return err
|
|
case []sql.NullInt64:
|
|
err := dataGetBytes(&y, data)
|
|
*x = y
|
|
return err
|
|
|
|
default:
|
|
return fmt.Errorf("awaited []byte/string/Number, got %T (%#v)", x, x)
|
|
}
|
|
|
|
default:
|
|
return fmt.Errorf("awaited []byte/string/Number, got %T (%#v)", v, v)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func dataSetBytes(dv *C.dpiVar, data []C.dpiData, vv interface{}) error {
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
if vv == nil {
|
|
return dataSetNull(dv, data, nil)
|
|
}
|
|
var p *C.char
|
|
switch slice := vv.(type) {
|
|
case []byte:
|
|
i, x := 0, slice
|
|
if len(x) == 0 {
|
|
data[i].isNull = 1
|
|
return nil
|
|
}
|
|
data[i].isNull = 0
|
|
p = (*C.char)(unsafe.Pointer(&x[0]))
|
|
//if logger != nil {Log("C", "dpiVar_setFromBytes", "dv", dv, "pos", pos, "p", p, "len", len(x)) }
|
|
C.dpiVar_setFromBytes(dv, C.uint32_t(i), p, C.uint32_t(len(x)))
|
|
case [][]byte:
|
|
for i, x := range slice {
|
|
if len(x) == 0 {
|
|
data[i].isNull = 1
|
|
continue
|
|
}
|
|
data[i].isNull = 0
|
|
p = (*C.char)(unsafe.Pointer(&x[0]))
|
|
//if logger != nil {Log("C", "dpiVar_setFromBytes", "dv", dv, "pos", pos, "p", p, "len", len(x)) }
|
|
C.dpiVar_setFromBytes(dv, C.uint32_t(i), p, C.uint32_t(len(x)))
|
|
}
|
|
|
|
case Number:
|
|
i, x := 0, slice
|
|
if len(x) == 0 {
|
|
data[i].isNull = 1
|
|
return nil
|
|
}
|
|
data[i].isNull = 0
|
|
dpiSetFromString(dv, C.uint32_t(i), string(x))
|
|
case []Number:
|
|
for i, x := range slice {
|
|
if len(x) == 0 {
|
|
data[i].isNull = 1
|
|
continue
|
|
}
|
|
data[i].isNull = 0
|
|
dpiSetFromString(dv, C.uint32_t(i), string(x))
|
|
}
|
|
|
|
case decimalDecompose:
|
|
i, x := 0, slice
|
|
if x == nil {
|
|
data[i].isNull = 1
|
|
return nil
|
|
}
|
|
var n Number
|
|
if err := n.Compose(x.Decompose(nil)); err != nil {
|
|
return err
|
|
}
|
|
data[i].isNull = 0
|
|
dpiSetFromString(dv, C.uint32_t(i), string(n))
|
|
case []decimalDecompose:
|
|
var n Number
|
|
var a [22]byte
|
|
for i, x := range slice {
|
|
if x == nil {
|
|
data[i].isNull = 1
|
|
continue
|
|
}
|
|
if err := n.Compose(x.Decompose(a[:0])); err != nil {
|
|
return err
|
|
}
|
|
data[i].isNull = 0
|
|
dpiSetFromString(dv, C.uint32_t(i), string(n))
|
|
}
|
|
|
|
case string:
|
|
i, x := 0, slice
|
|
if len(x) == 0 {
|
|
data[i].isNull = 1
|
|
return nil
|
|
}
|
|
data[i].isNull = 0
|
|
dpiSetFromString(dv, C.uint32_t(i), x)
|
|
case []string:
|
|
for i, x := range slice {
|
|
if len(x) == 0 {
|
|
data[i].isNull = 1
|
|
continue
|
|
}
|
|
data[i].isNull = 0
|
|
dpiSetFromString(dv, C.uint32_t(i), x)
|
|
}
|
|
|
|
default:
|
|
return fmt.Errorf("awaited [][]byte/[]string/[]Number, got %T (%#v)", vv, vv)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (st *statement) dataGetBoolBytes(v interface{}, data []C.dpiData) error {
|
|
switch x := v.(type) {
|
|
case *bool:
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*x = false
|
|
return nil
|
|
}
|
|
*x = st.stmtOptions.boolString.FromString(string(dpiData_getBytes(&data[0])))
|
|
|
|
case *[]bool:
|
|
*x = (*x)[:0]
|
|
for i := range data {
|
|
if data[i].isNull == 1 {
|
|
*x = append(*x, false)
|
|
continue
|
|
}
|
|
*x = append(*x, st.stmtOptions.boolString.FromString(string(dpiData_getBytes(&data[i]))))
|
|
}
|
|
|
|
case *interface{}:
|
|
switch y := (*x).(type) {
|
|
case bool:
|
|
err := st.dataGetBoolBytes(&y, data[:1])
|
|
*x = y
|
|
return err
|
|
case []bool:
|
|
err := st.dataGetBoolBytes(&y, data)
|
|
*x = y
|
|
return err
|
|
|
|
default:
|
|
return fmt.Errorf("awaited bool, got %T (%#v)", x, x)
|
|
}
|
|
|
|
default:
|
|
return fmt.Errorf("awaited bool, got %T (%#v)", v, v)
|
|
}
|
|
return nil
|
|
}
|
|
func (st *statement) dataSetBoolBytes(dv *C.dpiVar, data []C.dpiData, vv interface{}) error {
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
if vv == nil {
|
|
return dataSetNull(dv, data, nil)
|
|
}
|
|
var p *C.char
|
|
switch slice := vv.(type) {
|
|
case bool:
|
|
i, x := 0, slice
|
|
data[i].isNull = 0
|
|
s := []byte(st.stmtOptions.boolString.ToString(x))
|
|
p = (*C.char)(unsafe.Pointer(&s[0]))
|
|
C.dpiVar_setFromBytes(dv, C.uint32_t(i), p, C.uint32_t(len(s)))
|
|
case []bool:
|
|
for i, x := range slice {
|
|
data[i].isNull = 0
|
|
s := []byte(st.stmtOptions.boolString.ToString(x))
|
|
p = (*C.char)(unsafe.Pointer(&s[0]))
|
|
C.dpiVar_setFromBytes(dv, C.uint32_t(i), p, C.uint32_t(len(s)))
|
|
}
|
|
|
|
default:
|
|
return fmt.Errorf("awaited bool/[]bool, got %T (%#v)", vv, vv)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (st *statement) dataGetStmt(v interface{}, data []C.dpiData) error {
|
|
if row, ok := v.(*driver.Rows); ok {
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*row = nil
|
|
return nil
|
|
}
|
|
return st.dataGetStmtC(row, &data[0])
|
|
}
|
|
rows := v.(*[]driver.Rows)
|
|
if cap(*rows) >= len(data) {
|
|
*rows = (*rows)[:len(data)]
|
|
} else {
|
|
*rows = make([]driver.Rows, len(data))
|
|
}
|
|
var firstErr error
|
|
for i := range data {
|
|
if err := st.dataGetStmtC(&((*rows)[i]), &data[i]); err != nil && firstErr == nil {
|
|
firstErr = err
|
|
}
|
|
}
|
|
return firstErr
|
|
}
|
|
|
|
func (st *statement) dataGetStmtC(row *driver.Rows, data *C.dpiData) error {
|
|
if data.isNull == 1 {
|
|
*row = nil
|
|
return nil
|
|
}
|
|
st2 := &statement{conn: st.conn, dpiStmt: C.dpiData_getStmt(data),
|
|
stmtOptions: st.stmtOptions, // inherit parent statement's options
|
|
}
|
|
|
|
logger := getLogger()
|
|
var n C.uint32_t
|
|
if err := st.checkExec(func() C.int { return C.dpiStmt_getNumQueryColumns(st2.dpiStmt, &n) }); err != nil {
|
|
err = fmt.Errorf("dataGetStmtC.getNumQueryColumns: %+v: %w", err, io.EOF)
|
|
*row = &rows{err: err}
|
|
if logger != nil {
|
|
logger.Log("msg", "dataGetStmtC", "st", fmt.Sprintf("%p", st2.dpiStmt), "error", err)
|
|
}
|
|
return nil
|
|
}
|
|
r2, err := st2.openRows(int(n))
|
|
if err != nil {
|
|
if logger != nil {
|
|
logger.Log("msg", "dataGetStmtC.openRows", "st", fmt.Sprintf("%p", st2.dpiStmt), "error", err)
|
|
}
|
|
st2.Close()
|
|
return err
|
|
}
|
|
stmtSetFinalizer(st2, "dataGetStmtC")
|
|
r2.fromData = true
|
|
*row = r2
|
|
return nil
|
|
}
|
|
|
|
func (c *conn) dataGetLOB(v interface{}, data []C.dpiData) error {
|
|
if L, ok := v.(*Lob); ok {
|
|
if len(data) == 0 || data[0].isNull == 1 {
|
|
*L = Lob{}
|
|
return nil
|
|
}
|
|
c.dataGetLOBC(L, &data[0])
|
|
return nil
|
|
}
|
|
slice := v.(*[]Lob)
|
|
n := len(data)
|
|
if cap(*slice) >= n {
|
|
*slice = (*slice)[:n]
|
|
} else {
|
|
*slice = make([]Lob, n)
|
|
}
|
|
for i := range data {
|
|
c.dataGetLOBC(&((*slice)[i]), &data[i])
|
|
}
|
|
return nil
|
|
}
|
|
func (c *conn) dataGetLOBC(L *Lob, data *C.dpiData) {
|
|
L.Reader = nil
|
|
if data.isNull == 1 {
|
|
return
|
|
}
|
|
lob := C.dpiData_getLOB(data)
|
|
if lob == nil {
|
|
return
|
|
}
|
|
L.Reader = &dpiLobReader{drv: c.drv, dpiLob: lob, IsClob: L.IsClob}
|
|
}
|
|
|
|
func (c *conn) dataSetLOB(dv *C.dpiVar, data []C.dpiData, vv interface{}) error {
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
if vv == nil {
|
|
return dataSetNull(dv, data, nil)
|
|
}
|
|
|
|
lobs := []Lob{{}}
|
|
if L, ok := vv.(Lob); ok {
|
|
lobs[0] = L
|
|
} else {
|
|
lobs = vv.([]Lob)
|
|
}
|
|
var firstErr error
|
|
for i, L := range lobs {
|
|
if err := c.dataSetLOBOne(dv, data, i, L); err != nil {
|
|
if firstErr == nil {
|
|
firstErr = fmt.Errorf("%d. %w", i, err)
|
|
}
|
|
}
|
|
}
|
|
return firstErr
|
|
}
|
|
func (c *conn) dataSetLOBOne(dv *C.dpiVar, data []C.dpiData, i int, L Lob) error {
|
|
if L.Reader == nil {
|
|
data[i].isNull = 1
|
|
return nil
|
|
}
|
|
data[i].isNull = 0
|
|
if r, ok := L.Reader.(*dpiLobReader); ok {
|
|
if err := c.checkExec(func() C.int { return C.dpiVar_setFromLob(dv, C.uint32_t(i), r.dpiLob) }); err != nil {
|
|
return fmt.Errorf("dpiVar_setFromLob(%p): %w", r.dpiLob, err)
|
|
}
|
|
}
|
|
logger := getLogger()
|
|
|
|
// For small reads it is faster to set it as byte slice
|
|
var a [1 << 20]byte
|
|
n, _ := io.ReadFull(L.Reader, a[:])
|
|
if logger != nil {
|
|
logger.Log("msg", "setLob", "n", n)
|
|
}
|
|
if n < cap(a) {
|
|
if err := c.checkExec(func() C.int {
|
|
return C.dpiVar_setFromBytes(dv, C.uint32_t(i), (*C.char)(unsafe.Pointer(&a[0])), C.uint32_t(n))
|
|
}); err != nil {
|
|
return fmt.Errorf("dpiVar_setFromBytes(%d): %w", n, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
L.Reader = io.MultiReader(bytes.NewReader(a[:n]), L.Reader)
|
|
typ := C.dpiOracleTypeNum(C.DPI_ORACLE_TYPE_BLOB)
|
|
if L.IsClob {
|
|
typ = C.DPI_ORACLE_TYPE_CLOB
|
|
}
|
|
|
|
var lob *C.dpiLob
|
|
if err := c.checkExec(func() C.int { return C.dpiConn_newTempLob(c.dpiConn, typ, &lob) }); err != nil {
|
|
return fmt.Errorf("newTempLob(typ=%d): %w", typ, err)
|
|
}
|
|
var chunkSize C.uint32_t
|
|
_ = C.dpiLob_getChunkSize(lob, &chunkSize)
|
|
if chunkSize == 0 {
|
|
chunkSize = 8192
|
|
}
|
|
for chunkSize < minChunkSize {
|
|
chunkSize <<= 1
|
|
}
|
|
lw := &dpiLobWriter{dpiLob: lob, drv: c.drv, isClob: L.IsClob}
|
|
defer lw.Close() // Do NOT close before dpiVar_setFromLob !
|
|
written, err := io.CopyBuffer(lw, L, make([]byte, int(chunkSize)))
|
|
if logger != nil {
|
|
logger.Log("msg", "setLOB", "written", n, "tempLob", fmt.Sprintf("%p", lob), "chunkSize", chunkSize, "error", err)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
{
|
|
var lobType C.dpiOracleTypeNum
|
|
var lobSize C.uint64_t
|
|
err := c.checkExec(func() C.int {
|
|
if rc := C.dpiLob_getType(lob, &lobType); rc != 0 {
|
|
return rc
|
|
}
|
|
return C.dpiLob_getSize(lob, &lobSize)
|
|
})
|
|
if logger != nil {
|
|
logger.Log("msg", "setLOB", "type", lobType, "size", lobSize, "error", err)
|
|
}
|
|
if int64(lobSize) != int64(written) {
|
|
return fmt.Errorf("lobSize=%d, wanted %d", lobSize, written)
|
|
}
|
|
}
|
|
if err = c.checkExec(func() C.int { return C.dpiVar_setFromLob(dv, C.uint32_t(i), lob) }); err != nil {
|
|
return fmt.Errorf("dpiVar_setFromLob(%d. %p): %w", i, lob, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type userType interface {
|
|
ObjectRef() *Object
|
|
}
|
|
|
|
// ObjectScanner assigns a value from a database object
|
|
type ObjectScanner interface {
|
|
sql.Scanner
|
|
userType
|
|
}
|
|
|
|
// ObjectWriter update database object before binding
|
|
type ObjectWriter interface {
|
|
WriteObject() error
|
|
userType
|
|
}
|
|
|
|
func (c *conn) dataSetObject(dv *C.dpiVar, data []C.dpiData, vv interface{}) error {
|
|
//fmt.Printf("\ndataSetObject(dv=%+v, data=%+v, vv=%+v)\n", dv, data, vv)
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
if vv == nil {
|
|
return dataSetNull(dv, data, nil)
|
|
}
|
|
objs := []Object{{}}
|
|
switch o := vv.(type) {
|
|
case Object:
|
|
objs[0] = o
|
|
case *Object:
|
|
objs[0] = *o
|
|
case []Object:
|
|
objs = o
|
|
case []*Object:
|
|
objs = make([]Object, len(o))
|
|
for i, x := range o {
|
|
objs[i] = *x
|
|
}
|
|
case ObjectWriter:
|
|
err := o.WriteObject()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
objs[0] = *o.ObjectRef()
|
|
case []ObjectWriter:
|
|
for _, ut := range o {
|
|
err := ut.WriteObject()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
objs = append(objs, *ut.ObjectRef())
|
|
}
|
|
case userType:
|
|
objs[0] = *o.ObjectRef()
|
|
case []userType:
|
|
for _, ut := range o {
|
|
objs = append(objs, *ut.ObjectRef())
|
|
}
|
|
}
|
|
for i, obj := range objs {
|
|
if obj.dpiObject == nil {
|
|
data[i].isNull = 1
|
|
continue
|
|
}
|
|
data[i].isNull = 0
|
|
if err := c.checkExec(func() C.int { return C.dpiVar_setFromObject(dv, C.uint32_t(i), obj.dpiObject) }); err != nil {
|
|
return fmt.Errorf("setFromObject: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// dataSetObjectStructObj creates an ot typed object from rv.
|
|
func (c *conn) dataSetObjectStructObj(ot *ObjectType, rv reflect.Value) (*Object, error) {
|
|
logger := getLogger()
|
|
if rv.Type().Kind() == reflect.Ptr {
|
|
rv = rv.Elem()
|
|
}
|
|
if rv.IsZero() {
|
|
return nil, nil
|
|
}
|
|
rvt := rv.Type()
|
|
switch rvt.Kind() {
|
|
case reflect.Slice:
|
|
coll, err := ot.NewCollection()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if logger != nil {
|
|
logger.Log("msg", "collection", "obj", coll)
|
|
}
|
|
for i, n := 0, rv.Len(); i < n; i++ {
|
|
if logger != nil {
|
|
logger.Log("msg", "dataSetObjectStructObj", "i", i, "collectionOf", ot.CollectionOf, "elt", rv.Index(i).Interface())
|
|
}
|
|
if !ot.CollectionOf.IsObject() {
|
|
coll.Append(rv.Index(i).Interface())
|
|
} else {
|
|
sub, err := c.dataSetObjectStructObj(ot.CollectionOf, rv.Index(i))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%d. dataSetObjectStruct: %w", i, err)
|
|
}
|
|
err = coll.Append(sub)
|
|
sub.Close() // ?
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
return coll.Object, nil
|
|
|
|
case reflect.Struct:
|
|
var obj *Object
|
|
if ot.CollectionOf == nil {
|
|
var err error
|
|
if obj, err = ot.NewObject(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
for i, n := 0, rvt.NumField(); i < n; i++ {
|
|
f := rvt.Field(i)
|
|
if !f.IsExported() || f.Name == "ObjectTypeName" {
|
|
continue
|
|
}
|
|
rf := rv.FieldByIndex(f.Index)
|
|
if obj == nil {
|
|
// we must find the slice in the struct
|
|
if f.Type.Kind() != reflect.Slice {
|
|
continue
|
|
}
|
|
return c.dataSetObjectStructObj(ot, rf)
|
|
}
|
|
nm, typ, _ := parseStructTag(f.Tag)
|
|
if nm == "-" {
|
|
continue
|
|
}
|
|
if nm == "" {
|
|
nm = strings.ToUpper(f.Name)
|
|
}
|
|
attr, ok := obj.Attributes[nm]
|
|
if !ok {
|
|
return nil, fmt.Errorf("%s[%q]: %w", obj, nm, ErrNoSuchKey)
|
|
}
|
|
var ad Data
|
|
if !attr.IsObject() {
|
|
ad.Set(rv.Interface())
|
|
} else {
|
|
ot := attr.ObjectType
|
|
if ot == nil && typ != "" {
|
|
var err error
|
|
if ot, err = c.GetObjectType(typ); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if logger != nil {
|
|
logger.Log("msg", "dataSetObjectStructObj", "name", nm, "tag", f.Tag, "ot", ot, "typ", typ)
|
|
}
|
|
sub, err := c.dataSetObjectStructObj(ot, rf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ad.SetObject(sub)
|
|
}
|
|
if err := obj.SetAttribute(nm, &ad); err != nil {
|
|
if logger != nil {
|
|
logger.Log("msg", "SetAttribute", "obj", ot.Name, "nm", nm,
|
|
"index", f.Index, "kind", f.Type.Kind(),
|
|
"value", rv.Interface(), "data", ad.Get(),
|
|
"dataNative", ad.NativeTypeNum, "dataObject", ad.ObjectType,
|
|
"attrNative", attr.NativeTypeNum, "dataObject", attr.ObjectType,
|
|
)
|
|
}
|
|
return nil, fmt.Errorf("SetAttribute(%q): %w", nm, err)
|
|
}
|
|
}
|
|
return obj, nil
|
|
default:
|
|
return nil, fmt.Errorf("%T: not a struct or a slice: %w", rv.Interface(), errUnknownType)
|
|
}
|
|
}
|
|
|
|
// dataSetObjectStruct reads from vv, writes it to an ot typed object, and puts it into data.
|
|
func (c *conn) dataSetObjectStruct(ot *ObjectType, dv *C.dpiVar, data *C.dpiData, vv interface{}) error {
|
|
if ot == nil {
|
|
panic("dataSetObjectStruct with nil ObjectType")
|
|
}
|
|
logger := getLogger()
|
|
rv := reflect.ValueOf(vv)
|
|
if rv.Type().Kind() == reflect.Ptr {
|
|
rv = rv.Elem()
|
|
}
|
|
if logger != nil {
|
|
logger.Log("msg", "dataSetObjectStruct", "v", fmt.Sprintf("%T", vv))
|
|
}
|
|
if vv == nil || rv.IsZero() {
|
|
return nil
|
|
}
|
|
obj, err := c.dataSetObjectStructObj(ot, rv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if obj.dpiObject == nil {
|
|
data.isNull = 1
|
|
return nil
|
|
}
|
|
data.isNull = 0
|
|
if err := c.checkExec(func() C.int { return C.dpiVar_setFromObject(dv, C.uint32_t(0), obj.dpiObject) }); err != nil {
|
|
if logger != nil {
|
|
logger.Log("msg", "setFromObject", "i", 0, "dv", dv, "obj", obj, "error", err)
|
|
}
|
|
return fmt.Errorf("setFromObject[%d]: %w", 0, err)
|
|
}
|
|
return obj.Close()
|
|
}
|
|
func (c *conn) dataGetObject(v interface{}, data []C.dpiData) error {
|
|
logger := getLogger()
|
|
switch out := v.(type) {
|
|
case *ObjectCollection:
|
|
d := Data{
|
|
ObjectType: out.Object.ObjectType,
|
|
dpiData: data[0],
|
|
}
|
|
if logger != nil {
|
|
logger.Log("msg", "dataGetObject", "typ", "ObjectCollection", "v", fmt.Sprintf("%T", v), "d", d)
|
|
}
|
|
obj := d.GetObject()
|
|
if obj == nil {
|
|
*out = ObjectCollection{}
|
|
} else {
|
|
// copy the underlying object
|
|
obj2 := *obj
|
|
*out = ObjectCollection{Object: &obj2}
|
|
}
|
|
case *Object:
|
|
d := Data{
|
|
ObjectType: out.ObjectType,
|
|
dpiData: data[0],
|
|
}
|
|
if logger != nil {
|
|
logger.Log("msg", "dataGetObject", "typ", "Object", "v", fmt.Sprintf("%T", v), "d", d)
|
|
}
|
|
obj := d.GetObject()
|
|
if obj == nil {
|
|
*out = Object{ObjectType: d.ObjectType}
|
|
} else {
|
|
*out = *obj
|
|
}
|
|
case ObjectScanner:
|
|
d := Data{
|
|
ObjectType: out.ObjectRef().ObjectType,
|
|
dpiData: data[0],
|
|
}
|
|
if logger != nil {
|
|
logger.Log("msg", "dataGetObjectScanner", "typ", "ObjectScanner", "v", fmt.Sprintf("%T", v), "d", d, "obj", d.GetObject())
|
|
}
|
|
obj := d.GetObject()
|
|
err := out.Scan(obj)
|
|
obj.Close()
|
|
return err
|
|
|
|
default:
|
|
return fmt.Errorf("dataGetObject not implemented for type %T (maybe you need to implement the Scan method)", v)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// dataGetObjectStructObj reads an object and writes it to rv.
|
|
func (c *conn) dataGetObjectStructObj(rv reflect.Value, obj *Object) error {
|
|
logger := getLogger()
|
|
rvt := rv.Type()
|
|
|
|
if obj == nil {
|
|
rv.Set(reflect.Zero(rvt))
|
|
return nil
|
|
}
|
|
if logger != nil {
|
|
logger.Log("msg", "dataGetObjectStructObj", "kind", rvt.Kind(), "collectionOf", obj.CollectionOf)
|
|
}
|
|
if obj.CollectionOf != nil && rvt.Kind() == reflect.Slice {
|
|
coll := obj.Collection()
|
|
if logger != nil {
|
|
length, _ := coll.Len()
|
|
logger.Log("msg", "dataGetObjectStructObj", "length", length, "cap", rv.Cap())
|
|
}
|
|
orig := rv
|
|
rv.SetLen(0)
|
|
first := true
|
|
re := reflect.New(rvt.Elem()).Elem()
|
|
ret := re.Type()
|
|
for i, err := coll.First(); err == nil; i, err = coll.Next(i) {
|
|
if err != nil {
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
return err
|
|
}
|
|
if first {
|
|
first = false
|
|
length, err := coll.Len()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if rv.Cap() < length {
|
|
rv = reflect.MakeSlice(rvt, 0, length)
|
|
}
|
|
}
|
|
var d Data
|
|
if err := coll.GetItem(&d, i); err != nil {
|
|
return err
|
|
}
|
|
elt := d.Get()
|
|
switch x := elt.(type) {
|
|
case *Object:
|
|
err := c.dataGetObjectStructObj(re, x)
|
|
x.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
ev := reflect.ValueOf(elt)
|
|
if ev.Type() != ret {
|
|
ev = ev.Convert(ret)
|
|
}
|
|
re.Set(ev)
|
|
}
|
|
rv = reflect.Append(rv, re)
|
|
}
|
|
orig.Set(rv)
|
|
if logger != nil {
|
|
length, _ := coll.Len()
|
|
logger.Log("msg", "dataGetObjectStructObj", "coll", length, "rv", orig.Len())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
Loop:
|
|
for i, n := 0, rvt.NumField(); i < n; i++ {
|
|
f := rvt.Field(i)
|
|
if !f.IsExported() || f.Name == "ObjectTypeName" {
|
|
continue
|
|
}
|
|
rf := rv.FieldByIndex(f.Index)
|
|
if obj.CollectionOf != nil {
|
|
// we must find the slice in the struct
|
|
if f.Type.Kind() != reflect.Slice {
|
|
continue
|
|
}
|
|
return c.dataGetObjectStructObj(rf, obj)
|
|
}
|
|
nm, typ, _ := parseStructTag(f.Tag)
|
|
if nm == "-" {
|
|
continue
|
|
}
|
|
fieldTag := typ
|
|
if fieldTag == "" {
|
|
fieldTag = nm
|
|
}
|
|
if logger != nil {
|
|
logger.Log("msg", "dataGetObjectStruct", "fieldTag", fieldTag, "nm", nm, "tag", f.Tag, "name", f.Name)
|
|
}
|
|
|
|
if nm == "" {
|
|
nm = strings.ToUpper(f.Name)
|
|
}
|
|
var ad Data
|
|
if err := obj.GetAttribute(&ad, nm); err != nil {
|
|
return fmt.Errorf("GetAttribute(%q): %w", nm, err)
|
|
}
|
|
v := ad.Get()
|
|
switch x := v.(type) {
|
|
case *Object:
|
|
err := c.dataGetObjectStructObj(rf, x)
|
|
x.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
x.Close()
|
|
continue Loop
|
|
case string:
|
|
if rf.Kind() == reflect.String {
|
|
rf.SetString(x)
|
|
} else {
|
|
rf.SetBytes([]byte(x))
|
|
}
|
|
case []byte:
|
|
if rf.Kind() == reflect.String {
|
|
rf.SetString(string(x))
|
|
} else {
|
|
rf.SetBytes(x)
|
|
}
|
|
default:
|
|
switch vv := reflect.ValueOf(v); vv.Kind() {
|
|
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
rf.SetUint(ad.GetUint64())
|
|
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
rf.SetInt(ad.GetInt64())
|
|
case reflect.Float32:
|
|
rf.SetFloat(float64(ad.GetFloat32()))
|
|
case reflect.Float64:
|
|
rf.SetFloat(ad.GetFloat64())
|
|
default:
|
|
// TODO: slice/Collection of sth
|
|
if kind := vv.Kind(); kind == reflect.Struct || (kind == reflect.Ptr && vv.Elem().Kind() == reflect.Struct) {
|
|
if err := c.dataGetObjectStruct(obj.ObjectType.Attributes[nm].ObjectType, v, []C.dpiData{ad.dpiData}); err != nil {
|
|
return err
|
|
}
|
|
} else if kind == reflect.Slice &&
|
|
(vv.Elem().Kind() == reflect.Struct || (vv.Elem().Kind() == reflect.Ptr && vv.Elem().Elem().Kind() == reflect.Struct)) {
|
|
ot, err := c.getStructObjectType(v, fieldTag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := c.dataGetObjectStruct(ot, v, []C.dpiData{ad.dpiData}); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if f.Type != vv.Type() {
|
|
vv = vv.Convert(f.Type)
|
|
}
|
|
rf.Set(vv)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// dataGetObjectStruct reads the object from data and writes it to v.
|
|
func (c *conn) dataGetObjectStruct(ot *ObjectType, v interface{}, data []C.dpiData) error {
|
|
logger := getLogger()
|
|
// Pointer to a struct with ObjectTypeName field and optional "godror" struct tags for struct field-object attribute mapping.
|
|
rv := reflect.ValueOf(v)
|
|
if rv.Type().Kind() == reflect.Ptr {
|
|
rv = rv.Elem()
|
|
}
|
|
if kind := rv.Type().Kind(); kind != reflect.Struct && kind != reflect.Slice {
|
|
return fmt.Errorf("dataGetObjectStruct: not a struct: %T: %w", v, errUnknownType)
|
|
}
|
|
d := Data{
|
|
ObjectType: ot,
|
|
dpiData: data[0],
|
|
}
|
|
if logger != nil {
|
|
logger.Log("msg", "dataGetObjectStruct", "v", fmt.Sprintf("%T", v), "d", d)
|
|
}
|
|
obj := d.GetObject()
|
|
err := c.dataGetObjectStructObj(rv, obj)
|
|
obj.Close()
|
|
return err
|
|
}
|
|
|
|
// ObjectTypeName is for allowing reflection-based Object - struct mapping.
|
|
//
|
|
// Include an ObjectTypeName in your struct, and set the "godror" struct tag to the type name.
|
|
type ObjectTypeName struct{}
|
|
|
|
func parseStructTag(s reflect.StructTag) (tag, typ string, opts map[string]string) {
|
|
tag = s.Get(StructTag)
|
|
if strings.IndexByte(tag, ',') < 0 {
|
|
return tag, typ, opts
|
|
}
|
|
vv := strings.Split(tag, ",")
|
|
tag, vv = vv[0], vv[1:]
|
|
for _, s := range vv {
|
|
if strings.HasPrefix(s, "type=") {
|
|
typ = strings.TrimPrefix(s, "type=")
|
|
continue
|
|
}
|
|
if i := strings.IndexByte(s, '='); i >= 0 {
|
|
if opts == nil {
|
|
opts = make(map[string]string, len(vv))
|
|
}
|
|
opts[s[:i]] = s[i+1:]
|
|
}
|
|
}
|
|
return tag, typ, opts
|
|
}
|
|
|
|
// StructTag is the prefix that tags godror-specific struct fields
|
|
const StructTag = "godror"
|
|
|
|
func (c *conn) getStructObjectType(v interface{}, fieldTag string) (*ObjectType, error) {
|
|
logger := getLogger()
|
|
rv := reflect.ValueOf(v)
|
|
if rv.Type().Kind() == reflect.Ptr {
|
|
rv = rv.Elem()
|
|
}
|
|
rvt := rv.Type()
|
|
switch rvt.Kind() {
|
|
case reflect.Slice:
|
|
if logger != nil {
|
|
logger.Log("msg", "getStructObjectType", "fieldTag", fieldTag)
|
|
}
|
|
return c.GetObjectType(fieldTag)
|
|
|
|
case reflect.Struct:
|
|
const otnName = "ObjectTypeName"
|
|
otnType := reflect.TypeOf(ObjectTypeName{})
|
|
otFt, ok := rvt.FieldByName(otnName)
|
|
if !ok {
|
|
for i, n := 0, rvt.NumField(); i < n; i++ {
|
|
f := rvt.Field(i)
|
|
if f.Name == otnName || f.Type == otnType {
|
|
otFt, ok = f, true
|
|
break
|
|
}
|
|
}
|
|
if !ok {
|
|
return nil, fmt.Errorf("no ObjectTypeName field found: %w", errUnknownType)
|
|
}
|
|
}
|
|
var otName string
|
|
if s := otFt.Tag; s.Get(StructTag) != "" {
|
|
otName, _, _ = parseStructTag(s)
|
|
} else {
|
|
for i, n := 0, rvt.NumField(); i < n; i++ {
|
|
f := rvt.Field(i)
|
|
if f.Name == otnName || f.Type == otnType {
|
|
continue
|
|
}
|
|
if f.Type.Kind() == reflect.Slice {
|
|
_, otName, _ = parseStructTag(f.Tag)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if otName == "" {
|
|
return nil, fmt.Errorf("%T: no ObjectTypeName specified: %w", v, errUnknownType)
|
|
}
|
|
if logger != nil {
|
|
logger.Log("msg", "parseStructTag", "name", otName)
|
|
}
|
|
return c.GetObjectType(otName)
|
|
|
|
default:
|
|
return nil, fmt.Errorf("getStructObjectType: %T: not a struct: %w", v, errUnknownType)
|
|
}
|
|
}
|
|
|
|
func (c *conn) dataGetJSON(v interface{}, data []C.dpiData) error {
|
|
|
|
switch out := v.(type) {
|
|
case *JSON:
|
|
*out = JSON{dpiJson: *((**C.dpiJson)(unsafe.Pointer(&(data[0].value))))}
|
|
default:
|
|
return fmt.Errorf("dataGetJSONNode not implemented for type %T", v)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *conn) dataSetJSON(dv *C.dpiVar, data []C.dpiData, vv interface{}) error {
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
i := 0
|
|
if vv == nil {
|
|
return dataSetNull(dv, data, nil)
|
|
}
|
|
switch x := vv.(type) {
|
|
case JSON:
|
|
*((**C.dpiJson)(unsafe.Pointer(&(data[0].value)))) = x.dpiJson
|
|
|
|
C.dpiVar_setFromJson(dv, C.uint32_t(i), *((**C.dpiJson)(unsafe.Pointer(&(data[0].value)))))
|
|
case []JSON:
|
|
for i := range x {
|
|
*((*C.dpiJson)(unsafe.Pointer(&(data[i].value)))) = *x[i].dpiJson
|
|
}
|
|
default:
|
|
return fmt.Errorf("dataSetJSONArray not implemented for type %T", x)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *conn) dataSetJSONValue(dv *C.dpiVar, data []C.dpiData, vv interface{}) error {
|
|
var err error = nil
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
if vv == nil {
|
|
return dataSetNull(dv, data, nil)
|
|
}
|
|
switch x := vv.(type) {
|
|
case JSONValue:
|
|
v := reflect.ValueOf(x.Value)
|
|
t := v.Type()
|
|
switch t.Kind() {
|
|
case reflect.Map, reflect.String, reflect.Slice, reflect.Bool,
|
|
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
|
reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16,
|
|
reflect.Uint32, reflect.Uint64, reflect.Float32,
|
|
reflect.Float64:
|
|
data[0].isNull = 0
|
|
var dpijsonnode *C.dpiJsonNode
|
|
err = allocdpiJSONNode(x.Value, &dpijsonnode)
|
|
if err != nil {
|
|
return fmt.Errorf("dataSetJSONValue %w", err)
|
|
}
|
|
defer freedpiJSONNode(dpijsonnode)
|
|
if err = c.checkExec(func() C.int { return C.dpiJson_setValue(C.dpiData_getJson(&(data[0])), dpijsonnode) }); err != nil {
|
|
return fmt.Errorf("dataSetJSONValue %w", err)
|
|
}
|
|
default:
|
|
return fmt.Errorf("dataSetJSONValue Unsupported JSON doc type %#v: ", t.Name())
|
|
}
|
|
case []JSONValue:
|
|
for i := range x {
|
|
data[i].isNull = 0
|
|
|
|
v := reflect.ValueOf(x[i].Value)
|
|
t := v.Type()
|
|
switch t.Kind() {
|
|
case reflect.Map, reflect.String, reflect.Slice, reflect.Bool,
|
|
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
|
reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16,
|
|
reflect.Uint32, reflect.Uint64, reflect.Float32,
|
|
reflect.Float64:
|
|
var dpijsonnode *C.dpiJsonNode
|
|
err = allocdpiJSONNode(x[i].Value, &dpijsonnode)
|
|
if err != nil {
|
|
return fmt.Errorf("dataSetJSONValue[%d] %w", i, err)
|
|
}
|
|
defer freedpiJSONNode(dpijsonnode)
|
|
if err = c.checkExec(func() C.int { return C.dpiJson_setValue(C.dpiData_getJson(&(data[i])), dpijsonnode) }); err != nil {
|
|
return fmt.Errorf("dataSetJSONValue[%d] %w", i, err)
|
|
}
|
|
default:
|
|
return fmt.Errorf("dataSetJSONValue Unsupported JSON doc[%d] type %#v: ", i, t.Name())
|
|
}
|
|
}
|
|
default:
|
|
return fmt.Errorf("dataSetJSONValue not implemented for type %T", x)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *conn) dataGetJSONValue(v interface{}, data []C.dpiData) error {
|
|
switch out := v.(type) {
|
|
case *JSON:
|
|
*out = JSON{dpiJson: (*(**C.dpiJson)(unsafe.Pointer(&(data[0].value))))}
|
|
default:
|
|
return fmt.Errorf("dataGetJSONValue not implemented for type %T", out)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *conn) dataSetJSONString(dv *C.dpiVar, data []C.dpiData, vv interface{}) error {
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
if vv == nil {
|
|
return dataSetNull(dv, data, nil)
|
|
}
|
|
switch js := vv.(type) {
|
|
case JSONString:
|
|
if len(js.Value) == 0 {
|
|
data[0].isNull = 1
|
|
return nil
|
|
}
|
|
cstr := C.CString(js.Value)
|
|
defer C.free(unsafe.Pointer(cstr))
|
|
data[0].isNull = 0
|
|
if err := c.checkExec(func() C.int {
|
|
return C.dpiJson_setFromText(C.dpiData_getJson(&(data[0])), cstr, C.uint64_t(len(js.Value)), C.uint32_t(js.Flags))
|
|
}); err != nil {
|
|
return fmt.Errorf("setFromJsonString(string=%#v): %w", vv, err)
|
|
}
|
|
case []JSONString:
|
|
for i := range js {
|
|
if len(js[i].Value) == 0 {
|
|
data[0].isNull = 1
|
|
continue
|
|
}
|
|
data[i].isNull = 0
|
|
cstr := C.CString(js[i].Value)
|
|
defer C.free(unsafe.Pointer(cstr))
|
|
if err := c.checkExec(func() C.int {
|
|
return C.dpiJson_setFromText(C.dpiData_getJson(&(data[i])), cstr, C.uint64_t(len(js[i].Value)), C.uint32_t(js[i].Flags))
|
|
}); err != nil {
|
|
return fmt.Errorf("setFromJsonString(string=%#v): %w", js[i].Value, err)
|
|
}
|
|
}
|
|
default:
|
|
return fmt.Errorf("setFromJsonString Unsupported JSON string [%T] %#v", vv, vv)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *conn) dataGetJSONString(v interface{}, data []C.dpiData) error {
|
|
|
|
switch out := v.(type) {
|
|
case *string:
|
|
js := JSON{dpiJson: (*(**C.dpiJson)(unsafe.Pointer(&(data[0].value))))}
|
|
*out = js.String()
|
|
default:
|
|
return fmt.Errorf("dataGetJSONString not implemented for type %T", out)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
// ErrNotImplemented is returned when the functionality is not implemented
|
|
ErrNotImplemented = errors.New("not implemented")
|
|
// ErrBadDate is returned when the date is not assignable to Oracle DATE type
|
|
ErrBadDate = errors.New("date out of range (year must be between -4713 and 9999, and must not be 0)")
|
|
)
|
|
|
|
// CheckNamedValue is called before passing arguments to the driver
|
|
// and is called in place of any ColumnConverter. CheckNamedValue must do type
|
|
// validation and conversion as appropriate for the driver.
|
|
//
|
|
// If CheckNamedValue returns ErrRemoveArgument, the NamedValue will not be included
|
|
// in the final query arguments.
|
|
// This may be used to pass special options to the query itself.
|
|
//
|
|
// If ErrSkip is returned the column converter error checking path is used
|
|
// for the argument.
|
|
// Drivers may wish to return ErrSkip after they have exhausted their own special cases.
|
|
func (st *statement) CheckNamedValue(nv *driver.NamedValue) error {
|
|
if nv == nil {
|
|
return nil
|
|
}
|
|
if apply, ok := nv.Value.(Option); ok {
|
|
if apply != nil {
|
|
apply(&st.stmtOptions)
|
|
}
|
|
return driver.ErrRemoveArgument
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ColumnConverter may be optionally implemented by Stmt
|
|
// if the statement is aware of its own columns' types and
|
|
// can convert from any type to a driver Value.
|
|
func (st *statement) ColumnConverter(idx int) driver.ValueConverter {
|
|
c := driver.ValueConverter(driver.DefaultParameterConverter)
|
|
switch col := st.columns[idx]; col.OracleType {
|
|
case C.DPI_ORACLE_TYPE_NUMBER:
|
|
switch col.NativeType {
|
|
case C.DPI_NATIVE_TYPE_INT64, C.DPI_NATIVE_TYPE_UINT64:
|
|
c = Int64
|
|
//case C.DPI_NATIVE_TYPE_FLOAT, C.DPI_NATIVE_TYPE_DOUBLE:
|
|
// c = Float64
|
|
default:
|
|
c = Num
|
|
}
|
|
}
|
|
logger := getLogger()
|
|
if logger != nil {
|
|
logger.Log("msg", "ColumnConverter", "c", c)
|
|
}
|
|
return driver.Null{Converter: c}
|
|
}
|
|
|
|
func (st *statement) openRows(colCount int) (*rows, error) {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
|
|
sliceLen := st.FetchArraySize()
|
|
|
|
r := rows{
|
|
statement: st,
|
|
columns: make([]Column, colCount),
|
|
vars: make([]*C.dpiVar, colCount),
|
|
data: make([][]C.dpiData, colCount),
|
|
}
|
|
|
|
var info C.dpiQueryInfo
|
|
var ti C.dpiDataTypeInfo
|
|
logger := getLogger()
|
|
for i := 0; i < colCount; i++ {
|
|
if err := st.checkExecNoLOT(func() C.int {
|
|
return C.dpiStmt_getQueryInfo(st.dpiStmt, C.uint32_t(i+1), &info)
|
|
}); err != nil {
|
|
return nil, fmt.Errorf("getQueryInfo[%d]: %w", i, err)
|
|
}
|
|
ti = info.typeInfo
|
|
bufSize := int(ti.clientSizeInBytes)
|
|
if logger != nil {
|
|
logger.Log("msg", "openRows", "col", i, "info", ti)
|
|
}
|
|
//if logger != nil {Log("dNTN", int(ti.defaultNativeTypeNum), "number", C.DPI_ORACLE_TYPE_NUMBER) }
|
|
effTypeNum := ti.oracleTypeNum
|
|
switch effTypeNum {
|
|
case C.DPI_ORACLE_TYPE_NUMBER:
|
|
switch ti.defaultNativeTypeNum {
|
|
case C.DPI_NATIVE_TYPE_FLOAT, C.DPI_NATIVE_TYPE_DOUBLE:
|
|
ti.defaultNativeTypeNum = C.DPI_NATIVE_TYPE_BYTES
|
|
bufSize = 40
|
|
}
|
|
case C.DPI_ORACLE_TYPE_DATE,
|
|
C.DPI_ORACLE_TYPE_TIMESTAMP, C.DPI_ORACLE_TYPE_TIMESTAMP_TZ, C.DPI_ORACLE_TYPE_TIMESTAMP_LTZ:
|
|
ti.defaultNativeTypeNum = C.DPI_NATIVE_TYPE_TIMESTAMP
|
|
|
|
case C.DPI_ORACLE_TYPE_BLOB:
|
|
if !st.LobAsReader() {
|
|
effTypeNum = C.DPI_ORACLE_TYPE_LONG_RAW
|
|
ti.defaultNativeTypeNum = C.DPI_NATIVE_TYPE_BYTES
|
|
}
|
|
case C.DPI_ORACLE_TYPE_CLOB:
|
|
if !st.LobAsReader() {
|
|
effTypeNum = C.DPI_ORACLE_TYPE_LONG_VARCHAR
|
|
ti.defaultNativeTypeNum = C.DPI_NATIVE_TYPE_BYTES
|
|
}
|
|
}
|
|
r.columns[i] = Column{
|
|
Name: C.GoStringN(info.name, C.int(info.nameLength)),
|
|
OracleType: effTypeNum,
|
|
OrigOracleType: ti.oracleTypeNum,
|
|
NativeType: ti.defaultNativeTypeNum,
|
|
Size: ti.clientSizeInBytes,
|
|
Precision: ti.precision,
|
|
Scale: ti.scale,
|
|
Nullable: info.nullOk == 1,
|
|
ObjectType: ti.objectType,
|
|
SizeInChars: ti.sizeInChars,
|
|
DBSize: ti.dbSizeInBytes,
|
|
}
|
|
var err error
|
|
//fmt.Printf("%d. %+v\n", i, r.columns[i])
|
|
vi := varInfo{
|
|
Typ: effTypeNum,
|
|
NatTyp: ti.defaultNativeTypeNum,
|
|
ObjectType: ti.objectType,
|
|
BufSize: bufSize,
|
|
SliceLen: sliceLen,
|
|
}
|
|
if r.vars[i], r.data[i], err = st.newVar(vi); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = st.checkExecNoLOT(func() C.int {
|
|
return C.dpiStmt_define(st.dpiStmt, C.uint32_t(i+1), r.vars[i])
|
|
}); err != nil {
|
|
return nil, fmt.Errorf("define[%d]: %w", i, err)
|
|
}
|
|
}
|
|
if err := st.checkExecNoLOT(func() C.int {
|
|
return C.dpiStmt_addRef(st.dpiStmt)
|
|
}); err != nil {
|
|
return &r, fmt.Errorf("dpiStmt_addRef: %w", err)
|
|
}
|
|
st.columns = r.columns
|
|
return &r, nil
|
|
}
|
|
|
|
// Column holds the info from a column.
|
|
type Column struct {
|
|
ObjectType *C.dpiObjectType
|
|
Name string
|
|
OracleType, OrigOracleType C.dpiOracleTypeNum
|
|
NativeType C.dpiNativeTypeNum
|
|
Size, SizeInChars, DBSize C.uint32_t
|
|
Precision C.int16_t
|
|
Scale C.int8_t
|
|
Nullable bool
|
|
}
|
|
|
|
func dpiSetFromString(dv *C.dpiVar, pos C.uint32_t, x string) {
|
|
C.godror_setFromString(dv, pos, x)
|
|
}
|
|
|
|
var stringBuilders = stringBuilderPool{
|
|
p: &sync.Pool{New: func() interface{} { return &strings.Builder{} }},
|
|
}
|
|
|
|
type stringBuilderPool struct {
|
|
p *sync.Pool
|
|
}
|
|
|
|
func (sb stringBuilderPool) Get() *strings.Builder {
|
|
return sb.p.Get().(*strings.Builder)
|
|
}
|
|
func (sb *stringBuilderPool) Put(b *strings.Builder) {
|
|
b.Reset()
|
|
sb.p.Put(b)
|
|
}
|
|
|
|
func isInvalidErr(err error) bool {
|
|
var cdr interface{ Code() int }
|
|
if err == nil || !errors.As(err, &cdr) {
|
|
return false
|
|
}
|
|
code := cdr.Code()
|
|
// ORA-04068: "existing state of packages has been discarded"
|
|
return code == 4061 || code == 4065 || code == 4068
|
|
}
|
|
|
|
/*
|
|
// ResetSession is called while a connection is in the connection
|
|
// pool. No queries will run on this connection until this method returns.
|
|
//
|
|
// If the connection is bad this should return driver.ErrBadConn to prevent
|
|
// the connection from being returned to the connection pool. Any other
|
|
// error will be discarded.
|
|
func (c *conn) ResetSession(ctx context.Context) error {
|
|
if logger := ctxGetLog(ctx); logger != nil {
|
|
logger.Log("msg", "ResetSession", "conn", c.dpiConn)
|
|
}
|
|
//subCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
|
//err := c.Ping(subCtx)
|
|
//cancel()
|
|
return c.Ping(ctx)
|
|
}
|
|
*/
|
|
|
|
var maxStackSize uint32 = 2048
|
|
|
|
func stmtSetFinalizer(st *statement, tag string) {
|
|
// Store the current stack for printing later.
|
|
n := atomic.LoadUint32(&maxStackSize)
|
|
var stack []byte
|
|
for {
|
|
stack = make([]byte, n)
|
|
stack = stack[:runtime.Stack(stack, false)]
|
|
if len(stack) < cap(stack) {
|
|
break
|
|
}
|
|
n *= 2
|
|
}
|
|
if atomic.LoadUint32(&maxStackSize) < n {
|
|
atomic.StoreUint32(&maxStackSize, n)
|
|
}
|
|
|
|
runtime.SetFinalizer(st, func(st *statement) {
|
|
if st != nil && st.dpiStmt != nil {
|
|
if logger := getLogger(); logger != nil {
|
|
logger.Log("msg", "ERROR: statement is not closed!", "stmt", st, "tag", tag, "stack", string(stack))
|
|
} else {
|
|
fmt.Printf("ERROR: statement %p of %s is not closed!\n%s\n", st, tag, stack)
|
|
}
|
|
st.closeNotLocking()
|
|
}
|
|
})
|
|
}
|
|
|
|
func dpiData_getBytes(data *C.dpiData) []byte {
|
|
db := ((*C.dpiBytes)(unsafe.Pointer(&data.value)))
|
|
return ((*[32767]byte)(unsafe.Pointer(db.ptr)))[:db.length:db.length]
|
|
}
|