Patch pdo_oci_stmt_col_meta for PDO OCI Bug #76908
Patch version 2018-09-20 10:15 UTC
Return to Bug #76908 |
Download this patch
Patch Revisions:
Developer: valentin@famillecollet.com
diff --git a/ext/pdo_oci/oci_statement.c b/ext/pdo_oci/oci_statement.c
index f2c43b9..e5d40e8 100644
--- a/ext/pdo_oci/oci_statement.c
+++ b/ext/pdo_oci/oci_statement.c
@@ -793,15 +793,198 @@ static int oci_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, size_t *len
}
} /* }}} */
+
+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;
+ pdo_oci_column *C = &S->cols[colno];
+
+ OCIParam *param = NULL;
+ OraText *colname;
+ OraText * schema;
+ ub2 dtype, data_size, precis = 0;
+ ub4 namelen, schemalen, typelen, objlen;
+ char *str;
+ zval flags;
+ ub1 isnull;
+ 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 length */
+ STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_DATA_SIZE",
+ (param, OCI_DTYPE_PARAM, &data_size, 0, OCI_ATTR_DATA_SIZE, S->err));
+ /* column precision */
+ STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_PRECISION",
+ (param, OCI_DTYPE_PARAM, &precis, 0, OCI_ATTR_PRECISION, 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_NUM:
+ 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:
+ add_assoc_string(return_value, "oci:decl_type", "VARCHAR2");
+ add_assoc_string(return_value, "native_type", "VARCHAR2");
+ break;
+ case SQLT_AFC:
+ 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:
+ add_assoc_string(return_value, "oci:decl_type", "CLOB");
+ add_next_index_string(&flags, "blob");
+ add_assoc_string(return_value, "native_type", "CLOB");
+ 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_FLT :
+ case SQLT_BFLOAT:
+ case SQLT_IBFLOAT:
+ add_assoc_string(return_value, "oci:decl_type", "FLOAT");
+ add_assoc_string(return_value, "native_type", "FLOAT");
+ break;
+ case SQLT_BDOUBLE:
+ case SQLT_IBDOUBLE:
+ add_assoc_string(return_value, "oci:decl_type", "DOUBLE");
+ add_assoc_string(return_value, "native_type", "DOUBLE");
+ break;
+ default:
+ add_assoc_long(return_value, "oci:decl_type", dtype);
+ add_assoc_string(return_value, "native_type", "UNKNOWN");
+ }
+ } else if (data_size) {
+ // if the column is the result of a function
+ 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));
+
+ /* column name */
+ STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_NAME",
+ (param, OCI_DTYPE_PARAM, &colname, (ub4 *) &namelen, OCI_ATTR_NAME, S->err));
+
+ add_assoc_long(return_value, "precision", precis);
+ add_assoc_long(return_value, "len", data_size);
+ add_assoc_string(return_value, "name", (char *) colname);
+
+ 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;
+ case SQLT_BIN:
+ default:
+ add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR);
+ }
+
+ 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
+ oci_stmt_param_hook,
+ NULL, /* set_attr */
+ NULL, /* get_attr */
+ oci_stmt_col_meta
};
+
/*
* Local variables:
* tab-width: 4
diff --git a/ext/pdo_oci/tests/pdo_oci_stmt_getcolumnmeta.phpt b/ext/pdo_oci/tests/pdo_oci_stmt_getcolumnmeta.phpt
new file mode 100755
index 0000000..87a1f13
--- /dev/null
+++ b/ext/pdo_oci/tests/pdo_oci_stmt_getcolumnmeta.phpt
@@ -0,0 +1,264 @@
+--TEST--
+PDO_OCI: PDOStatement->getColumnMeta
+--SKIPIF--
+<?php # vim:ft=php
+if (!extension_loaded('pdo') || !extension_loaded('pdo_oci')) die('skip not loaded');
+require(dirname(__FILE__) . '/../../pdo/tests/pdo_test.inc');
+PDOTest::skip();
+?>
+--FILE--
+<?php
+require(dirname(__FILE__) . '/../../pdo/tests/pdo_test.inc');
+$db = PDOTest::factory();
+$db->exec(<<<SQL
+BEGIN
+ EXECUTE IMMEDIATE 'DROP TABLE test';
+EXCEPTION
+ WHEN OTHERS THEN
+ IF SQLCODE != -942 THEN
+ RAISE;
+ END IF;
+END;
+SQL
+);
+$db->exec("CREATE TABLE test(id INT)");
+
+$db->beginTransaction();
+
+try {
+
+ $stmt = $db->prepare('SELECT id FROM test ORDER BY id ASC');
+
+ // execute() has not been called yet
+ // NOTE: no warning
+ if (false !== ($tmp = $stmt->getColumnMeta(0)))
+ printf("[002] Expecting false got %s\n", var_export($tmp, true));
+
+ $stmt->execute();
+ // Warning: PDOStatement::getColumnMeta() expects exactly 1 parameter, 0 given in
+ if (false !== ($tmp = @$stmt->getColumnMeta()))
+ printf("[003] Expecting false got %s\n", var_export($tmp, true));
+
+ // invalid offset
+ if (false !== ($tmp = @$stmt->getColumnMeta(-1)))
+ printf("[004] Expecting false got %s\n", var_export($tmp, true));
+
+ // Warning: PDOStatement::getColumnMeta() expects parameter 1 to be int, array given in
+ if (false !== ($tmp = @$stmt->getColumnMeta(array())))
+ printf("[005] Expecting false got %s\n", var_export($tmp, true));
+
+ // Warning: PDOStatement::getColumnMeta() expects exactly 1 parameter, 2 given in
+ if (false !== ($tmp = @$stmt->getColumnMeta(1, 1)))
+ printf("[006] Expecting false got %s\n", var_export($tmp, true));
+
+ $emulated = $stmt->getColumnMeta(0);
+
+ printf("Testing native PS...\n");
+
+ $stmt = $db->prepare('SELECT id FROM test ORDER BY id ASC');
+ $stmt->execute();
+ $native = $stmt->getColumnMeta(0);
+ if (count($native) == 0) {
+ printf("[008] Meta data seems wrong, %s / %s\n",
+ var_export($native, true), var_export($emulated, true));
+ }
+
+ // invalid offset
+ if (false !== ($tmp = $stmt->getColumnMeta(1)))
+ printf("[009] Expecting false because of invalid offset got %s\n", var_export($tmp, true));
+
+
+ function test_meta(&$db, $offset, $sql_type, $value, $native_type, $pdo_type) {
+
+ $db->exec(<<<SQL
+BEGIN
+ EXECUTE IMMEDIATE 'DROP TABLE test';
+EXCEPTION
+ WHEN OTHERS THEN
+ IF SQLCODE != -942 THEN
+ RAISE;
+ END IF;
+END;
+SQL
+);
+
+ $sql = sprintf('CREATE TABLE test(id INT, label %s)', $sql_type);
+ if (!($stmt = @$db->prepare($sql)) || (!@$stmt->execute())) {
+ // Some engines might not support the data type
+ return true;
+ }
+
+ if (!$db->exec(sprintf("INSERT INTO test(id, label) VALUES (1, '%s')", $value))) {
+ printf("[%03d] + 1] Insert failed, %d - %s\n", $offset,
+ $db->errorCode(), var_export($db->errorInfo(), true));
+ return false;
+ }
+
+ $stmt = $db->prepare('SELECT id, label FROM test');
+ $stmt->execute();
+ $meta = $stmt->getColumnMeta(1);
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ if (empty($meta)) {
+ printf("[%03d + 2] getColumnMeta() failed, %d - %s\n", $offset,
+ $stmt->errorCode(), var_export($stmt->errorInfo(), true));
+ return false;
+ }
+
+ $elements = array('flags', 'name', 'len', 'precision', 'pdo_type');
+ foreach ($elements as $k => $element)
+ if (!isset($meta[$element])) {
+ printf("[%03d + 3] Element %s missing, %s\n", $offset,
+ $element, var_export($meta, true));
+ return false;
+ }
+
+ if (!is_null($native_type)) {
+ if (!isset($meta['native_type'])) {
+ printf("[%03d + 5] Element native_type missing, %s\n", $offset,
+ var_export($meta, true));
+ return false;
+ }
+
+ if (!is_array($native_type))
+ $native_type = array($native_type);
+
+ $found = false;
+ foreach ($native_type as $k => $type) {
+ if ($meta['native_type'] == $type) {
+ $found = true;
+ break;
+ }
+ }
+
+ if (!$found) {
+ printf("[%03d + 6] Expecting native type %s, %s\n", $offset,
+ var_export($native_type, true), var_export($meta, true));
+ return false;
+ }
+ }
+
+ if (!is_null($pdo_type) && ($meta['pdo_type'] != $pdo_type)) {
+ printf("[%03d + 6] Expecting PDO type %s got %s (%s)\n", $offset,
+ $pdo_type, var_export($meta, true), var_export($meta['native_type']));
+ return false;
+ }
+
+ return true;
+ }
+
+ test_meta($db, 10, 'NUMBER' , 0 , 'NUMBER', PDO::PARAM_STR);
+ test_meta($db, 20, 'NUMBER' , 256 , 'NUMBER', PDO::PARAM_STR);
+ test_meta($db, 20, 'NUMBER' , 256 , 'NUMBER', PDO::PARAM_STR);
+ test_meta($db, 20, 'INT' , 256 , 'NUMBER', PDO::PARAM_STR);
+ test_meta($db, 20, 'INTEGER' , 256 , 'NUMBER', PDO::PARAM_STR);
+ test_meta($db, 20, 'NUMBER' , 256.01 , 'NUMBER', PDO::PARAM_STR);
+ test_meta($db, 30, 'NUMBER' , -8388608 , 'NUMBER', PDO::PARAM_STR);
+
+ test_meta($db, 40, 'NUMBER' , 2147483648 , 'NUMBER', PDO::PARAM_STR);
+ test_meta($db, 50, 'NUMBER' , 4294967295 , 'NUMBER', PDO::PARAM_STR);
+
+ test_meta($db, 60, 'DECIMAL' , 1.01 , 'NUMBER', PDO::PARAM_STR);
+ test_meta($db, 60, 'FLOAT' , 1.01 , 'NUMBER', PDO::PARAM_STR);
+ test_meta($db, 70, 'DOUBLE' , 1.01 , 'NUMBER', PDO::PARAM_STR);
+ test_meta($db, 60, 'BINARY_FLOAT' , 1.01 , 'FLOAT', PDO::PARAM_STR);
+ test_meta($db, 70, 'BINARY_DOUBLE' , 1.01 , 'DOUBLE', PDO::PARAM_STR);
+
+ test_meta($db, 80, 'DATE' , '2008-04-23' , 'DATE', PDO::PARAM_STR);
+ test_meta($db, 90, 'TIME' , '14:37:00' , 'TIME', PDO::PARAM_STR);
+ test_meta($db, 110, 'YEAR' , '2008' , 'YEAR', PDO::PARAM_STR);
+
+ test_meta($db, 120, 'CHAR(1)' , 'a' , 'CHAR', PDO::PARAM_STR);
+ test_meta($db, 130, 'CHAR(10)' , '0123456789' , 'CHAR', PDO::PARAM_STR);
+ test_meta($db, 140, 'CHAR(255)' , str_repeat('z', 255) , 'CHAR', PDO::PARAM_STR);
+ test_meta($db, 150, 'VARCHAR(1)' , 'a' , 'VARCHAR2', PDO::PARAM_STR);
+ test_meta($db, 160, 'VARCHAR(10)' , '0123456789' , 'VARCHAR2', PDO::PARAM_STR);
+ test_meta($db, 170, 'VARCHAR(255)' , str_repeat('z', 255) , 'VARCHAR2', PDO::PARAM_STR);
+ test_meta($db, 180, 'VARCHAR2(1)' , 'a' , 'VARCHAR2', PDO::PARAM_STR);
+ test_meta($db, 190, 'VARCHAR2(10)' , '0123456789' , 'VARCHAR2', PDO::PARAM_STR);
+ test_meta($db, 200, 'VARCHAR2(255)' , str_repeat('z', 255) , 'VARCHAR2', PDO::PARAM_STR);
+
+ test_meta($db, 210, 'NCHAR(1)' , 'a' , 'CHAR', PDO::PARAM_STR);
+ test_meta($db, 220, 'NCHAR(10)' , '0123456789' , 'CHAR', PDO::PARAM_STR);
+ test_meta($db, 230, 'NCHAR(255)' , str_repeat('z', 255) , 'CHAR', PDO::PARAM_STR);
+ test_meta($db, 240, 'NVARCHAR2(1)' , 'a' , 'VARCHAR2', PDO::PARAM_STR);
+ test_meta($db, 250, 'NVARCHAR2(10)' , '0123456789' , 'VARCHAR2', PDO::PARAM_STR);
+ test_meta($db, 260, 'NVARCHAR2(255)', str_repeat('z', 255) , 'VARCHAR2', PDO::PARAM_STR);
+
+ test_meta($db, 270, 'CLOB' , str_repeat('b', 255) , 'CLOB', PDO::PARAM_LOB);
+ test_meta($db, 280, 'BLOB' , str_repeat('b', 256) , 'BLOB', PDO::PARAM_LOB);
+ test_meta($db, 290, 'NCLOB' , str_repeat('b', 255) , 'CLOB', PDO::PARAM_LOB);
+
+ test_meta($db, 300, 'LONG' , str_repeat('b', 256) , 'LONG', PDO::PARAM_STR);
+ test_meta($db, 310, 'LONG RAW' , str_repeat('b', 256) , 'LONG RAW', PDO::PARAM_STR);
+ test_meta($db, 320, 'RAW' , str_repeat('b', 256) , 'RAW', PDO::PARAM_STR);
+
+ $db->exec(<<<SQL
+BEGIN
+ EXECUTE IMMEDIATE 'DROP TABLE test';
+EXCEPTION
+ WHEN OTHERS THEN
+ IF SQLCODE != -942 THEN
+ RAISE;
+ END IF;
+END;
+SQL
+);
+ $sql = sprintf('CREATE TABLE test(id INT NOT NULL, label INT NULL)');
+ if (($stmt = $db->prepare($sql)) && $stmt->execute()) {
+ $db->exec('INSERT INTO test(id, label) VALUES (1, 1)');
+ $stmt = $db->query('SELECT id, label FROM test');
+ $meta = $stmt->getColumnMeta(0);
+ if (!isset($meta['flags'])) {
+ printf("[1002] No flags contained in metadata %s\n", var_export($meta, true));
+ } else {
+ $flags = $meta['flags'];
+ $found = false;
+ foreach ($flags as $k => $flag) {
+ if ($flag == 'not_null')
+ $found = true;
+ if ($flag == 'nullable')
+ printf("[1003] Flags seem wrong %s\n", var_export($meta, true));
+ }
+ if (!$found)
+ printf("[1003] Flags seem wrong %s\n", var_export($meta, true));
+ }
+ $meta = $stmt->getColumnMeta(1);
+ if (!isset($meta['flags'])) {
+ printf("[1002] No flags contained in metadata %s\n", var_export($meta, true));
+ } else {
+ $flags = $meta['flags'];
+ $found = false;
+ foreach ($flags as $k => $flag) {
+ if ($flag == 'not_null')
+ printf("[1003] Flags seem wrong %s\n", var_export($meta, true));
+ if ($flag == 'nullable')
+ $found = true;
+ }
+ if (!$found)
+ printf("[1003] Flags seem wrong %s\n", var_export($meta, true));
+ }
+ }
+
+} catch (PDOException $e) {
+ // we should never get here, we use warnings, but never trust a system...
+ printf("[001] %s, [%s} %s\n",
+ $e->getMessage(), $db->errorInfo(), implode(' ', $db->errorInfo()));
+}
+
+$db->exec(<<<SQL
+BEGIN
+ EXECUTE IMMEDIATE 'DROP TABLE test';
+EXCEPTION
+ WHEN OTHERS THEN
+ IF SQLCODE != -942 THEN
+ RAISE;
+ END IF;
+END;
+SQL
+);
+print "done!";
+?>
+--EXPECT--
+Testing native PS...
+done!
|