Tom, Geoff,

Thanks for your feedback! Here is version 2 of this patch.

> You could just change it to
> if (str[strspn(str, " \t\n\r\f")] == '\0')
> to mitigate calling strlen. It's safe to do so because strspn will
> only return values from 0 to strlen(str).

> [...] I have serious doubts that the "optimized" implementation
> you propose is actually faster than a naive one; it may be slower, and
> it's certainly longer and harder to understand/test.

I would like to point out that I never said it's optimized. However I
like the code Geoff proposed. It definitely doesn't make anything worse.
I decided to keep pg_str_contansonly procedure (i.e. not to inline this
code) for now. Code with strspn looks OK in a simple example. However in
a concrete context it looks like a bad Perl code in ROT13 to me:

```
/* try to figure out what's exactly going on */
if(somelongname[strspn(somelongname /* yes, again */, "longlistofchars")] != 
'\0')
```
> Function name seems weirdly spelled.

I named it the same way pg_str_endswith is named. However I'm open for
better suggestions here.

> Whether it's worth worrying about, I dunno.  This is hardly the only
> C idiom you need to be familiar with to read the PG code.

Well, at least this v2 version of the patch removes second string
scanning. And I still believe that this inlined strspn is not readable
or obvious at all.

-- 
Best regards,
Aleksander Alekseev
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index a8bb472..a1c5853 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -17,6 +17,7 @@
 #include "access/htup_details.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
+#include "common/string.h"
 #include "lib/stringinfo.h"
 #include "nodes/makefuncs.h"
 #include "parser/parser.h"
@@ -696,7 +697,7 @@ typeStringToTypeName(const char *str)
 	ErrorContextCallback ptserrcontext;
 
 	/* make sure we give useful error for empty input */
-	if (strspn(str, " \t\n\r\f") == strlen(str))
+	if (pg_str_containsonly(str, " \t\n\r\f"))
 		goto fail;
 
 	initStringInfo(&buf);
diff --git a/src/backend/tsearch/ts_utils.c b/src/backend/tsearch/ts_utils.c
index 9c3e15c..81d6132 100644
--- a/src/backend/tsearch/ts_utils.c
+++ b/src/backend/tsearch/ts_utils.c
@@ -17,6 +17,7 @@
 #include <ctype.h>
 
 #include "miscadmin.h"
+#include "common/string.h"
 #include "tsearch/ts_locale.h"
 #include "tsearch/ts_utils.h"
 
