Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion yb-voyager/src/query/queryissue/detectors.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ func (j *JsonbSubscriptingDetector) Detect(msg protoreflect.Message) error {
if queryparser.GetMsgFullName(msg) != queryparser.PG_QUERY_A_INDIRECTION_NODE {
return nil
}
aIndirectionNode, ok := queryparser.GetAIndirectionNode(msg)
aIndirectionNode, ok := queryparser.ProtoAsAIndirectionNode(msg)
if !ok {
return nil
}
Expand Down
101 changes: 81 additions & 20 deletions yb-voyager/src/query/queryparser/helpers_protomsg.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"fmt"
"strings"

"strconv"

pg_query "github.com/pganalyze/pg_query_go/v6"
log "github.com/sirupsen/logrus"
"google.golang.org/protobuf/reflect/protoreflect"
Expand Down Expand Up @@ -762,12 +764,14 @@ func ProtoAsCreateRangeStmtNode(msg protoreflect.Message) (*pg_query.CreateRange
return createRangeStmt, ok
}

/*
Example:
options:{def_elem:{defname:"security_invoker" arg:{string:{sval:"true"}} defaction:DEFELEM_UNSPEC location:32}}
options:{def_elem:{defname:"security_barrier" arg:{string:{sval:"false"}} defaction:DEFELEM_UNSPEC location:57}}
Extract all defnames from the def_eleme node
*/
func ProtoAsAIndirectionNode(msg protoreflect.Message) (*pg_query.A_Indirection, bool) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just function rename

aIndirectionNode, ok := msg.Interface().(*pg_query.A_Indirection)
if !ok {
return nil, false
}
return aIndirectionNode, true
}

