Skip to content

Commit e609a21

Browse files
committed
ext/pgsql: pgsql_copy_from to support iterable.
inspired from the Pdo\Pgsql new feature GH-15893. close GH-16124
1 parent 62a1eb9 commit e609a21

File tree

6 files changed

+121
-25
lines changed

6 files changed

+121
-25
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ PHP NEWS
1111
- PGSQL:
1212
. Added pg_close_stmt to close a prepared statement while allowing
1313
its name to be reused. (David Carlier)
14+
. Added Iterable support for pgsql_copy_from. (David Carlier)
1415

1516
- Random:
1617
. Moves from /dev/urandom usage to arc4random_buf on Haiku. (David Carlier)

UPGRADING

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ PHP 8.5 UPGRADE NOTES
6363
PDO::ATTR_PREFETCH sets to 0 which set to lazy fetch mode.
6464
In this mode, statements cannot be run parallely.
6565

66+
- PGSQL:
67+
. pg_copy_from also supports inputs as Iterable.
68+
6669
========================================
6770
6. New Functions
6871
========================================

ext/pgsql/pgsql.c

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "php_globals.h"
3939
#include "zend_exceptions.h"
4040
#include "zend_attributes.h"
41+
#include "zend_interfaces.h"
4142
#include "php_network.h"
4243

4344
#ifdef HAVE_PGSQL
@@ -3357,6 +3358,29 @@ PHP_FUNCTION(pg_copy_to)
33573358
}
33583359
/* }}} */
33593360

3361+
static zend_result pgsql_copy_from_query(PGconn *pgsql, PGresult *pgsql_result, zval *value)
3362+
{
3363+
zend_string *tmp = zval_try_get_string(value);
3364+
if (UNEXPECTED(!tmp)) {
3365+
return FAILURE;
3366+
}
3367+
zend_string *zquery = zend_string_alloc(ZSTR_LEN(tmp) + 1, false);
3368+
memcpy(ZSTR_VAL(zquery), ZSTR_VAL(tmp), ZSTR_LEN(tmp) + 1);
3369+
ZSTR_LEN(zquery) = ZSTR_LEN(tmp);
3370+
if (ZSTR_LEN(tmp) > 0 && ZSTR_VAL(zquery)[ZSTR_LEN(tmp)] != '\n') {
3371+
ZSTR_VAL(zquery)[ZSTR_LEN(tmp)] = '\n';
3372+
ZSTR_LEN(zquery) ++;
3373+
}
3374+
if (PQputCopyData(pgsql, ZSTR_VAL(zquery), ZSTR_LEN(zquery)) != 1) {
3375+
zend_string_release_ex(zquery, false);
3376+
zend_string_release(tmp);
3377+
return FAILURE;
3378+
}
3379+
zend_string_release_ex(zquery, false);
3380+
zend_string_release(tmp);
3381+
return SUCCESS;
3382+
}
3383+
33603384
/* {{{ Copy table from array */
33613385
PHP_FUNCTION(pg_copy_from)
33623386
{
@@ -3376,7 +3400,7 @@ PHP_FUNCTION(pg_copy_from)
33763400
ZEND_PARSE_PARAMETERS_START(3, 5)
33773401
Z_PARAM_OBJECT_OF_CLASS(pgsql_link, pgsql_link_ce)
33783402
Z_PARAM_PATH_STR(table_name)
3379-
Z_PARAM_ARRAY(pg_rows)
3403+
Z_PARAM_ITERABLE(pg_rows)
33803404
Z_PARAM_OPTIONAL
33813405
Z_PARAM_STR(pg_delimiter)
33823406
Z_PARAM_STRING(pg_null_as, pg_null_as_len)
@@ -3417,38 +3441,44 @@ PHP_FUNCTION(pg_copy_from)
34173441
switch (status) {
34183442
case PGRES_COPY_IN:
34193443
if (pgsql_result) {
3420-
int command_failed = 0;
34213444
PQclear(pgsql_result);
3422-
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pg_rows), value) {
3423-
zend_string *tmp = zval_try_get_string(value);
3424-
if (UNEXPECTED(!tmp)) {
3425-
return;
3426-
}
3427-
zend_string *zquery = zend_string_alloc(ZSTR_LEN(tmp) + 1, false);
3428-
memcpy(ZSTR_VAL(zquery), ZSTR_VAL(tmp), ZSTR_LEN(tmp) + 1);
3429-
ZSTR_LEN(zquery) = ZSTR_LEN(tmp);
3430-
if (ZSTR_LEN(tmp) > 0 && ZSTR_VAL(zquery)[ZSTR_LEN(tmp)] != '\n') {
3431-
ZSTR_VAL(zquery)[ZSTR_LEN(tmp)] = '\n';
3432-
ZSTR_LEN(zquery) ++;
3445+
bool command_failed = false;
3446+
if (Z_TYPE_P(pg_rows) == IS_ARRAY) {
3447+
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pg_rows), value) {
3448+
if (pgsql_copy_from_query(pgsql, pgsql_result, value) == FAILURE) {
3449+
PHP_PQ_ERROR("copy failed: %s", pgsql);
3450+
RETURN_FALSE;
3451+
}
3452+
} ZEND_HASH_FOREACH_END();
3453+
} else {
3454+
zend_object_iterator *iter = Z_OBJCE_P(pg_rows)->get_iterator(Z_OBJCE_P(pg_rows), pg_rows, 0);
3455+
if (UNEXPECTED(EG(exception) || iter == NULL)) {
3456+
RETURN_THROWS();
34333457
}
3434-
if (PQputCopyData(pgsql, ZSTR_VAL(zquery), ZSTR_LEN(zquery)) != 1) {
3435-
zend_string_release_ex(zquery, false);
3436-
zend_string_release(tmp);
3437-
PHP_PQ_ERROR("copy failed: %s", pgsql);
3438-
RETURN_FALSE;
3458+
3459+
if (iter->funcs->rewind) {
3460+
iter->funcs->rewind(iter);
34393461
}
3440-
zend_string_release_ex(zquery, false);
3441-
zend_string_release(tmp);
3442-
} ZEND_HASH_FOREACH_END();
34433462

3463+
while (iter->funcs->valid(iter) == SUCCESS && EG(exception) == NULL) {
3464+
zval *value = iter->funcs->get_current_data(iter);
3465+
if (pgsql_copy_from_query(pgsql, pgsql_result, value) == FAILURE) {
3466+
zend_iterator_dtor(iter);
3467+
PHP_PQ_ERROR("copy failed: %s", pgsql);
3468+
RETURN_FALSE;
3469+
}
3470+
iter->funcs->move_forward(iter);
3471+
}
3472+
zend_iterator_dtor(iter);
3473+
}
34443474
if (PQputCopyEnd(pgsql, NULL) != 1) {
34453475
PHP_PQ_ERROR("putcopyend failed: %s", pgsql);
34463476
RETURN_FALSE;
34473477
}
34483478
while ((pgsql_result = PQgetResult(pgsql))) {
34493479
if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) {
34503480
PHP_PQ_ERROR("Copy command failed: %s", pgsql);
3451-
command_failed = 1;
3481+
command_failed = true;
34523482
}
34533483
PQclear(pgsql_result);
34543484
}

