diff options
| -rw-r--r-- | Makefile | 18 | ||||
| -rw-r--r-- | test/Makefile | 3 | ||||
| -rw-r--r-- | test/test_common.c | 1 | ||||
| -rw-r--r-- | test/test_common.h | 1 | ||||
| -rw-r--r-- | test/test_pgutil.c | 125 | ||||
| -rw-r--r-- | usual/pgutil.c | 261 | ||||
| -rw-r--r-- | usual/pgutil.h | 24 | ||||
| -rw-r--r-- | usual/pgutil_kwlookup.g | 12 |
8 files changed, 443 insertions, 2 deletions
@@ -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 + diff --git a/test/Makefile b/test/Makefile index 971705d..7ccdde1 100644 --- a/test/Makefile +++ b/test/Makefile @@ -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 diff --git a/test/test_common.c b/test/test_common.c index fa821dc..f8526b1 100644 --- a/test/test_common.c +++ b/test/test_common.c @@ -11,6 +11,7 @@ struct testgroup_t groups[] = { { "list/", list_tests }, { "utf8/", utf8_tests }, { "strpool/", strpool_tests }, + { "pgutil/", pgutil_tests }, END_OF_GROUPS }; diff --git a/test/test_common.h b/test/test_common.h index 54f04fa..677b1bf 100644 --- a/test/test_common.h +++ b/test/test_common.h @@ -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 index 0000000..8b102ca --- /dev/null +++ b/test/test_pgutil.c @@ -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 index 0000000..8028525 --- /dev/null +++ b/usual/pgutil.c @@ -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 index 0000000..c4fa5f6 --- /dev/null +++ b/usual/pgutil.h @@ -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 index 0000000..38b5a36 --- /dev/null +++ b/usual/pgutil_kwlookup.g @@ -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; }; + +%% |
