Skip to content

Commit 1eef6df

Browse files
committed
ext/pgsql: pgsql_copy_from to support iterable.
inspired from the Pdo\Pgsql new feature phpGH-15893.
1 parent bca73f1 commit 1eef6df

File tree

4 files changed

+118
-26
lines changed

4 files changed

+118
-26
lines changed

ext/pgsql/pgsql.c

Lines changed: 53 additions & 23 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_ARRAY_OR_OBJECT(pg_rows)
33803404
Z_PARAM_OPTIONAL
33813405
Z_PARAM_STR(pg_delimiter)
33823406
Z_PARAM_STRING(pg_null_as, pg_null_as_len)
@@ -3392,6 +3416,10 @@ PHP_FUNCTION(pg_copy_from)
33923416
zend_argument_value_error(4, "must be one character");
33933417
RETURN_THROWS();
33943418
}
3419+
if (Z_TYPE_P(pg_rows) == IS_OBJECT && !instanceof_function(Z_OBJCE_P(pg_rows), zend_ce_traversable)) {
3420+
zend_argument_type_error(3, "must be of type Traversable");
3421+
RETURN_THROWS();
3422+
}
33953423
if (!pg_null_as) {
33963424
pg_null_as = estrdup("\\\\N");
33973425
pg_null_as_free = true;
@@ -3417,38 +3445,40 @@ PHP_FUNCTION(pg_copy_from)
34173445
switch (status) {
34183446
case PGRES_COPY_IN:
34193447
if (pgsql_result) {
3420-
int command_failed = 0;
34213448
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) ++;
3433-
}
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;
3449+
bool command_failed = false;
3450+
if (Z_TYPE_P(pg_rows) == IS_ARRAY) {
3451+
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pg_rows), value) {
3452+
if (pgsql_copy_from_query(pgsql, pgsql_result, value) == FAILURE) {
3453+
PHP_PQ_ERROR("copy failed: %s", pgsql);
3454+
RETURN_FALSE;
3455+
}
3456+
} ZEND_HASH_FOREACH_END();
3457+
} else {
3458+
zend_object_iterator *iter = Z_OBJ_P(pg_rows)->ce->get_iterator(Z_OBJCE_P(pg_rows), pg_rows, 0);
3459+
if (UNEXPECTED(EG(exception) || iter == NULL)) {
3460+
RETURN_THROWS();
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|object $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
61+
bool(true)
62+
bool(true)

0 commit comments

Comments
 (0)