Commit 4173477b authored by Fujii Masao's avatar Fujii Masao

postgres_fdw: Tighten up allowed values for batch_size, fetch_size options.

Previously the values such as '100$%$#$#', '9,223,372,' were accepted and
treated as valid integers for postgres_fdw options batch_size and fetch_size.
Whereas this is not the case with fdw_startup_cost and fdw_tuple_cost options
for which an error is thrown. This was because endptr was not used
while converting strings to integers using strtol.

This commit changes the logic so that it uses parse_int function
instead of strtol as it serves the purpose by returning false in case
if it is unable to convert the string to integer. Note that
this function also rounds off the values such as '100.456' to 100 and
'100.567' or '100.678' to 101.

While on this, use parse_real for fdw_startup_cost and fdw_tuple_cost options.

Since parse_int and parse_real are being used for reloptions and GUCs,
it is more appropriate to use in postgres_fdw rather than using strtol
and strtod directly.

Back-patch to v14.

Author: Bharath Rupireddy
Reviewed-by: Ashutosh Bapat, Tom Lane, Kyotaro Horiguchi, Fujii Masao
Discussion: https://postgr.es/m/CALj2ACVMO6wY5Pc4oe1OCgUOAtdjHuFsBDw8R5uoYR86eWFQDA@mail.gmail.com
parent 86d49142
...@@ -10480,3 +10480,22 @@ DROP TABLE result_tbl; ...@@ -10480,3 +10480,22 @@ DROP TABLE result_tbl;
DROP TABLE join_tbl; DROP TABLE join_tbl;
ALTER SERVER loopback OPTIONS (DROP async_capable); ALTER SERVER loopback OPTIONS (DROP async_capable);
ALTER SERVER loopback2 OPTIONS (DROP async_capable); ALTER SERVER loopback2 OPTIONS (DROP async_capable);
-- ===================================================================
-- test invalid server and foreign table options
-- ===================================================================
-- Invalid fdw_startup_cost option
CREATE SERVER inv_scst FOREIGN DATA WRAPPER postgres_fdw
OPTIONS(fdw_startup_cost '100$%$#$#');
ERROR: invalid value for floating point option "fdw_startup_cost": 100$%$#$#
-- Invalid fdw_tuple_cost option
CREATE SERVER inv_scst FOREIGN DATA WRAPPER postgres_fdw
OPTIONS(fdw_tuple_cost '100$%$#$#');
ERROR: invalid value for floating point option "fdw_tuple_cost": 100$%$#$#
-- Invalid fetch_size option
CREATE FOREIGN TABLE inv_fsz (c1 int )
SERVER loopback OPTIONS (fetch_size '100$%$#$#');
ERROR: invalid value for integer option "fetch_size": 100$%$#$#
-- Invalid batch_size option
CREATE FOREIGN TABLE inv_bsz (c1 int )
SERVER loopback OPTIONS (batch_size '100$%$#$#');
ERROR: invalid value for integer option "batch_size": 100$%$#$#
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include "commands/extension.h" #include "commands/extension.h"
#include "postgres_fdw.h" #include "postgres_fdw.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/varlena.h" #include "utils/varlena.h"
/* /*
...@@ -119,14 +120,23 @@ postgres_fdw_validator(PG_FUNCTION_ARGS) ...@@ -119,14 +120,23 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
strcmp(def->defname, "fdw_tuple_cost") == 0) strcmp(def->defname, "fdw_tuple_cost") == 0)
{ {
/* these must have a non-negative numeric value */ /* these must have a non-negative numeric value */
double val; char *value;
char *endp; double real_val;
bool is_parsed;
val = strtod(defGetString(def), &endp); value = defGetString(def);
if (*endp || val < 0) is_parsed = parse_real(value, &real_val, 0, NULL);
if (!is_parsed)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid value for floating point option \"%s\": %s",
def->defname, value)));
if (real_val < 0)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("%s requires a non-negative numeric value", errmsg("\"%s\" requires a non-negative floating point value",
def->defname))); def->defname)));
} }
else if (strcmp(def->defname, "extensions") == 0) else if (strcmp(def->defname, "extensions") == 0)
...@@ -134,26 +144,26 @@ postgres_fdw_validator(PG_FUNCTION_ARGS) ...@@ -134,26 +144,26 @@ postgres_fdw_validator(PG_FUNCTION_ARGS)
/* check list syntax, warn about uninstalled extensions */ /* check list syntax, warn about uninstalled extensions */
(void) ExtractExtensionList(defGetString(def), true); (void) ExtractExtensionList(defGetString(def), true);
} }
else if (strcmp(def->defname, "fetch_size") == 0) else if (strcmp(def->defname, "fetch_size") == 0 ||
strcmp(def->defname, "batch_size") == 0)
{ {
int fetch_size; char *value;
int int_val;
bool is_parsed;
value = defGetString(def);
is_parsed = parse_int(value, &int_val, 0, NULL);
fetch_size = strtol(defGetString(def), NULL, 10); if (!is_parsed)
if (fetch_size <= 0)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("%s requires a non-negative integer value", errmsg("invalid value for integer option \"%s\": %s",
def->defname))); def->defname, value)));
}
else if (strcmp(def->defname, "batch_size") == 0)
{
int batch_size;
batch_size = strtol(defGetString(def), NULL, 10); if (int_val <= 0)
if (batch_size <= 0)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("%s requires a non-negative integer value", errmsg("\"%s\" requires a non-negative integer value",
def->defname))); def->defname)));
} }
else if (strcmp(def->defname, "password_required") == 0) else if (strcmp(def->defname, "password_required") == 0)
......
...@@ -5024,7 +5024,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel, ...@@ -5024,7 +5024,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
if (strcmp(def->defname, "fetch_size") == 0) if (strcmp(def->defname, "fetch_size") == 0)
{ {
fetch_size = strtol(defGetString(def), NULL, 10); (void) parse_int(defGetString(def), &fetch_size, 0, NULL);
break; break;
} }
} }
...@@ -5034,7 +5034,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel, ...@@ -5034,7 +5034,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
if (strcmp(def->defname, "fetch_size") == 0) if (strcmp(def->defname, "fetch_size") == 0)
{ {
fetch_size = strtol(defGetString(def), NULL, 10); (void) parse_int(defGetString(def), &fetch_size, 0, NULL);
break; break;
} }
} }
...@@ -5801,14 +5801,16 @@ apply_server_options(PgFdwRelationInfo *fpinfo) ...@@ -5801,14 +5801,16 @@ apply_server_options(PgFdwRelationInfo *fpinfo)
if (strcmp(def->defname, "use_remote_estimate") == 0) if (strcmp(def->defname, "use_remote_estimate") == 0)
fpinfo->use_remote_estimate = defGetBoolean(def); fpinfo->use_remote_estimate = defGetBoolean(def);
else if (strcmp(def->defname, "fdw_startup_cost") == 0) else if (strcmp(def->defname, "fdw_startup_cost") == 0)
fpinfo->fdw_startup_cost = strtod(defGetString(def), NULL); (void) parse_real(defGetString(def), &fpinfo->fdw_startup_cost, 0,
NULL);
else if (strcmp(def->defname, "fdw_tuple_cost") == 0) else if (strcmp(def->defname, "fdw_tuple_cost") == 0)
fpinfo->fdw_tuple_cost = strtod(defGetString(def), NULL); (void) parse_real(defGetString(def), &fpinfo->fdw_tuple_cost, 0,
NULL);
else if (strcmp(def->defname, "extensions") == 0) else if (strcmp(def->defname, "extensions") == 0)
fpinfo->shippable_extensions = fpinfo->shippable_extensions =
ExtractExtensionList(defGetString(def), false); ExtractExtensionList(defGetString(def), false);
else if (strcmp(def->defname, "fetch_size") == 0) else if (strcmp(def->defname, "fetch_size") == 0)
fpinfo->fetch_size = strtol(defGetString(def), NULL, 10); (void) parse_int(defGetString(def), &fpinfo->fetch_size, 0, NULL);
else if (strcmp(def->defname, "async_capable") == 0) else if (strcmp(def->defname, "async_capable") == 0)
fpinfo->async_capable = defGetBoolean(def); fpinfo->async_capable = defGetBoolean(def);
} }
...@@ -5831,7 +5833,7 @@ apply_table_options(PgFdwRelationInfo *fpinfo) ...@@ -5831,7 +5833,7 @@ apply_table_options(PgFdwRelationInfo *fpinfo)
if (strcmp(def->defname, "use_remote_estimate") == 0) if (strcmp(def->defname, "use_remote_estimate") == 0)
fpinfo->use_remote_estimate = defGetBoolean(def); fpinfo->use_remote_estimate = defGetBoolean(def);
else if (strcmp(def->defname, "fetch_size") == 0) else if (strcmp(def->defname, "fetch_size") == 0)
fpinfo->fetch_size = strtol(defGetString(def), NULL, 10); (void) parse_int(defGetString(def), &fpinfo->fetch_size, 0, NULL);
else if (strcmp(def->defname, "async_capable") == 0) else if (strcmp(def->defname, "async_capable") == 0)
fpinfo->async_capable = defGetBoolean(def); fpinfo->async_capable = defGetBoolean(def);
} }
...@@ -7341,7 +7343,7 @@ get_batch_size_option(Relation rel) ...@@ -7341,7 +7343,7 @@ get_batch_size_option(Relation rel)
if (strcmp(def->defname, "batch_size") == 0) if (strcmp(def->defname, "batch_size") == 0)
{ {
batch_size = strtol(defGetString(def), NULL, 10); (void) parse_int(defGetString(def), &batch_size, 0, NULL);
break; break;
} }
} }
......
...@@ -3339,3 +3339,19 @@ DROP TABLE join_tbl; ...@@ -3339,3 +3339,19 @@ DROP TABLE join_tbl;
ALTER SERVER loopback OPTIONS (DROP async_capable); ALTER SERVER loopback OPTIONS (DROP async_capable);
ALTER SERVER loopback2 OPTIONS (DROP async_capable); ALTER SERVER loopback2 OPTIONS (DROP async_capable);
-- ===================================================================
-- test invalid server and foreign table options
-- ===================================================================
-- Invalid fdw_startup_cost option
CREATE SERVER inv_scst FOREIGN DATA WRAPPER postgres_fdw
OPTIONS(fdw_startup_cost '100$%$#$#');
-- Invalid fdw_tuple_cost option
CREATE SERVER inv_scst FOREIGN DATA WRAPPER postgres_fdw
OPTIONS(fdw_tuple_cost '100$%$#$#');
-- Invalid fetch_size option
CREATE FOREIGN TABLE inv_fsz (c1 int )
SERVER loopback OPTIONS (fetch_size '100$%$#$#');
-- Invalid batch_size option
CREATE FOREIGN TABLE inv_bsz (c1 int )
SERVER loopback OPTIONS (batch_size '100$%$#$#');
...@@ -266,11 +266,11 @@ OPTIONS (ADD password_required 'false'); ...@@ -266,11 +266,11 @@ OPTIONS (ADD password_required 'false');
<term><literal>fdw_startup_cost</literal></term> <term><literal>fdw_startup_cost</literal></term>
<listitem> <listitem>
<para> <para>
This option, which can be specified for a foreign server, is a numeric This option, which can be specified for a foreign server, is a floating
value that is added to the estimated startup cost of any foreign-table point value that is added to the estimated startup cost of any
scan on that server. This represents the additional overhead of foreign-table scan on that server. This represents the additional
establishing a connection, parsing and planning the query on the overhead of establishing a connection, parsing and planning the query on
remote side, etc. the remote side, etc.
The default value is <literal>100</literal>. The default value is <literal>100</literal>.
</para> </para>
</listitem> </listitem>
...@@ -280,8 +280,8 @@ OPTIONS (ADD password_required 'false'); ...@@ -280,8 +280,8 @@ OPTIONS (ADD password_required 'false');
<term><literal>fdw_tuple_cost</literal></term> <term><literal>fdw_tuple_cost</literal></term>
<listitem> <listitem>
<para> <para>
This option, which can be specified for a foreign server, is a numeric This option, which can be specified for a foreign server, is a floating
value that is used as extra cost per-tuple for foreign-table point value that is used as extra cost per-tuple for foreign-table
scans on that server. This represents the additional overhead of scans on that server. This represents the additional overhead of
data transfer between servers. You might increase or decrease this data transfer between servers. You might increase or decrease this
number to reflect higher or lower network delay to the remote server. number to reflect higher or lower network delay to the remote server.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment