Skip to content

Let's implement PDO getColumnMeta more sensibly #6930

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
3 changes: 0 additions & 3 deletions ext/mysqlnd/mysqlnd_enum_n_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -364,9 +364,6 @@ typedef enum mysqlnd_server_option
#define GROUP_FLAG 32768
#define NUM_FLAG 32768

#define IS_PRI_KEY(n) ((n) & PRI_KEY_FLAG)
#define IS_NOT_NULL(n) ((n) & NOT_NULL_FLAG)
#define IS_BLOB(n) ((n) & BLOB_FLAG)
#define IS_NUM(t) ((t) <= FIELD_TYPE_INT24 || (t) == FIELD_TYPE_YEAR || (t) == FIELD_TYPE_NEWDECIMAL)


Expand Down
17 changes: 13 additions & 4 deletions ext/pdo/pdo_stmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -1670,16 +1670,25 @@ PHP_METHOD(PDOStatement, getColumnMeta)
ZEND_PARSE_PARAMETERS_END();

PHP_STMT_GET_OBJ;
if (colno < 0) {
zend_argument_value_error(1, "must be greater than or equal to 0");
RETURN_THROWS();
}

if (!stmt->methods->get_column_meta) {
pdo_raise_impl_error(stmt->dbh, stmt, "IM001", "driver doesn't support meta data");
RETURN_FALSE;
}

if (stmt->column_count == 0) {
/* Can't fetch meta data for a column if there are no columns */
RETURN_FALSE;
}

if (colno < 0) {
zend_argument_value_error(1, "must be greater than or equal to 0");
RETURN_THROWS();
} else if (colno >= stmt->column_count) {
zend_argument_value_error(1, "must be less than the number of columns");
RETURN_THROWS();
}

PDO_STMT_CLEAR_ERR();
if (FAILURE == stmt->methods->get_column_meta(stmt, colno, return_value)) {
PDO_HANDLE_STMT_ERR();
Expand Down
72 changes: 49 additions & 23 deletions ext/pdo/tests/pdo_022.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -17,45 +17,71 @@ PDOTest::skip();
* test file.
*/
?>
--XFAIL--
This feature is not yet finalized, no test makes sense
--FILE--
<?php
if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.__DIR__ . '/../../pdo/tests/');

if (getenv('REDIR_TEST_DIR') === false) {
putenv('REDIR_TEST_DIR='.__DIR__ . '/../../pdo/tests/');
}
require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
$db = PDOTest::factory();

$db->exec('CREATE TABLE test(id INT NOT NULL PRIMARY KEY, val VARCHAR(10), val2 VARCHAR(16))');

$data = array(
array('10', 'Abc', 'zxy'),
array('20', 'Def', 'wvu'),
array('30', 'Ghi', 'tsr'),
array('40', 'Jkl', 'qpo'),
array('50', 'Mno', 'nml'),
array('60', 'Pqr', 'kji'),
);
/**
* Enforce basic columns and forbid native_type
*/
function checkColumns(array $meta): void
{
$expected = [
'name' => 'string',
'len' => 'int',
'precision' => 'int',
];
$forbidden = [
'native_type'
];
$existing = array_keys($meta);

foreach ($expected as $key => $type) {
if (!in_array($key, $existing, true)) {
throw new Exception('Missing column '.$key);
}
if (get_debug_type($meta[$key]) !== $type) {
throw new Exception('Wrong type for column '.$key.', expected '.$type.' but got '.get_debug_type($meta[$key]));
}
}
foreach ($forbidden as $key) {
if (in_array($key, $existing, true)) {
throw new Exception('Forbidden column '.$key);
}
}
}

// Insert using question mark placeholders
$stmt = $db->prepare("INSERT INTO test VALUES(?, ?, ?)");
foreach ($data as $row) {
$stmt->execute($row);
}
$stmt->execute(array('10', 'Abc', 'zxy'));

// Retrieve column metadata for a result set returned by explicit SELECT
$select = $db->query('SELECT id, val, val2 FROM test');
$meta = $select->getColumnMeta(0);
var_dump($meta);
$meta = $select->getColumnMeta(1);
var_dump($meta);
$meta = $select->getColumnMeta(2);
var_dump($meta);
checkColumns($select->getColumnMeta(0));

try {
$meta = $select->getColumnMeta(3);
} catch (ValueError $e) {
printf("%s\n", $e->getMessage());
}
unset($select);

$stmt = $db->prepare("SELECT 42");
if (false !== $stmt->getColumnMeta(0)) {
throw new Exception('If no columns are present in the statement then the function should return false.');
}

// Retrieve column metadata for a result set returned by a function
$select = $db->query('SELECT COUNT(*) FROM test');
$meta = $select->getColumnMeta(0);
var_dump($meta);
checkColumns($select->getColumnMeta(0));

?>
--EXPECT--
The unexpected!
PDOStatement::getColumnMeta(): Argument #1 ($column) must be less than the number of columns
6 changes: 1 addition & 5 deletions ext/pdo_dblib/dblib_stmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -469,10 +469,6 @@ static int pdo_dblib_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zva
DBTYPEINFO* dbtypeinfo;
int coltype;