func TraverseAndExtractDefNamesFromDefElem(msg protoreflect.Message) (map[string]string, error) {
defNamesWithValues := make(map[string]string)
collectorFunc := func(msg protoreflect.Message) error {
Expand All @@ -781,16 +785,7 @@ func TraverseAndExtractDefNamesFromDefElem(msg protoreflect.Message) (map[string
}

defName := defElemNode.Defname
arg := defElemNode.GetArg()
if arg != nil && arg.GetString_() != nil {
defElemVal := arg.GetString_().Sval
defNamesWithValues[defName] = defElemVal
} else {
log.Warnf("defElem Node doesn't have arg or the arg is not the string type [%s]", defElemNode)
//TODO: see how to handle this later where GetString_() is not directly available or arg is of different type
//e.g. defname:"provider" arg:{type_name:{names:{string:{sval:"icu"}} typemod:-1 location:37}} defaction:DEFELEM_UNSPEC location:26defname:"locale"
defNamesWithValues[defName] = ""
}
defNamesWithValues[defName] = NormalizeDefElemArgToString(defElemNode.Arg)
return nil
}
visited := make(map[protoreflect.Message]bool)
Expand All @@ -802,8 +797,74 @@ func TraverseAndExtractDefNamesFromDefElem(msg protoreflect.Message) (map[string
return defNamesWithValues, nil
}

func GetAIndirectionNode(msg protoreflect.Message) (*pg_query.A_Indirection, bool) {
protoMsg := msg.Interface().(protoreflect.ProtoMessage)
aIndirection, ok := protoMsg.(*pg_query.A_Indirection)
return aIndirection, ok
/*
Example:
options:{def_elem:{defname:"security_invoker" arg:{string:{sval:"true"}} defaction:DEFELEM_UNSPEC location:32}}
options:{def_elem:{defname:"security_barrier" arg:{string:{sval:"false"}} defaction:DEFELEM_UNSPEC location:57}}

Presence-only flag example where arg is absent (nil in parse tree):
SQL: CREATE TABLE t(id int) WITH (autovacuum_enabled);

ParseTree: stmt:{create_stmt:{relation:{relname:"t" inh:true relpersistence:"p" location:13}

table_elts:{column_def:{colname:"id" type_name:{names:{string:{sval:"pg_catalog"}} names:{string:{sval:"int4"}} }}
options:{def_elem:{defname:"autovacuum_enabled" defaction:DEFELEM_UNSPEC }} }}

NormalizeDefElemArgToString converts common DefElem Arg node shapes to string to avoid warn/noise:
- nil => "true" (presence-only flags)
- String_ => sval
- A_Const => sval/ival/fval/bsval/boolval
- TypeName => last component of Names (e.g. "icu")
- List => comma-joined normalized items
- TypeCast => normalize inner Arg
- Fallback => arg.String() to keep value non-empty
*/
func NormalizeDefElemArgToString(arg *pg_query.Node) string {
if arg == nil {
return "true"
}
if s := arg.GetString_(); s != nil {
return s.Sval
}
if a := arg.GetAConst(); a != nil {
switch {
case a.GetSval() != nil:
return a.GetSval().Sval
case a.GetIval() != nil:
return strconv.FormatInt(int64(a.GetIval().Ival), 10)
case a.GetFval() != nil:
return a.GetFval().Fval
case a.GetBsval() != nil:
return a.GetBsval().Bsval
case a.GetBoolval() != nil:
return a.GetBoolval().String()
default:
return arg.String()
}
}

if i := arg.GetInteger(); i != nil {
return strconv.FormatInt(int64(i.Ival), 10)
}

if tn := arg.GetTypeName(); tn != nil {
if len(tn.Names) > 0 && tn.Names[len(tn.Names)-1].GetString_() != nil {
return tn.Names[len(tn.Names)-1].GetString_().Sval
}
return arg.String()
}
if l := arg.GetList(); l != nil {
parts := make([]string, 0, len(l.Items))
for _, it := range l.Items {
v := NormalizeDefElemArgToString(it)
if v != "" {
parts = append(parts, v)
}
}
return strings.Join(parts, ",")
}
if tc := arg.GetTypeCast(); tc != nil {
return NormalizeDefElemArgToString(tc.Arg)
}
return arg.String()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a complete normalization, right?
As there could be other arg types like expression, function call

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any other known cases for def elements?
I added what i could find/think of.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added log line for fallback

}
88 changes: 88 additions & 0 deletions yb-voyager/src/query/queryparser/helpers_protomsg_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//go:build unit

/*
Copyright (c) YugabyteDB, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package queryparser

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestTraverseAndExtractDefNamesFromDefElem(t *testing.T) {
tests := []struct {
name string
sql string
expected map[string]string
}{
{
name: "table_presence_flag",
sql: `CREATE TABLE t(id int) WITH (autovacuum_enabled);`,
expected: map[string]string{
"autovacuum_enabled": "true",
},
},
{
name: "view_boolean_options",
sql: `CREATE VIEW v WITH (security_barrier=true, security_invoker=false) AS SELECT 1;`,
expected: map[string]string{
"security_barrier": "true",
"security_invoker": "false",
},
},
{
name: "collation_provider_typename",
sql: `CREATE COLLATION c1 (provider = icu);`,
expected: map[string]string{
"provider": "icu",
},
},
{
name: "extension_schema_identifier",
sql: `CREATE EXTENSION hstore WITH SCHEMA public;`,
expected: map[string]string{
"schema": "public",
},
},
{
name: "createdb_integer_option",
sql: `CREATE DATABASE db WITH CONNECTION LIMIT 5;`,
expected: map[string]string{
// pg_query_go uses defname "connection_limit" (with underscore)
"connection_limit": "5",
},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
parseTree, err := Parse(tc.sql)
assert.NoError(t, err)

msg := GetProtoMessageFromParseTree(parseTree)
defs, err := TraverseAndExtractDefNamesFromDefElem(msg)
assert.NoError(t, err)

for k, v := range tc.expected {
actual, ok := defs[k]
assert.True(t, ok, "expected key %q not found in defs: %v", k, defs)
assert.Equalf(t, v, actual, "expected: %v, actual: %v for key %q", v, actual, k)
}
})
}
}