pgutil: move common postgres helper functions here.
authorMarko Kreen <markokr@gmail.com>
Wed, 1 Sep 2010 14:26:48 +0000 (17:26 +0300)
committerMarko Kreen <markokr@gmail.com>
Wed, 1 Sep 2010 14:26:48 +0000 (17:26 +0300)
Makefile
test/Makefile
test/test_common.c
test/test_common.h
test/test_pgutil.c [new file with mode: 0644]
usual/pgutil.c [new file with mode: 0644]
usual/pgutil.h [new file with mode: 0644]
usual/pgutil_kwlookup.g [new file with mode: 0644]

index 688a4babfc58e7391a628b1ad1c731f495887f08..3f63be9273219ed4d45561fcd25d1f7b2d567878 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -12,6 +12,11 @@ USUAL_OBJDIR = obj
 USUAL_MODULES = $(filter-out pgsocket, $(subst .h,, $(notdir $(wildcard $(USUAL_DIR)/usual/*.h))))
 include $(USUAL_DIR)/Setup.mk
 
+# pgutil_kwlookup generation
+PG_CONFIG ?= pg_config
+KWLIST = $(shell $(PG_CONFIG) --includedir-server)/parser/kwlist.h
+GPERF = gperf -m5
+
 # full path for files
 srcs = $(USUAL_SRCS)
 hdrs = $(USUAL_HDRS)
@@ -65,10 +70,14 @@ check: config.mak
 clean:
        rm -f libusual.a obj/*.[os] obj/test* aclocal* config.log
        rm -rf autom4te*
+       rm -f usual/pgutil_kwlookup.h.gp
 
 distclean: clean
        rm -f config.mak usual/config.h
 
+realclean:
+       rm -f usual/pgutil_kwlookup.h
+
 boot:
        rm -rf usual/config.*
        aclocal -I ./m4
@@ -98,3 +107,12 @@ dbg:
        @echo hdrs=$(hdrs)
        @echo CPPFLAGS=$(CPPFLAGS)
 
+kws:
+       @test -f "$(KWLIST)" || { echo "kwlist.h not found"; exit 1; }
+       cat usual/pgutil_kwlookup.g > usual/pgutil_kwlookup.gp
+       grep '^PG_KEYWORD' "$(KWLIST)" \
+       | sed 's/.*"\(.*\)",.*, *\(.*\)[)].*/\1, PG_\2/' \
+       >> usual/pgutil_kwlookup.gp
+       $(GPERF) usual/pgutil_kwlookup.gp > usual/pgutil_kwlookup.h
+       rm -f usual/pgutil_kwlookup.gp
+
index 971705d1873cec2d798289f3de5ec052ad9f9300..7ccdde1765cf9fae3c1cac198d6329fa42c497f0 100644 (file)
@@ -11,11 +11,10 @@ override USUAL_DIR = ..
 override DEFS = -DUSUAL_TEST_CONFIG
 OBJS = test_string.o test_crypto.o test_aatree.o test_heap.o \
        test_common.o test_list.o tinytest.o test_cbtree.o \
-       test_utf8.o test_strpool.o
+       test_utf8.o test_strpool.o test_pgutil.o
 
 test-all: regtest
 
-
 include ../Makefile
 
 test_config.h: ../usual/config.h force_compat.sed
index fa821dc0bbaff23d588c0b13ec1b8c6d06cd0f2b..f8526b1257f65e9f31d88a8ea913200d26c29529 100644 (file)
@@ -11,6 +11,7 @@ struct testgroup_t groups[] = {
        { "list/", list_tests },
        { "utf8/", utf8_tests },
        { "strpool/", strpool_tests },
+       { "pgutil/", pgutil_tests },
        END_OF_GROUPS
 };
 
index 54f04fa81b714e0098e20e8bca4c5a9a775bc657..677b1bf2842e16c309ba02a90b32c3ac45c5b3b5 100644 (file)
@@ -15,4 +15,5 @@ extern struct testcase_t heap_tests[];
 extern struct testcase_t list_tests[];
 extern struct testcase_t utf8_tests[];
 extern struct testcase_t strpool_tests[];
+extern struct testcase_t pgutil_tests[];
 
diff --git a/test/test_pgutil.c b/test/test_pgutil.c
new file mode 100644 (file)
index 0000000..8b102ca
--- /dev/null
@@ -0,0 +1,125 @@
+
+#include <usual/pgutil.h>
+
+#include "test_common.h"
+
+/*
+ * pg_quote_literal
+ */
+
+static char *run_quote_lit(char *dst, const char *src, int size)
+{
+       if (pg_quote_literal(dst, src, size))
+               return dst;
+       return "FAIL";
+}
+
+static void test_quote_lit(void *ptr)
+{
+       char buf[128];
+       str_check(run_quote_lit(buf, "", 16), "''");
+       str_check(run_quote_lit(buf, "a", 16), "'a'");
+       str_check(run_quote_lit(buf, "a'a", 16), "'a''a'");
+       str_check(run_quote_lit(buf, "a\\a", 16), "E'a\\\\a'");
+
+       str_check(run_quote_lit(buf, "", 3), "''");
+       str_check(run_quote_lit(buf, "", 2), "FAIL");
+       str_check(run_quote_lit(buf, "", 1), "FAIL");
+       str_check(run_quote_lit(buf, "", 0), "FAIL");
+
+       str_check(run_quote_lit(buf, "a'a", 7), "'a''a'");
+       str_check(run_quote_lit(buf, "a'a", 6), "FAIL");
+
+       str_check(run_quote_lit(buf, "a\\a", 8), "E'a\\\\a'");
+       str_check(run_quote_lit(buf, "a\\a", 7), "FAIL");
+
+       str_check(run_quote_lit(buf, "a", 4), "'a'");
+       str_check(run_quote_lit(buf, "a", 3), "FAIL");
+end:;
+}
+
+/*
+ * quote_ident
+ */
+
+static char *qident(char *dst, const char *src, int size)
+{
+       if (pg_quote_ident(dst, src, size))
+               return dst;
+       return "FAIL";
+}
+
+static void test_quote_ident(void *ptr)
+{
+       char buf[128];
+       str_check(qident(buf, "", 16), "\"\"");
+       str_check(qident(buf, "id_", 16), "id_");
+       str_check(qident(buf, "_id", 16), "_id");
+       str_check(qident(buf, "Baz", 16), "\"Baz\"");
+       str_check(qident(buf, "baZ", 16), "\"baZ\"");
+       str_check(qident(buf, "b z", 16), "\"b z\"");
+       str_check(qident(buf, "5id", 16), "\"5id\"");
+       str_check(qident(buf, "\"", 16), "\"\"\"\"");
+       str_check(qident(buf, "a\"b", 16), "\"a\"\"b\"");
+       str_check(qident(buf, "WHERE", 16), "\"WHERE\"");
+       str_check(qident(buf, "where", 16), "\"where\"");
+       str_check(qident(buf, "here", 16), "here");
+       str_check(qident(buf, "in", 16), "\"in\"");
+
+       str_check(qident(buf, "", 3), "\"\"");
+       str_check(qident(buf, "", 2), "FAIL");
+       str_check(qident(buf, "", 1), "FAIL");
+       str_check(qident(buf, "", 0), "FAIL");
+
+       str_check(qident(buf, "i", 2), "i");
+       str_check(qident(buf, "i", 1), "FAIL");
+       str_check(qident(buf, "i", 0), "FAIL");
+
+       str_check(qident(buf, "a\"b", 7), "\"a\"\"b\"");
+       str_check(qident(buf, "a\"b", 6), "FAIL");
+       str_check(qident(buf, "a\"b", 5), "FAIL");
+       str_check(qident(buf, "a\"b", 4), "FAIL");
+       str_check(qident(buf, "a\"b", 3), "FAIL");
+end:;
+}
+
+/*
+ * quote_fqident
+ */
+
+static char *fqident(char *dst, const char *src, int size)
+{
+       if (pg_quote_fqident(dst, src, size))
+               return dst;
+       return "FAIL";
+}
+
+static void test_quote_fqident(void *ptr)
+{
+       char buf[128];
+       str_check(fqident(buf, "", 16), "public.\"\"");
+       str_check(fqident(buf, "baz.foo", 16), "baz.foo");
+       str_check(fqident(buf, "baz.foo.bar", 16), "baz.\"foo.bar\"");
+       str_check(fqident(buf, "where.in", 16), "\"where\".\"in\"");
+
+       str_check(fqident(buf, "a.b", 4), "a.b");
+       str_check(fqident(buf, "a.b", 3), "FAIL");
+       str_check(fqident(buf, "a.b", 1), "FAIL");
+       str_check(fqident(buf, "a.b", 0), "FAIL");
+
+       str_check(fqident(buf, "i", 9), "public.i");
+       str_check(fqident(buf, "i", 8), "FAIL");
+end:;
+}
+
+/*
+ * Describe
+ */
+
+struct testcase_t pgutil_tests[] = {
+       { "pg_quote_literal", test_quote_lit },
+       { "pg_quote_ident", test_quote_ident },
+       { "pg_quote_fqident", test_quote_fqident },
+       END_OF_TESTCASES
+};
+
diff --git a/usual/pgutil.c b/usual/pgutil.c
new file mode 100644 (file)
index 0000000..8028525
--- /dev/null
@@ -0,0 +1,261 @@
+/*
+ * Some utility functions for Postgres.
+ *
+ * - Literal & ident quoting.
+ * - Array parsing
+ */
+
+#include <usual/pgutil.h>
+
+#include <ctype.h>
+
+struct PgKeyword;
+const struct PgKeyword *pg_keyword_lookup_hash(const char *str, unsigned int len);
+#include "usual/pgutil_kwlookup.h"
+
+enum PgKeywordType pg_keyword_lookup(const char *str)
+{
+       const struct PgKeyword *kw;
+       kw = pg_keyword_lookup_hash(str, strlen(str));
+       return kw ? kw->type : 0;
+}
+
+bool pg_is_reserved_word(const char *str)
+{
+       enum PgKeywordType t = pg_keyword_lookup(str);
+       return t && (t != PG_UNRESERVED_KEYWORD);
+}
+
+/* str -> E'str' */
+bool pg_quote_literal(char *_dst, const char *_src, int dstlen)
+{
+       char *dst = _dst;
+       char *end = _dst + dstlen - 2;
+       const char *src = _src;
+       bool stdquote = true;
+
+       if (dstlen < 3)
+               return false;
+
+retry:
+       *dst++ = '\'';
+       while (*src && dst < end) {
+               if (*src == '\'')
+                       *dst++ = '\'';
+               else if (*src == '\\') {
+                       if (stdquote)
+                               goto retry_ext;
+                       *dst++ = '\\';
+               }
+               *dst++ = *src++;
+       }
+       if (*src || dst > end)
+               return false;
+
+       *dst++ = '\'';
+       *dst = 0;
+
+       return true;
+retry_ext:
+       /* string contains '\\', retry as E'' string */
+       dst = _dst;
+       src = _src;
+       *dst++ = 'E';
+       stdquote = false;
+       goto retry;
+}
+
+static inline bool id_start(unsigned char c)
+{
+       return (c >= 'a' && c <= 'z') || c == '_';
+}
+
+static inline bool id_body(unsigned char c)
+{
+       return id_start(c) || (c >= '0' && c <= '9');
+}
+
+/* ident -> "ident" */
+bool pg_quote_ident(char *_dst, const char *_src, int dstlen)
+{
+       char *dst = _dst;
+       char *end = _dst + dstlen - 1;
+       const char *src = _src;
+
+       if (dstlen < 1)
+               return false;
+
+       if (!id_start(*src))
+               goto needs_quoting;
+
+       while (*src && dst < end) {
+               if (!id_body(*src))
+                       goto needs_quoting;
+               *dst++ = *src++;
+       }
+       if (*src)
+               return false;
+       *dst = 0;
+
+       if (!pg_is_reserved_word(_dst))
+               return true;
+
+needs_quoting:
+       dst = _dst;
+       src = _src;
+       end = _dst + dstlen - 2;
+       if (dstlen < 3)
+               return false;
+       *dst++ = '"';
+       while (*src && dst < end) {
+               if (*src == '"')
+                       *dst++ = *src;
+               *dst++ = *src++;
+       }
+       if (*src)
+               return false;
+       *dst++ = '"';
+       *dst = 0;
+       return true;
+}
+
+/* schema.name -> "schema"."name" */
+bool pg_quote_fqident(char *_dst, const char *_src, int dstlen)
+{
+       const char *dot = strchr(_src, '.');
+       char scmbuf[128];
+       const char *scm;
+       int scmlen;
+       if (dot) {
+               scmlen = dot - _src;
+               if (scmlen >= (int)sizeof(scmbuf))
+                       return false;
+               memcpy(scmbuf, _src, scmlen);
+               scmbuf[scmlen] = 0;
+               scm = scmbuf;
+               _src = dot + 1;
+       } else {
+               scm = "public";
+       }
+       if (!pg_quote_ident(_dst, scm, dstlen))
+               return false;
+
+       scmlen = strlen(_dst);
+       _dst[scmlen] = '.';
+       _dst += scmlen + 1;
+       dstlen -= scmlen + 1;
+       if (!pg_quote_ident(_dst, _src, dstlen))
+               return false;
+       return true;
+}
+
+/*
+ * pgarray parsing
+ */
+
+static bool pgarr_add_value(struct StrList *arr, const char *val, const char *vend)
+{
+       int len = vend - val + 1;
+       const char *s = val;
+       char *str, *p;
+       unsigned c;
+
+       if ((vend - val) == 4 && !strncasecmp(val, "null", 4)) {
+               //log_warning("pgarr_add_value: ignoring NULL value");
+               return true;
+       }
+       p = str = malloc(len);
+       if (!str)
+               return false;
+
+       /* unquote & copy */
+       while (s < vend) {
+               c = *s++;
+               if (c == '"') {
+                       while (1) {
+                               c = *s++;
+                               if (c == '"')
+                                       break;
+                               else if (c == '\\')
+                                       *p++ = *s++;
+                               else
+                                       *p++ = c;
+                       }
+               } else if (c == '\\') {
+                       *p++ = *s++;
+               } else
+                       *p++ = c;
+       }
+       *p++ = 0;
+       if (!strlist_append_ref(arr, str)) {
+               free(str);
+               return false;
+       }
+       return true;
+}
+
+struct StrList *parse_pgarray(const char *pgarr)
+{
+       const char *s = pgarr;
+       struct StrList *lst;
+       const char *val = NULL;
+       unsigned c;
+
+       if (*s++ != '{')
+               return NULL;
+       lst = strlist_new();
+       if (!lst)
+               return NULL;
+       while (*s) {
+               /* array end */
+               if (s[0] == '}') {
+                       if (s[1] != 0) {
+                               goto failed;
+                       }
+                       if (val) {
+                               if (!pgarr_add_value(lst, val, s))
+                                       goto failed;
+                       }
+                       return lst;
+               }
+
+               /* cannot init earlier to support empty arrays */
+               if (!val)
+                       val = s;
+
+               /* val done? */
+               if (*s == ',') {
+                       if (!pgarr_add_value(lst, val, s))
+                               goto failed;
+                       val = ++s;
+                       continue;
+               }
+
+               /* scan value */
+               c = *s++;
+               if (c == '"') {
+                       while (1) {
+                               c = *s++;
+                               if (c == '"')
+                                       break;
+                               else if (c == '\\') {
+                                       if (!*s) goto failed;
+                                       s++;
+                               } else if (!*s)
+                                       goto failed;
+                       }
+               } else if (c == '\\') {
+                       if (!*s) goto failed;
+                       s++;
+               }
+       }
+       if (s[-1] != '}') {
+               //log_warning("parse_pgarray: missing }");
+               goto failed;
+       }
+       return lst;
+failed:
+       strlist_free(lst);
+       return NULL;
+}
+
diff --git a/usual/pgutil.h b/usual/pgutil.h
new file mode 100644 (file)
index 0000000..c4fa5f6
--- /dev/null
@@ -0,0 +1,24 @@
+
+#ifndef _USUAL_PGUTIL_H_
+#define _USUAL_PGUTIL_H_
+
+#include <usual/string.h>
+
+enum PgKeywordType {
+       PG_NOT_KEYWORD = 0,
+       PG_UNRESERVED_KEYWORD = 1,
+       PG_RESERVED_KEYWORD = 2,
+       PG_TYPE_FUNC_NAME_KEYWORD = 3,
+       PG_COL_NAME_KEYWORD = 4
+};
+enum PgKeywordType pg_keyword_lookup(const char *str);
+
+bool pg_is_reserved_word(const char *str);
+
+bool pg_quote_literal(char *_dst, const char *_src, int dstlen);
+bool pg_quote_ident(char *_dst, const char *_src, int dstlen);
+bool pg_quote_fqident(char *_dst, const char *_src, int dstlen);
+struct StrList *parse_pgarray(const char *pgarr);
+
+#endif
+
diff --git a/usual/pgutil_kwlookup.g b/usual/pgutil_kwlookup.g
new file mode 100644 (file)
index 0000000..38b5a36
--- /dev/null
@@ -0,0 +1,12 @@
+/* gperf header for kwlookup */
+
+%language=ANSI-C
+%readonly-tables
+%pic
+
+%define lookup-function-name pg_keyword_lookup_hash
+
+%struct-type
+struct PgKeyword { short name; short type; };
+
+%%