diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index ef0cbac40b8ec..9002ce22e967b 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -3211,6 +3211,7 @@ PHP_FUNCTION(pg_copy_to) pgsql_link_handle *link; zend_string *table_name; zend_string *pg_delimiter = NULL; + zend_string *pg_escape = NULL; char *pg_null_as = NULL; size_t pg_null_as_len = 0; bool free_pg_null = false; @@ -3218,14 +3219,15 @@ PHP_FUNCTION(pg_copy_to) PGconn *pgsql; PGresult *pgsql_result; ExecStatusType status; - char *csv = (char *)NULL; + char *csv = NULL; - ZEND_PARSE_PARAMETERS_START(2, 4) + ZEND_PARSE_PARAMETERS_START(2, 5) Z_PARAM_OBJECT_OF_CLASS(pgsql_link, pgsql_link_ce) Z_PARAM_PATH_STR(table_name) Z_PARAM_OPTIONAL Z_PARAM_STR(pg_delimiter) Z_PARAM_STRING(pg_null_as, pg_null_as_len) + Z_PARAM_STR(pg_escape) ZEND_PARSE_PARAMETERS_END(); link = Z_PGSQL_LINK_P(pgsql_link); @@ -3242,8 +3244,14 @@ PHP_FUNCTION(pg_copy_to) pg_null_as = estrdup("\\\\N"); free_pg_null = true; } + if (!pg_escape) { + pg_escape = ZSTR_CHAR('"'); + } else if (ZSTR_LEN(pg_escape) != 1) { + zend_argument_value_error(5, "must be one character"); + RETURN_THROWS(); + } - spprintf(&query, 0, "COPY %s TO STDOUT DELIMITER E'%c' NULL AS E'%s'", ZSTR_VAL(table_name), *ZSTR_VAL(pg_delimiter), pg_null_as); + spprintf(&query, 0, "COPY %s TO STDOUT CSV DELIMITER E'%c' ESCAPE E'%c' NULL AS E'%s'", ZSTR_VAL(table_name), *ZSTR_VAL(pg_delimiter), *ZSTR_VAL(pg_escape), pg_null_as); while ((pgsql_result = PQgetResult(pgsql))) { PQclear(pgsql_result); @@ -3310,6 +3318,7 @@ PHP_FUNCTION(pg_copy_from) zval *value; zend_string *table_name; zend_string *pg_delimiter = NULL; + zend_string *pg_escape = NULL; char *pg_null_as = NULL; size_t pg_null_as_len; bool pg_null_as_free = false; @@ -3318,13 +3327,14 @@ PHP_FUNCTION(pg_copy_from) PGresult *pgsql_result; ExecStatusType status; - ZEND_PARSE_PARAMETERS_START(3, 5) + ZEND_PARSE_PARAMETERS_START(3, 6) Z_PARAM_OBJECT_OF_CLASS(pgsql_link, pgsql_link_ce) Z_PARAM_PATH_STR(table_name) Z_PARAM_ARRAY(pg_rows) Z_PARAM_OPTIONAL Z_PARAM_STR(pg_delimiter) Z_PARAM_STRING(pg_null_as, pg_null_as_len) + Z_PARAM_STR(pg_escape) ZEND_PARSE_PARAMETERS_END(); link = Z_PGSQL_LINK_P(pgsql_link); @@ -3341,8 +3351,14 @@ PHP_FUNCTION(pg_copy_from) pg_null_as = estrdup("\\\\N"); pg_null_as_free = true; } + if (!pg_escape) { + pg_escape = ZSTR_CHAR('"'); + } else if (ZSTR_LEN(pg_escape) != 1) { + zend_argument_value_error(6, "must be one character"); + RETURN_THROWS(); + } - spprintf(&query, 0, "COPY %s FROM STDIN DELIMITER E'%c' NULL AS E'%s'", ZSTR_VAL(table_name), *ZSTR_VAL(pg_delimiter), pg_null_as); + spprintf(&query, 0, "COPY %s FROM STDIN CSV DELIMITER E'%c' ESCAPE E'%c' NULL AS E'%s'", ZSTR_VAL(table_name), *ZSTR_VAL(pg_delimiter), *ZSTR_VAL(pg_escape), pg_null_as); while ((pgsql_result = PQgetResult(pgsql))) { PQclear(pgsql_result); } diff --git a/ext/pgsql/pgsql.stub.php b/ext/pgsql/pgsql.stub.php index 189232c1566c5..d6e0de9bd27c1 100644 --- a/ext/pgsql/pgsql.stub.php +++ b/ext/pgsql/pgsql.stub.php @@ -834,9 +834,9 @@ function pg_put_line($connection, string $query = UNKNOWN): bool {} * @return array|false * @refcount 1 */ - function pg_copy_to(PgSql\Connection $connection, string $table_name, string $separator = "\t", string $null_as = "\\\\N"): array|false {} + function pg_copy_to(PgSql\Connection $connection, string $table_name, string $separator = "\t", string $null_as = "\\\\N", string $escape = "\""): 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 $rows, string $separator = "\t", string $null_as = "\\\\N", string $escape = "\""): bool {} /** * @param PgSql\Connection|string $connection diff --git a/ext/pgsql/pgsql_arginfo.h b/ext/pgsql/pgsql_arginfo.h index 23460edbbde47..6f7d01f3bb32a 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: 4a2a5778003aa741952e16617e5bdb2ad06e6e16 */ + * Stub hash: 44dfbb8d4909fe1e28646b6a1c3d1b01c39ef670 */ 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) @@ -311,6 +311,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_pg_copy_to, 0, 2, MAY_BE_ARRAY|M ZEND_ARG_TYPE_INFO(0, table_name, IS_STRING, 0) 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_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, escape, IS_STRING, 0, "\"\\\"\"") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pg_copy_from, 0, 3, _IS_BOOL, 0) @@ -319,6 +320,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pg_copy_from, 0, 3, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, rows, IS_ARRAY, 0) 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_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, escape, IS_STRING, 0, "\"\\\"\"") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pg_escape_string, 0, 1, IS_STRING, 0) diff --git a/ext/pgsql/tests/06copy_escape.phpt b/ext/pgsql/tests/06copy_escape.phpt new file mode 100644 index 0000000000000..c2dd172ac05ba --- /dev/null +++ b/ext/pgsql/tests/06copy_escape.phpt @@ -0,0 +1,39 @@ +--TEST-- +PostgreSQL copy with escape settings +--EXTENSIONS-- +pgsql +--SKIPIF-- + +--FILE-- + +--CLEAN-- + +--EXPECT-- +array(2) { + [0]=> + string(34) "0;"%"test A%"";"%"just testing%"" +" + [1]=> + string(33) "1;"%"test B%"";"%"test again%"" +" +}