if(colno >= stmt->column_count || colno < 0) {
return FAILURE;
}

array_init(return_value);

dbtypeinfo = dbcoltypeinfo(H->link, colno+1);
Expand All @@ -485,7 +481,7 @@ static int pdo_dblib_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zva
add_assoc_long(return_value, "precision", (int) dbtypeinfo->precision );
add_assoc_long(return_value, "scale", (int) dbtypeinfo->scale );
add_assoc_string(return_value, "column_source", dbcolsource(H->link, colno+1));
add_assoc_string(return_value, "native_type", pdo_dblib_get_field_name(coltype));
add_assoc_string(return_value, "dblib:decl_type", pdo_dblib_get_field_name(coltype));
add_assoc_long(return_value, "native_type_id", coltype);
add_assoc_long(return_value, "native_usertype_id", dbcolutype(H->link, colno+1));

Expand Down
2 changes: 1 addition & 1 deletion ext/pdo_dblib/tests/bug_45876.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ array(10) {
int(0)
["column_source"]=>
string(13) "TABLE_CATALOG"
["native_type"]=>
["dblib:decl_type"]=>
string(4) "char"
["native_type_id"]=>
int(%d)
Expand Down
150 changes: 83 additions & 67 deletions ext/pdo_mysql/mysql_statement.c
Original file line number Diff line number Diff line change
Expand Up @@ -713,71 +713,83 @@ static int pdo_mysql_stmt_get_col(
#endif
} /* }}} */

static char *type_to_name_native(int type) /* {{{ */
static char *type_to_name_native(int type, int flags) /* {{{ */
{
#define PDO_MYSQL_NATIVE_TYPE_NAME(x) case FIELD_TYPE_##x: return #x;

switch (type) {
PDO_MYSQL_NATIVE_TYPE_NAME(STRING)
PDO_MYSQL_NATIVE_TYPE_NAME(VAR_STRING)
#ifdef FIELD_TYPE_TINY
PDO_MYSQL_NATIVE_TYPE_NAME(TINY)
#endif
#ifdef FIELD_TYPE_BIT
PDO_MYSQL_NATIVE_TYPE_NAME(BIT)
#endif
PDO_MYSQL_NATIVE_TYPE_NAME(SHORT)
PDO_MYSQL_NATIVE_TYPE_NAME(LONG)
PDO_MYSQL_NATIVE_TYPE_NAME(LONGLONG)
PDO_MYSQL_NATIVE_TYPE_NAME(INT24)
PDO_MYSQL_NATIVE_TYPE_NAME(FLOAT)
PDO_MYSQL_NATIVE_TYPE_NAME(DOUBLE)
PDO_MYSQL_NATIVE_TYPE_NAME(DECIMAL)
#ifdef FIELD_TYPE_NEWDECIMAL
PDO_MYSQL_NATIVE_TYPE_NAME(NEWDECIMAL)
#endif
#ifdef FIELD_TYPE_GEOMETRY
PDO_MYSQL_NATIVE_TYPE_NAME(GEOMETRY)
#endif
PDO_MYSQL_NATIVE_TYPE_NAME(TIMESTAMP)
#ifdef FIELD_TYPE_YEAR
PDO_MYSQL_NATIVE_TYPE_NAME(YEAR)
#endif
PDO_MYSQL_NATIVE_TYPE_NAME(SET)
PDO_MYSQL_NATIVE_TYPE_NAME(ENUM)
PDO_MYSQL_NATIVE_TYPE_NAME(DATE)
#ifdef FIELD_TYPE_NEWDATE
PDO_MYSQL_NATIVE_TYPE_NAME(NEWDATE)
#endif
PDO_MYSQL_NATIVE_TYPE_NAME(TIME)
PDO_MYSQL_NATIVE_TYPE_NAME(DATETIME)
PDO_MYSQL_NATIVE_TYPE_NAME(TINY_BLOB)
PDO_MYSQL_NATIVE_TYPE_NAME(MEDIUM_BLOB)
PDO_MYSQL_NATIVE_TYPE_NAME(LONG_BLOB)
PDO_MYSQL_NATIVE_TYPE_NAME(BLOB)
PDO_MYSQL_NATIVE_TYPE_NAME(NULL)
default:
case MYSQL_TYPE_DECIMAL:
case MYSQL_TYPE_NEWDECIMAL:
return "DECIMAL";
case MYSQL_TYPE_TINY:
return "TINYINT";
case MYSQL_TYPE_SHORT:
return "SMALLINT";
case MYSQL_TYPE_LONG:
return "INT";
case MYSQL_TYPE_FLOAT:
return "FLOAT";
case MYSQL_TYPE_DOUBLE:
return "DOUBLE";
case MYSQL_TYPE_NULL:
return "NULL";
case MYSQL_TYPE_TIMESTAMP:
return "TIMESTAMP";
case MYSQL_TYPE_LONGLONG:
return "BIGINT";
case MYSQL_TYPE_INT24:
return "MEDIUMINT";
case MYSQL_TYPE_DATE:
case MYSQL_TYPE_NEWDATE:
return "DATE";
case MYSQL_TYPE_TIME:
return "TIME";
case MYSQL_TYPE_DATETIME:
return "DATETIME";
case MYSQL_TYPE_YEAR:
return "YEAR";
case MYSQL_TYPE_VARCHAR:
return "VARCHAR";
case MYSQL_TYPE_BIT:
return "BIT";
case MYSQL_TYPE_JSON:
return "JSON";
case MYSQL_TYPE_ENUM:
return "ENUM";
case MYSQL_TYPE_SET:
return "SET";
case MYSQL_TYPE_TINY_BLOB:
return (flags & BINARY_FLAG) ? "TINYBLOB" : "TINYTEXT";
case MYSQL_TYPE_MEDIUM_BLOB:
return (flags & BINARY_FLAG) ? "MEDIUMBLOB" : "MEDIUMTEXT";
case MYSQL_TYPE_LONG_BLOB:
return (flags & BINARY_FLAG) ? "LONGBLOB" : "LONGTEXT";
case MYSQL_TYPE_BLOB:
return (flags & BINARY_FLAG) ? "BLOB" : "TEXT";
case MYSQL_TYPE_VAR_STRING:
return (flags & BINARY_FLAG) ? "VARBINARY" : "VARCHAR";
case MYSQL_TYPE_STRING:
if(flags & ENUM_FLAG) return "ENUM";
if(flags & SET_FLAG) return "SET";
return (flags & BINARY_FLAG) ? "BINARY" : "CHAR";
case MYSQL_TYPE_GEOMETRY:
return "GEOMETRY";
default:
return NULL;
}
#undef PDO_MYSQL_NATIVE_TYPE_NAME
} /* }}} */

static int pdo_mysql_stmt_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) /* {{{ */
static int pdo_mysql_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) /* {{{ */
{
pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
const MYSQL_FIELD *F;
zval flags;
char *str;

PDO_DBG_ENTER("pdo_mysql_stmt_col_meta");
PDO_DBG_ENTER("pdo_mysql_stmt_get_column_meta");
PDO_DBG_INF_FMT("stmt=%p", S->stmt);
if (!S->result) {
PDO_DBG_RETURN(FAILURE);
}
if (colno >= stmt->column_count) {
/* error invalid column */
PDO_DBG_RETURN(FAILURE);
}

array_init(return_value);
array_init(&flags);
Expand All @@ -787,24 +799,10 @@ static int pdo_mysql_stmt_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *retu
if (F->def) {
add_assoc_string(return_value, "mysql:def", F->def);
}
if (IS_NOT_NULL(F->flags)) {
add_next_index_string(&flags, "not_null");
}
if (IS_PRI_KEY(F->flags)) {
add_next_index_string(&flags, "primary_key");
}
if (F->flags & MULTIPLE_KEY_FLAG) {
add_next_index_string(&flags, "multiple_key");
}
if (F->flags & UNIQUE_KEY_FLAG) {
add_next_index_string(&flags, "unique_key");
}
if (IS_BLOB(F->flags)) {
add_next_index_string(&flags, "blob");
}
str = type_to_name_native(F->type);

str = type_to_name_native(F->type, F->flags);
if (str) {
add_assoc_string(return_value, "native_type", str);
add_assoc_string(return_value, "mysql:decl_type", str);
}

enum pdo_param_type param_type;
Expand All @@ -820,12 +818,30 @@ static int pdo_mysql_stmt_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *retu
#endif
param_type = PDO_PARAM_INT;
break;
case MYSQL_TYPE_NULL:
param_type = PDO_PARAM_NULL;
break;
default:
param_type = PDO_PARAM_STR;
break;
}
add_assoc_long(return_value, "pdo_type", param_type);

