From fca4caca62f15bb8c61cd84e647e7482156c2707 Mon Sep 17 00:00:00 2001 From: Sean Wu <111744549+VWagen1989@users.noreply.github.com> Date: Thu, 16 Jan 2025 18:37:21 +0800 Subject: [PATCH 1/3] fix: replace 'xxx=ANY(yyy)' with 'my_list_contains(yyy, xxx) (#354) --- catalog/internal_macro.go | 19 +++++++++++++++++++ pgserver/in_place_handler.go | 13 +++++++++++++ pgserver/stmt.go | 19 +++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/catalog/internal_macro.go b/catalog/internal_macro.go index 3a99800..053c5a3 100644 --- a/catalog/internal_macro.go +++ b/catalog/internal_macro.go @@ -7,6 +7,9 @@ type MacroDefinition struct { DDL string } +var SchemaNameMyListContains string = "__sys__" +var MacroNameMyListContains string = "my_list_contains" + type InternalMacro struct { Schema string Name string @@ -55,4 +58,20 @@ var InternalMacros = []InternalMacro{ }, }, }, + { + Schema: SchemaNameMyListContains, + Name: MacroNameMyListContains, + IsTableMacro: false, + Definitions: []MacroDefinition{ + { + Params: []string{"l", "v"}, + DDL: `CASE + WHEN typeof(l) = 'VARCHAR' THEN + list_contains(regexp_split_to_array(l::VARCHAR, '[{},\s]+'), v) + ELSE + list_contains(l::text[], v) + END`, + }, + }, + }, } diff --git a/pgserver/in_place_handler.go b/pgserver/in_place_handler.go index 6e70488..eb06ebf 100644 --- a/pgserver/in_place_handler.go +++ b/pgserver/in_place_handler.go @@ -246,6 +246,19 @@ var selectionConversions = []SelectionConversion{ return nil }, }, + { + needConvert: func(query *ConvertedStatement) bool { + sqlStr := RemoveComments(query.String) + // TODO: Evaluate the conditions by iterating over the AST. + return getPgAnyOpRegex().MatchString(sqlStr) + }, + doConvert: func(h *ConnectionHandler, query *ConvertedStatement) error { + sqlStr := RemoveComments(query.String) + sqlStr = ConvertAnyOp(sqlStr) + query.String = sqlStr + return nil + }, + }, } // The key is the statement tag of the query. diff --git a/pgserver/stmt.go b/pgserver/stmt.go index 4471d17..50e719c 100644 --- a/pgserver/stmt.go +++ b/pgserver/stmt.go @@ -294,6 +294,25 @@ func ConvertToSys(sql string) string { return getPgCatalogRegex().ReplaceAllString(RemoveComments(sql), "$1 __sys__.$2") } +var ( + pgAnyOpRegex *regexp.Regexp + initPgAnyOpRegex sync.Once +) + +// get the regex to match the operator 'ANY' +func getPgAnyOpRegex() *regexp.Regexp { + initPgAnyOpRegex.Do(func() { + pgAnyOpRegex = regexp.MustCompile(`(?i)([^\s(]+)\s*=\s*any\s*\(\s*([^)]*)\s*\)`) + }) + return pgAnyOpRegex +} + +// Replace the operator 'ANY' with a function call. +func ConvertAnyOp(sql string) string { + re := getPgAnyOpRegex() + return re.ReplaceAllString(sql, catalog.SchemaNameMyListContains+"."+catalog.MacroNameMyListContains+"($2, $1)") +} + var ( typeCastRegex *regexp.Regexp initTypeCastRegex sync.Once From ee1139f653ae2c758892bef75f63554f0345e8f0 Mon Sep 17 00:00:00 2001 From: Sean Wu <111744549+VWagen1989@users.noreply.github.com> Date: Thu, 16 Jan 2025 18:58:09 +0800 Subject: [PATCH 2/3] fix: support pg_catalog.pg_get_expr with 3 params (#354) --- catalog/internal_macro.go | 17 +++++++++++++++++ pgserver/stmt.go | 1 + 2 files changed, 18 insertions(+) diff --git a/catalog/internal_macro.go b/catalog/internal_macro.go index 053c5a3..829d67d 100644 --- a/catalog/internal_macro.go +++ b/catalog/internal_macro.go @@ -58,6 +58,23 @@ var InternalMacros = []InternalMacro{ }, }, }, + { + Schema: "pg_catalog", + Name: "pg_get_expr", + IsTableMacro: false, + Definitions: []MacroDefinition{ + { + Params: []string{"pg_node_tree", "relation_oid"}, + // Do nothing currently + DDL: `pg_catalog.pg_get_expr(pg_node_tree, relation_oid)`, + }, + { + Params: []string{"pg_node_tree", "relation_oid", "pretty_bool"}, + // Do nothing currently + DDL: `pg_catalog.pg_get_expr(pg_node_tree, relation_oid)`, + }, + }, + }, { Schema: SchemaNameMyListContains, Name: MacroNameMyListContains, diff --git a/pgserver/stmt.go b/pgserver/stmt.go index 50e719c..cb099ce 100644 --- a/pgserver/stmt.go +++ b/pgserver/stmt.go @@ -321,6 +321,7 @@ var ( // The Key must be in lowercase. Because the key used for value retrieval is in lowercase. var typeCastConversion = map[string]string{ "::regclass": "::varchar", + "::regtype": "::integer", } // This function will return a regex that matches all type casts in the query. From 5a2a16885da8c87be5db6ef267385401c396a6cb Mon Sep 17 00:00:00 2001 From: Sean Wu <111744549+VWagen1989@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:39:19 +0800 Subject: [PATCH 3/3] fix: wrap columns 'proallargtypes' and 'proargtypes' to split string into string array (#354) --- catalog/internal_macro.go | 21 +++++++++++++++++--- pgserver/in_place_handler.go | 4 ++-- pgserver/stmt.go | 38 +++++++++++++++++++++--------------- 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/catalog/internal_macro.go b/catalog/internal_macro.go index 829d67d..27852ad 100644 --- a/catalog/internal_macro.go +++ b/catalog/internal_macro.go @@ -7,8 +7,12 @@ type MacroDefinition struct { DDL string } -var SchemaNameMyListContains string = "__sys__" -var MacroNameMyListContains string = "my_list_contains" +var ( + SchemaNameSYS string = "__sys__" + MacroNameMyListContains string = "my_list_contains" + + MacroNameMySplitListStr string = "my_split_list_str" +) type InternalMacro struct { Schema string @@ -76,7 +80,7 @@ var InternalMacros = []InternalMacro{ }, }, { - Schema: SchemaNameMyListContains, + Schema: SchemaNameSYS, Name: MacroNameMyListContains, IsTableMacro: false, Definitions: []MacroDefinition{ @@ -91,4 +95,15 @@ var InternalMacros = []InternalMacro{ }, }, }, + { + Schema: SchemaNameSYS, + Name: MacroNameMySplitListStr, + IsTableMacro: false, + Definitions: []MacroDefinition{ + { + Params: []string{"l"}, + DDL: `regexp_split_to_array(l::VARCHAR, '[{},\s]+')`, + }, + }, + }, } diff --git a/pgserver/in_place_handler.go b/pgserver/in_place_handler.go index eb06ebf..329d6bc 100644 --- a/pgserver/in_place_handler.go +++ b/pgserver/in_place_handler.go @@ -237,11 +237,11 @@ var selectionConversions = []SelectionConversion{ needConvert: func(query *ConvertedStatement) bool { sqlStr := RemoveComments(query.String) // TODO(sean): Evaluate the conditions by iterating over the AST. - return getTypeCastRegex().MatchString(sqlStr) + return getSimpleStringMatchingRegex().MatchString(sqlStr) }, doConvert: func(h *ConnectionHandler, query *ConvertedStatement) error { sqlStr := RemoveComments(query.String) - sqlStr = ConvertTypeCast(sqlStr) + sqlStr = SimpleStrReplacement(sqlStr) query.String = sqlStr return nil }, diff --git a/pgserver/stmt.go b/pgserver/stmt.go index cb099ce..2db3ff1 100644 --- a/pgserver/stmt.go +++ b/pgserver/stmt.go @@ -310,36 +310,42 @@ func getPgAnyOpRegex() *regexp.Regexp { // Replace the operator 'ANY' with a function call. func ConvertAnyOp(sql string) string { re := getPgAnyOpRegex() - return re.ReplaceAllString(sql, catalog.SchemaNameMyListContains+"."+catalog.MacroNameMyListContains+"($2, $1)") + return re.ReplaceAllString(sql, catalog.SchemaNameSYS+"."+catalog.MacroNameMyListContains+"($2, $1)") } var ( - typeCastRegex *regexp.Regexp - initTypeCastRegex sync.Once + simpleStrMatchingRegex *regexp.Regexp + initSimpleStrMatchingRegex sync.Once ) +// TODO(sean): This is a temporary solution. We need to find a better way to handle type cast conversion and column conversion. e.g. Iterating the AST with a visitor pattern. // The Key must be in lowercase. Because the key used for value retrieval is in lowercase. -var typeCastConversion = map[string]string{ +var simpleStringsConversion = map[string]string{ + // type cast conversion "::regclass": "::varchar", - "::regtype": "::integer", + "::regtype": "::varchar", + + // column conversion + "proallargtypes": catalog.SchemaNameSYS + "." + catalog.MacroNameMySplitListStr + "(proallargtypes)", + "proargtypes": catalog.SchemaNameSYS + "." + catalog.MacroNameMySplitListStr + "(proargtypes)", } // This function will return a regex that matches all type casts in the query. -func getTypeCastRegex() *regexp.Regexp { - initTypeCastRegex.Do(func() { - var typeCasts []string - for typeCast := range typeCastConversion { - typeCasts = append(typeCasts, regexp.QuoteMeta(typeCast)) +func getSimpleStringMatchingRegex() *regexp.Regexp { + initSimpleStrMatchingRegex.Do(func() { + var simpleStrings []string + for simpleString := range simpleStringsConversion { + simpleStrings = append(simpleStrings, regexp.QuoteMeta(simpleString)) } - typeCastRegex = regexp.MustCompile(`(?i)(` + strings.Join(typeCasts, "|") + `)`) + simpleStrMatchingRegex = regexp.MustCompile(`(?i)(` + strings.Join(simpleStrings, "|") + `)`) }) - return typeCastRegex + return simpleStrMatchingRegex } -// This function will replace all type casts in the query with the corresponding type cast in the typeCastConversion map. -func ConvertTypeCast(sql string) string { - return getTypeCastRegex().ReplaceAllStringFunc(sql, func(m string) string { - return typeCastConversion[strings.ToLower(m)] +// This function will replace all type casts in the query with the corresponding type cast in the simpleStringsConversion map. +func SimpleStrReplacement(sql string) string { + return getSimpleStringMatchingRegex().ReplaceAllStringFunc(sql, func(m string) string { + return simpleStringsConversion[strings.ToLower(m)] }) }