mirror of
https://github.com/php/php-src.git
synced 2025-08-15 13:38:49 +02:00
ext/pdo_pgsql: Expanding COPY input from an array to an iterable
close GH-15893
This commit is contained in:
parent
332e9a47ae
commit
7f5e96d030
7 changed files with 177 additions and 29 deletions
3
NEWS
3
NEWS
|
@ -2,4 +2,7 @@ PHP NEWS
|
|||
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||
?? ??? ????, PHP 8.5.0alpha1
|
||||
|
||||
- PDO_PGSQL:
|
||||
. Added Iterable support for PDO::pgsqlCopyFromArray. (KentarouTakeda)
|
||||
|
||||
<<< NOTE: Insert NEWS from last stable release here prior to actual release! >>>
|
||||
|
|
|
@ -39,6 +39,9 @@ PHP 8.5 UPGRADE NOTES
|
|||
5. Changed Functions
|
||||
========================================
|
||||
|
||||
- PDO_PGSQL:
|
||||
. PDO::pgsqlCopyFromArray also supports inputs as Iterable.
|
||||
|
||||
========================================
|
||||
6. New Functions
|
||||
========================================
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "php_pdo_pgsql.h"
|
||||
#include "php_pdo_pgsql_int.h"
|
||||
#include "zend_exceptions.h"
|
||||
#include "zend_interfaces.h"
|
||||
#include "zend_smart_str.h"
|
||||
#include "pgsql_driver_arginfo.h"
|
||||
|
||||
|
@ -606,6 +607,32 @@ static bool pgsql_handle_rollback(pdo_dbh_t *dbh)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static bool _pdo_pgsql_send_copy_data(pdo_pgsql_db_handle *H, zval *line) {
|
||||
size_t query_len;
|
||||
char *query;
|
||||
|
||||
if (!try_convert_to_string(line)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
query_len = Z_STRLEN_P(line);
|
||||
query = emalloc(query_len + 2); /* room for \n\0 */
|
||||
memcpy(query, Z_STRVAL_P(line), query_len);
|
||||
|
||||
if (query[query_len - 1] != '\n') {
|
||||
query[query_len++] = '\n';
|
||||
}
|
||||
query[query_len] = '\0';
|
||||
|
||||
if (PQputCopyData(H->server, query, query_len) != 1) {
|
||||
efree(query);
|
||||
return false;
|
||||
}
|
||||
|
||||
efree(query);
|
||||
return true;
|
||||
}
|
||||
|
||||
void pgsqlCopyFromArray_internal(INTERNAL_FUNCTION_PARAMETERS)
|
||||
{
|
||||
pdo_dbh_t *dbh;
|
||||
|
@ -620,14 +647,14 @@ void pgsqlCopyFromArray_internal(INTERNAL_FUNCTION_PARAMETERS)
|
|||
PGresult *pgsql_result;
|
||||
ExecStatusType status;
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa|sss!",
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sA|sss!",
|
||||
&table_name, &table_name_len, &pg_rows,
|
||||
&pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
if (!zend_hash_num_elements(Z_ARRVAL_P(pg_rows))) {
|
||||
zend_argument_must_not_be_empty_error(2);
|
||||
if ((Z_TYPE_P(pg_rows) != IS_ARRAY && !instanceof_function(Z_OBJCE_P(pg_rows), zend_ce_traversable))) {
|
||||
zend_argument_type_error(2, "must be of type array or Traversable");
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
|
@ -661,36 +688,35 @@ void pgsqlCopyFromArray_internal(INTERNAL_FUNCTION_PARAMETERS)
|
|||
|
||||
if (status == PGRES_COPY_IN && pgsql_result) {
|
||||
int command_failed = 0;
|
||||
size_t buffer_len = 0;
|
||||
zval *tmp;
|
||||
zend_object_iterator *iter;
|
||||
|
||||
PQclear(pgsql_result);
|
||||
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pg_rows), tmp) {
|
||||
size_t query_len;
|
||||
if (!try_convert_to_string(tmp)) {
|
||||
efree(query);
|
||||
|
||||
if (Z_TYPE_P(pg_rows) == IS_ARRAY) {
|
||||
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pg_rows), tmp) {
|
||||
if (!_pdo_pgsql_send_copy_data(H, tmp)) {
|
||||
pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL);
|
||||
PDO_HANDLE_DBH_ERR();
|
||||
RETURN_FALSE;
|
||||
}
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
} else {
|
||||
iter = Z_OBJ_P(pg_rows)->ce->get_iterator(Z_OBJCE_P(pg_rows), pg_rows, 0);
|
||||
if (iter == NULL || EG(exception)) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
if (buffer_len < Z_STRLEN_P(tmp)) {
|
||||
buffer_len = Z_STRLEN_P(tmp);
|
||||
query = erealloc(query, buffer_len + 2); /* room for \n\0 */
|
||||
for (; iter->funcs->valid(iter) == SUCCESS && EG(exception) == NULL; iter->funcs->move_forward(iter)) {
|
||||
tmp = iter->funcs->get_current_data(iter);
|
||||
if (!_pdo_pgsql_send_copy_data(H, tmp)) {
|
||||
zend_iterator_dtor(iter);
|
||||
pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL);
|
||||
PDO_HANDLE_DBH_ERR();
|
||||
RETURN_FALSE;
|
||||
}
|
||||
}
|
||||
query_len = Z_STRLEN_P(tmp);
|
||||
memcpy(query, Z_STRVAL_P(tmp), query_len);
|
||||
if (query[query_len - 1] != '\n') {
|
||||
query[query_len++] = '\n';
|
||||
}
|
||||
query[query_len] = '\0';
|
||||
if (PQputCopyData(H->server, query, query_len) != 1) {
|
||||
efree(query);
|
||||
pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL);
|
||||
PDO_HANDLE_DBH_ERR();
|
||||
RETURN_FALSE;
|
||||
}
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
if (query) {
|
||||
efree(query);
|
||||
zend_iterator_dtor(iter);
|
||||
}
|
||||
|
||||
if (PQputCopyEnd(H->server, NULL) != 1) {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
class PDO_PGSql_Ext {
|
||||
/** @tentative-return-type */
|
||||
public function pgsqlCopyFromArray(string $tableName, array $rows, string $separator = "\t", string $nullAs = "\\\\N", ?string $fields = null): bool {}
|
||||
public function pgsqlCopyFromArray(string $tableName, array | Traversable $rows, string $separator = "\t", string $nullAs = "\\\\N", ?string $fields = null): bool {}
|
||||
|
||||
/** @tentative-return-type */
|
||||
public function pgsqlCopyFromFile(string $tableName, string $filename, string $separator = "\t", string $nullAs = "\\\\N", ?string $fields = null): bool {}
|
||||
|
|
4
ext/pdo_pgsql/pgsql_driver_arginfo.h
generated
4
ext/pdo_pgsql/pgsql_driver_arginfo.h
generated
|
@ -1,9 +1,9 @@
|
|||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: dd20abc5d8580d72b25bfb3c598b1ca54a501fcc */
|
||||
* Stub hash: 30c01b4d2e7f836b81a31dc0c1a115883eb41568 */
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, 0, 2, _IS_BOOL, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, rows, IS_ARRAY, 0)
|
||||
ZEND_ARG_OBJ_TYPE_MASK(0, rows, Traversable, MAY_BE_ARRAY, NULL)
|
||||
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"")
|
||||
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"")
|
||||
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null")
|
||||
|
|
51
ext/pdo_pgsql/tests/copy_from_generator.phpt
Normal file
51
ext/pdo_pgsql/tests/copy_from_generator.phpt
Normal file
|
@ -0,0 +1,51 @@
|
|||
--TEST--
|
||||
PDO PgSQL pgsqlCopyFromArray using Generator
|
||||
--EXTENSIONS--
|
||||
pdo_pgsql
|
||||
--SKIPIF--
|
||||
<?php
|
||||
require __DIR__ . '/config.inc';
|
||||
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
|
||||
PDOTest::skip();
|
||||
?>
|
||||
--FILE--
|
||||
<?php
|
||||
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
|
||||
$db = PDOTest::test_factory(__DIR__ . '/common.phpt');
|
||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
|
||||
|
||||
$db->exec('CREATE TABLE test_copy_from_generator (v int)');
|
||||
|
||||
$generator = (function(){
|
||||
$position = 0;
|
||||
$values = [1, 1, 2, 3, 5];
|
||||
|
||||
while(isset($values[$position])){
|
||||
yield $values[$position];
|
||||
++$position;
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
$db->pgsqlCopyFromArray('test_copy_from_generator',$generator);
|
||||
|
||||
$stmt = $db->query("select * from test_copy_from_generator order by 1");
|
||||
$result = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
var_export($result);
|
||||
|
||||
?>
|
||||
--CLEAN--
|
||||
<?php
|
||||
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
|
||||
$db = PDOTest::test_factory(__DIR__ . '/common.phpt');
|
||||
$db->query('DROP TABLE IF EXISTS test_copy_from_generator CASCADE');
|
||||
?>
|
||||
--EXPECT--
|
||||
array (
|
||||
0 => 1,
|
||||
1 => 1,
|
||||
2 => 2,
|
||||
3 => 3,
|
||||
4 => 5,
|
||||
)
|
65
ext/pdo_pgsql/tests/copy_from_iterator.phpt
Normal file
65
ext/pdo_pgsql/tests/copy_from_iterator.phpt
Normal file
|
@ -0,0 +1,65 @@
|
|||
--TEST--
|
||||
PDO PgSQL pgsqlCopyFromArray using Iterator
|
||||
--EXTENSIONS--
|
||||
pdo_pgsql
|
||||
--SKIPIF--
|
||||
<?php
|
||||
require __DIR__ . '/config.inc';
|
||||
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
|
||||
PDOTest::skip();
|
||||
?>
|
||||
--FILE--
|
||||
<?php
|
||||
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
|
||||
$db = PDOTest::test_factory(__DIR__ . '/common.phpt');
|
||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
|
||||
|
||||
$db->exec('CREATE TABLE test_copy_from_traversable (v int)');
|
||||
|
||||
$iterator = new class implements Iterator{
|
||||
private $position = 0;
|
||||
private $values = [1, 1, 2, 3, 5];
|
||||
|
||||
public function rewind(): void {
|
||||
$this->position = 0;
|
||||
}
|
||||
|
||||
public function current(): int {
|
||||
return $this->values[$this->position];
|
||||
}
|
||||
|
||||
public function key(): int {
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function next(): void {
|
||||
++$this->position;
|
||||
}
|
||||
|
||||
public function valid(): bool {
|
||||
return isset($this->values[$this->position]);
|
||||
}
|
||||
};
|
||||
|
||||
$db->pgsqlCopyFromArray('test_copy_from_traversable',$iterator);
|
||||
|
||||
$stmt = $db->query("select * from test_copy_from_traversable order by 1");
|
||||
$result = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
var_export($result);
|
||||
|
||||
?>
|
||||
--CLEAN--
|
||||
<?php
|
||||
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
|
||||
$db = PDOTest::test_factory(__DIR__ . '/common.phpt');
|
||||
$db->query('DROP TABLE IF EXISTS test_copy_from_traversable CASCADE');
|
||||
?>
|
||||
--EXPECT--
|
||||
array (
|
||||
0 => 1,
|
||||
1 => 1,
|
||||
2 => 2,
|
||||
3 => 3,
|
||||
4 => 5,
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue