|
1
|
|
|
// Copyright 2017, 2022 The Godror Authors |
|
2
|
|
|
// |
|
3
|
|
|
// |
|
4
|
|
|
// SPDX-License-Identifier: UPL-1.0 OR Apache-2.0 |
|
5
|
|
|
|
|
6
|
|
|
package godror |
|
7
|
|
|
|
|
8
|
|
|
/* |
|
9
|
|
|
#include "dpiImpl.h" |
|
10
|
|
|
*/ |
|
11
|
|
|
import "C" |
|
12
|
|
|
import ( |
|
13
|
|
|
"bufio" |
|
14
|
|
|
"errors" |
|
15
|
|
|
"fmt" |
|
16
|
|
|
"io" |
|
17
|
|
|
"runtime" |
|
18
|
|
|
"strings" |
|
19
|
|
|
"sync" |
|
20
|
|
|
"unicode/utf8" |
|
21
|
|
|
"unsafe" |
|
22
|
|
|
) |
|
23
|
|
|
|
|
24
|
|
|
// Lob is for reading/writing a LOB. |
|
25
|
|
|
type Lob struct { |
|
26
|
|
|
io.Reader |
|
27
|
|
|
IsClob bool |
|
28
|
|
|
} |
|
29
|
|
|
|
|
30
|
|
|
var _ = (io.Reader)((*Lob)(nil)) |
|
31
|
|
|
var _ = (io.ReaderAt)((*Lob)(nil)) |
|
32
|
|
|
|
|
33
|
|
|
// Hijack the underlying lob reader/writer, and |
|
34
|
|
|
// return a DirectLob for reading/writing the lob directly. |
|
35
|
|
|
// |
|
36
|
|
|
// After this, the Lob is unusable! |
|
37
|
|
|
func (lob *Lob) Hijack() (*DirectLob, error) { |
|
38
|
|
|
if lob == nil || lob.Reader == nil { |
|
39
|
|
|
return nil, errors.New("lob is nil") |
|
40
|
|
|
} |
|
41
|
|
|
lr, ok := lob.Reader.(*dpiLobReader) |
|
42
|
|
|
if !ok { |
|
43
|
|
|
return nil, fmt.Errorf("Lob.Reader is %T, not *dpiLobReader", lob.Reader) |
|
44
|
|
|
} |
|
45
|
|
|
lob.Reader = nil |
|
46
|
|
|
return &DirectLob{drv: lr.drv, dpiLob: lr.dpiLob}, nil |
|
47
|
|
|
} |
|
48
|
|
|
|
|
49
|
|
|
// WriteTo writes data to w until there's no more data to write or when an error occurs. |
|
50
|
|
|
// The return value n is the number of bytes written. Any error encountered during the write is also returned. |
|
51
|
|
|
// |
|
52
|
|
|
// Will use Lob.Reader.WriteTo, if Lob.Reader implements io.WriterTo. |
|
53
|
|
|
func (lob *Lob) WriteTo(w io.Writer) (n int64, err error) { |
|
54
|
|
|
if wt, ok := lob.Reader.(io.WriterTo); ok { |
|
55
|
|
|
return wt.WriteTo(w) |
|
56
|
|
|
} |
|
57
|
|
|
return io.CopyBuffer(w, lob.Reader, make([]byte, 1<<20)) |
|
58
|
|
|
} |
|
59
|
|
|
|
|
60
|
|
|
// NewBufferedReader returns a new bufio.Reader with the given size (or 1M if 0). |
|
61
|
|
|
func (lob *Lob) NewBufferedReader(size int) *bufio.Reader { |
|
62
|
|
|
if size <= 0 { |
|
63
|
|
|
size = 1 << 20 |
|
64
|
|
|
} |
|
65
|
|
|
return bufio.NewReaderSize(lob.Reader, size) |
|
66
|
|
|
} |
|
67
|
|
|
|
|
68
|
|
|
// Size exposes the underlying Reader's Size method, if it is supported. |
|
69
|
|
|
func (lob *Lob) Size() (int64, error) { |
|
70
|
|
|
if lr, ok := lob.Reader.(interface{ Size() (int64, error) }); ok { |
|
71
|
|
|
return lr.Size() |
|
72
|
|
|
} |
|
73
|
|
|
return 0, ErrNotSupported |
|
74
|
|
|
} |
|
75
|
|
|
|
|
76
|
|
|
// ReadAt exposes the underlying Reader's ReadAt method, if it is supported. |
|
77
|
|
|
func (lob *Lob) ReadAt(p []byte, off int64) (int, error) { |
|
78
|
|
|
if lr, ok := lob.Reader.(io.ReaderAt); ok { |
|
79
|
|
|
return lr.ReadAt(p, off) |
|
80
|
|
|
} |
|
81
|
|
|
return 0, ErrNotSupported |
|
82
|
|
|
} |
|
83
|
|
|
|
|
84
|
|
|
// Scan assigns a value from a database driver. |
|
85
|
|
|
// |
|
86
|
|
|
// The src value will be of one of the following types: |
|
87
|
|
|
// |
|
88
|
|
|
// int64 |
|
89
|
|
|
// float64 |
|
90
|
|
|
// bool |
|
91
|
|
|
// []byte |
|
92
|
|
|
// string |
|
93
|
|
|
// time.Time |
|
94
|
|
|
// nil - for NULL values |
|
95
|
|
|
// |
|
96
|
|
|
// An error should be returned if the value cannot be stored |
|
97
|
|
|
// without loss of information. |
|
98
|
|
|
func (dlr *dpiLobReader) Scan(src interface{}) error { |
|
99
|
|
|
b, ok := src.([]byte) |
|
100
|
|
|
if !ok { |
|
101
|
|
|
return fmt.Errorf("cannot convert LOB to %T", src) |
|
102
|
|
|
} |
|
103
|
|
|
_ = b |
|
104
|
|
|
return nil |
|
105
|
|
|
} |
|
106
|
|
|
|
|
107
|
|
|
var _ = io.ReadCloser((*dpiLobReader)(nil)) |
|
108
|
|
|
var _ = io.ReaderAt((*dpiLobReader)(nil)) |
|
109
|
|
|
|
|
110
|
|
|
type dpiLobReader struct { |
|
111
|
|
|
*drv |
|
112
|
|
|
dpiLob *C.dpiLob |
|
113
|
|
|
buf []byte |
|
114
|
|
|
offset, sizePlusOne C.uint64_t |
|
115
|
|
|
mu sync.Mutex |
|
116
|
|
|
chunkSize C.uint32_t |
|
117
|
|
|
bufR, bufW int |
|
118
|
|
|
finished bool |
|
119
|
|
|
IsClob bool |
|
120
|
|
|
} |
|
121
|
|
|
|
|
122
|
|
|
// WriteTo writes data to w until there's no more data to write or when an error occurs. |
|
123
|
|
|
// The return value n is the number of bytes written. Any error encountered during the write is also returned. |
|
124
|
|
|
// |
|
125
|
|
|
// Uses efficient, multiple-of-LOB-chunk-size buffered reads. |
|
126
|
|
|
func (dlr *dpiLobReader) WriteTo(w io.Writer) (n int64, err error) { |
|
127
|
|
|
size := dlr.ChunkSize() |
|
128
|
|
|
const minBufferSize = 1 << 20 |
|
129
|
|
|
if size <= 0 { |
|
130
|
|
|
size = minBufferSize |
|
131
|
|
|
} else { |
|
132
|
|
|
for size < minBufferSize/2 { // at most 1M |
|
133
|
|
|
size *= 2 |
|
134
|
|
|
} |
|
135
|
|
|
} |
|
136
|
|
|
return io.CopyBuffer( |
|
137
|
|
|
w, |
|
138
|
|
|
// Mask WriteTo method |
|
139
|
|
|
io.Reader(struct { |
|
140
|
|
|
io.Reader |
|
141
|
|
|
}{dlr}), |
|
142
|
|
|
make([]byte, size)) |
|
143
|
|
|
} |
|
144
|
|
|
|
|
145
|
|
|
// ChunkSize returns the LOB's native chunk size. Reads/writes with a multiply of this size is the most performant. |
|
146
|
|
|
func (dlr *dpiLobReader) ChunkSize() int { |
|
147
|
|
|
dlr.mu.Lock() |
|
148
|
|
|
defer dlr.mu.Unlock() |
|
149
|
|
|
if dlr.chunkSize != 0 { |
|
150
|
|
|
return int(dlr.chunkSize) |
|
151
|
|
|
} |
|
152
|
|
|
if err := dlr.checkExec(func() C.int { |
|
153
|
|
|
return C.dpiLob_getChunkSize(dlr.dpiLob, &dlr.chunkSize) |
|
154
|
|
|
}); err != nil { |
|
155
|
|
|
dlr.chunkSize = 0 |
|
156
|
|
|
return -1 |
|
157
|
|
|
} |
|
158
|
|
|
return int(dlr.chunkSize) |
|
159
|
|
|
} |
|
160
|
|
|
|
|
161
|
|
|
// Read from LOB. It does buffer the reading internally against short buffers (io.ReadAll). |
|
162
|
|
|
func (dlr *dpiLobReader) Read(p []byte) (int, error) { |
|
163
|
|
|
dlr.mu.Lock() |
|
164
|
|
|
defer dlr.mu.Unlock() |
|
165
|
|
|
logger := getLogger() |
|
166
|
|
|
if logger != nil { |
|
167
|
|
|
logger.Log("msg", "Read", "bufR", dlr.bufR, "bufW", dlr.bufW, "buf", cap(dlr.buf)) |
|
168
|
|
|
} |
|
169
|
|
|
if dlr.buf == nil { |
|
170
|
|
|
if dlr.chunkSize == 0 { |
|
171
|
|
|
if err := dlr.checkExec(func() C.int { |
|
172
|
|
|
return C.dpiLob_getChunkSize(dlr.dpiLob, &dlr.chunkSize) |
|
173
|
|
|
}); err != nil { |
|
174
|
|
|
return 0, fmt.Errorf("getChunkSize: %w", err) |
|
175
|
|
|
} |
|
176
|
|
|
} |
|
177
|
|
|
// If the dest buffer is big enough, avoid copying. |
|
178
|
|
|
if ulen := C.uint64_t(len(p)); ulen >= C.uint64_t(dlr.chunkSize) || dlr.sizePlusOne != 0 && ulen+1 >= dlr.sizePlusOne { |
|
179
|
|
|
if logger != nil { |
|
180
|
|
|
logger.Log("msg", "direct read", "p", len(p), "chunkSize", dlr.chunkSize) |
|
181
|
|
|
} |
|
182
|
|
|
return dlr.read(p) |
|
183
|
|
|
} |
|
184
|
|
|
cs := int(dlr.chunkSize) |
|
185
|
|
|
dlr.buf = make([]byte, (maxI(len(p), 1<<20-cs)/cs+1)*cs) |
|
186
|
|
|
dlr.bufR, dlr.bufW = 0, 0 |
|
187
|
|
|
} else if dlr.bufW != 0 && cap(dlr.buf) != 0 { |
|
188
|
|
|
var n int |
|
189
|
|
|
if dlr.bufR < dlr.bufW { |
|
190
|
|
|
n = copy(p, dlr.buf[dlr.bufR:dlr.bufW]) |
|
191
|
|
|
dlr.bufR += n |
|
192
|
|
|
} |
|
193
|
|
|
if dlr.bufR == dlr.bufW { |
|
194
|
|
|
dlr.bufR, dlr.bufW = 0, 0 |
|
195
|
|
|
} |
|
196
|
|
|
if n != 0 { |
|
197
|
|
|
return n, nil |
|
198
|
|
|
} |
|
199
|
|
|
} |
|
200
|
|
|
var err error |
|
201
|
|
|
// We only read into dlr.buf when it's empty, dlr.bufR == dlr.bufW == 0 |
|
202
|
|
|
dlr.bufW, err = dlr.read(dlr.buf) |
|
203
|
|
|
if logger != nil { |
|
204
|
|
|
logger.Log("msg", "dlr.read", "bufR", dlr.bufR, "bufW", dlr.bufW, "chunkSize", dlr.chunkSize, "error", err) |
|
205
|
|
|
} |
|
206
|
|
|
dlr.bufR = copy(p, dlr.buf[:dlr.bufW]) |
|
207
|
|
|
if err == io.EOF && dlr.bufW != dlr.bufR { |
|
208
|
|
|
err = nil |
|
209
|
|
|
} |
|
210
|
|
|
return dlr.bufR, err |
|
211
|
|
|
} |
|
212
|
|
|
|
|
213
|
|
|
var ErrCLOB = errors.New("CLOB is not supported") |
|
214
|
|
|
|
|
215
|
|
|
// Size returns the LOB's size. It returns ErrCLOB for CLOB, |
|
216
|
|
|
// (works only for BLOBs), as Oracle reports CLOB size in runes, not in bytes! |
|
217
|
|
|
func (dlr *dpiLobReader) Size() (int64, error) { |
|
218
|
|
|
dlr.mu.Lock() |
|
219
|
|
|
err := dlr.getSize() |
|
220
|
|
|
size := dlr.sizePlusOne - 1 |
|
221
|
|
|
isClob := dlr.IsClob |
|
222
|
|
|
dlr.mu.Unlock() |
|
223
|
|
|
if err == nil && isClob { |
|
224
|
|
|
err = ErrCLOB |
|
225
|
|
|
} |
|
226
|
|
|
return int64(size), err |
|
227
|
|
|
} |
|
228
|
|
|
func (dlr *dpiLobReader) getSize() error { |
|
229
|
|
|
if dlr.sizePlusOne != 0 { |
|
230
|
|
|
return nil |
|
231
|
|
|
} |
|
232
|
|
|
var err error |
|
233
|
|
|
runtime.LockOSThread() |
|
234
|
|
|
if err = dlr.checkExecNoLOT(func() C.int { |
|
235
|
|
|
return C.dpiLob_getSize(dlr.dpiLob, &dlr.sizePlusOne) |
|
236
|
|
|
}); err != nil { |
|
237
|
|
|
err = fmt.Errorf("getSize: %w", err) |
|
238
|
|
|
C.dpiLob_close(dlr.dpiLob) |
|
239
|
|
|
dlr.dpiLob = nil |
|
240
|
|
|
} |
|
241
|
|
|
runtime.UnlockOSThread() |
|
242
|
|
|
dlr.sizePlusOne++ |
|
243
|
|
|
return err |
|
244
|
|
|
} |
|
245
|
|
|
|
|
246
|
|
|
// read does the real LOB reading. |
|
247
|
|
|
func (dlr *dpiLobReader) read(p []byte) (int, error) { |
|
248
|
|
|
if dlr == nil { |
|
249
|
|
|
return 0, errors.New("read on nil dpiLobReader") |
|
250
|
|
|
} |
|
251
|
|
|
logger := getLogger() |
|
252
|
|
|
if logger != nil { |
|
253
|
|
|
logger.Log("msg", "LOB Read", "dlr", fmt.Sprintf("%p", dlr), "offset", dlr.offset, "size", dlr.sizePlusOne, "finished", dlr.finished, "clob", dlr.IsClob) |
|
254
|
|
|
} |
|
255
|
|
|
if dlr.finished || dlr.dpiLob == nil { |
|
256
|
|
|
return 0, io.EOF |
|
257
|
|
|
} |
|
258
|
|
|
if len(p) < 1 || dlr.IsClob && len(p) < 4 { |
|
259
|
|
|
return 0, io.ErrShortBuffer |
|
260
|
|
|
} |
|
261
|
|
|
runtime.LockOSThread() |
|
262
|
|
|
defer runtime.UnlockOSThread() |
|
263
|
|
|
// For CLOB, sizePlusOne and offset counts the CHARACTERS! |
|
264
|
|
|
// See https://oracle.github.io/odpi/doc/public_functions/dpiLob.html dpiLob_readBytes |
|
265
|
|
|
if dlr.sizePlusOne == 0 { // first read |
|
266
|
|
|
// never read size before |
|
267
|
|
|
if err := dlr.getSize(); err != nil { |
|
268
|
|
|
var coder interface{ Code() int } |
|
269
|
|
|
if errors.As(err, &coder) && coder.Code() == 22922 || strings.Contains(err.Error(), "invalid dpiLob handle") { |
|
270
|
|
|
return 0, io.EOF |
|
271
|
|
|
} |
|
272
|
|
|
return 0, err |
|
273
|
|
|
} |
|
274
|
|
|
|
|
275
|
|
|
var lobType C.dpiOracleTypeNum |
|
276
|
|
|
if err := dlr.checkExecNoLOT(func() C.int { |
|
277
|
|
|
return C.dpiLob_getType(dlr.dpiLob, &lobType) |
|
278
|
|
|
}); err == nil && |
|
279
|
|
|
(2017 <= lobType && lobType <= 2019) { |
|
280
|
|
|
dlr.IsClob = lobType == 2017 || lobType == 2018 // CLOB and NCLOB |
|
281
|
|
|
} |
|
282
|
|
|
} |
|
283
|
|
|
n := C.uint64_t(len(p)) |
|
284
|
|
|
amount := n |
|
285
|
|
|
if dlr.IsClob { |
|
286
|
|
|
amount /= 4 // dpiLob_readBytes' amount is the number of CHARACTERS for CLOBs. |
|
287
|
|
|
} |
|
288
|
|
|
// fmt.Printf("%p.Read offset=%d sizePlusOne=%d n=%d\n", dlr.dpiLob, dlr.offset, dlr.sizePlusOne, n) |
|
289
|
|
|
if logger != nil { |
|
290
|
|
|
logger.Log("msg", "Read", "offset", dlr.offset, "sizePlusOne", dlr.sizePlusOne, "n", n, "amount", amount) |
|
291
|
|
|
} |
|
292
|
|
|
if !dlr.IsClob && dlr.offset+1 >= dlr.sizePlusOne { |
|
293
|
|
|
if logger != nil { |
|
294
|
|
|
logger.Log("msg", "LOB reached end", "offset", dlr.offset, "size", dlr.sizePlusOne) |
|
295
|
|
|
} |
|
296
|
|
|
return 0, io.EOF |
|
297
|
|
|
} |
|
298
|
|
|
if err := dlr.drv.checkExecNoLOT(func() C.int { |
|
299
|
|
|
return C.dpiLob_readBytes(dlr.dpiLob, dlr.offset+1, amount, (*C.char)(unsafe.Pointer(&p[0])), &n) |
|
300
|
|
|
}); err != nil { |
|
301
|
|
|
if logger != nil { |
|
302
|
|
|
logger.Log("msg", "readBytes", "error", err) |
|
303
|
|
|
} |
|
304
|
|
|
C.dpiLob_close(dlr.dpiLob) |
|
305
|
|
|
dlr.dpiLob = nil |
|
306
|
|
|
if logger != nil { |
|
307
|
|
|
logger.Log("msg", "LOB read", "error", err) |
|
308
|
|
|
} |
|
309
|
|
|
var codeErr interface{ Code() int } |
|
310
|
|
|
if dlr.finished = errors.As(err, &codeErr) && codeErr.Code() == 1403; dlr.finished { |
|
311
|
|
|
dlr.offset += n |
|
312
|
|
|
return int(n), io.EOF |
|
313
|
|
|
} |
|
314
|
|
|
return int(n), fmt.Errorf("dpiLob_readbytes(lob=%p offset=%d n=%d): %w", dlr.dpiLob, dlr.offset, len(p), err) |
|
315
|
|
|
} |
|
316
|
|
|
if logger != nil { |
|
317
|
|
|
logger.Log("msg", "read", "n", n) |
|
318
|
|
|
} |
|
319
|
|
|
if dlr.IsClob { |
|
320
|
|
|
dlr.offset += C.uint64_t(utf8.RuneCount(p[:n])) |
|
321
|
|
|
} else { |
|
322
|
|
|
dlr.offset += n |
|
323
|
|
|
} |
|
324
|
|
|
var err error |
|
325
|
|
|
if amount != 0 && n == 0 || !dlr.IsClob && dlr.offset+1 >= dlr.sizePlusOne { |
|
326
|
|
|
C.dpiLob_close(dlr.dpiLob) |
|
327
|
|
|
dlr.dpiLob = nil |
|
328
|
|
|
dlr.finished = true |
|
329
|
|
|
err = io.EOF |
|
330
|
|
|
} |
|
331
|
|
|
if logger != nil { |
|
332
|
|
|
logger.Log("msg", "LOB", "n", n, "offset", dlr.offset, "size", dlr.sizePlusOne, "finished", dlr.finished, "clob", dlr.IsClob, "error", err) |
|
333
|
|
|
} |
|
334
|
|
|
return int(n), err |
|
335
|
|
|
} |
|
336
|
|
|
|
|
337
|
|
|
// ReadAt reads at the specified offset (in bytes). |
|
338
|
|
|
// Works only for BLOBs! |
|
339
|
|
|
func (dlr *dpiLobReader) ReadAt(p []byte, off int64) (int, error) { |
|
340
|
|
|
dlr.mu.Lock() |
|
341
|
|
|
defer dlr.mu.Unlock() |
|
342
|
|
|
if dlr.IsClob { |
|
343
|
|
|
return 0, ErrCLOB |
|
344
|
|
|
} |
|
345
|
|
|
n := C.uint64_t(len(p)) |
|
346
|
|
|
err := dlr.checkExec(func() C.int { |
|
347
|
|
|
return C.dpiLob_readBytes(dlr.dpiLob, C.uint64_t(off+1), n, (*C.char)(unsafe.Pointer(&p[0])), &n) |
|
348
|
|
|
}) |
|
349
|
|
|
if err != nil { |
|
350
|
|
|
err = fmt.Errorf("readBytes at %d for %d: %w", off, n, dlr.getError()) |
|
351
|
|
|
} |
|
352
|
|
|
return int(n), err |
|
353
|
|
|
} |
|
354
|
|
|
func (dlr *dpiLobReader) Close() error { |
|
355
|
|
|
if dlr == nil || dlr.dpiLob == nil { |
|
356
|
|
|
return nil |
|
357
|
|
|
} |
|
358
|
|
|
lob := dlr.dpiLob |
|
359
|
|
|
dlr.dpiLob = nil |
|
360
|
|
|
return closeLob(dlr, lob) |
|
361
|
|
|
} |
|
362
|
|
|
|
|
363
|
|
|
type dpiLobWriter struct { |
|
364
|
|
|
*drv |
|
365
|
|
|
dpiLob *C.dpiLob |
|
366
|
|
|
offset C.uint64_t |
|
367
|
|
|
opened bool |
|
368
|
|
|
isClob bool |
|
369
|
|
|
} |
|
370
|
|
|
|
|
371
|
|
|
func (dlw *dpiLobWriter) Write(p []byte) (int, error) { |
|
372
|
|
|
runtime.LockOSThread() |
|
373
|
|
|
defer runtime.UnlockOSThread() |
|
374
|
|
|
|
|
375
|
|
|
lob := dlw.dpiLob |
|
376
|
|
|
if !dlw.opened { |
|
377
|
|
|
// fmt.Printf("open %p\n", lob) |
|
378
|
|
|
if err := dlw.drv.checkExecNoLOT(func() C.int { |
|
379
|
|
|
return C.dpiLob_openResource(lob) |
|
380
|
|
|
}); err != nil { |
|
381
|
|
|
return 0, fmt.Errorf("openResources(%p): %w", lob, err) |
|
382
|
|
|
} |
|
383
|
|
|
dlw.opened = true |
|
384
|
|
|
} |
|
385
|
|
|
|
|
386
|
|
|
n := C.uint64_t(len(p)) |
|
387
|
|
|
if err := dlw.drv.checkExecNoLOT(func() C.int { |
|
388
|
|
|
return C.dpiLob_writeBytes(lob, dlw.offset+1, (*C.char)(unsafe.Pointer(&p[0])), n) |
|
389
|
|
|
}); err != nil { |
|
390
|
|
|
err = fmt.Errorf("writeBytes(%p, offset=%d, data=%d): %w", lob, dlw.offset, n, err) |
|
391
|
|
|
dlw.dpiLob = nil |
|
392
|
|
|
closeLob(dlw, lob) |
|
393
|
|
|
return 0, err |
|
394
|
|
|
} |
|
395
|
|
|
// fmt.Printf("written %q into %p@%d\n", p[:n], lob, dlw.offset) |
|
396
|
|
|
dlw.offset += n |
|
397
|
|
|
|
|
398
|
|
|
return int(n), nil |
|
399
|
|
|
} |
|
400
|
|
|
|
|
401
|
|
|
func (dlw *dpiLobWriter) Close() error { |
|
402
|
|
|
if dlw == nil || dlw.dpiLob == nil { |
|
403
|
|
|
return nil |
|
404
|
|
|
} |
|
405
|
|
|
lob := dlw.dpiLob |
|
406
|
|
|
dlw.dpiLob = nil |
|
407
|
|
|
return closeLob(dlw, lob) |
|
408
|
|
|
} |
|
409
|
|
|
|
|
410
|
|
|
func closeLob(d interface{ getError() error }, lob *C.dpiLob) error { |
|
411
|
|
|
if lob == nil { |
|
412
|
|
|
return nil |
|
413
|
|
|
} |
|
414
|
|
|
|
|
415
|
|
|
runtime.LockOSThread() |
|
416
|
|
|
defer runtime.UnlockOSThread() |
|
417
|
|
|
|
|
418
|
|
|
var isOpen C.int |
|
419
|
|
|
if C.dpiLob_getIsResourceOpen(lob, &isOpen) != C.DPI_FAILURE && isOpen == 1 { |
|
420
|
|
|
if C.dpiLob_closeResource(lob) == C.DPI_FAILURE { |
|
421
|
|
|
if err := d.getError(); err != nil { |
|
422
|
|
|
var codeErr interface{ Code() int } |
|
423
|
|
|
if errors.As(err, &codeErr) && codeErr.Code() != 22289 { // cannot perform %s operation on an unopened file or LOB |
|
424
|
|
|
return fmt.Errorf("closeResource(%p): %w", lob, err) |
|
425
|
|
|
} |
|
426
|
|
|
} |
|
427
|
|
|
} |
|
428
|
|
|
} |
|
429
|
|
|
C.dpiLob_release(lob) |
|
430
|
|
|
return nil |
|
431
|
|
|
} |
|
432
|
|
|
|
|
433
|
|
|
// DirectLob holds a Lob and allows direct (Read/WriteAt, not streaming Read/Write) operations on it. |
|
434
|
|
|
type DirectLob struct { |
|
435
|
|
|
drv *drv |
|
436
|
|
|
dpiLob *C.dpiLob |
|
437
|
|
|
opened, isClob bool |
|
438
|
|
|
} |
|
439
|
|
|
|
|
440
|
|
|
var _ = io.ReaderAt((*DirectLob)(nil)) |
|
441
|
|
|
var _ = io.WriterAt((*DirectLob)(nil)) |
|
442
|
|
|
|
|
443
|
|
|
// NewTempLob returns a temporary LOB as DirectLob. |
|
444
|
|
|
func (c *conn) NewTempLob(isClob bool) (*DirectLob, error) { |
|
445
|
|
|
typ := C.uint(C.DPI_ORACLE_TYPE_BLOB) |
|
446
|
|
|
if isClob { |
|
447
|
|
|
typ = C.DPI_ORACLE_TYPE_CLOB |
|
448
|
|
|
} |
|
449
|
|
|
lob := DirectLob{drv: c.drv, isClob: isClob} |
|
450
|
|
|
if err := c.checkExec(func() C.int { return C.dpiConn_newTempLob(c.dpiConn, typ, &lob.dpiLob) }); err != nil { |
|
451
|
|
|
return nil, fmt.Errorf("newTempLob: %w", err) |
|
452
|
|
|
} |
|
453
|
|
|
return &lob, nil |
|
454
|
|
|
} |
|
455
|
|
|
|
|
456
|
|
|
// Close the Lob. |
|
457
|
|
|
func (dl *DirectLob) Close() error { |
|
458
|
|
|
if !dl.opened { |
|
459
|
|
|
return nil |
|
460
|
|
|
} |
|
461
|
|
|
lob := dl.dpiLob |
|
462
|
|
|
dl.opened, dl.dpiLob = false, nil |
|
463
|
|
|
return closeLob(dl.drv, lob) |
|
464
|
|
|
} |
|
465
|
|
|
|
|
466
|
|
|
// Size returns the size of the LOB. |
|
467
|
|
|
// |
|
468
|
|
|
// WARNING: for historical reasons, Oracle stores CLOBs and NCLOBs using the UTF-16 encoding, |
|
469
|
|
|
// regardless of what encoding is otherwise in use by the database. |
|
470
|
|
|
// The number of characters, however, is defined by the number of UCS-2 codepoints. |
|
471
|
|
|
// For this reason, if a character requires more than one UCS-2 codepoint, |
|
472
|
|
|
// the size returned will be inaccurate and care must be taken to account for the difference! |
|
473
|
|
|
func (dl *DirectLob) Size() (int64, error) { |
|
474
|
|
|
var n C.uint64_t |
|
475
|
|
|
if dl.dpiLob == nil { |
|
476
|
|
|
return 0, nil |
|
477
|
|
|
} |
|
478
|
|
|
if err := dl.drv.checkExec(func() C.int { |
|
479
|
|
|
return C.dpiLob_getSize(dl.dpiLob, &n) |
|
480
|
|
|
}); err != nil { |
|
481
|
|
|
var coder interface{ Code() int } |
|
482
|
|
|
if errors.As(err, &coder) && coder.Code() == 22922 || strings.Contains(err.Error(), "invalid dpiLob handle") { |
|
483
|
|
|
return 0, nil |
|
484
|
|
|
} |
|
485
|
|
|
return int64(n), fmt.Errorf("getSize: %w", err) |
|
486
|
|
|
} |
|
487
|
|
|
return int64(n), nil |
|
488
|
|
|
} |
|
489
|
|
|
|
|
490
|
|
|
// Trim the LOB to the given size. |
|
491
|
|
|
func (dl *DirectLob) Trim(size int64) error { |
|
492
|
|
|
if err := dl.drv.checkExec(func() C.int { |
|
493
|
|
|
return C.dpiLob_trim(dl.dpiLob, C.uint64_t(size)) |
|
494
|
|
|
}); err != nil { |
|
495
|
|
|
return fmt.Errorf("trim: %w", err) |
|
496
|
|
|
} |
|
497
|
|
|
return nil |
|
498
|
|
|
} |
|
499
|
|
|
|
|
500
|
|
|
// Set the contents of the LOB to the given byte slice. |
|
501
|
|
|
// The LOB is cleared first. |
|
502
|
|
|
func (dl *DirectLob) Set(p []byte) error { |
|
503
|
|
|
if err := dl.drv.checkExec(func() C.int { |
|
504
|
|
|
return C.dpiLob_setFromBytes(dl.dpiLob, (*C.char)(unsafe.Pointer(&p[0])), C.uint64_t(len(p))) |
|
505
|
|
|
}); err != nil { |
|
506
|
|
|
return fmt.Errorf("setFromBytes: %w", err) |
|
507
|
|
|
} |
|
508
|
|
|
return nil |
|
509
|
|
|
} |
|
510
|
|
|
|
|
511
|
|
|
// ReadAt reads at most len(p) bytes into p at offset. |
|
512
|
|
|
// |
|
513
|
|
|
// CLOB's offset must be in amount of characters, and does not work reliably! |
|
514
|
|
|
// |
|
515
|
|
|
// WARNING: for historical reasons, Oracle stores CLOBs and NCLOBs using the UTF-16 encoding, |
|
516
|
|
|
// regardless of what encoding is otherwise in use by the database. |
|
517
|
|
|
// The number of characters, however, is defined by the number of UCS-2 codepoints. |
|
518
|
|
|
// For this reason, if a character requires more than one UCS-2 codepoint, |
|
519
|
|
|
// the size returned will be inaccurate and care must be taken to account for the difference! |
|
520
|
|
|
func (dl *DirectLob) ReadAt(p []byte, offset int64) (int, error) { |
|
521
|
|
|
n := C.uint64_t(len(p)) |
|
522
|
|
|
if dl.dpiLob == nil { |
|
523
|
|
|
return 0, io.EOF |
|
524
|
|
|
} |
|
525
|
|
|
amount := n |
|
526
|
|
|
if dl.isClob { |
|
527
|
|
|
amount /= 4 |
|
528
|
|
|
if amount == 0 { |
|
529
|
|
|
return 0, io.ErrShortBuffer |
|
530
|
|
|
} |
|
531
|
|
|
} |
|
532
|
|
|
if err := dl.drv.checkExec(func() C.int { |
|
533
|
|
|
return C.dpiLob_readBytes(dl.dpiLob, C.uint64_t(offset)+1, n, (*C.char)(unsafe.Pointer(&p[0])), &n) |
|
534
|
|
|
}); err != nil { |
|
535
|
|
|
return int(n), fmt.Errorf("readBytes: %w", err) |
|
536
|
|
|
} |
|
537
|
|
|
return int(n), nil |
|
538
|
|
|
} |
|
539
|
|
|
|
|
540
|
|
|
// WriteAt writes p starting at offset. |
|
541
|
|
|
func (dl *DirectLob) WriteAt(p []byte, offset int64) (int, error) { |
|
542
|
|
|
runtime.LockOSThread() |
|
543
|
|
|
defer runtime.UnlockOSThread() |
|
544
|
|
|
|
|
545
|
|
|
if !dl.opened { |
|
546
|
|
|
// fmt.Printf("open %p\n", lob) |
|
547
|
|
|
if err := dl.drv.checkExecNoLOT(func() C.int { |
|
548
|
|
|
return C.dpiLob_openResource(dl.dpiLob) |
|
549
|
|
|
}); err != nil { |
|
550
|
|
|
return 0, fmt.Errorf("openResources(%p): %w", dl.dpiLob, err) |
|
551
|
|
|
} |
|
552
|
|
|
dl.opened = true |
|
553
|
|
|
} |
|
554
|
|
|
|
|
555
|
|
|
n := C.uint64_t(len(p)) |
|
556
|
|
|
if err := dl.drv.checkExecNoLOT(func() C.int { |
|
557
|
|
|
return C.dpiLob_writeBytes(dl.dpiLob, C.uint64_t(offset)+1, (*C.char)(unsafe.Pointer(&p[0])), n) |
|
558
|
|
|
}); err != nil { |
|
559
|
|
|
return int(n), fmt.Errorf("writeBytes: %w", err) |
|
560
|
|
|
} |
|
561
|
|
|
return int(n), nil |
|
562
|
|
|
} |
|
563
|
|
|
|
|
564
|
|
|
// GetFileName Return directory alias and file name for a BFILE type LOB. |
|
565
|
|
|
func (dl *DirectLob) GetFileName() (dir, file string, err error) { |
|
566
|
|
|
var directoryAliasLength, fileNameLength C.uint32_t |
|
567
|
|
|
var directoryAlias, fileName *C.char |
|
568
|
|
|
if err := dl.drv.checkExec(func() C.int { |
|
569
|
|
|
return C.dpiLob_getDirectoryAndFileName(dl.dpiLob, |
|
570
|
|
|
&directoryAlias, |
|
571
|
|
|
&directoryAliasLength, |
|
572
|
|
|
&fileName, |
|
573
|
|
|
&fileNameLength, |
|
574
|
|
|
) |
|
575
|
|
|
}); err != nil { |
|
576
|
|
|
return dir, file, fmt.Errorf("GetFileName: %w", err) |
|
577
|
|
|
} |
|
578
|
|
|
dir = C.GoStringN(directoryAlias, C.int(directoryAliasLength)) |
|
579
|
|
|
file = C.GoStringN(fileName, C.int(fileNameLength)) |
|
580
|
|
|
return dir, file, nil |
|
581
|
|
|
} |
|
582
|
|
|
|
|
583
|
|
|
func maxI(a, b int) int { |
|
584
|
|
|
if a < b { |
|
585
|
|
|
return b |
|
586
|
|
|
} |
|
587
|
|
|
return a |
|
588
|
|
|
} |
|
589
|
|
|
|