php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Return to Bug #76908
Patch pdo_oci_stmt_col_meta revision 2018-09-20 10:15 UTC by valentin at famillecollet dot com

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*)&param, 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!
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Tue Apr 23 10:01:29 2024 UTC