/* +----------------------------------------------------------------------+ | PHP Version 7 | +----------------------------------------------------------------------+ | Copyright (c) The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Wez Furlong | +----------------------------------------------------------------------+ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" #include "pdo/php_pdo.h" #include "pdo/php_pdo_driver.h" #include "php_pdo_oci.h" #include "php_pdo_oci_int.h" #include "Zend/zend_extensions.h" #define PDO_OCI_LOBMAXSIZE (4294967295UL) /* OCI_LOBMAXSIZE */ #define STMT_CALL(name, params) \ do { \ S->last_err = name params; \ S->last_err = _oci_error(S->err, stmt->dbh, stmt, #name, S->last_err, FALSE, __FILE__, __LINE__); \ if (S->last_err) { \ return 0; \ } \ } while(0) #define STMT_CALL_MSG(name, msg, params) \ do { \ S->last_err = name params; \ S->last_err = _oci_error(S->err, stmt->dbh, stmt, #name ": " #msg, S->last_err, FALSE, __FILE__, __LINE__); \ if (S->last_err) { \ return 0; \ } \ } while(0) static php_stream *oci_create_lob_stream(zval *dbh, pdo_stmt_t *stmt, OCILobLocator *lob); #define OCI_TEMPLOB_CLOSE(envhp, svchp, errhp, lob) \ do \ { \ boolean isTempLOB; \ OCILobIsTemporary(envhp, errhp, lob, &isTempLOB); \ if (isTempLOB) \ OCILobFreeTemporary(svchp, errhp, lob); \ } while(0) static int oci_stmt_dtor(pdo_stmt_t *stmt) /* {{{ */ { pdo_oci_stmt *S = (pdo_oci_stmt*)stmt->driver_data; HashTable *BC = stmt->bound_columns; HashTable *BP = stmt->bound_params; int i; if (S->stmt) { /* cancel server side resources for the statement if we didn't * fetch it all */ OCIStmtFetch(S->stmt, S->err, 0, OCI_FETCH_NEXT, OCI_DEFAULT); /* free the handle */ OCIHandleFree(S->stmt, OCI_HTYPE_STMT); S->stmt = NULL; } if (S->err) { OCIHandleFree(S->err, OCI_HTYPE_ERROR); S->err = NULL; } /* need to ensure these go away now */ if (BC) { zend_hash_destroy(BC); FREE_HASHTABLE(stmt->bound_columns); stmt->bound_columns = NULL; } if (BP) { zend_hash_destroy(BP); FREE_HASHTABLE(stmt->bound_params); stmt->bound_params = NULL; } if (S->einfo.errmsg) { pefree(S->einfo.errmsg, stmt->dbh->is_persistent); S->einfo.errmsg = NULL; } if (S->cols) { for (i = 0; i < stmt->column_count; i++) { if (S->cols[i].data) { switch (S->cols[i].dtype) { case SQLT_BLOB: case SQLT_CLOB: OCI_TEMPLOB_CLOSE(S->H->env, S->H->svc, S->H->err, (OCILobLocator *) S->cols[i].data); OCIDescriptorFree(S->cols[i].data, OCI_DTYPE_LOB); break; default: efree(S->cols[i].data); } } } efree(S->cols); S->cols = NULL; } efree(S); stmt->driver_data = NULL; return 1; } /* }}} */ static int oci_stmt_execute(pdo_stmt_t *stmt) /* {{{ */ { pdo_oci_stmt *S = (pdo_oci_stmt*)stmt->driver_data; ub4 rowcount; b4 mode; if (!S->stmt_type) { STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_STMT_TYPE", (S->stmt, OCI_HTYPE_STMT, &S->stmt_type, 0, OCI_ATTR_STMT_TYPE, S->err)); } if (stmt->executed) { /* ensure that we cancel the cursor from a previous fetch */ OCIStmtFetch(S->stmt, S->err, 0, OCI_FETCH_NEXT, OCI_DEFAULT); } #ifdef OCI_STMT_SCROLLABLE_READONLY /* needed for oci8 ? */ if (S->exec_type == OCI_STMT_SCROLLABLE_READONLY) { mode = OCI_STMT_SCROLLABLE_READONLY; } else #endif if (stmt->dbh->auto_commit && !stmt->dbh->in_txn) { mode = OCI_COMMIT_ON_SUCCESS; } else { mode = OCI_DEFAULT; } STMT_CALL(OCIStmtExecute, (S->H->svc, S->stmt, S->err, (S->stmt_type == OCI_STMT_SELECT && !S->have_blobs) ? 0 : 1, 0, NULL, NULL, mode)); if (!stmt->executed) { ub4 colcount; /* do first-time-only definition of bind/mapping stuff */ /* how many columns do we have ? */ STMT_CALL_MSG(OCIAttrGet, "ATTR_PARAM_COUNT", (S->stmt, OCI_HTYPE_STMT, &colcount, 0, OCI_ATTR_PARAM_COUNT, S->err)); stmt->column_count = (int)colcount; if (S->cols) { int i; for (i = 0; i < stmt->column_count; i++) { if (S->cols[i].data) { switch (S->cols[i].dtype) { case SQLT_BLOB: case SQLT_CLOB: /* do nothing */ break; default: efree(S->cols[i].data); } } } efree(S->cols); } S->cols = ecalloc(colcount, sizeof(pdo_oci_column)); } STMT_CALL_MSG(OCIAttrGet, "ATTR_ROW_COUNT", (S->stmt, OCI_HTYPE_STMT, &rowcount, 0, OCI_ATTR_ROW_COUNT, S->err)); stmt->row_count = (long)rowcount; return 1; } /* }}} */ static sb4 oci_bind_input_cb(dvoid *ctx, OCIBind *bindp, ub4 iter, ub4 index, dvoid **bufpp, ub4 *alenp, ub1 *piecep, dvoid **indpp) /* {{{ */ { struct pdo_bound_param_data *param = (struct pdo_bound_param_data*)ctx; pdo_oci_bound_param *P = (pdo_oci_bound_param*)param->driver_data; zval *parameter; if (!param) { php_error_docref(NULL, E_WARNING, "param is NULL in oci_bind_input_cb; this should not happen"); return OCI_ERROR; } *indpp = &P->indicator; if (Z_ISREF(param->parameter)) parameter = Z_REFVAL(param->parameter); else parameter = ¶m->parameter; if (P->thing) { *bufpp = P->thing; *alenp = sizeof(void*); } else if (ZVAL_IS_NULL(parameter)) { /* insert a NULL value into the column */ P->indicator = -1; /* NULL */ *bufpp = 0; *alenp = -1; } else if (!P->thing) { /* regular string bind */ if (!try_convert_to_string(parameter)) { return OCI_ERROR; } *bufpp = Z_STRVAL_P(parameter); *alenp = (ub4) Z_STRLEN_P(parameter); } *piecep = OCI_ONE_PIECE; return OCI_CONTINUE; } /* }}} */ static sb4 oci_bind_output_cb(dvoid *ctx, OCIBind *bindp, ub4 iter, ub4 index, dvoid **bufpp, ub4 **alenpp, ub1 *piecep, dvoid **indpp, ub2 **rcodepp) /* {{{ */ { struct pdo_bound_param_data *param = (struct pdo_bound_param_data*)ctx; pdo_oci_bound_param *P = (pdo_oci_bound_param*)param->driver_data; zval *parameter; if (!param) { php_error_docref(NULL, E_WARNING, "param is NULL in oci_bind_output_cb; this should not happen"); return OCI_ERROR; } if (Z_ISREF(param->parameter)) parameter = Z_REFVAL(param->parameter); else parameter = ¶m->parameter; if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { P->actual_len = sizeof(OCILobLocator*); *bufpp = P->thing; *alenpp = &P->actual_len; *piecep = OCI_ONE_PIECE; *rcodepp = &P->retcode; *indpp = &P->indicator; return OCI_CONTINUE; } if (Z_TYPE_P(parameter) == IS_OBJECT || Z_TYPE_P(parameter) == IS_RESOURCE) { return OCI_CONTINUE; } zval_ptr_dtor(parameter); Z_STR_P(parameter) = zend_string_alloc(param->max_value_len, 1); P->used_for_output = 1; P->actual_len = (ub4) Z_STRLEN_P(parameter); *alenpp = &P->actual_len; *bufpp = (Z_STR_P(parameter))->val; *piecep = OCI_ONE_PIECE; *rcodepp = &P->retcode; *indpp = &P->indicator; return OCI_CONTINUE; } /* }}} */ static int oci_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, enum pdo_param_event event_type) /* {{{ */ { pdo_oci_stmt *S = (pdo_oci_stmt*)stmt->driver_data; /* we're only interested in parameters for prepared SQL right now */ if (param->is_param) { pdo_oci_bound_param *P; sb4 value_sz = -1; zval *parameter; if (Z_ISREF(param->parameter)) parameter = Z_REFVAL(param->parameter); else parameter = ¶m->parameter; P = (pdo_oci_bound_param*)param->driver_data; switch (event_type) { case PDO_PARAM_EVT_FETCH_PRE: case PDO_PARAM_EVT_FETCH_POST: case PDO_PARAM_EVT_NORMALIZE: /* Do nothing */ break; case PDO_PARAM_EVT_FREE: P = param->driver_data; if (P && P->thing) { OCI_TEMPLOB_CLOSE(S->H->env, S->H->svc, S->H->err, P->thing); OCIDescriptorFree(P->thing, OCI_DTYPE_LOB); P->thing = NULL; efree(P); } else if (P) { efree(P); } break; case PDO_PARAM_EVT_ALLOC: P = (pdo_oci_bound_param*)ecalloc(1, sizeof(pdo_oci_bound_param)); param->driver_data = P; /* figure out what we're doing */ switch (PDO_PARAM_TYPE(param->param_type)) { case PDO_PARAM_STMT: return 0; case PDO_PARAM_LOB: /* P->thing is now an OCILobLocator * */ P->oci_type = SQLT_BLOB; value_sz = (sb4) sizeof(OCILobLocator*); break; case PDO_PARAM_STR: default: P->oci_type = SQLT_CHR; value_sz = (sb4) param->max_value_len; if (param->max_value_len == 0) { value_sz = (sb4) 1332; /* maximum size before value is interpreted as a LONG value */ } } if (param->name) { STMT_CALL(OCIBindByName, (S->stmt, &P->bind, S->err, (text*)param->name->val, (sb4) param->name->len, 0, value_sz, P->oci_type, &P->indicator, 0, &P->retcode, 0, 0, OCI_DATA_AT_EXEC)); } else { STMT_CALL(OCIBindByPos, (S->stmt, &P->bind, S->err, ((ub4)param->paramno)+1, 0, value_sz, P->oci_type, &P->indicator, 0, &P->retcode, 0, 0, OCI_DATA_AT_EXEC)); } STMT_CALL(OCIBindDynamic, (P->bind, S->err, param, oci_bind_input_cb, param, oci_bind_output_cb)); return 1; case PDO_PARAM_EVT_EXEC_PRE: P->indicator = 0; P->used_for_output = 0; if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { ub4 empty = 0; STMT_CALL(OCIDescriptorAlloc, (S->H->env, &P->thing, OCI_DTYPE_LOB, 0, NULL)); STMT_CALL(OCIAttrSet, (P->thing, OCI_DTYPE_LOB, &empty, 0, OCI_ATTR_LOBEMPTY, S->err)); S->have_blobs = 1; } return 1; case PDO_PARAM_EVT_EXEC_POST: /* fixup stuff set in motion in oci_bind_output_cb */ if (P->used_for_output) { if (P->indicator == -1) { /* set up a NULL value */ if (Z_TYPE_P(parameter) == IS_STRING) { /* OCI likes to stick non-terminated strings in things */ *Z_STRVAL_P(parameter) = '\0'; } zval_ptr_dtor_str(parameter); ZVAL_UNDEF(parameter); } else if (Z_TYPE_P(parameter) == IS_STRING) { Z_STR_P(parameter) = zend_string_init(Z_STRVAL_P(parameter), P->actual_len, 1); } } else if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && P->thing) { php_stream *stm; if (Z_TYPE_P(parameter) == IS_NULL) { /* if the param is NULL, then we assume that they * wanted to bind a lob locator into it from the query * */ stm = oci_create_lob_stream(&stmt->database_object_handle, stmt, (OCILobLocator*)P->thing); if (stm) { OCILobOpen(S->H->svc, S->err, (OCILobLocator*)P->thing, OCI_LOB_READWRITE); php_stream_to_zval(stm, parameter); } } else { /* we're a LOB being used for insert; transfer the data now */ size_t n; ub4 amt, offset = 1; char *consume; php_stream_from_zval_no_verify(stm, parameter); if (stm) { OCILobOpen(S->H->svc, S->err, (OCILobLocator*)P->thing, OCI_LOB_READWRITE); do { char buf[8192]; n = php_stream_read(stm, buf, sizeof(buf)); if ((int)n <= 0) { break; } consume = buf; do { amt = (ub4) n; OCILobWrite(S->H->svc, S->err, (OCILobLocator*)P->thing, &amt, offset, consume, (ub4) n, OCI_ONE_PIECE, NULL, NULL, 0, SQLCS_IMPLICIT); offset += amt; n -= amt; consume += amt; } while (n); } while (1); OCILobClose(S->H->svc, S->err, (OCILobLocator*)P->thing); OCILobFlushBuffer(S->H->svc, S->err, (OCILobLocator*)P->thing, 0); } else if (Z_TYPE_P(parameter) == IS_STRING) { /* stick the string into the LOB */ consume = Z_STRVAL_P(parameter); n = Z_STRLEN_P(parameter); if (n) { OCILobOpen(S->H->svc, S->err, (OCILobLocator*)P->thing, OCI_LOB_READWRITE); while (n) { amt = (ub4) n; OCILobWrite(S->H->svc, S->err, (OCILobLocator*)P->thing, &amt, offset, consume, (ub4) n, OCI_ONE_PIECE, NULL, NULL, 0, SQLCS_IMPLICIT); consume += amt; n -= amt; } OCILobClose(S->H->svc, S->err, (OCILobLocator*)P->thing); } } OCI_TEMPLOB_CLOSE(S->H->env, S->H->svc, S->H->err, P->thing); OCIDescriptorFree(P->thing, OCI_DTYPE_LOB); P->thing = NULL; } } return 1; } } return 1; } /* }}} */ static int oci_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset) /* {{{ */ { #if HAVE_OCISTMTFETCH2 ub4 ociori; #endif pdo_oci_stmt *S = (pdo_oci_stmt*)stmt->driver_data; #if HAVE_OCISTMTFETCH2 switch (ori) { case PDO_FETCH_ORI_NEXT: ociori = OCI_FETCH_NEXT; break; case PDO_FETCH_ORI_PRIOR: ociori = OCI_FETCH_PRIOR; break; case PDO_FETCH_ORI_FIRST: ociori = OCI_FETCH_FIRST; break; case PDO_FETCH_ORI_LAST: ociori = OCI_FETCH_LAST; break; case PDO_FETCH_ORI_ABS: ociori = OCI_FETCH_ABSOLUTE; break; case PDO_FETCH_ORI_REL: ociori = OCI_FETCH_RELATIVE; break; } S->last_err = OCIStmtFetch2(S->stmt, S->err, 1, ociori, (sb4) offset, OCI_DEFAULT); #else S->last_err = OCIStmtFetch(S->stmt, S->err, 1, OCI_FETCH_NEXT, OCI_DEFAULT); #endif if (S->last_err == OCI_NO_DATA) { /* no (more) data */ return 0; } if (S->last_err == OCI_NEED_DATA) { oci_stmt_error("OCI_NEED_DATA"); return 0; } if (S->last_err == OCI_SUCCESS_WITH_INFO || S->last_err == OCI_SUCCESS) { return 1; } oci_stmt_error("OCIStmtFetch"); return 0; } /* }}} */ static sb4 oci_define_callback(dvoid *octxp, OCIDefine *define, ub4 iter, dvoid **bufpp, ub4 **alenpp, ub1 *piecep, dvoid **indpp, ub2 **rcodepp) { pdo_oci_column *col = (pdo_oci_column*)octxp; switch (col->dtype) { case SQLT_BLOB: case SQLT_CLOB: *piecep = OCI_ONE_PIECE; *bufpp = col->data; *alenpp = &col->datalen; *indpp = (dvoid *)&col->indicator; break; default: php_error_docref(NULL, E_WARNING, "unhandled datatype in oci_define_callback; this should not happen"); return OCI_ERROR; } return OCI_CONTINUE; } static int oci_stmt_describe(pdo_stmt_t *stmt, int colno) /* {{{ */ { pdo_oci_stmt *S = (pdo_oci_stmt*)stmt->driver_data; OCIParam *param = NULL; text *colname; ub2 dtype, data_size, precis; ub4 namelen; struct pdo_column_data *col = &stmt->columns[colno]; zend_bool dyn = FALSE; /* describe the column */ STMT_CALL(OCIParamGet, (S->stmt, OCI_HTYPE_STMT, S->err, (dvoid*)¶m, colno+1)); /* what type ? */ STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_DATA_TYPE", (param, OCI_DTYPE_PARAM, &dtype, 0, OCI_ATTR_DATA_TYPE, S->err)); /* how big ? */ STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_DATA_SIZE", (param, OCI_DTYPE_PARAM, &data_size, 0, OCI_ATTR_DATA_SIZE, S->err)); /* precision ? */ STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_PRECISION", (param, OCI_DTYPE_PARAM, &precis, 0, OCI_ATTR_PRECISION, S->err)); /* name ? */ STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_NAME", (param, OCI_DTYPE_PARAM, &colname, &namelen, OCI_ATTR_NAME, S->err)); col->precision = precis; col->maxlen = data_size; col->name = zend_string_init((char *)colname, namelen, 0); S->cols[colno].dtype = dtype; /* how much room do we need to store the field */ switch (dtype) { case SQLT_LBI: case SQLT_LNG: if (dtype == SQLT_LBI) { dtype = SQLT_BIN; } else { dtype = SQLT_CHR; } S->cols[colno].datalen = 512; /* XXX should be INT_MAX and fetched by pieces */ S->cols[colno].data = emalloc(S->cols[colno].datalen + 1); col->param_type = PDO_PARAM_STR; break; case SQLT_BLOB: case SQLT_CLOB: col->param_type = PDO_PARAM_LOB; STMT_CALL(OCIDescriptorAlloc, (S->H->env, (dvoid**)&S->cols[colno].data, OCI_DTYPE_LOB, 0, NULL)); S->cols[colno].datalen = sizeof(OCILobLocator*); dyn = TRUE; break; case SQLT_BIN: default: if (dtype == SQLT_DAT || dtype == SQLT_NUM || dtype == SQLT_RDD #ifdef SQLT_TIMESTAMP || dtype == SQLT_TIMESTAMP #endif #ifdef SQLT_TIMESTAMP_TZ || dtype == SQLT_TIMESTAMP_TZ #endif ) { /* should be big enough for most date formats and numbers */ S->cols[colno].datalen = 512; #if defined(SQLT_IBFLOAT) && defined(SQLT_IBDOUBLE) } else if (dtype == SQLT_IBFLOAT || dtype == SQLT_IBDOUBLE) { S->cols[colno].datalen = 1024; #endif } else if (dtype == SQLT_BIN) { S->cols[colno].datalen = (ub4) col->maxlen * 2; /* raw characters to hex digits */ } else { S->cols[colno].datalen = (ub4) (col->maxlen * S->H->max_char_width); } S->cols[colno].data = emalloc(S->cols[colno].datalen + 1); dtype = SQLT_CHR; /* returning data as a string */ col->param_type = PDO_PARAM_STR; } STMT_CALL(OCIDefineByPos, (S->stmt, &S->cols[colno].def, S->err, colno+1, S->cols[colno].data, S->cols[colno].datalen, dtype, &S->cols[colno].indicator, &S->cols[colno].fetched_len, &S->cols[colno].retcode, dyn ? OCI_DYNAMIC_FETCH : OCI_DEFAULT)); if (dyn) { STMT_CALL(OCIDefineDynamic, (S->cols[colno].def, S->err, &S->cols[colno], oci_define_callback)); } return 1; } /* }}} */ struct _oci_lob_env { OCISvcCtx *svc; OCIError *err; }; typedef struct _oci_lob_env oci_lob_env; struct oci_lob_self { zval dbh; pdo_stmt_t *stmt; pdo_oci_stmt *S; OCILobLocator *lob; oci_lob_env *E; ub4 offset; }; static ssize_t oci_blob_write(php_stream *stream, const char *buf, size_t count) { struct oci_lob_self *self = (struct oci_lob_self*)stream->abstract; ub4 amt; sword r; amt = (ub4) count; r = OCILobWrite(self->E->svc, self->E->err, self->lob, &amt, self->offset, (char*)buf, (ub4) count, OCI_ONE_PIECE, NULL, NULL, 0, SQLCS_IMPLICIT); if (r != OCI_SUCCESS) { return (ssize_t)-1; } self->offset += amt; return amt; } static size_t oci_blob_read(php_stream *stream, char *buf, size_t count) { struct oci_lob_self *self = (struct oci_lob_self*)stream->abstract; ub4 amt; sword r; amt = (ub4) count; r = OCILobRead(self->E->svc, self->E->err, self->lob, &amt, self->offset, buf, (ub4) count, NULL, NULL, 0, SQLCS_IMPLICIT); if (r != OCI_SUCCESS && r != OCI_NEED_DATA) { return (size_t)-1; } self->offset += amt; if (amt < count) { stream->eof = 1; } return amt; } static int oci_blob_close(php_stream *stream, int close_handle) { struct oci_lob_self *self = (struct oci_lob_self *)stream->abstract; pdo_stmt_t *stmt = self->stmt; if (close_handle) { zend_object *obj = &stmt->std; OCILobClose(self->E->svc, self->E->err, self->lob); zval_ptr_dtor(&self->dbh); GC_DELREF(obj); efree(self->E); efree(self); } /* php_pdo_free_statement(stmt); */ return 0; } static int oci_blob_flush(php_stream *stream) { struct oci_lob_self *self = (struct oci_lob_self*)stream->abstract; OCILobFlushBuffer(self->E->svc, self->E->err, self->lob, 0); return 0; } static int oci_blob_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset) { struct oci_lob_self *self = (struct oci_lob_self*)stream->abstract; if (offset >= PDO_OCI_LOBMAXSIZE) { return -1; } else { self->offset = (ub4) offset + 1; /* Oracle LOBS are 1-based, but PHP is 0-based */ return 0; } } static const php_stream_ops oci_blob_stream_ops = { oci_blob_write, oci_blob_read, oci_blob_close, oci_blob_flush, "pdo_oci blob stream", oci_blob_seek, NULL, NULL, NULL }; static php_stream *oci_create_lob_stream(zval *dbh, pdo_stmt_t *stmt, OCILobLocator *lob) { php_stream *stm; struct oci_lob_self *self = ecalloc(1, sizeof(*self)); ZVAL_COPY_VALUE(&self->dbh, dbh); self->lob = lob; self->offset = 1; /* 1-based */ self->stmt = stmt; self->S = (pdo_oci_stmt*)stmt->driver_data; self->E = ecalloc(1, sizeof(oci_lob_env)); self->E->svc = self->S->H->svc; self->E->err = self->S->err; stm = php_stream_alloc(&oci_blob_stream_ops, self, 0, "r+b"); if (stm) { zend_object *obj; obj = &stmt->std; Z_ADDREF(self->dbh); GC_ADDREF(obj); return stm; } efree(self); return NULL; } static int oci_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, size_t *len, int *caller_frees) /* {{{ */ { pdo_oci_stmt *S = (pdo_oci_stmt*)stmt->driver_data; pdo_oci_column *C = &S->cols[colno]; /* check the indicator to ensure that the data is intact */ if (C->indicator == -1) { /* A NULL value */ *ptr = NULL; *len = 0; return 1; } else if (C->indicator == 0) { /* it was stored perfectly */ if (C->dtype == SQLT_BLOB || C->dtype == SQLT_CLOB) { if (C->data) { *ptr = (char*)oci_create_lob_stream(&stmt->database_object_handle, stmt, (OCILobLocator*)C->data); OCILobOpen(S->H->svc, S->err, (OCILobLocator*)C->data, OCI_LOB_READONLY); } *len = (size_t) 0; return *ptr ? 1 : 0; } *ptr = C->data; *len = (size_t) C->fetched_len; return 1; } else { /* it was truncated */ php_error_docref(NULL, E_WARNING, "column %d data was too large for buffer and was truncated to fit it", colno); *ptr = C->data; *len = (size_t) C->fetched_len; return 1; } } /* }}} */ static int oci_stmt_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) /* {{{ */ { pdo_oci_stmt *S = (pdo_oci_stmt*)stmt->driver_data; OCIParam *param = NULL; ub2 dtype, precis; sb1 scale; zval flags; ub1 isnull, charset_form; if (!S->stmt) { return FAILURE; } if (colno >= stmt->column_count) { /* error invalid column */ return FAILURE; } array_init(return_value); array_init(&flags); /* describe the column */ STMT_CALL(OCIParamGet, (S->stmt, OCI_HTYPE_STMT, S->err, (dvoid*)¶m, colno+1)); /* column data type */ STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_DATA_TYPE", (param, OCI_DTYPE_PARAM, &dtype, 0, OCI_ATTR_DATA_TYPE, S->err)); /* column precision */ STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_PRECISION", (param, OCI_DTYPE_PARAM, &precis, 0, OCI_ATTR_PRECISION, S->err)); /* column scale */ STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_SCALE", (param, OCI_DTYPE_PARAM, &scale, 0, OCI_ATTR_SCALE, S->err)); /* string column charset form */ if (dtype == SQLT_CHR || dtype == SQLT_VCS || dtype == SQLT_AFC || dtype == SQLT_CLOB) { STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_CHARSET_FORM", (param, OCI_DTYPE_PARAM, &charset_form, 0, OCI_ATTR_CHARSET_FORM, S->err)); } if (dtype) { /* if there is a declared type */ switch (dtype) { #ifdef SQLT_TIMESTAMP case SQLT_TIMESTAMP: add_assoc_string(return_value, "oci:decl_type", "TIMESTAMP"); add_assoc_string(return_value, "native_type", "TIMESTAMP"); break; #endif #ifdef SQLT_TIMESTAMP_TZ case SQLT_TIMESTAMP_TZ: add_assoc_string(return_value, "oci:decl_type", "TIMESTAMP WITH TIMEZONE"); add_assoc_string(return_value, "native_type", "TIMESTAMP WITH TIMEZONE"); break; #endif #ifdef SQLT_TIMESTAMP_LTZ case SQLT_TIMESTAMP_LTZ: add_assoc_string(return_value, "oci:decl_type", "TIMESTAMP WITH LOCAL TIMEZONE"); add_assoc_string(return_value, "native_type", "TIMESTAMP WITH LOCAL TIMEZONE"); break; #endif #ifdef SQLT_INTERVAL_YM case SQLT_INTERVAL_YM: add_assoc_string(return_value, "oci:decl_type", "INTERVAL YEAR TO MONTH"); add_assoc_string(return_value, "native_type", "INTERVAL YEAR TO MONTH"); break; #endif #ifdef SQLT_INTERVAL_DS case SQLT_INTERVAL_DS: add_assoc_string(return_value, "oci:decl_type", "INTERVAL DAY TO SECOND"); add_assoc_string(return_value, "native_type", "INTERVAL DAY TO SECOND"); break; #endif case SQLT_DAT: add_assoc_string(return_value, "oci:decl_type", "DATE"); add_assoc_string(return_value, "native_type", "DATE"); break; case SQLT_FLT : case SQLT_NUM: /* if the precision is nonzero and scale is -127 then it is a FLOAT */ if (scale == -127 && precis != 0) { add_assoc_string(return_value, "oci:decl_type", "FLOAT"); add_assoc_string(return_value, "native_type", "FLOAT"); } else { add_assoc_string(return_value, "oci:decl_type", "NUMBER"); add_assoc_string(return_value, "native_type", "NUMBER"); } break; case SQLT_LNG: add_assoc_string(return_value, "oci:decl_type", "LONG"); add_assoc_string(return_value, "native_type", "LONG"); break; case SQLT_BIN: add_assoc_string(return_value, "oci:decl_type", "RAW"); add_assoc_string(return_value, "native_type", "RAW"); break; case SQLT_LBI: add_assoc_string(return_value, "oci:decl_type", "LONG RAW"); add_assoc_string(return_value, "native_type", "LONG RAW"); break; case SQLT_CHR: case SQLT_VCS: if (charset_form == SQLCS_NCHAR) { add_assoc_string(return_value, "oci:decl_type", "NVARCHAR2"); add_assoc_string(return_value, "native_type", "NVARCHAR2"); } else { add_assoc_string(return_value, "oci:decl_type", "VARCHAR2"); add_assoc_string(return_value, "native_type", "VARCHAR2"); } break; case SQLT_AFC: if (charset_form == SQLCS_NCHAR) { add_assoc_string(return_value, "oci:decl_type", "NCHAR"); add_assoc_string(return_value, "native_type", "NCHAR"); } else { add_assoc_string(return_value, "oci:decl_type", "CHAR"); add_assoc_string(return_value, "native_type", "CHAR"); } break; case SQLT_BLOB: add_assoc_string(return_value, "oci:decl_type", "BLOB"); add_next_index_string(&flags, "blob"); add_assoc_string(return_value, "native_type", "BLOB"); break; case SQLT_CLOB: if (charset_form == SQLCS_NCHAR) { add_assoc_string(return_value, "oci:decl_type", "NCLOB"); add_assoc_string(return_value, "native_type", "NCLOB"); } else { add_assoc_string(return_value, "oci:decl_type", "CLOB"); add_assoc_string(return_value, "native_type", "CLOB"); } add_next_index_string(&flags, "blob"); break; case SQLT_BFILE: add_assoc_string(return_value, "oci:decl_type", "BFILE"); add_next_index_string(&flags, "blob"); add_assoc_string(return_value, "native_type", "BFILE"); break; case SQLT_RDD: add_assoc_string(return_value, "oci:decl_type", "ROWID"); add_assoc_string(return_value, "native_type", "ROWID"); break; case SQLT_BFLOAT: case SQLT_IBFLOAT: add_assoc_string(return_value, "oci:decl_type", "BINARY_FLOAT"); add_assoc_string(return_value, "native_type", "BINARY_FLOAT"); break; case SQLT_BDOUBLE: case SQLT_IBDOUBLE: add_assoc_string(return_value, "oci:decl_type", "BINARY_DOUBLE"); add_assoc_string(return_value, "native_type", "BINARY_DOUBLE"); break; default: add_assoc_long(return_value, "oci:decl_type", dtype); add_assoc_string(return_value, "native_type", "UNKNOWN"); } } else { /* if the column is NULL */ add_assoc_long(return_value, "oci:decl_type", 0); add_assoc_string(return_value, "native_type", "NULL"); } /* column can be null */ STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_IS_NULL", (param, OCI_DTYPE_PARAM, &isnull, 0, OCI_ATTR_IS_NULL, S->err)); if (isnull) { add_next_index_string(&flags, "nullable"); } else { add_next_index_string(&flags, "not_null"); } /* PDO type */ switch (dtype) { case SQLT_BFILE: case SQLT_BLOB: case SQLT_CLOB: add_assoc_long(return_value, "pdo_type", PDO_PARAM_LOB); break; default: add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); } add_assoc_long(return_value, "scale", scale); add_assoc_zval(return_value, "flags", &flags); OCIDescriptorFree(param, OCI_DTYPE_PARAM); return SUCCESS; } /* }}} */ const struct pdo_stmt_methods oci_stmt_methods = { oci_stmt_dtor, oci_stmt_execute, oci_stmt_fetch, oci_stmt_describe, oci_stmt_get_col, oci_stmt_param_hook, NULL, /* set_attr */ NULL, /* get_attr */ oci_stmt_col_meta };