]> granicus.if.org Git - php/commitdiff
Add the PDOStatement::getColumnMeta() function to the pdo_oci driver
authorLetargie <valentin@famillecollet.com>
Thu, 20 Sep 2018 10:03:57 +0000 (12:03 +0200)
committerLetargie <valentin@famillecollet.com>
Thu, 20 Sep 2018 10:03:57 +0000 (12:03 +0200)
ext/pdo_oci/oci_statement.c
ext/pdo_oci/tests/pdo_oci_stmt_getcolumnmeta.phpt [new file with mode: 0755]

index f2c43b9a6975a444f5b3c76fde1c81801f6b84de..e5d40e872e541dbd055b5d9fd5fb008e08ea39a2 100644 (file)
@@ -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 (executable)
index 0000000..87a1f13
--- /dev/null
@@ -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!