/* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */
#include "oci8.h"
static ID id_plus;
static ID id_dir_alias;
static ID id_filename;
static VALUE cOCI8LOB;
static VALUE cOCI8CLOB;
static VALUE cOCI8NCLOB;
static VALUE cOCI8BLOB;
static VALUE cOCI8BFILE;
static VALUE seek_set;
static VALUE seek_cur;
static VALUE seek_end;
enum state {
S_NO_OPEN_CLOSE,
S_OPEN,
S_CLOSE,
S_BFILE_CLOSE,
S_BFILE_OPEN,
};
typedef struct {
oci8_base_t base;
VALUE svc;
OCISvcCtx *svchp;
ub4 pos;
int char_width;
ub1 csfrm;
ub1 lobtype;
enum state state;
} oci8_lob_t;
static VALUE oci8_lob_write(VALUE self, VALUE data);
static VALUE oci8_make_lob(VALUE klass, oci8_svcctx_t *svcctx, OCILobLocator *s)
{
oci8_lob_t *lob;
boolean is_temp;
VALUE lob_obj;
lob_obj = rb_funcall(klass, oci8_id_new, 1, svcctx->base.self);
lob = DATA_PTR(lob_obj);
/* If 's' is a temporary lob, use OCILobLocatorAssign instead. */
chker2(OCILobIsTemporary(oci8_envhp, oci8_errhp, s, &is_temp), &svcctx->base);
if (is_temp)
chker2(OCILobLocatorAssign_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, s, &lob->base.hp.lob),
&svcctx->base);
else
chker2(OCILobAssign(oci8_envhp, oci8_errhp, s, &lob->base.hp.lob),
&svcctx->base);
return lob_obj;
}
VALUE oci8_make_clob(oci8_svcctx_t *svcctx, OCILobLocator *s)
{
return oci8_make_lob(cOCI8CLOB, svcctx, s);
}
VALUE oci8_make_nclob(oci8_svcctx_t *svcctx, OCILobLocator *s)
{
return oci8_make_lob(cOCI8NCLOB, svcctx, s);
}
VALUE oci8_make_blob(oci8_svcctx_t *svcctx, OCILobLocator *s)
{
return oci8_make_lob(cOCI8BLOB, svcctx, s);
}
VALUE oci8_make_bfile(oci8_svcctx_t *svcctx, OCILobLocator *s)
{
return oci8_make_lob(cOCI8BFILE, svcctx, s);
}
static void oci8_assign_lob(VALUE klass, oci8_svcctx_t *svcctx, VALUE lob, OCILobLocator **dest)
{
oci8_base_t *base = oci8_get_handle(lob, klass);
chker2(OCILobLocatorAssign_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, base->hp.lob, dest), base);
}
void oci8_assign_clob(oci8_svcctx_t *svcctx, VALUE lob, OCILobLocator **dest)
{
oci8_assign_lob(cOCI8CLOB, svcctx, lob, dest);
}
void oci8_assign_nclob(oci8_svcctx_t *svcctx, VALUE lob, OCILobLocator **dest)
{
oci8_assign_lob(cOCI8NCLOB, svcctx, lob, dest);
}
void oci8_assign_blob(oci8_svcctx_t *svcctx, VALUE lob, OCILobLocator **dest)
{
oci8_assign_lob(cOCI8BLOB, svcctx, lob, dest);
}
void oci8_assign_bfile(oci8_svcctx_t *svcctx, VALUE lob, OCILobLocator **dest)
{
oci8_assign_lob(cOCI8BFILE, svcctx, lob, dest);
}
static void oci8_lob_mark(oci8_base_t *base)
{
oci8_lob_t *lob = (oci8_lob_t *)base;
rb_gc_mark(lob->svc);
}
static void oci8_lob_free(oci8_base_t *base)
{
oci8_lob_t *lob = (oci8_lob_t *)base;
boolean is_temporary;
if (lob->svchp != NULL
&& OCILobIsTemporary(oci8_envhp, oci8_errhp, lob->base.hp.lob, &is_temporary) == OCI_SUCCESS
&& is_temporary) {
#ifdef HAVE_RB_THREAD_BLOCKING_REGION
oci8_svcctx_t *svcctx = oci8_get_svcctx(lob->svc);
oci8_temp_lob_t *temp_lob = ALLOC(oci8_temp_lob_t);
temp_lob->next = svcctx->temp_lobs;
temp_lob->lob = lob->base.hp.lob;
svcctx->temp_lobs = temp_lob;
lob->base.type = 0;
lob->base.hp.ptr = NULL;
#else
/* FIXME: This may stall the GC. */
OCILobFreeTemporary(lob->svchp, oci8_errhp, lob->base.hp.lob);
#endif
}
lob->svc = Qnil;
lob->svchp = NULL;
}
static oci8_base_vtable_t oci8_lob_vtable = {
oci8_lob_mark,
oci8_lob_free,
sizeof(oci8_lob_t),
};
static ub4 oci8_lob_get_length(oci8_lob_t *lob)
{
oci8_svcctx_t *svcctx = oci8_get_svcctx(lob->svc);
ub4 len;
chker2(OCILobGetLength_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, lob->base.hp.lob, &len),
&svcctx->base);
return len;
}
static void lob_open(oci8_lob_t *lob)
{
if (lob->state == S_CLOSE) {
oci8_svcctx_t *svcctx = oci8_get_svcctx(lob->svc);
chker2(OCILobOpen_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, lob->base.hp.lob, OCI_DEFAULT),
&svcctx->base);
lob->state = S_OPEN;
}
}
static void lob_close(oci8_lob_t *lob)
{
if (lob->state == S_OPEN) {
oci8_svcctx_t *svcctx = oci8_get_svcctx(lob->svc);
chker2(OCILobClose_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, lob->base.hp.lob),
&svcctx->base);
lob->state = S_CLOSE;
}
}
static void bfile_close(oci8_lob_t *lob)
{
if (lob->state == S_BFILE_OPEN) {
oci8_svcctx_t *svcctx = oci8_get_svcctx(lob->svc);
chker2(OCILobFileClose_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, lob->base.hp.lob),
&svcctx->base);
lob->state = S_BFILE_CLOSE;
}
}
/*
* Document-class: OCI8::LOB
*
* This is the abstract base class of large-object data types; BFILE, BLOB, CLOB and NCLOB.
*
*/
/*
* Document-class: OCI8::CLOB
*
*/
/*
* Document-class: OCI8::NCLOB
*
*/
/*
* Document-class: OCI8::BLOB
*
*/
/*
* Document-class: OCI8::BFILE
*
* @method truncate(length)
* @method size = length
* @method write(data)
*/
/*
* Closes the lob.
*
* @return [self]
*/
static VALUE oci8_lob_close(VALUE self)
{
oci8_lob_t *lob = DATA_PTR(self);
lob_close(lob);
oci8_base_free(DATA_PTR(self));
return self;
}
static VALUE oci8_lob_do_initialize(int argc, VALUE *argv, VALUE self, ub1 csfrm, ub1 lobtype)
{
oci8_lob_t *lob = DATA_PTR(self);
VALUE svc;
VALUE val;
sword rv;
rb_scan_args(argc, argv, "11", &svc, &val);
TO_SVCCTX(svc); /* check argument type */
rv = OCIDescriptorAlloc(oci8_envhp, &lob->base.hp.ptr, OCI_DTYPE_LOB, 0, NULL);
if (rv != OCI_SUCCESS)
oci8_env_raise(oci8_envhp, rv);
lob->base.type = OCI_DTYPE_LOB;
lob->svc = svc;
lob->svchp = NULL;
lob->pos = 0;
lob->char_width = 1;
lob->csfrm = csfrm;
lob->lobtype = lobtype;
lob->state = S_NO_OPEN_CLOSE;
oci8_link_to_parent((oci8_base_t*)lob, (oci8_base_t*)DATA_PTR(svc));
if (!NIL_P(val)) {
oci8_svcctx_t *svcctx = oci8_get_svcctx(svc);
OCI8StringValue(val);
chker2(OCILobCreateTemporary_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, lob->base.hp.lob, 0, csfrm, lobtype, TRUE, OCI_DURATION_SESSION),
&svcctx->base);
lob->svchp = oci8_get_oci_svcctx(svc);
oci8_lob_write(self, val);
lob->pos = 0; /* reset the position */
}
return Qnil;
}
/*
* call-seq:
* initialize(conn, contents = nil)
*
* Creates a temporary CLOB when contents is not nil.
* Otherwise, it creates an uninitialized lob, which is used internally
* to fetch CLOB column data.
*
* @example
* # Inserts a file name and its contents as CLOB.
* clob = OCI8::CLOB.new(conn, File.read(file_name))
* conn.exec('insert into file_contents values (:1, :2)', file_name, clob)
*
* @param [OCI8] conn connection
* @param [String] contents
* @return [OCI8::CLOB]
*/
static VALUE oci8_clob_initialize(int argc, VALUE *argv, VALUE self)
{
oci8_lob_do_initialize(argc, argv, self, SQLCS_IMPLICIT, OCI_TEMP_CLOB);
return Qnil;
}
/*
* call-seq:
* initialize(conn, contents = nil)
*
* Creates a temporary NCLOB when contents is not nil.
* Otherwise, it creates an uninitialized lob, which is used internally
* to fetch NCLOB column data.
*
* @example
* # Inserts a file name and its contents as NCLOB.
* clob = OCI8::NCLOB.new(conn, File.read(file_name))
* conn.exec('insert into file_contents values (:1, :2)', file_name, clob)
*
* @param [OCI8] conn
* @param [String] contents
* @return [OCI8::NCLOB]
*/
static VALUE oci8_nclob_initialize(int argc, VALUE *argv, VALUE self)
{
oci8_lob_do_initialize(argc, argv, self, SQLCS_NCHAR, OCI_TEMP_CLOB);
return Qnil;
}
/*
* call-seq:
* initialize(conn, contents = nil)
*
* Creates a temporary BLOB when contents is not nil.
* Otherwise, it creates an uninitialized lob, which is used internally
* to fetch BLOB column data.
*
* @example
* # Inserts a file name and its contents as BLOB.
* clob = OCI8::BLOB.new(conn, File.read(file_name, :mode => 'rb'))
* conn.exec('insert into file_contents values (:1, :2)', file_name, clob)
*
* @param [OCI8] conn
* @param [String] contents
* @return [OCI8::BLOB]
*/
static VALUE oci8_blob_initialize(int argc, VALUE *argv, VALUE self)
{
oci8_lob_do_initialize(argc, argv, self, SQLCS_IMPLICIT, OCI_TEMP_BLOB);
return Qnil;
}
/*
* call-seq:
* __char_width = size
*
* @private
* IMO, nobody need and use this.
*/
static VALUE oci8_lob_set_char_width(VALUE self, VALUE vsize)
{
oci8_lob_t *lob = DATA_PTR(self);
int size;
size = NUM2INT(vsize); /* 1 */
if (size <= 0)
rb_raise(rb_eArgError, "size must be more than one.");
lob->char_width = size;
return vsize;
}
/*
* Returns +true+ when self is initialized.
*
* @return [true or false]
*/
static VALUE oci8_lob_available_p(VALUE self)
{
oci8_lob_t *lob = DATA_PTR(self);
boolean is_initialized;
chker2(OCILobLocatorIsInit(oci8_envhp, oci8_errhp, lob->base.hp.lob, &is_initialized),
&lob->base);
return is_initialized ? Qtrue : Qfalse;
}
/*
* Returns the size.
* For CLOB and NCLOB it is the number of characters,
* for BLOB and BFILE it is the number of bytes.
*
* @return [Integer]
*/
static VALUE oci8_lob_get_size(VALUE self)
{
return UB4_TO_NUM(oci8_lob_get_length(DATA_PTR(self)));
}
/*
* Returns the current offset.
* For CLOB and NCLOB it is the number of characters,
* for BLOB and BFILE it is the number of bytes.
*
* @return [Integer]
*/
static VALUE oci8_lob_get_pos(VALUE self)
{
oci8_lob_t *lob = DATA_PTR(self);
return UB4_TO_NUM(lob->pos);
}
/*
* Returns true if the current offset is at end of lob.
*
* @return [true or false]
*/
static VALUE oci8_lob_eof_p(VALUE self)
{
oci8_lob_t *lob = DATA_PTR(self);
if (oci8_lob_get_length(lob) < lob->pos)
return Qfalse;
else
return Qtrue;
}
/*
* call-seq:
* seek(amount, whence=IO::SEEK_SET)
*
* Seeks to the given offset in the stream. The new position, measured in characters,
* is obtained by adding offset amount to the position specified by whence.
* If whence is set to IO::SEEK_SET, IO::SEEK_CUR, or IO::SEEK_END,
* the offset is relative to the start of the file, the current position
* indicator, or end-of-file, respectively.
*
* @param [Integer] amount
* @param [IO::SEEK_SET, IO::SEEK_CUR or IO::SEEK_END] whence
* @return [self]
*/
static VALUE oci8_lob_seek(int argc, VALUE *argv, VALUE self)
{
oci8_lob_t *lob = DATA_PTR(self);
VALUE position, whence;
rb_scan_args(argc, argv, "11", &position, &whence);
if (argc == 2 && (whence != seek_set && whence != seek_cur && whence != seek_end)) {
if (FIXNUM_P(whence)) {
rb_raise(rb_eArgError, "expect IO::SEEK_SET, IO::SEEK_CUR or IO::SEEK_END but %d",
FIX2INT(whence));
} else {
rb_raise(rb_eArgError, "expect IO::SEEK_SET, IO::SEEK_CUR or IO::SEEK_END but %s",
rb_class2name(CLASS_OF(whence)));
}
}
if (whence == seek_cur) {
position = rb_funcall(UB4_TO_NUM(lob->pos), id_plus, 1, position);
} else if (whence == seek_end) {
position = rb_funcall(UB4_TO_NUM(oci8_lob_get_length(lob)), id_plus, 1, position);
}
lob->pos = NUM2UINT(position);
return self;
}
/*
* Sets the current offset at the beginning.
*
* @return [true or false]
*/
static VALUE oci8_lob_rewind(VALUE self)
{
oci8_lob_t *lob = DATA_PTR(self);
lob->pos = 0;
return self;
}
/*
* call-seq:
* truncate(length)
*
* Changes the lob size to the given length.
*
* @param [Integer] length
* @return [self]
*/
static VALUE oci8_lob_truncate(VALUE self, VALUE len)
{
oci8_lob_t *lob = DATA_PTR(self);
oci8_svcctx_t *svcctx = oci8_get_svcctx(lob->svc);
lob_open(lob);
chker2(OCILobTrim_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, lob->base.hp.lob, NUM2UINT(len)),
&svcctx->base);
return self;
}
/*
* call-seq:
* size = length
*
* Changes the lob size to the given length.
*
* @param [Integer] length
* @return [Integer]
*/
static VALUE oci8_lob_set_size(VALUE self, VALUE len)
{
oci8_lob_truncate(self, len);
return len;
}
/*
* call-seq:
* read(length = nil)
*
* Reads length characters for CLOB and NCLOB or length
* bytes for BLOB and BILF from the current position.
* If length is nil
, it reads data until EOF.
*
* It returns a string or nil
. nil
means it
* met EOF at beginning. As a special exception, when length is
* nil
and the lob length is zero, it returns an empty string ''.
*
* @param [Integer] length
* @return [String or nil]
*/
static VALUE oci8_lob_read(int argc, VALUE *argv, VALUE self)
{
oci8_lob_t *lob = DATA_PTR(self);
oci8_svcctx_t *svcctx = oci8_get_svcctx(lob->svc);
ub4 length;
ub4 nchar;
ub4 amt;
sword rv;
char buf[8192];
size_t buf_size_in_char;
VALUE size;
VALUE v = rb_ary_new();
rb_scan_args(argc, argv, "01", &size);
length = oci8_lob_get_length(lob);
if (length == 0 && NIL_P(size)) {
return rb_usascii_str_new("", 0);
}
if (length <= lob->pos) /* EOF */
return Qnil;
length -= lob->pos;
if (NIL_P(size)) {
nchar = length; /* read until EOF */
} else {
nchar = NUM2UINT(size);
if (nchar > length)
nchar = length;
}
amt = nchar;
buf_size_in_char = sizeof(buf) / lob->char_width;
do {
if (lob->state == S_BFILE_CLOSE) {
rv = OCILobFileOpen_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, lob->base.hp.lob, OCI_FILE_READONLY);
if (rv == OCI_ERROR && oci8_get_error_code(oci8_errhp) == 22290) {
/* ORA-22290: operation would exceed the maximum number of opened files or LOBs */
/* close all opened BFILE implicitly. */
oci8_base_t *base;
for (base = &lob->base; base != &lob->base; base = base->next) {
if (base->type == OCI_DTYPE_LOB) {
oci8_lob_t *tmp = (oci8_lob_t *)base;
if (tmp->state == S_BFILE_OPEN) {
tmp->state = S_BFILE_CLOSE;
}
}
}
chker2(OCILobFileCloseAll_nb(svcctx, svcctx->base.hp.svc, oci8_errhp),
&svcctx->base);
continue;
}
chker2(rv, &svcctx->base);
lob->state = S_BFILE_OPEN;
}
/* initialize buf in zeros everytime to check a nul characters. */
memset(buf, 0, sizeof(buf));
rv = OCILobRead_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, lob->base.hp.lob, &amt, lob->pos + 1, buf, sizeof(buf), NULL, NULL, 0, lob->csfrm);
svcctx->suppress_free_temp_lobs = 0;
switch (rv) {
case OCI_SUCCESS:
break;
case OCI_NEED_DATA:
/* prevent OCILobFreeTemporary() from being called.
* See: https://github.com/kubo/ruby-oci8/issues/20
*/
svcctx->suppress_free_temp_lobs = 1;
break;
case OCI_ERROR:
if (oci8_get_error_code(oci8_errhp) == 22289) {
/* ORA-22289: cannot perform FILEREAD operation on an unopened file or LOB */
if (lob->state == S_BFILE_CLOSE)
continue;
}
/* FALLTHROUGH */
default:
chker2(rv, &svcctx->base);
}
/* Workaround when using Oracle 10.2.0.4 or 11.1.0.6 client and
* variable-length character set (e.g. AL32UTF8).
*
* When the above mentioned condition, amt may be shorter. So
* amt is increaded until a nul character to know the actually
* read size.
*/
while (amt < sizeof(buf) && buf[amt] != '\0') {
amt++;
}
if (amt == 0)
break;
/* for fixed size charset, amt is the number of characters stored in buf. */
if (amt > buf_size_in_char)
rb_raise(eOCIException, "Too large buffer fetched or you set too large size of a character.");
amt *= lob->char_width;
rb_ary_push(v, rb_str_new(buf, amt));
} while (rv == OCI_NEED_DATA);
lob->pos += nchar;
if (nchar == length) {
lob_close(lob);
bfile_close(lob);
}
if (RARRAY_LEN(v) == 0) {
return Qnil;
}
v = rb_ary_join(v, Qnil);
OBJ_TAINT(v);
if (lob->lobtype == OCI_TEMP_CLOB) {
/* set encoding */
rb_enc_associate(v, oci8_encoding);
return rb_str_conv_enc(v, oci8_encoding, rb_default_internal_encoding());
} else {
/* ASCII-8BIT */
return v;
}
}
/*
* call-seq:
* write(data)
*
* Writes data.
*
* @param [String] data
* @return [Integer]
*/
static VALUE oci8_lob_write(VALUE self, VALUE data)
{
oci8_lob_t *lob = DATA_PTR(self);
oci8_svcctx_t *svcctx = oci8_get_svcctx(lob->svc);
ub4 amt;
lob_open(lob);
if (TYPE(data) != T_STRING) {
data = rb_obj_as_string(data);
}
if (lob->lobtype == OCI_TEMP_CLOB) {
data = rb_str_export_to_enc(data, oci8_encoding);
}
RB_GC_GUARD(data);
amt = RSTRING_LEN(data);
if (amt == 0) {
/* to avoid ORA-24801: illegal parameter value in OCI lob function */
return INT2FIX(0);
}
chker2(OCILobWrite_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, lob->base.hp.lob, &amt, lob->pos + 1, RSTRING_PTR(data), amt, OCI_ONE_PIECE, NULL, NULL, 0, lob->csfrm),
&svcctx->base);
lob->pos += amt;
return UINT2NUM(amt);
}
/*
* @deprecated I'm not sure that this is what the name indicates.
* @private
*/
static VALUE oci8_lob_get_sync(VALUE self)
{
oci8_lob_t *lob = DATA_PTR(self);
return (lob->state == S_NO_OPEN_CLOSE) ? Qtrue : Qfalse;
}
/*
* @deprecated I'm not sure that this is what the name indicates.
* @private
*/
static VALUE oci8_lob_set_sync(VALUE self, VALUE b)
{
oci8_lob_t *lob = DATA_PTR(self);
if (RTEST(b)) {
lob_close(lob);
lob->state = S_NO_OPEN_CLOSE;
} else {
if (lob->state == S_NO_OPEN_CLOSE)
lob->state = S_CLOSE;
}
return b;
}
/*
* @deprecated I'm not sure that this is what the name indicates.
* @private
*/
static VALUE oci8_lob_flush(VALUE self)
{
oci8_lob_t *lob = DATA_PTR(self);
lob_close(lob);
return self;
}
/*
* Returns the chunk size of a LOB.
*
* @see http://docs.oracle.com/cd/E16338_01/appdev.112/e10646/oci17msc002.htm#i493090
* @return [Integer]
*/
static VALUE oci8_lob_get_chunk_size(VALUE self)
{
oci8_lob_t *lob = DATA_PTR(self);
oci8_svcctx_t *svcctx = oci8_get_svcctx(lob->svc);
ub4 len;
chker2(OCILobGetChunkSize_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, lob->base.hp.lob, &len),
&svcctx->base);
return UINT2NUM(len);
}
static VALUE oci8_lob_clone(VALUE self)
{
oci8_lob_t *lob = DATA_PTR(self);
oci8_lob_t *newlob;
VALUE newobj;
boolean is_temporary;
newobj = rb_funcall(CLASS_OF(self), oci8_id_new, 1, lob->svc);
newlob = DATA_PTR(newobj);
if (OCILobIsTemporary(oci8_envhp, oci8_errhp, lob->base.hp.lob, &is_temporary) == OCI_SUCCESS
&& is_temporary) {
oci8_svcctx_t *svcctx = oci8_get_svcctx(lob->svc);
chker2(OCILobLocatorAssign_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, lob->base.hp.lob, &newlob->base.hp.lob),
&svcctx->base);
} else {
chker2(OCILobAssign(oci8_envhp, oci8_errhp, lob->base.hp.lob, &newlob->base.hp.lob), &lob->base);
}
return newobj;
}
static void oci8_bfile_get_name(VALUE self, VALUE *dir_alias_p, VALUE *filename_p)
{
int need_get = 0;
if (dir_alias_p != NULL) {
*dir_alias_p = rb_ivar_get(self, id_dir_alias);
if (NIL_P(*dir_alias_p))
need_get = 1;
}
if (filename_p != NULL) {
*filename_p = rb_ivar_get(self, id_filename);
if (NIL_P(*filename_p))
need_get = 1;
}
if (need_get) {
oci8_lob_t *lob = DATA_PTR(self);
char d_buf[31];
ub2 d_length = sizeof(d_buf);
char f_buf[256];
ub2 f_length = sizeof(f_buf);
VALUE dir_alias;
VALUE filename;
chker2(OCILobFileGetName(oci8_envhp, oci8_errhp, lob->base.hp.lob, TO_ORATEXT(d_buf), &d_length, TO_ORATEXT(f_buf), &f_length),
&lob->base);
dir_alias = rb_external_str_new_with_enc(d_buf, d_length, oci8_encoding);
filename = rb_external_str_new_with_enc(f_buf, f_length, oci8_encoding);
rb_ivar_set(self, id_dir_alias, dir_alias);
rb_ivar_set(self, id_filename, filename);
if (dir_alias_p != NULL) {
*dir_alias_p = dir_alias;
}
if (filename_p != NULL) {
*filename_p = filename;
}
}
}
static void oci8_bfile_set_name(VALUE self, VALUE dir_alias, VALUE filename)
{
oci8_lob_t *lob = DATA_PTR(self);
bfile_close(lob);
if (RSTRING_LEN(dir_alias) > UB2MAXVAL) {
rb_raise(rb_eRuntimeError, "dir_alias is too long.");
}
if (RSTRING_LEN(filename) > UB2MAXVAL) {
rb_raise(rb_eRuntimeError, "filename is too long.");
}
chker2(OCILobFileSetName(oci8_envhp, oci8_errhp, &lob->base.hp.lob,
RSTRING_ORATEXT(dir_alias), (ub2)RSTRING_LEN(dir_alias),
RSTRING_ORATEXT(filename), (ub2)RSTRING_LEN(filename)),
&lob->base);
}
/*
* call-seq:
* initialize(conn, dir_alias = nil, filename = nil)
*
* Creates a BFILE object.
* This is correspond to {http://docs.oracle.com/cd/E11882_01/server.112/e17118/functions019.htm#sthref953 BFILENAME}.
*
* @param [OCI8] conn
* @param [String] dir_alias a directory object created by
* {http://docs.oracle.com/cd/E11882_01/server.112/e17118/statements_5007.htm "CREATE DIRECTORY"}.
* @param [String] filename
* @return [OCI8::BFILE]
*/
static VALUE oci8_bfile_initialize(int argc, VALUE *argv, VALUE self)
{
oci8_lob_t *lob = DATA_PTR(self);
VALUE svc;
VALUE dir_alias;
VALUE filename;
int rv;
rb_scan_args(argc, argv, "12", &svc, &dir_alias, &filename);
TO_SVCCTX(svc); /* check argument type */
rv = OCIDescriptorAlloc(oci8_envhp, &lob->base.hp.ptr, OCI_DTYPE_LOB, 0, NULL);
if (rv != OCI_SUCCESS) {
oci8_env_raise(oci8_envhp, rv);
}
lob->base.type = OCI_DTYPE_LOB;
lob->svc = svc;
lob->pos = 0;
lob->char_width = 1;
lob->csfrm = SQLCS_IMPLICIT;
lob->lobtype = OCI_TEMP_BLOB;
lob->state = S_BFILE_CLOSE;
if (argc != 1) {
OCI8SafeStringValue(dir_alias);
OCI8SafeStringValue(filename);
oci8_bfile_set_name(self, dir_alias, filename);
}
oci8_link_to_parent((oci8_base_t*)lob, (oci8_base_t*)DATA_PTR(svc));
return Qnil;
}
/*
* Returns the directory object name.
*
* @return [String]
*/
static VALUE oci8_bfile_get_dir_alias(VALUE self)
{
VALUE dir_alias;
oci8_bfile_get_name(self, &dir_alias, NULL);
return dir_alias;
}
/*
* Returns the file name.
*
* @return [String]
*/
static VALUE oci8_bfile_get_filename(VALUE self)
{
VALUE filename;
oci8_bfile_get_name(self, NULL, &filename);
return filename;
}
/*
* call-seq:
* dir_alias = name
*
* Changes the directory object name.
*
* @param [String] name
*/
static VALUE oci8_bfile_set_dir_alias(VALUE self, VALUE dir_alias)
{
VALUE filename;
OCI8SafeStringValue(dir_alias);
oci8_bfile_get_name(self, NULL, &filename);
oci8_bfile_set_name(self, dir_alias, filename);
rb_ivar_set(self, id_dir_alias, dir_alias);
return dir_alias;
}
/*
* call-seq:
* filename = name
*
* Changes the file name.
*
* @param [String] name
*/
static VALUE oci8_bfile_set_filename(VALUE self, VALUE filename)
{
VALUE dir_alias;
OCI8SafeStringValue(filename);
oci8_bfile_get_name(self, &dir_alias, NULL);
oci8_bfile_set_name(self, dir_alias, filename);
rb_ivar_set(self, id_filename, filename);
return filename;
}
/*
* Returns true
when the BFILE exists on the server's operating system.
*
* @return [true or false]
*/
static VALUE oci8_bfile_exists_p(VALUE self)
{
oci8_lob_t *lob = DATA_PTR(self);
oci8_svcctx_t *svcctx = oci8_get_svcctx(lob->svc);
boolean flag;
chker2(OCILobFileExists_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, lob->base.hp.lob, &flag),
&svcctx->base);
return flag ? Qtrue : Qfalse;
}
/*
* Document-method: OCI8::BFILE#truncate
*
* call-seq:
* truncate(length)
*
* Raises RuntimeError
.
*
* @raise [RuntimeError] cannot modify a read-only BFILE object
*/
/*
* Document-method: OCI8::BFILE#size=
*
* call-seq:
* size = length
*
* Raises RuntimeError
.
*
* @raise [RuntimeError] cannot modify a read-only BFILE object
*/
/*
* Document-method: OCI8::BFILE#write
*
* call-seq:
* write(data)
*
* Raises RuntimeError
.
*
* @raise [RuntimeError] cannot modify a read-only BFILE object
*/
static VALUE oci8_bfile_error(VALUE self, VALUE dummy)
{
rb_raise(rb_eRuntimeError, "cannot modify a read-only BFILE object");
}
/*
* bind_clob/blob/bfile
*/
typedef struct {
oci8_bind_vtable_t bind;
VALUE *klass;
} oci8_bind_lob_vtable_t;
static VALUE bind_lob_get(oci8_bind_t *obind, void *data, void *null_struct)
{
oci8_hp_obj_t *oho = (oci8_hp_obj_t *)data;
return oci8_lob_clone(oho->obj);
}
static void bind_lob_set(oci8_bind_t *obind, void *data, void **null_structp, VALUE val)
{
oci8_hp_obj_t *oho = (oci8_hp_obj_t *)data;
const oci8_bind_lob_vtable_t *vptr = (const oci8_bind_lob_vtable_t *)obind->base.vptr;
oci8_base_t *h;
if (!rb_obj_is_kind_of(val, *vptr->klass))
rb_raise(rb_eArgError, "Invalid argument: %s (expect %s)", rb_class2name(CLASS_OF(val)), rb_class2name(*vptr->klass));
h = DATA_PTR(val);
oho->hp = h->hp.ptr;
oho->obj = val;
}
static void bind_lob_init(oci8_bind_t *obind, VALUE svc, VALUE val, VALUE length)
{
obind->value_sz = sizeof(void *);
obind->alloc_sz = sizeof(oci8_hp_obj_t);
}
static void bind_lob_init_elem(oci8_bind_t *obind, VALUE svc)
{
const oci8_bind_lob_vtable_t *vptr = (const oci8_bind_lob_vtable_t *)obind->base.vptr;
oci8_hp_obj_t *oho = (oci8_hp_obj_t *)obind->valuep;
oci8_base_t *h;
ub4 idx = 0;
do {
oho[idx].obj = rb_funcall(*vptr->klass, oci8_id_new, 1, svc);
h = DATA_PTR(oho[idx].obj);
oho[idx].hp = h->hp.ptr;
} while (++idx < obind->maxar_sz);
}
static void bind_lob_post_bind_hook_for_nclob(oci8_bind_t *obind)
{
ub1 csfrm = SQLCS_NCHAR;
chker2(OCIAttrSet(obind->base.hp.ptr, obind->base.type, (void*)&csfrm, 0, OCI_ATTR_CHARSET_FORM, oci8_errhp),
&obind->base);
}
static const oci8_bind_lob_vtable_t bind_clob_vtable = {
{
{
oci8_bind_hp_obj_mark,
oci8_bind_free,
sizeof(oci8_bind_t)
},
bind_lob_get,
bind_lob_set,
bind_lob_init,
bind_lob_init_elem,
NULL,
SQLT_CLOB
},
&cOCI8CLOB
};
static const oci8_bind_lob_vtable_t bind_nclob_vtable = {
{
{
oci8_bind_hp_obj_mark,
oci8_bind_free,
sizeof(oci8_bind_t)
},
bind_lob_get,
bind_lob_set,
bind_lob_init,
bind_lob_init_elem,
NULL,
SQLT_CLOB,
bind_lob_post_bind_hook_for_nclob,
},
&cOCI8NCLOB
};
static const oci8_bind_lob_vtable_t bind_blob_vtable = {
{
{
oci8_bind_hp_obj_mark,
oci8_bind_free,
sizeof(oci8_bind_t)
},
bind_lob_get,
bind_lob_set,
bind_lob_init,
bind_lob_init_elem,
NULL,
SQLT_BLOB
},
&cOCI8BLOB
};
static const oci8_bind_lob_vtable_t bind_bfile_vtable = {
{
{
oci8_bind_hp_obj_mark,
oci8_bind_free,
sizeof(oci8_bind_t)
},
bind_lob_get,
bind_lob_set,
bind_lob_init,
bind_lob_init_elem,
NULL,
SQLT_BFILE
},
&cOCI8BFILE
};
void Init_oci8_lob(VALUE cOCI8)
{
id_plus = rb_intern("+");
id_dir_alias = rb_intern("@dir_alias");
id_filename = rb_intern("@filename");
seek_set = rb_eval_string("::IO::SEEK_SET");
seek_cur = rb_eval_string("::IO::SEEK_CUR");
seek_end = rb_eval_string("::IO::SEEK_END");
#if 0
cOCIHandle = rb_define_class("OCIHandle", rb_cObject);
cOCI8 = rb_define_class("OCI8", cOCIHandle);
cOCI8LOB = rb_define_class_under(cOCI8, "LOB", cOCIHandle);
#endif
cOCI8LOB = oci8_define_class_under(cOCI8, "LOB", &oci8_lob_vtable);
cOCI8CLOB = rb_define_class_under(cOCI8, "CLOB", cOCI8LOB);
cOCI8NCLOB = rb_define_class_under(cOCI8, "NCLOB", cOCI8LOB);
cOCI8BLOB = rb_define_class_under(cOCI8, "BLOB", cOCI8LOB);
cOCI8BFILE = rb_define_class_under(cOCI8, "BFILE", cOCI8LOB);
rb_define_method(cOCI8CLOB, "initialize", oci8_clob_initialize, -1);
rb_define_method(cOCI8NCLOB, "initialize", oci8_nclob_initialize, -1);
rb_define_method(cOCI8BLOB, "initialize", oci8_blob_initialize, -1);
rb_define_private_method(cOCI8LOB, "__char_width=", oci8_lob_set_char_width, 1);
rb_define_method(cOCI8LOB, "available?", oci8_lob_available_p, 0);
rb_define_method(cOCI8LOB, "size", oci8_lob_get_size, 0);
rb_define_method(cOCI8LOB, "pos", oci8_lob_get_pos, 0);
rb_define_alias(cOCI8LOB, "tell", "pos");
rb_define_method(cOCI8LOB, "eof?", oci8_lob_eof_p, 0);
rb_define_method(cOCI8LOB, "seek", oci8_lob_seek, -1);
rb_define_method(cOCI8LOB, "rewind", oci8_lob_rewind, 0);
rb_define_method(cOCI8LOB, "truncate", oci8_lob_truncate, 1);
rb_define_method(cOCI8LOB, "size=", oci8_lob_set_size, 1);
rb_define_method(cOCI8LOB, "read", oci8_lob_read, -1);
rb_define_method(cOCI8LOB, "write", oci8_lob_write, 1);
rb_define_method(cOCI8LOB, "close", oci8_lob_close, 0);
rb_define_method(cOCI8LOB, "sync", oci8_lob_get_sync, 0);
rb_define_method(cOCI8LOB, "sync=", oci8_lob_set_sync, 1);
rb_define_method(cOCI8LOB, "flush", oci8_lob_flush, 0);
rb_define_method(cOCI8LOB, "chunk_size", oci8_lob_get_chunk_size, 0);
rb_define_method(cOCI8BFILE, "initialize", oci8_bfile_initialize, -1);
rb_define_method(cOCI8BFILE, "dir_alias", oci8_bfile_get_dir_alias, 0);
rb_define_method(cOCI8BFILE, "filename", oci8_bfile_get_filename, 0);
rb_define_method(cOCI8BFILE, "dir_alias=", oci8_bfile_set_dir_alias, 1);
rb_define_method(cOCI8BFILE, "filename=", oci8_bfile_set_filename, 1);
rb_define_method(cOCI8BFILE, "exists?", oci8_bfile_exists_p, 0);
rb_define_method(cOCI8BFILE, "truncate", oci8_bfile_error, 1);
rb_define_method(cOCI8BFILE, "size=", oci8_bfile_error, 1);
rb_define_method(cOCI8BFILE, "write", oci8_bfile_error, 1);
oci8_define_bind_class("CLOB", &bind_clob_vtable.bind);
oci8_define_bind_class("NCLOB", &bind_nclob_vtable.bind);
oci8_define_bind_class("BLOB", &bind_blob_vtable.bind);
oci8_define_bind_class("BFILE", &bind_bfile_vtable.bind);
}