ext/pgsql/pgsql.stub.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,7 @@ function pg_put_line($connection, string $query = UNKNOWN): bool {}
847847
*/
848848
function pg_copy_to(PgSql\Connection $connection, string $table_name, string $separator = "\t", string $null_as = "\\\\N"): array|false {}
849849

850-
function pg_copy_from(PgSql\Connection $connection, string $table_name, array $rows, string $separator = "\t", string $null_as = "\\\\N"): bool {}
850+
function pg_copy_from(PgSql\Connection $connection, string $table_name, array|Traversable $rows, string $separator = "\t", string $null_as = "\\\\N"): bool {}
851851

852852
/**
853853
* @param PgSql\Connection|string $connection

ext/pgsql/pgsql_arginfo.h

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
--TEST--
2+
pg_copy_from with an iterable
3+
--EXTENSIONS--
4+
pgsql
5+
--SKIPIF--
6+
<?php include("inc/skipif.inc"); ?>
7+
--FILE--
8+
<?php
9+
10+
include('inc/config.inc');
11+
$table_name = "table_copy_iter";
12+
13+
$db = pg_connect($conn_str);
14+
pg_query($db, "CREATE TABLE {$table_name} (num int)");
15+
16+
$iter = new class implements Iterator {
17+
var $count = 0;
18+
var $values = Array(1,2,3);
19+
20+
public function next(): void {
21+
++$this->count;
22+
}
23+
24+
public function rewind(): void {
25+
$this->count = 0;
26+
}
27+
28+
public function current(): int {
29+
return $this->values[$this->count];
30+
}
31+
32+
public function key(): int {
33+
return $this->count;
34+
}
35+
36+
public function valid(): bool {
37+
return $this->count < count($this->values);
38+
}
39+
};
40+
41+
try {
42+
pg_copy_from($db, $table_name, new stdClass());
43+
} catch (\TypeError $e) {
44+
echo $e->getMessage() . PHP_EOL;
45+
}
46+
var_dump(pg_copy_from($db, $table_name, $iter));
47+
$res = pg_query($db, "SELECT FROM {$table_name}");
48+
var_dump(count(pg_fetch_all($res)) == 3);
49+
50+
?>
51+
--CLEAN--
52+
<?php
53+
include('inc/config.inc');
54+
$table_name = "table_copy_iter";
55+
56+
$db = pg_connect($conn_str);
57+
pg_query($db, "DROP TABLE IF EXISTS {$table_name}");
58+
?>
59+
--EXPECT--
60+
pg_copy_from(): Argument #3 ($rows) must be of type Traversable|array, stdClass given
61+
bool(true)
62+
bool(true)

0 commit comments

Comments
 (0)