if (F->flags & NOT_NULL_FLAG) {
add_next_index_string(&flags, "not_null");
}
if (F->flags & PRI_KEY_FLAG) {
add_next_index_string(&flags, "primary_key");
}
if (F->flags & MULTIPLE_KEY_FLAG) {
add_next_index_string(&flags, "multiple_key");
}
if (F->flags & UNIQUE_KEY_FLAG) {
add_next_index_string(&flags, "unique_key");
}
if (F->flags & BLOB_FLAG) {
add_next_index_string(&flags, "blob");
}
add_assoc_zval(return_value, "flags", &flags);
add_assoc_string(return_value, "table", (char *) (F->table?F->table : ""));

Expand Down Expand Up @@ -869,7 +885,7 @@ const struct pdo_stmt_methods mysql_stmt_methods = {
pdo_mysql_stmt_param_hook,
NULL, /* set_attr */
NULL, /* get_attr */
pdo_mysql_stmt_col_meta,
pdo_mysql_stmt_get_column_meta,
pdo_mysql_stmt_next_rowset,
pdo_mysql_stmt_cursor_closer
};
2 changes: 1 addition & 1 deletion ext/pdo_mysql/tests/bug_33689.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Array
)
Array
(
[native_type] => LONG
[mysql:decl_type] => INT
[flags] => Array
(
[0] => not_null
Expand Down
Loading