ext/pgsql: pgsql_copy_from to support iterable.

inspired from the Pdo\Pgsql new feature GH-15893.

close GH-16124
This commit is contained in:
David Carlier 2024-09-29 21:28:40 +01:00
parent 62a1eb9d68
commit e609a21906
No known key found for this signature in database
GPG Key ID: 8486F847B4B94EF1
6 changed files with 122 additions and 26 deletions

1
NEWS
View File

@ -11,6 +11,7 @@ PHP NEWS
- PGSQL:
. Added pg_close_stmt to close a prepared statement while allowing
its name to be reused. (David Carlier)
. Added Iterable support for pgsql_copy_from. (David Carlier)
- Random:
. Moves from /dev/urandom usage to arc4random_buf on Haiku. (David Carlier)

View File

@ -63,6 +63,9 @@ PHP 8.5 UPGRADE NOTES
PDO::ATTR_PREFETCH sets to 0 which set to lazy fetch mode.
In this mode, statements cannot be run parallely.
- PGSQL:
. pg_copy_from also supports inputs as Iterable.
========================================
6. New Functions
========================================

View File

@ -38,6 +38,7 @@
#include "php_globals.h"
#include "zend_exceptions.h"
#include "zend_attributes.h"
#include "zend_interfaces.h"
#include "php_network.h"
#ifdef HAVE_PGSQL
@ -3357,6 +3358,29 @@ PHP_FUNCTION(pg_copy_to)
}
/* }}} */
static zend_result pgsql_copy_from_query(PGconn *pgsql, PGresult *pgsql_result, zval *value)
{
zend_string *tmp = zval_try_get_string(value);
if (UNEXPECTED(!tmp)) {
return FAILURE;
}
zend_string *zquery = zend_string_alloc(ZSTR_LEN(tmp) + 1, false);
memcpy(ZSTR_VAL(zquery), ZSTR_VAL(tmp), ZSTR_LEN(tmp) + 1);
ZSTR_LEN(zquery) = ZSTR_LEN(tmp);
if (ZSTR_LEN(tmp) > 0 && ZSTR_VAL(zquery)[ZSTR_LEN(tmp)] != '\n') {
ZSTR_VAL(zquery)[ZSTR_LEN(tmp)] = '\n';
ZSTR_LEN(zquery) ++;
}
if (PQputCopyData(pgsql, ZSTR_VAL(zquery), ZSTR_LEN(zquery)) != 1) {
zend_string_release_ex(zquery, false);
zend_string_release(tmp);
return FAILURE;
}
zend_string_release_ex(zquery, false);
zend_string_release(tmp);
return SUCCESS;
}
/* {{{ Copy table from array */
PHP_FUNCTION(pg_copy_from)
{
@ -3376,7 +3400,7 @@ PHP_FUNCTION(pg_copy_from)
ZEND_PARSE_PARAMETERS_START(3, 5)
Z_PARAM_OBJECT_OF_CLASS(pgsql_link, pgsql_link_ce)
Z_PARAM_PATH_STR(table_name)
Z_PARAM_ARRAY(pg_rows)
Z_PARAM_ITERABLE(pg_rows)
Z_PARAM_OPTIONAL
Z_PARAM_STR(pg_delimiter)
Z_PARAM_STRING(pg_null_as, pg_null_as_len)
@ -3417,30 +3441,36 @@ PHP_FUNCTION(pg_copy_from)
switch (status) {
case PGRES_COPY_IN:
if (pgsql_result) {
int command_failed = 0;
PQclear(pgsql_result);
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pg_rows), value) {
zend_string *tmp = zval_try_get_string(value);
if (UNEXPECTED(!tmp)) {
return;
bool command_failed = false;
if (Z_TYPE_P(pg_rows) == IS_ARRAY) {
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pg_rows), value) {
if (pgsql_copy_from_query(pgsql, pgsql_result, value) == FAILURE) {
PHP_PQ_ERROR("copy failed: %s", pgsql);
RETURN_FALSE;
}
} ZEND_HASH_FOREACH_END();
} else {
zend_object_iterator *iter = Z_OBJCE_P(pg_rows)->get_iterator(Z_OBJCE_P(pg_rows), pg_rows, 0);
if (UNEXPECTED(EG(exception) || iter == NULL)) {
RETURN_THROWS();
}
zend_string *zquery = zend_string_alloc(ZSTR_LEN(tmp) + 1, false);
memcpy(ZSTR_VAL(zquery), ZSTR_VAL(tmp), ZSTR_LEN(tmp) + 1);
ZSTR_LEN(zquery) = ZSTR_LEN(tmp);
if (ZSTR_LEN(tmp) > 0 && ZSTR_VAL(zquery)[ZSTR_LEN(tmp)] != '\n') {
ZSTR_VAL(zquery)[ZSTR_LEN(tmp)] = '\n';
ZSTR_LEN(zquery) ++;
}
if (PQputCopyData(pgsql, ZSTR_VAL(zquery), ZSTR_LEN(zquery)) != 1) {
zend_string_release_ex(zquery, false);
zend_string_release(tmp);
PHP_PQ_ERROR("copy failed: %s", pgsql);
RETURN_FALSE;
}
zend_string_release_ex(zquery, false);
zend_string_release(tmp);
} ZEND_HASH_FOREACH_END();
if (iter->funcs->rewind) {
iter->funcs->rewind(iter);
}
while (iter->funcs->valid(iter) == SUCCESS && EG(exception) == NULL) {
zval *value = iter->funcs->get_current_data(iter);
if (pgsql_copy_from_query(pgsql, pgsql_result, value) == FAILURE) {
zend_iterator_dtor(iter);
PHP_PQ_ERROR("copy failed: %s", pgsql);
RETURN_FALSE;
}
iter->funcs->move_forward(iter);
}
zend_iterator_dtor(iter);
}
if (PQputCopyEnd(pgsql, NULL) != 1) {
PHP_PQ_ERROR("putcopyend failed: %s", pgsql);
RETURN_FALSE;
@ -3448,7 +3478,7 @@ PHP_FUNCTION(pg_copy_from)
while ((pgsql_result = PQgetResult(pgsql))) {
if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) {
PHP_PQ_ERROR("Copy command failed: %s", pgsql);
command_failed = 1;
command_failed = true;
}
PQclear(pgsql_result);
}

