From 2a012f3cee817b6b17bf02e2279984256f5386fc Mon Sep 17 00:00:00 2001 From: Arjun Lall Date: Fri, 6 Dec 2024 14:39:56 -0800 Subject: [PATCH] Add pg_get_keywords() support --- scripts/install.sh | 2 +- src/main.go | 2 +- src/parser.go | 522 ++++++++++++++++++++++++++++++++++++++ src/query_handler_test.go | 11 + src/select_remapper.go | 23 ++ 5 files changed, 558 insertions(+), 2 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 0a9e1ec..56be2c7 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,6 +1,6 @@ #!/bin/bash -VERSION="0.19.2" +VERSION="0.19.3" # Detect OS and architecture OS=$(uname -s | tr '[:upper:]' '[:lower:]') diff --git a/src/main.go b/src/main.go index e43062f..2e95ee0 100644 --- a/src/main.go +++ b/src/main.go @@ -6,7 +6,7 @@ import ( "time" ) -const VERSION = "0.19.2" +const VERSION = "0.19.3" func main() { config := LoadConfig() diff --git a/src/parser.go b/src/parser.go index 002042e..0f7656f 100644 --- a/src/parser.go +++ b/src/parser.go @@ -159,6 +159,494 @@ var PG_SHADOW_VALUE_BY_COLUMN = NewOrderedMap([][]string{ {"useconfig", "NULL"}, }) +type DuckDBKeyword struct { + word string + category string +} + +var DUCKDB_KEYWORDS = []DuckDBKeyword{ + {"abort", "unreserved"}, + {"absolute", "unreserved"}, + {"access", "unreserved"}, + {"action", "unreserved"}, + {"add", "unreserved"}, + {"admin", "unreserved"}, + {"after", "unreserved"}, + {"aggregate", "unreserved"}, + {"all", "reserved"}, + {"also", "unreserved"}, + {"alter", "unreserved"}, + {"always", "unreserved"}, + {"analyse", "reserved"}, + {"analyze", "reserved"}, + {"and", "reserved"}, + {"anti", "type_function"}, + {"any", "reserved"}, + {"array", "reserved"}, + {"as", "reserved"}, + {"asc", "reserved"}, + {"asof", "type_function"}, + {"assertion", "unreserved"}, + {"assignment", "unreserved"}, + {"asymmetric", "reserved"}, + {"at", "unreserved"}, + {"attach", "unreserved"}, + {"attribute", "unreserved"}, + {"authorization", "type_function"}, + {"backward", "unreserved"}, + {"before", "unreserved"}, + {"begin", "unreserved"}, + {"between", "column_name"}, + {"bigint", "column_name"}, + {"binary", "type_function"}, + {"bit", "column_name"}, + {"boolean", "column_name"}, + {"both", "reserved"}, + {"by", "unreserved"}, + {"cache", "unreserved"}, + {"call", "unreserved"}, + {"called", "unreserved"}, + {"cascade", "unreserved"}, + {"cascaded", "unreserved"}, + {"case", "reserved"}, + {"cast", "reserved"}, + {"catalog", "unreserved"}, + {"centuries", "unreserved"}, + {"century", "unreserved"}, + {"chain", "unreserved"}, + {"char", "column_name"}, + {"character", "column_name"}, + {"characteristics", "unreserved"}, + {"check", "reserved"}, + {"checkpoint", "unreserved"}, + {"class", "unreserved"}, + {"close", "unreserved"}, + {"cluster", "unreserved"}, + {"coalesce", "column_name"}, + {"collate", "reserved"}, + {"collation", "type_function"}, + {"column", "reserved"}, + {"columns", "type_function"}, + {"comment", "unreserved"}, + {"comments", "unreserved"}, + {"commit", "unreserved"}, + {"committed", "unreserved"}, + {"compression", "unreserved"}, + {"concurrently", "type_function"}, + {"configuration", "unreserved"}, + {"conflict", "unreserved"}, + {"connection", "unreserved"}, + {"constraint", "reserved"}, + {"constraints", "unreserved"}, + {"content", "unreserved"}, + {"continue", "unreserved"}, + {"conversion", "unreserved"}, + {"copy", "unreserved"}, + {"cost", "unreserved"}, + {"create", "reserved"}, + {"cross", "type_function"}, + {"csv", "unreserved"}, + {"cube", "unreserved"}, + {"current", "unreserved"}, + {"cursor", "unreserved"}, + {"cycle", "unreserved"}, + {"data", "unreserved"}, + {"database", "unreserved"}, + {"day", "unreserved"}, + {"days", "unreserved"}, + {"deallocate", "unreserved"}, + {"dec", "column_name"}, + {"decade", "unreserved"}, + {"decades", "unreserved"}, + {"decimal", "column_name"}, + {"declare", "unreserved"}, + {"default", "reserved"}, + {"defaults", "unreserved"}, + {"deferrable", "reserved"}, + {"deferred", "unreserved"}, + {"definer", "unreserved"}, + {"delete", "unreserved"}, + {"delimiter", "unreserved"}, + {"delimiters", "unreserved"}, + {"depends", "unreserved"}, + {"desc", "reserved"}, + {"describe", "reserved"}, + {"detach", "unreserved"}, + {"dictionary", "unreserved"}, + {"disable", "unreserved"}, + {"discard", "unreserved"}, + {"distinct", "reserved"}, + {"do", "reserved"}, + {"document", "unreserved"}, + {"domain", "unreserved"}, + {"double", "unreserved"}, + {"drop", "unreserved"}, + {"each", "unreserved"}, + {"else", "reserved"}, + {"enable", "unreserved"}, + {"encoding", "unreserved"}, + {"encrypted", "unreserved"}, + {"end", "reserved"}, + {"enum", "unreserved"}, + {"escape", "unreserved"}, + {"event", "unreserved"}, + {"except", "reserved"}, + {"exclude", "unreserved"}, + {"excluding", "unreserved"}, + {"exclusive", "unreserved"}, + {"execute", "unreserved"}, + {"exists", "column_name"}, + {"explain", "unreserved"}, + {"export", "unreserved"}, + {"export_state", "unreserved"}, + {"extension", "unreserved"}, + {"extensions", "unreserved"}, + {"external", "unreserved"}, + {"extract", "column_name"}, + {"false", "reserved"}, + {"family", "unreserved"}, + {"fetch", "reserved"}, + {"filter", "unreserved"}, + {"first", "unreserved"}, + {"float", "column_name"}, + {"following", "unreserved"}, + {"for", "reserved"}, + {"force", "unreserved"}, + {"foreign", "reserved"}, + {"forward", "unreserved"}, + {"freeze", "type_function"}, + {"from", "reserved"}, + {"full", "type_function"}, + {"function", "unreserved"}, + {"functions", "unreserved"}, + {"generated", "type_function"}, + {"glob", "type_function"}, + {"global", "unreserved"}, + {"grant", "reserved"}, + {"granted", "unreserved"}, + {"group", "reserved"}, + {"grouping", "column_name"}, + {"grouping_id", "column_name"}, + {"groups", "unreserved"}, + {"handler", "unreserved"}, + {"having", "reserved"}, + {"header", "unreserved"}, + {"hold", "unreserved"}, + {"hour", "unreserved"}, + {"hours", "unreserved"}, + {"identity", "unreserved"}, + {"if", "unreserved"}, + {"ignore", "unreserved"}, + {"ilike", "type_function"}, + {"immediate", "unreserved"}, + {"immutable", "unreserved"}, + {"implicit", "unreserved"}, + {"import", "unreserved"}, + {"in", "reserved"}, + {"include", "unreserved"}, + {"including", "unreserved"}, + {"increment", "unreserved"}, + {"index", "unreserved"}, + {"indexes", "unreserved"}, + {"inherit", "unreserved"}, + {"inherits", "unreserved"}, + {"initially", "reserved"}, + {"inline", "unreserved"}, + {"inner", "type_function"}, + {"inout", "column_name"}, + {"input", "unreserved"}, + {"insensitive", "unreserved"}, + {"insert", "unreserved"}, + {"install", "unreserved"}, + {"instead", "unreserved"}, + {"int", "column_name"}, + {"integer", "column_name"}, + {"intersect", "reserved"}, + {"interval", "column_name"}, + {"into", "reserved"}, + {"invoker", "unreserved"}, + {"is", "type_function"}, + {"isnull", "type_function"}, + {"isolation", "unreserved"}, + {"join", "type_function"}, + {"json", "unreserved"}, + {"key", "unreserved"}, + {"label", "unreserved"}, + {"language", "unreserved"}, + {"large", "unreserved"}, + {"last", "unreserved"}, + {"lateral", "reserved"}, + {"leading", "reserved"}, + {"leakproof", "unreserved"}, + {"left", "type_function"}, + {"level", "unreserved"}, + {"like", "type_function"}, + {"limit", "reserved"}, + {"listen", "unreserved"}, + {"load", "unreserved"}, + {"local", "unreserved"}, + {"location", "unreserved"}, + {"lock", "unreserved"}, + {"locked", "unreserved"}, + {"logged", "unreserved"}, + {"macro", "unreserved"}, + {"map", "type_function"}, + {"mapping", "unreserved"}, + {"match", "unreserved"}, + {"materialized", "unreserved"}, + {"maxvalue", "unreserved"}, + {"method", "unreserved"}, + {"microsecond", "unreserved"}, + {"microseconds", "unreserved"}, + {"millennia", "unreserved"}, + {"millennium", "unreserved"}, + {"millisecond", "unreserved"}, + {"milliseconds", "unreserved"}, + {"minute", "unreserved"}, + {"minutes", "unreserved"}, + {"minvalue", "unreserved"}, + {"mode", "unreserved"}, + {"month", "unreserved"}, + {"months", "unreserved"}, + {"move", "unreserved"}, + {"name", "unreserved"}, + {"names", "unreserved"}, + {"national", "column_name"}, + {"natural", "type_function"}, + {"nchar", "column_name"}, + {"new", "unreserved"}, + {"next", "unreserved"}, + {"no", "unreserved"}, + {"none", "column_name"}, + {"not", "reserved"}, + {"nothing", "unreserved"}, + {"notify", "unreserved"}, + {"notnull", "type_function"}, + {"nowait", "unreserved"}, + {"null", "reserved"}, + {"nullif", "column_name"}, + {"nulls", "unreserved"}, + {"numeric", "column_name"}, + {"object", "unreserved"}, + {"of", "unreserved"}, + {"off", "unreserved"}, + {"offset", "reserved"}, + {"oids", "unreserved"}, + {"old", "unreserved"}, + {"on", "reserved"}, + {"only", "reserved"}, + {"operator", "unreserved"}, + {"option", "unreserved"}, + {"options", "unreserved"}, + {"or", "reserved"}, + {"order", "reserved"}, + {"ordinality", "unreserved"}, + {"others", "unreserved"}, + {"out", "column_name"}, + {"outer", "type_function"}, + {"over", "unreserved"}, + {"overlaps", "type_function"}, + {"overlay", "column_name"}, + {"overriding", "unreserved"}, + {"owned", "unreserved"}, + {"owner", "unreserved"}, + {"parallel", "unreserved"}, + {"parser", "unreserved"}, + {"partial", "unreserved"}, + {"partition", "unreserved"}, + {"passing", "unreserved"}, + {"password", "unreserved"}, + {"percent", "unreserved"}, + {"persistent", "unreserved"}, + {"pivot", "reserved"}, + {"pivot_longer", "reserved"}, + {"pivot_wider", "reserved"}, + {"placing", "reserved"}, + {"plans", "unreserved"}, + {"policy", "unreserved"}, + {"position", "column_name"}, + {"positional", "type_function"}, + {"pragma", "unreserved"}, + {"preceding", "unreserved"}, + {"precision", "column_name"}, + {"prepare", "unreserved"}, + {"prepared", "unreserved"}, + {"preserve", "unreserved"}, + {"primary", "reserved"}, + {"prior", "unreserved"}, + {"privileges", "unreserved"}, + {"procedural", "unreserved"}, + {"procedure", "unreserved"}, + {"program", "unreserved"}, + {"publication", "unreserved"}, + {"qualify", "reserved"}, + {"quarter", "unreserved"}, + {"quarters", "unreserved"}, + {"quote", "unreserved"}, + {"range", "unreserved"}, + {"read", "unreserved"}, + {"real", "column_name"}, + {"reassign", "unreserved"}, + {"recheck", "unreserved"}, + {"recursive", "unreserved"}, + {"ref", "unreserved"}, + {"references", "reserved"}, + {"referencing", "unreserved"}, + {"refresh", "unreserved"}, + {"reindex", "unreserved"}, + {"relative", "unreserved"}, + {"release", "unreserved"}, + {"rename", "unreserved"}, + {"repeatable", "unreserved"}, + {"replace", "unreserved"}, + {"replica", "unreserved"}, + {"reset", "unreserved"}, + {"respect", "unreserved"}, + {"restart", "unreserved"}, + {"restrict", "unreserved"}, + {"returning", "reserved"}, + {"returns", "unreserved"}, + {"revoke", "unreserved"}, + {"right", "type_function"}, + {"role", "unreserved"}, + {"rollback", "unreserved"}, + {"rollup", "unreserved"}, + {"row", "column_name"}, + {"rows", "unreserved"}, + {"rule", "unreserved"}, + {"sample", "unreserved"}, + {"savepoint", "unreserved"}, + {"schema", "unreserved"}, + {"schemas", "unreserved"}, + {"scope", "unreserved"}, + {"scroll", "unreserved"}, + {"search", "unreserved"}, + {"second", "unreserved"}, + {"seconds", "unreserved"}, + {"secret", "unreserved"}, + {"security", "unreserved"}, + {"select", "reserved"}, + {"semi", "type_function"}, + {"sequence", "unreserved"}, + {"sequences", "unreserved"}, + {"serializable", "unreserved"}, + {"server", "unreserved"}, + {"session", "unreserved"}, + {"set", "unreserved"}, + {"setof", "column_name"}, + {"sets", "unreserved"}, + {"share", "unreserved"}, + {"show", "reserved"}, + {"similar", "type_function"}, + {"simple", "unreserved"}, + {"skip", "unreserved"}, + {"smallint", "column_name"}, + {"snapshot", "unreserved"}, + {"some", "reserved"}, + {"sql", "unreserved"}, + {"stable", "unreserved"}, + {"standalone", "unreserved"}, + {"start", "unreserved"}, + {"statement", "unreserved"}, + {"statistics", "unreserved"}, + {"stdin", "unreserved"}, + {"stdout", "unreserved"}, + {"storage", "unreserved"}, + {"stored", "unreserved"}, + {"strict", "unreserved"}, + {"strip", "unreserved"}, + {"struct", "type_function"}, + {"subscription", "unreserved"}, + {"substring", "column_name"}, + {"summarize", "reserved"}, + {"symmetric", "reserved"}, + {"sysid", "unreserved"}, + {"system", "unreserved"}, + {"table", "reserved"}, + {"tables", "unreserved"}, + {"tablesample", "type_function"}, + {"tablespace", "unreserved"}, + {"temp", "unreserved"}, + {"template", "unreserved"}, + {"temporary", "unreserved"}, + {"text", "unreserved"}, + {"then", "reserved"}, + {"ties", "unreserved"}, + {"time", "column_name"}, + {"timestamp", "column_name"}, + {"to", "reserved"}, + {"trailing", "reserved"}, + {"transaction", "unreserved"}, + {"transform", "unreserved"}, + {"treat", "column_name"}, + {"trigger", "unreserved"}, + {"trim", "column_name"}, + {"true", "reserved"}, + {"truncate", "unreserved"}, + {"trusted", "unreserved"}, + {"try_cast", "type_function"}, + {"type", "unreserved"}, + {"types", "unreserved"}, + {"unbounded", "unreserved"}, + {"uncommitted", "unreserved"}, + {"unencrypted", "unreserved"}, + {"union", "reserved"}, + {"unique", "reserved"}, + {"unknown", "unreserved"}, + {"unlisten", "unreserved"}, + {"unlogged", "unreserved"}, + {"unpivot", "reserved"}, + {"until", "unreserved"}, + {"update", "unreserved"}, + {"use", "unreserved"}, + {"user", "unreserved"}, + {"using", "reserved"}, + {"vacuum", "unreserved"}, + {"valid", "unreserved"}, + {"validate", "unreserved"}, + {"validator", "unreserved"}, + {"value", "unreserved"}, + {"values", "column_name"}, + {"varchar", "column_name"}, + {"variable", "unreserved"}, + {"variadic", "reserved"}, + {"varying", "unreserved"}, + {"verbose", "type_function"}, + {"version", "unreserved"}, + {"view", "unreserved"}, + {"views", "unreserved"}, + {"virtual", "unreserved"}, + {"volatile", "unreserved"}, + {"week", "unreserved"}, + {"weeks", "unreserved"}, + {"when", "reserved"}, + {"where", "reserved"}, + {"whitespace", "unreserved"}, + {"window", "reserved"}, + {"with", "reserved"}, + {"within", "unreserved"}, + {"without", "unreserved"}, + {"work", "unreserved"}, + {"wrapper", "unreserved"}, + {"write", "unreserved"}, + {"xml", "unreserved"}, + {"xmlattributes", "column_name"}, + {"xmlconcat", "column_name"}, + {"xmlelement", "column_name"}, + {"xmlexists", "column_name"}, + {"xmlforest", "column_name"}, + {"xmlnamespaces", "column_name"}, + {"xmlparse", "column_name"}, + {"xmlpi", "column_name"}, + {"xmlroot", "column_name"}, + {"xmlserialize", "column_name"}, + {"xmltable", "column_name"}, + {"year", "unreserved"}, + {"years", "unreserved"}, + {"yes", "unreserved"}, + {"zone", "unreserved"}, +} + func IsSystemTable(table string) bool { return PG_SYSTEM_TABLES.Contains(table) || PG_SYSTEM_VIEWS.Contains(table) } @@ -240,6 +728,40 @@ func MakePgShadowNode(user string, encryptedPassword string) *pgQuery.Node { return makeSubselectNode(columns, rowsValues) } +// FROM pg_catalog.pg_get_keywords(): +func MakePgGetKeywordsNode() *pgQuery.Node { + columns := []string{"word", "catcode", "barelabel", "catdesc", "baredesc"} + + var rows [][]string + for _, kw := range DUCKDB_KEYWORDS { + catcode := "U" + catdesc := "unreserved" + + switch kw.category { + case "reserved": + catcode = "R" + catdesc = "reserved" + case "type_function": + catcode = "T" + catdesc = "reserved (can be function or type name)" + case "column_name": + catcode = "C" + catdesc = "unreserved (cannot be function or type name)" + } + + row := []string{ + kw.word, + catcode, + "t", + catdesc, + "can be bare label", + } + rows = append(rows, row) + } + + return makeSubselectNode(columns, rows) +} + func MakeStringExpressionNode(column string, operation string, value string) *pgQuery.Node { return pgQuery.MakeAExprNode( pgQuery.A_Expr_Kind_AEXPR_OP, diff --git a/src/query_handler_test.go b/src/query_handler_test.go index 0013c3f..c653ff9 100644 --- a/src/query_handler_test.go +++ b/src/query_handler_test.go @@ -376,6 +376,17 @@ func TestHandleQuery(t *testing.T) { "description": {"search_path"}, "values": {`"$user", public`}, }, + // Keywords query + "SELECT * FROM pg_catalog.pg_get_keywords() limit 1": { + "description": {"word", "catcode", "barelabel", "catdesc", "baredesc"}, + "values": { + "abort", + "U", + "t", + "unreserved", + "can be bare label", + }, + }, } for query, responses := range responsesByQuery { diff --git a/src/select_remapper.go b/src/select_remapper.go index a364b75..0a7f73a 100644 --- a/src/select_remapper.go +++ b/src/select_remapper.go @@ -17,6 +17,7 @@ const ( PG_SHADOW = "pg_shadow" PG_QUOTE_INDENT_FUNCTION_NAME = "quote_ident" + PG_GET_KEYWORDS_FUNCTION_NAME = "pg_get_keywords" ) var REMAPPED_CONSTANT_BY_PG_FUNCTION_NAME = map[string]string{ @@ -99,6 +100,10 @@ func (selectRemapper *SelectRemapper) remapSelectStatement(selectStatement *pgQu } else if fromNode.GetRangeSubselect() != nil { selectRemapper.remapSelectStatement(fromNode.GetRangeSubselect().Subquery.GetSelectStmt(), indentLevel+1) } + + if fromNode.GetRangeFunction() != nil { + selectStatement.FromClause[i] = selectRemapper.remapTableFunction(fromNode) + } } return selectStatement } @@ -296,6 +301,24 @@ func (selectRemapper *SelectRemapper) remapTable(node *pgQuery.Node) *pgQuery.No return node } +// FROM [PG_FUNCTION()] +func (selectRemapper *SelectRemapper) remapTableFunction(node *pgQuery.Node) *pgQuery.Node { + for _, funcf := range node.GetRangeFunction().Functions { + for _, item := range funcf.GetList().Items { + functionCall := item.GetFuncCall() + if len(functionCall.Funcname) == 2 { + schema := functionCall.Funcname[0].GetString_().Sval + functionName := functionCall.Funcname[1].GetString_().Sval + + if schema == PG_SYSTEM_SCHEMA && functionName == PG_GET_KEYWORDS_FUNCTION_NAME { + return MakePgGetKeywordsNode() + } + } + } + } + return node +} + func (selectRemapper *SelectRemapper) appendWhereCondition(selectStatement *pgQuery.SelectStmt, whereCondition *pgQuery.Node) *pgQuery.SelectStmt { whereClause := selectStatement.WhereClause