root/pdo_oci/oci_statement.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. oci_stmt_dtor
  2. oci_stmt_execute
  3. oci_bind_input_cb
  4. oci_bind_output_cb
  5. oci_stmt_param_hook
  6. oci_stmt_fetch
  7. oci_define_callback
  8. oci_stmt_describe
  9. oci_blob_write
  10. oci_blob_read
  11. oci_blob_close
  12. oci_blob_flush
  13. oci_blob_seek
  14. oci_create_lob_stream
  15. oci_stmt_get_col

/*
  +----------------------------------------------------------------------+
  | PHP Version 5                                                        |
  +----------------------------------------------------------------------+
  | Copyright (c) 1997-2013 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 <wez@php.net>                                    |
  +----------------------------------------------------------------------+
*/

/* $Id$ */

#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__ TSRMLS_CC); \
                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__ TSRMLS_CC); \
                if (S->last_err) {                                                                                              \
                        return 0;                                                                                                       \
                }                                                                                                                               \
        } while(0)

static php_stream *oci_create_lob_stream(pdo_stmt_t *stmt, OCILobLocator *lob TSRMLS_DC);

static int oci_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC) /* {{{ */
{
        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:
                                                /* do nothing */
                                                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 TSRMLS_DC) /* {{{ */
{
        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;
        TSRMLS_FETCH();

        if (!param || !param->parameter) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "param is NULL in oci_bind_input_cb; this should not happen");
                return OCI_ERROR;
        }

        *indpp = &P->indicator;

        if (P->thing) {
                *bufpp = P->thing;
                *alenp = sizeof(void*);
        } else if (ZVAL_IS_NULL(param->parameter)) {
                /* insert a NULL value into the column */
                P->indicator = -1; /* NULL */
                *bufpp = 0;
                *alenp = -1;
        } else if (!P->thing) {
                /* regular string bind */
                convert_to_string(param->parameter);
                *bufpp = Z_STRVAL_P(param->parameter);
                *alenp = Z_STRLEN_P(param->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;
        TSRMLS_FETCH();

        if (!param || !param->parameter) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "param is NULL in oci_bind_output_cb; this should not happen");
                return OCI_ERROR;
        }

        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(param->parameter) == IS_OBJECT || Z_TYPE_P(param->parameter) == IS_RESOURCE) {
                return OCI_CONTINUE;
        }

        convert_to_string(param->parameter);
        zval_dtor(param->parameter);

        Z_STRLEN_P(param->parameter) = param->max_value_len;
        Z_STRVAL_P(param->parameter) = ecalloc(1, Z_STRLEN_P(param->parameter)+1);
        P->used_for_output = 1;

        P->actual_len = Z_STRLEN_P(param->parameter);
        *alenpp = &P->actual_len;
        *bufpp = Z_STRVAL_P(param->parameter);
        *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 TSRMLS_DC) /* {{{ */
{
        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;

                P = (pdo_oci_bound_param*)param->driver_data;

                switch (event_type) {
                        case PDO_PARAM_EVT_FREE:
                                P = param->driver_data;
                                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 = sizeof(OCILobLocator*);
                                                break;

                                        case PDO_PARAM_STR:
                                        default:
                                                P->oci_type = SQLT_CHR;
                                                value_sz = param->max_value_len;
                                                if (param->max_value_len == 0) {
                                                        value_sz = 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,
                                                        param->namelen, 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, 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(param->parameter) == IS_STRING
#if ZEND_EXTENSION_API_NO < 220040718
                                                                && Z_STRVAL_P(param->parameter) != empty_string
#endif
                                                   ) {
                                                        /* OCI likes to stick non-terminated strings in things */
                                                        *Z_STRVAL_P(param->parameter) = '\0';
                                                }
                                                zval_dtor(param->parameter);
                                                ZVAL_NULL(param->parameter);
                                        } else if (Z_TYPE_P(param->parameter) == IS_STRING
#if ZEND_EXTENSION_API_NO < 220040718
                                                        && Z_STRVAL_P(param->parameter) != empty_string
#endif
                                                        ) {
                                                Z_STRLEN_P(param->parameter) = P->actual_len;
                                                Z_STRVAL_P(param->parameter) = erealloc(Z_STRVAL_P(param->parameter), P->actual_len+1);
                                                Z_STRVAL_P(param->parameter)[P->actual_len] = '\0';
                                        }
                                } else if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && P->thing) {
                                        php_stream *stm;

                                        if (Z_TYPE_P(param->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, (OCILobLocator*)P->thing TSRMLS_CC);
                                                if (stm) {
                                                        OCILobOpen(S->H->svc, S->err, (OCILobLocator*)P->thing, OCI_LOB_READWRITE);
                                                        php_stream_to_zval(stm, param->parameter);
                                                        P->thing = NULL;
                                                }
                                        } 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, &param->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 = n;
                                                                        OCILobWrite(S->H->svc, S->err, (OCILobLocator*)P->thing,
                                                                                        &amt, offset, consume, 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(param->parameter) == IS_STRING) {
                                                        /* stick the string into the LOB */
                                                        consume = Z_STRVAL_P(param->parameter);
                                                        n = Z_STRLEN_P(param->parameter);
                                                        if (n) {
                                                                OCILobOpen(S->H->svc, S->err, (OCILobLocator*)P->thing, OCI_LOB_READWRITE);
                                                                while (n) {
                                                                        amt = n;
                                                                        OCILobWrite(S->H->svc, S->err, (OCILobLocator*)P->thing,
                                                                                        &amt, offset, consume, n,
                                                                                        OCI_ONE_PIECE,
                                                                                        NULL, NULL, 0, SQLCS_IMPLICIT);
                                                                        consume += amt;
                                                                        n -= amt;
                                                                }
                                                                OCILobClose(S->H->svc, S->err, (OCILobLocator*)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,     long offset TSRMLS_DC) /* {{{ */
{
#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, 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;
        TSRMLS_FETCH();

        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 TSRMLS_CC, 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 TSRMLS_DC) /* {{{ */
{
        pdo_oci_stmt *S = (pdo_oci_stmt*)stmt->driver_data;
        OCIParam *param = NULL;
        text *colname;
        ub2 dtype, data_size, scale, 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*)&param, 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));

        /* scale ? */
        STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_SCALE",
                        (param, OCI_DTYPE_PARAM, &scale, 0, OCI_ATTR_SCALE, 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 = scale;
        col->maxlen = data_size;
        col->namelen = namelen;
        col->name = estrndup((char *)colname, namelen);

        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 {
                                S->cols[colno].datalen = col->maxlen;
                        }
                        if (dtype == SQLT_BIN) {
                                S->cols[colno].datalen *= 3;
                        }
                        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_self {
        pdo_stmt_t *stmt;
        pdo_oci_stmt *S;
        OCILobLocator *lob;
        ub4 offset;
};

static size_t oci_blob_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
{
        struct oci_lob_self *self = (struct oci_lob_self*)stream->abstract;
        ub4 amt;
        sword r;

        amt = count;
        r = OCILobWrite(self->S->H->svc, self->S->err, self->lob,
                &amt, self->offset, (char*)buf, count,
                OCI_ONE_PIECE,
                NULL, NULL, 0, SQLCS_IMPLICIT);

        if (r != OCI_SUCCESS) {
                return (size_t)-1;
        }

        self->offset += amt;
        return amt;
}

static size_t oci_blob_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
{
        struct oci_lob_self *self = (struct oci_lob_self*)stream->abstract;
        ub4 amt;
        sword r;

        amt = count;
        r = OCILobRead(self->S->H->svc, self->S->err, self->lob,
                &amt, self->offset, buf, 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 TSRMLS_DC)
{
        struct oci_lob_self *self = (struct oci_lob_self*)stream->abstract;
        pdo_stmt_t *stmt = self->stmt;

        if (close_handle) {
                OCILobClose(self->S->H->svc, self->S->err, self->lob);
                OCIDescriptorFree(self->lob, OCI_DTYPE_LOB);
                efree(self);
        }

        php_pdo_stmt_delref(stmt TSRMLS_CC);
        return 0;
}

static int oci_blob_flush(php_stream *stream TSRMLS_DC)
{
        struct oci_lob_self *self = (struct oci_lob_self*)stream->abstract;
        OCILobFlushBuffer(self->S->H->svc, self->S->err, self->lob, 0);
        return 0;
}

static int oci_blob_seek(php_stream *stream, off_t offset, int whence, off_t *newoffset TSRMLS_DC)
{
        struct oci_lob_self *self = (struct oci_lob_self*)stream->abstract;

        if (offset >= PDO_OCI_LOBMAXSIZE) {
                return -1;
        } else {
                self->offset = offset + 1;  /* Oracle LOBS are 1-based, but PHP is 0-based */
                return 0;
        }
}

static 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(pdo_stmt_t *stmt, OCILobLocator *lob TSRMLS_DC)
{
        php_stream *stm;
        struct oci_lob_self *self = ecalloc(1, sizeof(*self));
        self->lob = lob;
        self->offset = 1; /* 1-based */
        self->stmt = stmt;
        self->S = (pdo_oci_stmt*)stmt->driver_data;

        stm = php_stream_alloc(&oci_blob_stream_ops, self, 0, "r+b");

        if (stm) {
                php_pdo_stmt_addref(stmt TSRMLS_CC);
                return stm;
        }

        efree(self);
        return NULL;
}

static int oci_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, unsigned long *len, int *caller_frees TSRMLS_DC) /* {{{ */
{
        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, (OCILobLocator*)C->data TSRMLS_CC);
                                OCILobOpen(S->H->svc, S->err, (OCILobLocator*)C->data, OCI_LOB_READONLY);
                        }
                        *len = 0;
                        return *ptr ? 1 : 0;
                }

                *ptr = C->data;
                *len = C->fetched_len;
                return 1;
        } else {
                /* it was truncated */
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "column %d data was too large for buffer and was truncated to fit it", colno);

                *ptr = C->data;
                *len = C->fetched_len;
                return 1;
        }
} /* }}} */

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
};

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */

/* [<][>][^][v][top][bottom][index][help] */