diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index 09ed9d75623cc..f3e1a925dd5fc 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -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; - } - 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) ++; + 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(); } - 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; + + if (iter->funcs->rewind) { + iter->funcs->rewind(iter); } - zend_string_release_ex(zquery, false); - zend_string_release(tmp); - } ZEND_HASH_FOREACH_END(); + 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); } diff --git a/ext/pgsql/pgsql.stub.php b/ext/pgsql/pgsql.stub.php index 10ce60df9cdc8..5da341b20ad52 100644 --- a/ext/pgsql/pgsql.stub.php +++ b/ext/pgsql/pgsql.stub.php @@ -847,7 +847,7 @@ function pg_put_line($connection, string $query = UNKNOWN): bool {} */ 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 diff --git a/ext/pgsql/pgsql_arginfo.h b/ext/pgsql/pgsql_arginfo.h index 778698a287563..52012afee070e 100644 --- a/ext/pgsql/pgsql_arginfo.h +++ b/ext/pgsql/pgsql_arginfo.h @@ -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() diff --git a/ext/pgsql/tests/pg_copy_from_iterable.phpt b/ext/pgsql/tests/pg_copy_from_iterable.phpt new file mode 100644 index 0000000000000..aadde2549a811 --- /dev/null +++ b/ext/pgsql/tests/pg_copy_from_iterable.phpt @@ -0,0 +1,62 @@ +--TEST-- +pg_copy_from with an iterable +--EXTENSIONS-- +pgsql +--SKIPIF-- + +--FILE-- +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-- + +--EXPECT-- +pg_copy_from(): Argument #3 ($rows) must be of type Traversable|array, stdClass given +bool(true) +bool(true)