Skip to content

Commit

Permalink
Merge pull request #364 from MichaelS11/lob
Browse files Browse the repository at this point in the history
Fix LOB > 32767 for insert/update
  • Loading branch information
mattn authored Nov 8, 2019
2 parents 968efbc + 1b64c95 commit cbd8d5b
Show file tree
Hide file tree
Showing 6 changed files with 373 additions and 99 deletions.
97 changes: 90 additions & 7 deletions connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,17 +164,17 @@ func (conn *OCI8Conn) getError(result C.sword) error {
case C.OCI_SUCCESS:
return nil
case C.OCI_INVALID_HANDLE:
return errors.New("OCI_INVALID_HANDLE")
return ErrOCIInvalidHandle
case C.OCI_SUCCESS_WITH_INFO:
return ErrOCISuccessWithInfo
case C.OCI_RESERVED_FOR_INT_USE:
return errors.New("OCI_RESERVED_FOR_INT_USE")
return ErrOCIReservedForIntUse
case C.OCI_NO_DATA:
return errors.New("OCI_NO_DATA")
return ErrOCINoData
case C.OCI_NEED_DATA:
return errors.New("OCI_NEED_DATA")
return ErrOCINeedData
case C.OCI_STILL_EXECUTING:
return errors.New("OCI_STILL_EXECUTING")
return ErrOCIStillExecuting
case C.OCI_ERROR:
errorCode, err := conn.ociGetError()
switch errorCode {
Expand Down Expand Up @@ -322,12 +322,41 @@ func (conn *OCI8Conn) ociDescriptorAlloc(descriptorType C.ub4, size C.size_t) (*
return descriptor, nil, nil
}

// ociLobCreateTemporary calls OCILobCreateTemporary then returns error
func (conn *OCI8Conn) ociLobCreateTemporary(lobLocator *C.OCILobLocator, form C.ub1, lobType C.ub1) error {

result := C.OCILobCreateTemporary(
conn.svc, // service context handle
conn.errHandle, // error handle
lobLocator, // locator that points to the temporary LOB
C.OCI_DEFAULT, // LOB character set ID. For Oracle8i or later, pass as OCI_DEFAULT.
form, // character set form
lobType, // type of LOB to create: OCI_TEMP_BLOB or OCI_TEMP_CLOB
C.TRUE, // Pass TRUE if the temporary LOB should be read into the cache; pass FALSE if it should not. FALSE for NOCACHE functionality
C.OCI_DURATION_SESSION, // duration of the temporary LOB: OCI_DURATION_SESSION or OCI_DURATION_CALL
)

return conn.getError(result)
}

// ociLobRead calls OCILobRead then returns lob bytes and error.
func (conn *OCI8Conn) ociLobRead(lobLocator *C.OCILobLocator, form C.ub1) ([]byte, error) {
readBuffer := make([]byte, lobBufferSize)
buffer := make([]byte, 0)
result := (C.sword)(C.OCI_NEED_DATA)

// set character set form
result := C.OCILobCharSetForm(
conn.env, // environment handle
conn.errHandle, // error handle
lobLocator, // LOB locator
&form, // character set form
)
if result != C.OCI_SUCCESS {
return buffer, conn.getError(result)
}

readBuffer := byteBufferPool.Get().([]byte)
piece := (C.ub1)(C.OCI_FIRST_PIECE)
result = C.OCI_NEED_DATA

for result == C.OCI_NEED_DATA {
readBytes := (C.oraub8)(0)
Expand Down Expand Up @@ -361,6 +390,60 @@ func (conn *OCI8Conn) ociLobRead(lobLocator *C.OCILobLocator, form C.ub1) ([]byt
return buffer, conn.getError(result)
}

// ociLobWrite calls OCILobWrite then returns error.
func (conn *OCI8Conn) ociLobWrite(lobLocator *C.OCILobLocator, form C.ub1, data []byte) error {
start := 0
writeBuffer := byteBufferPool.Get().([]byte)
piece := (C.ub1)(C.OCI_FIRST_PIECE)
writeBytes := (C.oraub8)(len(data))
if len(data) <= lobBufferSize {
piece = (C.ub1)(C.OCI_ONE_PIECE)
copy(writeBuffer, data)
} else {
copy(writeBuffer, data[0:lobBufferSize])
}

for {
result := C.OCILobWrite2(
conn.svc, // service context handle
conn.errHandle, // error handle
lobLocator, // LOB or BFILE locator
&writeBytes, // IN - The number of bytes to write to the database. OUT - The number of bytes written to the database.
nil, // maximum number of characters to write
(C.oraub8)(1), // the offset in the first call and in subsequent polling calls the offset parameter is ignored
unsafe.Pointer(&writeBuffer[0]), // pointer to a buffer from which the piece is written
(C.oraub8)(lobBufferSize), // length, in bytes, of the data in the buffer
piece, // which piece of the buffer is being written. OCI_ONE_PIECE, indicating that the buffer is written in a single piece. Piecewise or callback mode: OCI_FIRST_PIECE, OCI_NEXT_PIECE, and OCI_LAST_PIECE.
nil, // callback function
nil, // callback that can be registered
0, // character set ID
form, // character set form
)

if result != C.OCI_SUCCESS && result != C.OCI_NEED_DATA {
err := conn.getError(result)
fmt.Println(err)
return err
}

start += lobBufferSize

if start >= len(data) {
break
}

if start+lobBufferSize < len(data) {
piece = C.OCI_NEXT_PIECE
copy(writeBuffer, data[start:start+lobBufferSize])
} else {
piece = C.OCI_LAST_PIECE
copy(writeBuffer, data[start:])
}
}

return nil
}

// ociDateTimeToTime coverts OCIDateTime to Go Time
// if useOCITimeZone is true, will use OCIDateTime time zone, otherwise will use conn.location
func (conn *OCI8Conn) ociDateTimeToTime(dateTime *C.OCIDateTime, useOCITimeZone bool) (*time.Time, error) {
Expand Down
18 changes: 18 additions & 0 deletions globals.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"log"
"regexp"
"strconv"
"sync"
"time"
"unsafe"
)
Expand Down Expand Up @@ -123,8 +124,19 @@ type (
)

var (
// ErrOCIInvalidHandle is OCI_INVALID_HANDLE
ErrOCIInvalidHandle = errors.New("OCI_INVALID_HANDLE")
// ErrOCISuccessWithInfo is OCI_SUCCESS_WITH_INFO
ErrOCISuccessWithInfo = errors.New("OCI_SUCCESS_WITH_INFO")
// ErrOCIReservedForIntUse is OCI_RESERVED_FOR_INT_USE
ErrOCIReservedForIntUse = errors.New("OCI_RESERVED_FOR_INT_USE")
// ErrOCINoData is OCI_NO_DATA
ErrOCINoData = errors.New("OCI_NO_DATA")
// ErrOCINeedData is OCI_NEED_DATA
ErrOCINeedData = errors.New("OCI_NEED_DATA")
// ErrOCIStillExecuting is OCI_STILL_EXECUTING
ErrOCIStillExecuting = errors.New("OCI_STILL_EXECUTING")

// ErrNoRowid is result has no rowid
ErrNoRowid = errors.New("result has no rowid")

Expand All @@ -137,6 +149,12 @@ var (
}

timeLocations []*time.Location

byteBufferPool = sync.Pool{
New: func() interface{} {
return make([]byte, lobBufferSize)
},
}
)

func init() {
Expand Down
Loading

0 comments on commit cbd8d5b

Please sign in to comment.