mirror of
https://github.com/php/php-src.git
synced 2024-11-23 01:44:06 +08:00
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:
parent
62a1eb9d68
commit
e609a21906
1
NEWS
1
NEWS
@ -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)
|
||||
|
@ -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
|
||||
========================================
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
4
ext/pgsql/pgsql_arginfo.h
generated
4
ext/pgsql/pgsql_arginfo.h
generated
@ -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()
|
||||
|
62
ext/pgsql/tests/pg_copy_from_iterable.phpt
Normal file
62
ext/pgsql/tests/pg_copy_from_iterable.phpt
Normal 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)
|
Loading…
Reference in New Issue
Block a user