@@ -45,7 +46,7 @@ get_tsearch_config_filename(const char *basename,
 	 * and case-insensitive filesystems, and non-ASCII characters create other
 	 * interesting risks, so on the whole a tight policy seems best.
 	 */
-	if (strspn(basename, "abcdefghijklmnopqrstuvwxyz0123456789_") != strlen(basename))
+	if (!pg_str_containsonly(basename, "abcdefghijklmnopqrstuvwxyz0123456789_"))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("invalid text search configuration file name \"%s\"",
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 394042c..7e41359 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -32,6 +32,7 @@
 #include "catalog/pg_ts_config.h"
 #include "catalog/pg_ts_dict.h"
 #include "catalog/pg_type.h"
+#include "common/string.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "parser/parse_type.h"
@@ -75,7 +76,7 @@ regprocin(PG_FUNCTION_ARGS)
 	/* Numeric OID? */
 	if (pro_name_or_oid[0] >= '0' &&
 		pro_name_or_oid[0] <= '9' &&
-		strspn(pro_name_or_oid, "0123456789") == strlen(pro_name_or_oid))
+		pg_str_containsonly(pro_name_or_oid, "0123456789"))
 	{
 		result = DatumGetObjectId(DirectFunctionCall1(oidin,
 										  CStringGetDatum(pro_name_or_oid)));
@@ -286,7 +287,7 @@ regprocedurein(PG_FUNCTION_ARGS)
 	/* Numeric OID? */
 	if (pro_name_or_oid[0] >= '0' &&
 		pro_name_or_oid[0] <= '9' &&
-		strspn(pro_name_or_oid, "0123456789") == strlen(pro_name_or_oid))
+		pg_str_containsonly(pro_name_or_oid, "0123456789"))
 	{
 		result = DatumGetObjectId(DirectFunctionCall1(oidin,
 										  CStringGetDatum(pro_name_or_oid)));
@@ -535,7 +536,7 @@ regoperin(PG_FUNCTION_ARGS)
 	/* Numeric OID? */
 	if (opr_name_or_oid[0] >= '0' &&
 		opr_name_or_oid[0] <= '9' &&
-		strspn(opr_name_or_oid, "0123456789") == strlen(opr_name_or_oid))
+		pg_str_containsonly(opr_name_or_oid, "0123456789"))
 	{
 		result = DatumGetObjectId(DirectFunctionCall1(oidin,
 										  CStringGetDatum(opr_name_or_oid)));
@@ -750,7 +751,7 @@ regoperatorin(PG_FUNCTION_ARGS)
 	/* Numeric OID? */
 	if (opr_name_or_oid[0] >= '0' &&
 		opr_name_or_oid[0] <= '9' &&
-		strspn(opr_name_or_oid, "0123456789") == strlen(opr_name_or_oid))
+		pg_str_containsonly(opr_name_or_oid, "0123456789"))
 	{
 		result = DatumGetObjectId(DirectFunctionCall1(oidin,
 										  CStringGetDatum(opr_name_or_oid)));
@@ -995,7 +996,7 @@ regclassin(PG_FUNCTION_ARGS)
 	/* Numeric OID? */
 	if (class_name_or_oid[0] >= '0' &&
 		class_name_or_oid[0] <= '9' &&
-		strspn(class_name_or_oid, "0123456789") == strlen(class_name_or_oid))
+		pg_str_containsonly(class_name_or_oid, "0123456789"))
 	{
 		result = DatumGetObjectId(DirectFunctionCall1(oidin,
 										CStringGetDatum(class_name_or_oid)));
@@ -1186,7 +1187,7 @@ regtypein(PG_FUNCTION_ARGS)
 	/* Numeric OID? */
 	if (typ_name_or_oid[0] >= '0' &&
 		typ_name_or_oid[0] <= '9' &&
-		strspn(typ_name_or_oid, "0123456789") == strlen(typ_name_or_oid))
+		pg_str_containsonly(typ_name_or_oid, "0123456789"))
 	{
 		result = DatumGetObjectId(DirectFunctionCall1(oidin,
 										  CStringGetDatum(typ_name_or_oid)));
@@ -1358,7 +1359,7 @@ regconfigin(PG_FUNCTION_ARGS)
 	/* Numeric OID? */
 	if (cfg_name_or_oid[0] >= '0' &&
 		cfg_name_or_oid[0] <= '9' &&
-		strspn(cfg_name_or_oid, "0123456789") == strlen(cfg_name_or_oid))
+		pg_str_containsonly(cfg_name_or_oid, "0123456789"))
 	{
 		result = DatumGetObjectId(DirectFunctionCall1(oidin,
 										  CStringGetDatum(cfg_name_or_oid)));
@@ -1468,7 +1469,7 @@ regdictionaryin(PG_FUNCTION_ARGS)
 	/* Numeric OID? */
 	if (dict_name_or_oid[0] >= '0' &&
 		dict_name_or_oid[0] <= '9' &&
-		strspn(dict_name_or_oid, "0123456789") == strlen(dict_name_or_oid))
+		pg_str_containsonly(dict_name_or_oid, "0123456789"))
 	{
 		result = DatumGetObjectId(DirectFunctionCall1(oidin,
 										 CStringGetDatum(dict_name_or_oid)));
@@ -1578,7 +1579,7 @@ regrolein(PG_FUNCTION_ARGS)
 	/* Numeric OID? */
 	if (role_name_or_oid[0] >= '0' &&
 		role_name_or_oid[0] <= '9' &&
-		strspn(role_name_or_oid, "0123456789") == strlen(role_name_or_oid))
+		pg_str_containsonly(role_name_or_oid, "0123456789"))
 	{
 		result = DatumGetObjectId(DirectFunctionCall1(oidin,
 										 CStringGetDatum(role_name_or_oid)));
@@ -1699,7 +1700,7 @@ regnamespacein(PG_FUNCTION_ARGS)
 	/* Numeric OID? */
 	if (nsp_name_or_oid[0] >= '0' &&
 		nsp_name_or_oid[0] <= '9' &&
-		strspn(nsp_name_or_oid, "0123456789") == strlen(nsp_name_or_oid))
+		pg_str_containsonly(nsp_name_or_oid, "0123456789"))
 	{
 		result = DatumGetObjectId(DirectFunctionCall1(oidin,
 										  CStringGetDatum(nsp_name_or_oid)));
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2a68359..5330154 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -62,6 +62,7 @@
 #include "catalog/storage.h"
 #include "commands/policy.h"
 #include "commands/trigger.h"
+#include "common/string.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -5874,7 +5875,7 @@ RelationCacheInitFileRemove(void)
 
 	while ((de = ReadDir(dir, tblspcdir)) != NULL)
 	{
-		if (strspn(de->d_name, "0123456789") == strlen(de->d_name))
+		if (pg_str_containsonly(de->d_name, "0123456789"))
 		{
 			/* Scan the tablespace dir for per-database dirs */
 			snprintf(path, sizeof(path), "%s/%s/%s",
@@ -5905,7 +5906,7 @@ RelationCacheInitFileRemoveInDir(const char *tblspcpath)
 
 	while ((de = ReadDir(dir, tblspcpath)) != NULL)
 	{
-		if (strspn(de->d_name, "0123456789") == strlen(de->d_name))
+		if (pg_str_containsonly(de->d_name, "0123456789"))
 		{
 			/* Try to remove the init file in each database */
 			snprintf(initfilename, sizeof(initfilename), "%s/%s/%s",
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index 6cf3829..db29c38 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -52,6 +52,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
+#include "common/string.h"
 #include "lib/pairingheap.h"
 #include "miscadmin.h"
 #include "storage/predicate.h"
@@ -1423,7 +1424,7 @@ ImportSnapshot(const char *idstr)
 	 * Verify the identifier: only 0-9, A-F and hyphens are allowed.  We do
 	 * this mainly to prevent reading arbitrary files.
 	 */
-	if (strspn(idstr, "0123456789ABCDEF-") != strlen(idstr))
+	if (!pg_str_containsonly(idstr, "0123456789ABCDEF-"))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("invalid snapshot identifier: \"%s\"", idstr)));
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index b89bd99..acfb760 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -27,6 +27,7 @@
 #include "pg_backup_utils.h"
 #include "dumputils.h"
 #include "fe_utils/string_utils.h"
+#include "common/string.h"
 
 #include <ctype.h>
 #include <fcntl.h>
@@ -1364,7 +1365,7 @@ SortTocFromFile(Archive *AHX)
 			cmnt[0] = '\0';
 
 		/* Ignore if all blank */
-		if (strspn(buf, " \t\r\n") == strlen(buf))
+		if (pg_str_containsonly(buf, " \t\r\n"))
 			continue;
 
 		/* Get an ID, check it's valid and not already seen */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 7949aad..d2f9555 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -54,6 +54,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "common/string.h"
 #include "libpq/libpq-fs.h"
 
 #include "dumputils.h"
@@ -1908,7 +1909,7 @@ dumpTableData_insert(Archive *fout, void *dcontext)
 							 */
 							const char *s = PQgetvalue(res, tuple, field);
 
-							if (strspn(s, "0123456789 +-eE.") == strlen(s))
+							if (pg_str_containsonly(s, "0123456789 +-eE."))
 								archputs(s, fout);
 							else
 								archprintf(fout, "'%s'", s);
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index a9a2fdb..eed8865 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -38,6 +38,7 @@
 #include "fe_utils/string_utils.h"
 
 #include "common.h"
+#include "common/string.h"
 #include "copy.h"
 #include "crosstabview.h"
 #include "describe.h"
@@ -591,7 +592,7 @@ exec_command(const char *cmd,
 				{
 					/* only one arg; maybe it is lineno not fname */
 					if (fname[0] &&
-						strspn(fname, "0123456789") == strlen(fname))
+						pg_str_containsonly(fname, "0123456789"))
 					{
 						/* all digits, so assume it is lineno */
 						ln = fname;
diff --git a/src/bin/psql/crosstabview.c b/src/bin/psql/crosstabview.c
index b283c24..f8f1942 100644
--- a/src/bin/psql/crosstabview.c
+++ b/src/bin/psql/crosstabview.c
@@ -10,6 +10,7 @@
 #include <string.h>
 
 #include "common.h"
+#include "common/string.h"
 #include "crosstabview.h"
 #include "pqexpbuffer.h"
 #include "psqlscanslash.h"
@@ -599,8 +600,8 @@ rankSort(int num_columns, pivot_field *piv_columns)
 		/* ranking information is valid if non null and matches /^-?\d+$/ */
 		if (val &&
 			((*val == '-' &&
-			  strspn(val + 1, "0123456789") == strlen(val + 1)) ||
-			 strspn(val, "0123456789") == strlen(val)))
+			  pg_str_containsonly(val + 1, "0123456789")) ||
+			 pg_str_containsonly(val, "0123456789")))
 		{
 			hmap[i * 2] = atoi(val);
 			hmap[i * 2 + 1] = i;
@@ -637,7 +638,7 @@ indexOfColumn(char *arg, const PGresult *res)
 {
 	int			idx;
 
-	if (arg[0] && strspn(arg, "0123456789") == strlen(arg))
+	if (arg[0] && pg_str_containsonly(arg, "0123456789"))
 	{
 		/* if arg contains only digits, it's a column number */
 		idx = atoi(arg) - 1;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f0d955b..47856e4 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -18,6 +18,7 @@
 #include "fe_utils/string_utils.h"
 
 #include "common.h"
+#include "common/string.h"
 #include "describe.h"
 #include "fe_utils/mbprint.h"
 #include "fe_utils/print.h"
@@ -309,7 +310,7 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
 	/* No "Parallel" column before 9.6 */
 	static const bool translate_columns_pre_96[] = {false, false, false, false, true, true, false, true, false, false, false, false};
 
-	if (strlen(functypes) != strspn(functypes, "antwS+"))
+	if (!pg_str_containsonly(functypes, "antwS+"))
 	{
 		psql_error("\\df only takes [antwS+] as options\n");
 		return true;
diff --git a/src/common/string.c b/src/common/string.c
index 69b26e7..231d5eb 100644
--- a/src/common/string.c
+++ b/src/common/string.c
@@ -41,3 +41,13 @@ pg_str_endswith(const char *str, const char *end)
 	str += slen - elen;
 	return strcmp(str, end) == 0;
 }
+
+/*
+ * Returns whether the string `str' contains only letters from `allowed'.
+ * If `str' is empty procedure always returns `true'.
+ */
+bool
+pg_str_containsonly(const char *str, const char *allowed)
+{
+	return str[strspn(str, allowed)] == '\0';
+}
diff --git a/src/include/common/string.h b/src/include/common/string.h
index bb54d28..77f1ae0 100644
--- a/src/include/common/string.h
+++ b/src/include/common/string.h
@@ -11,5 +11,6 @@
 #define COMMON_STRING_H
 
 extern bool pg_str_endswith(const char *str, const char *end);
+extern bool pg_str_containsonly(const char *str, const char *allowed);
 
 #endif   /* COMMON_STRING_H */

Attachment: signature.asc
Description: PGP signature

Reply via email to