View File

@ -847,7 +847,7 @@ namespace {
*/
function pg_copy_to(PgSql\Connection $connection, string $table_name, string $separator = "\t", string $null_as = "\\\\N"): array|false {}
function pg_copy_from(PgSql\Connection $connection, string $table_name, array $rows, string $separator = "\t", string $null_as = "\\\\N"): bool {}
function pg_copy_from(PgSql\Connection $connection, string $table_name, array|Traversable $rows, string $separator = "\t", string $null_as = "\\\\N"): bool {}
/**
* @param PgSql\Connection|string $connection

View File

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 1f0141abe7cf476c305b074e31ce69a48b6eee21 */
* Stub hash: 824e5aa07fd6753b5bc7821a39ccb76768f2470b */
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_pg_connect, 0, 1, PgSql\\Connection, MAY_BE_FALSE)
ZEND_ARG_TYPE_INFO(0, connection_string, IS_STRING, 0)
@ -318,7 +318,7 @@ ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pg_copy_from, 0, 3, _IS_BOOL, 0)
ZEND_ARG_OBJ_INFO(0, connection, PgSql\\Connection, 0)
ZEND_ARG_TYPE_INFO(0, table_name, 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, null_as, IS_STRING, 0, "\"\\\\\\\\N\"")
ZEND_END_ARG_INFO()

View File

@ -0,0 +1,62 @@
--TEST--
pg_copy_from with an iterable
--EXTENSIONS--
pgsql
--SKIPIF--
<?php include("inc/skipif.inc"); ?>
--FILE--
<?php
include('inc/config.inc');
$table_name = "table_copy_iter";
$db = pg_connect($conn_str);
pg_query($db, "CREATE TABLE {$table_name} (num int)");
$iter = new class implements Iterator {
var $count = 0;
var $values = Array(1,2,3);
public function next(): void {
++$this->count;
}
public function rewind(): void {
$this->count = 0;
}
public function current(): int {
return $this->values[$this->count];
}
public function key(): int {
return $this->count;
}
public function valid(): bool {
return $this->count < count($this->values);
}
};
try {
pg_copy_from($db, $table_name, new stdClass());
} catch (\TypeError $e) {
echo $e->getMessage() . PHP_EOL;
}
var_dump(pg_copy_from($db, $table_name, $iter));
$res = pg_query($db, "SELECT FROM {$table_name}");
var_dump(count(pg_fetch_all($res)) == 3);
?>
--CLEAN--
<?php
include('inc/config.inc');
$table_name = "table_copy_iter";
$db = pg_connect($conn_str);
pg_query($db, "DROP TABLE IF EXISTS {$table_name}");
?>
--EXPECT--
pg_copy_from(): Argument #3 ($rows) must be of type Traversable|array, stdClass given
bool(true)
bool(true)