<note>
<para>
- Schema qualifications can not be used
- in <xref linkend="guc-white-function-list">
- because <productname>Pgpool-II</productname> silently
- ignores a schema qualification in function names appearing
- in an input SQL while comparing the list and the input
- SQL. As a result, a schema qualified function name in the
- list never matches function names appearing in the input
- SQL.
+ If the queries can refer to the function with and without the schema
+ qualification then you must add both entries (with and without
+ schema name) in the list.
+ <programlisting>
+ #For example:
+ #If the queries sometime use "f1()" and other times "public.f1()"
+ #to refer the function f1 then the white_function_list
+ #would be configured as follows.
+
+ white_function_list = "f1,public.f1"
+
+ </programlisting>
+
</para>
</note>
<note>
<para>
- Schema qualifications can not be used
- in <xref linkend="guc-black-function-list">
- because <productname>Pgpool-II</productname> silently
- ignores a schema qualification in function names appearing
- in an input SQL while comparing the list and the input
- SQL. As a result, a schema qualified function name in the
- list never matches function names appearing in the input
- SQL.
+ If the queries can refer the function with and without the schema
+ qualification then you must add both entries(with and without
+ schema name) in the list.
+ <programlisting>
+ #For example:
+ #If the queries sometime use "f1()" and other times "public.f1()"
+ #to refer the function f1 then the black_function_list
+ #would be configured as follows.
+
+ black_function_list = "f1,public.f1"
+
+ </programlisting>
+
</para>
</note>
extern void *int_unregister_func(void *data);
extern void *string_register_func(POOL_SELECT_RESULT * res);
extern void *string_unregister_func(void *data);
+extern bool SplitIdentifierString(char *rawstring, char separator, Node **namelist);
#endif /* POOL_RELCACHE_H */
extern int pool_extract_table_oids_from_select_stmt(Node *node, SelectContext * ctx);
extern RangeVar *makeRangeVarFromNameList(List *names);
extern char *make_table_name_from_rangevar(RangeVar *rangevar);
+extern char *make_function_name_from_funccall(FuncCall *fcall);
extern int pattern_compare(char *str, const int type, const char *param_name);
extern bool is_unlogged_table(char *table_name);
extern bool is_view(char *table_name);
echo "backend_weight0 = 0" >> etc/pgpool.conf
echo "backend_weight1 = 1" >> etc/pgpool.conf
- echo "black_function_list = 'f1'" >> etc/pgpool.conf
+ echo "black_function_list = 'f1,public.f2'" >> etc/pgpool.conf
./startall
CREATE TABLE t1(i INTEGER);
CREATE TABLE t2(i INTEGER);
CREATE FUNCTION f1(INTEGER) returns INTEGER AS 'SELECT \$1' LANGUAGE SQL;
+CREATE FUNCTION f2(INTEGER) returns INTEGER AS 'SELECT \$1' LANGUAGE SQL;
SELECT * FROM t1; -- this load balances
-SELECT f1(1); -- this does not load balance
+SELECT f1(1); -- this does not load balance
+SELECT public.f2(1); -- this does not load balance
EOF
# check if simple load balance worked
./shutdownall
exit 1
fi
+ fgrep "SELECT public.f2(1);" log/pgpool.log |grep "DB node id: 0">/dev/null 2>&1
+ if [ $? != 0 ];then
+ # expected result not found
+ echo fail: black function is sent to node 1.
+ ./shutdownall
+ exit 1
+ fi
echo ok: black function list works.
- echo "white_function_list = 'f1'" >> etc/pgpool.conf
+ echo "white_function_list = 'f1,public.f2'" >> etc/pgpool.conf
echo "black_function_list = ''" >> etc/pgpool.conf
./pgpool_reload
sleep $st
$PSQL test <<EOF
-SELECT f1(1); -- this does load balance
+SELECT f1(1); -- this does load balance
+SELECT public.f2(1); -- this does load balance
EOF
# check if white function list worked
./shutdownall
exit 1
fi
+ fgrep "SELECT public.f2(1);" log/pgpool.log |grep "DB node id: 1">/dev/null 2>&1
+ if [ $? != 0 ];then
+ # expected result not found
+ echo fail: white function is sent to zero-weight node.
+ ./shutdownall
+ exit 1
+ fi
echo ok: white function list works.
# check if black query pattern list worked
wait_for_pgpool_startup
$PSQL test <<EOF
+CREATE SCHEMA other_schema;
CREATE TABLE t1 (i int);
CREATE TABLE black_t (i int);
CREATE TABLE with_modify (i int);
CREATE VIEW normal_v AS SELECT * FROM t1;
CREATE VIEW white_v AS SELECT * FROM t1;
+CREATE FUNCTION public.immutable_func(INTEGER) returns INTEGER AS 'SELECT \$1' LANGUAGE SQL IMMUTABLE;
+CREATE FUNCTION other_schema.volatile_func(INTEGER) returns INTEGER AS 'SELECT \$1' LANGUAGE SQL VOLATILE;
SELECT pg_sleep(2); -- Sleep for a while to make sure object creations are replicated
SELECT * FROM t1;
SELECT * FROM t1;
WITH cte AS (INSERT INTO with_modify values(1) RETURNING *) SELECT * FROM with_modify;
WITH cte AS (INSERT INTO with_modify values(1) RETURNING *) SELECT * FROM with_modify;
SELECT * FROM with_modify;
+select public.immutable_func(1);
+select public.immutable_func(1);
+select other_schema.volatile_func(1);
+select other_schema.volatile_func(1);
EOF
success=true
grep "fetched from cache" log/pgpool.log | grep normal_v > /dev/null && success=false
grep "fetched from cache" log/pgpool.log | grep white_v > /dev/null || success=false
grep "fetched from cache" log/pgpool.log | grep with_modify > /dev/null && success=false
+ grep "fetched from cache" log/pgpool.log | grep immutable_func > /dev/null || success=false
+ grep "fetched from cache" log/pgpool.log | grep volatile_func > /dev/null && success=false
if [ $success = false ];then
./shutdownall
exit 1
#include "utils/palloc.h"
#include "utils/memutils.h"
#include "utils/elog.h"
+#include "parser/scansup.h"
static void SearchRelCacheErrorCb(void *arg);
static POOL_SELECT_RESULT *query_cache_to_relation_cache(char *data, size_t size);
}
+/*
+ * SplitIdentifierString --- parse a string containing identifiers
+ *
+ * This is the guts of textToQualifiedNameList, and is exported for use in
+ * other situations such as parsing GUC variables. In the GUC case, it's
+ * important to avoid memory leaks, so the API is designed to minimize the
+ * amount of stuff that needs to be allocated and freed.
+ *
+ * Inputs:
+ * rawstring: the input string; must be overwritable! On return, it's
+ * been modified to contain the separated identifiers.
+ * separator: the separator punctuation expected between identifiers
+ * (typically '.' or ','). Whitespace may also appear around
+ * identifiers.
+ * Outputs:
+ * namelist: filled with a palloc'd list of pointers to identifiers within
+ * rawstring. Caller should list_free() this even on error return.
+ *
+ * Returns true if okay, false if there is a syntax error in the string.
+ *
+ * Note that an empty string is considered okay here, though not in
+ * textToQualifiedNameList.
+ */
+bool
+SplitIdentifierString(char *rawstring, char separator,
+ Node **nlist)
+{
+ char *nextp = rawstring;
+ bool done = false;
+ List **namelist = (List **) nlist;
+
+ *namelist = NIL;
+
+ while (scanner_isspace(*nextp))
+ nextp++; /* skip leading whitespace */
+
+ if (*nextp == '\0')
+ return true; /* allow empty string */
+
+ /* At the top of the loop, we are at start of a new identifier. */
+ do
+ {
+ char *curname;
+ char *endp;
+
+ if (*nextp == '"')
+ {
+ /* Quoted name --- collapse quote-quote pairs, no downcasing */
+ curname = nextp + 1;
+ for (;;)
+ {
+ endp = strchr(nextp + 1, '"');
+ if (endp == NULL)
+ return false; /* mismatched quotes */
+ if (endp[1] != '"')
+ break; /* found end of quoted name */
+ /* Collapse adjacent quotes into one quote, and look again */
+ memmove(endp, endp + 1, strlen(endp));
+ nextp = endp;
+ }
+ /* endp now points at the terminating quote */
+ nextp = endp + 1;
+ }
+ else
+ {
+ /* Unquoted name --- extends to separator or whitespace */
+ char *downname;
+ int len;
+
+ curname = nextp;
+ while (*nextp && *nextp != separator &&
+ !scanner_isspace(*nextp))
+ nextp++;
+ endp = nextp;
+ if (curname == nextp)
+ return false; /* empty unquoted name not allowed */
+
+ /*
+ * Downcase the identifier, using same code as main lexer does.
+ *
+ * XXX because we want to overwrite the input in-place, we cannot
+ * support a downcasing transformation that increases the string
+ * length. This is not a problem given the current implementation
+ * of downcase_truncate_identifier, but we'll probably have to do
+ * something about this someday.
+ */
+ len = endp - curname;
+ downname = downcase_truncate_identifier(curname, len, false);
+ Assert(strlen(downname) <= len);
+ strncpy(curname, downname, len); /* strncpy is required here */
+ pfree(downname);
+ }
+
+ while (scanner_isspace(*nextp))
+ nextp++; /* skip trailing whitespace */
+
+ if (*nextp == separator)
+ {
+ nextp++;
+ while (scanner_isspace(*nextp))
+ nextp++; /* skip leading whitespace for next */
+ /* we expect another name, so done remains false */
+ }
+ else if (*nextp == '\0')
+ done = true;
+ else
+ return false; /* invalid syntax */
+
+ /* Now safe to overwrite separator with a null */
+ *endp = '\0';
+
+ /* Truncate name if it's overlength */
+ truncate_identifier(curname, strlen(curname), false);
+
+ /*
+ * Finished isolating current name --- add it to list
+ */
+ *namelist = lappend(*namelist, curname);
+
+ /* Loop back if we didn't reach end of string */
+ } while (!done);
+
+ return true;
+}
+
char *
remove_quotes_and_schema_from_relname(char *table)
{
static char rel[MAX_ITEM_LENGTH];
- char *p;
- int i = 0;
+ char *rawstring;
+ List *names;
- /* get rid of schema name */
- p = strchr(table, '.');
- if (p)
- table = p + 1;
-
- /* get rid of quotation marks */
- for (i = 0; *table; table++)
+ rawstring = pstrdup(table);
+ if(SplitIdentifierString(rawstring, '.', (Node **) &names) && names != NIL)
+ {
+ /*
+ * Since table name is always the last one in the list,
+ * we use llast() to get table name.
+ */
+ strlcpy(rel, llast(names), sizeof(rel));
+ }
+ else
{
- if (*table != '"')
- rel[i++] = *table;
+ rel[0] = '\0';
}
- rel[i] = '\0';
+
+ pfree(rawstring);
+ list_free(names);
return rel;
}
if (length > 0)
{
- if (length == 1) /* no schema qualification? */
- {
- fname = strVal(linitial(fcall->funcname));
- }
- else
- {
- fname = strVal(lsecond(fcall->funcname)); /* with schema
- * qualification */
- }
+ fname = make_function_name_from_funccall(fcall);
ereport(DEBUG1,
(errmsg("function call walker, function name: \"%s\"", fname)));
- check_object_relationship_list(fname, true);
+ check_object_relationship_list(strVal(llast(fcall->funcname)), true);
- if (ctx->pg_terminate_backend_pid == 0 && strcmp("pg_terminate_backend", fname) == 0)
+ if (ctx->pg_terminate_backend_pid == 0 && strcmp("pg_terminate_backend", strVal(llast(fcall->funcname))) == 0)
{
if (list_length(fcall->args) == 1)
{
if (length > 0)
{
- if (length == 1) /* no schema qualification? */
- {
- fname = strVal(linitial(fcall->funcname));
- }
- else
- {
- fname = strVal(lsecond(fcall->funcname)); /* with schema
- * qualification */
- }
+ fname = make_function_name_from_funccall(fcall);
ereport(DEBUG1,
(errmsg("non immutable function walker. checking function \"%s\"", fname)));
/*
* Query to know if the function is IMMUTABLE
*/
-#define IS_STABLE_FUNCTION_QUERY "SELECT count(*) FROM pg_catalog.pg_proc AS p WHERE p.proname = '%s' AND p.provolatile = 'i'"
+#define IS_STABLE_FUNCTION_QUERY "SELECT count(*) FROM pg_catalog.pg_proc AS p, pg_catalog.pg_namespace AS n WHERE p.proname %s '%s' AND n.oid = p.pronamespace AND n.nspname = '%s' AND p.provolatile = 'i'"
bool result;
- static POOL_RELCACHE * relcache;
- POOL_CONNECTION_POOL *backend;
+ char query[1024];
+ char *rawstring = NULL;
+ char *key_str = NULL;
+ List *names = NIL;
+ POOL_CONNECTION_POOL *backend;
+ static POOL_RELCACHE *relcache;
+
+ /* We need a modifiable copy of the input string. */
+ rawstring = pstrdup(fname);
+
+ /* split "schemaname.funcname" */
+ if(!SplitIdentifierString(rawstring, '.', (Node **) &names) ||
+ names == NIL)
+ {
+ if(rawstring)
+ pfree(rawstring);
+ if(names)
+ list_free(names);
+
+ ereport(WARNING,
+ (errmsg("invalid function name %s", fname)));
+
+ return false;
+ }
+
+ /* with schema qualification */
+ if(list_length(names) == 2)
+ {
+ key_str = fname;
+ if(!relcache)
+ {
+ snprintf(query, sizeof(query), IS_STABLE_FUNCTION_QUERY,
+ "=", (char *) lsecond(names), (char *) linitial(names));
+ }
+ else
+ {
+ snprintf(relcache->sql, sizeof(relcache->sql), IS_STABLE_FUNCTION_QUERY,
+ "=", (char *) lsecond(names), (char *) linitial(names));
+ }
+ }
+ else
+ {
+ key_str = (char *) llast(names);
+ if(!relcache)
+ {
+ snprintf(query, sizeof(query), IS_STABLE_FUNCTION_QUERY,
+ "~", ".*", (char *) llast(names));
+ }
+ else
+ {
+ snprintf(relcache->sql, sizeof(relcache->sql), IS_STABLE_FUNCTION_QUERY,
+ "~", ".*", (char *) llast(names));
+ }
+ }
backend = pool_get_session_context(false)->backend;
if (!relcache)
{
- relcache = pool_create_relcache(pool_config->relcache_size, IS_STABLE_FUNCTION_QUERY,
+ relcache = pool_create_relcache(pool_config->relcache_size, query,
int_register_func, int_unregister_func,
false);
if (relcache == NULL)
{
+ pfree(rawstring);
+ list_free(names);
+
ereport(WARNING,
(errmsg("unable to create relcache, while checking if the function is immutable")));
return false;
errdetail("relcache created")));
}
- result = (pool_search_relcache(relcache, backend, fname) == 0) ? 0 : 1;
+ result = (pool_search_relcache(relcache, backend, key_str) == 0) ? 0 : 1;
+
+ pfree(rawstring);
+ list_free(names);
ereport(DEBUG1,
(errmsg("checking if the function is IMMUTABLE"),
return rel;
}
+/*
+ * Extract function name from FuncCall. Make schema qualification name if
+ * necessary. The returned function name is in static area. So next
+ * call to this function will break previous result.
+ */
+char *
+make_function_name_from_funccall(FuncCall *fcall)
+{
+ /*
+ * Function name. Max size is calculated as follows: schema
+ * name(POOL_NAMEDATALEN byte) + quotation marks for schmea name(2 byte) +
+ * period(1 byte) + table name (POOL_NAMEDATALEN byte) + quotation marks
+ * for table name(2 byte) + NULL(1 byte)
+ */
+ static char funcname[POOL_NAMEDATALEN * 2 + 1 + 2 * 2 + 1];
+ List *names;
+
+ if(fcall == NULL)
+ {
+ ereport(WARNING,
+ (errmsg("FuncCall argument is NULL, while getting function name from FuncCall")));
+ return "";
+ }
+
+ *funcname = '\0';
+ names = fcall->funcname;
+
+ switch (list_length(names))
+ {
+ case 1:
+ strcat(funcname, "\"");
+ strncat(funcname, strVal(linitial(names)), POOL_NAMEDATALEN);
+ strcat(funcname, "\"");
+ break;
+ case 2:
+ strcat(funcname, "\"");
+ strncat(funcname, strVal(linitial(names)), POOL_NAMEDATALEN);
+ strcat(funcname, "\"");
+
+ strcat(funcname, ".");
+
+ strcat(funcname, "\"");
+ strncat(funcname, strVal(lsecond(names)), POOL_NAMEDATALEN);
+ strcat(funcname, "\"");
+ break;
+ case 3:
+ strcat(funcname, "\"");
+ strncat(funcname, strVal(lsecond(names)), POOL_NAMEDATALEN);
+ strcat(funcname, "\"");
+
+ strcat(funcname, ".");
+
+ strcat(funcname, "\"");
+ strncat(funcname, strVal(lthird(names)), POOL_NAMEDATALEN);
+ strcat(funcname, "\"");
+
+ break;
+ default:
+ ereport(WARNING,
+ (errmsg("invalid function name, too many indirections, while getting function name from FuncCall")));
+ break;
+ }
+
+ ereport(DEBUG1,
+ (errmsg("make function name from funccall: funcname:\"%s\"", funcname)));
+
+ return funcname;
+}
+
/*
* Extract table name from RageVar. Make schema qualification name if
* necessary. The returned table name is in static area. So next