Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
77 changes: 61 additions & 16 deletions internal/inference/parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,24 +366,69 @@ func ParentAlias(join *query.Join) string {
}

func ExtractRelationColumns(join *query.Join) (string, string) {
relColumn := ""
refColumn := ""
sqlparser.Traverse(join.On, func(n node.Node) bool {
switch actual := n.(type) {
case *qexpr.Selector:
column := sqlparser.Stringify(actual.X)
if actual.Name == join.Alias {
if refColumn == "" {
refColumn = column
}
} else if relColumn == "" {
relColumn = column
pairs := ExtractRelationColumnPairs(join)
if len(pairs) == 0 {
return "", ""
}
return pairs[0][0], pairs[0][1]
}

func ExtractRelationColumnPairs(join *query.Join) [][2]string {
if join == nil || join.On == nil || join.On.X == nil {
return nil
}
return collectRelationColumnPairs(join.On.X, join.Alias)
}

func collectRelationColumnPairs(n node.Node, refAlias string) [][2]string {
switch actual := n.(type) {
case *qexpr.Binary:
actual = actual.Normalize()
op := strings.ToUpper(strings.TrimSpace(actual.Op))
if op == "AND" {
left := collectRelationColumnPairs(actual.X, refAlias)
right := collectRelationColumnPairs(actual.Y, refAlias)
return append(left, right...)
}
if op != "=" {
return nil
}
leftAlias, leftColumn, leftOK := selectorParts(actual.X)
rightAlias, rightColumn, rightOK := selectorParts(actual.Y)
if !leftOK || !rightOK {
return nil
}
switch {
case leftAlias == refAlias:
return [][2]string{{rightColumn, leftColumn}}
case rightAlias == refAlias:
return [][2]string{{leftColumn, rightColumn}}
}
case *qexpr.Parenthesis:
return collectRelationColumnPairs(actual.X, refAlias)
}
return nil
}

func selectorParts(n node.Node) (string, string, bool) {
switch actual := n.(type) {
case *qexpr.Selector:
return actual.Name, sqlparser.Stringify(actual.X), true
case *qexpr.Parenthesis:
return selectorParts(actual.X)
case *qexpr.Collate:
return selectorParts(actual.X)
case *qexpr.Call:
if alias, column, ok := selectorParts(actual.X); ok {
return alias, column, true
}
for _, arg := range actual.Args {
if alias, column, ok := selectorParts(arg); ok {
return alias, column, true
}
return true
}
return true
})
return relColumn, refColumn
}
return "", "", false
}

func (p *Parameter) EnsureCodec() {
Expand Down
47 changes: 30 additions & 17 deletions internal/inference/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,18 @@ import (
)

type (
RelationPair struct {
ParentField *Field
KeyField *Field
}

//Relation defines relation
Relation struct {
Name string
Join *query.Join
ParentField *Field
KeyField *Field
Pairs []*RelationPair
Cardinality state.Cardinality
*Spec
}
Expand Down Expand Up @@ -163,28 +169,35 @@ func (s *Spec) AddRelation(name string, join *query.Join, spec *Spec, cardinalit
if IsToOne(join) {
cardinality = state.One
}
relColumn, refColumn := ExtractRelationColumns(join)
parentField := s.Type.ByColumn(relColumn)
if parentField == nil {
var available []string
for _, item := range s.Type.columnFields {
available = append(available, item.Column.Name)
pairColumns := ExtractRelationColumnPairs(join)
if len(pairColumns) == 0 {
return fmt.Errorf("failed to extract relation columns for %v", join.Alias)
}
pairs := make([]*RelationPair, 0, len(pairColumns))
for _, pair := range pairColumns {
parentField := s.Type.ByColumn(pair[0])
if parentField == nil {
var available []string
for _, item := range s.Type.columnFields {
available = append(available, item.Column.Name)
}
return fmt.Errorf("failed to match rel field for %v, available: %v %v", pair[0], s.Type.Name, available)
}
return fmt.Errorf("failed to match rel field for %v, available: %v %v", relColumn, s.Type.Name, available)
}

keyField := spec.Type.ByColumn(refColumn)
if keyField == nil {
var available []string
for _, item := range spec.Type.columnFields {
available = append(available, item.Column.Name)
keyField := spec.Type.ByColumn(pair[1])
if keyField == nil {
var available []string
for _, item := range spec.Type.columnFields {
available = append(available, item.Column.Name)
}
return fmt.Errorf("failed to ref field for %v, available: %v on %v", pair[1], available, join.Alias)
}
return fmt.Errorf("failed to ref field for %v, available: %v on %v", refColumn, available, join.Alias)
pairs = append(pairs, &RelationPair{ParentField: parentField, KeyField: keyField})
}

rel := &Relation{Spec: spec,
KeyField: keyField,
ParentField: parentField,
KeyField: pairs[0].KeyField,
ParentField: pairs[0].ParentField,
Pairs: pairs,
Name: name,
Join: join,
Cardinality: cardinality}
Expand Down
7 changes: 7 additions & 0 deletions internal/inference/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,13 @@ func removeBuilinExpr(query string) string {
}
query = strings.ReplaceAll(query, fragment, "")
}
if index := strings.Index(query, "$View.ParentCompositeJoinOn"); index != -1 {
fragment := query[index:]
if endIndex := strings.Index(fragment, ")"); endIndex != -1 {
fragment = fragment[:endIndex+1]
}
query = strings.ReplaceAll(query, fragment, "")
}

if !strings.Contains(query, "${predicate.") {
return query
Expand Down
42 changes: 31 additions & 11 deletions internal/inference/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,19 +180,29 @@ func (t *Tags) buildRelation(spec *Spec, relation *Relation) {
Table: spec.Table,
}
joinTag := tags.LinkOn{}

parentColumn := relation.ParentField.Column.Name
if ns := relation.ParentField.Column.Namespace; ns != "" {
parentColumn = ns + "." + parentColumn
if len(relation.Pairs) == 0 {
relation.Pairs = []*RelationPair{{
ParentField: relation.ParentField,
KeyField: relation.KeyField,
}}
}
keyColumn := relation.KeyField.Column.Name
if ns := relation.KeyField.Column.Namespace; ns != "" {
keyColumn = ns + "." + keyColumn
for _, pair := range relation.Pairs {
if pair == nil || pair.ParentField == nil || pair.KeyField == nil {
continue
}
parentColumn := relationColumnName(pair.ParentField.Column)
if ns := pair.ParentField.Column.Namespace; ns != "" {
parentColumn = ns + "." + parentColumn
}
keyColumn := relationColumnName(pair.KeyField.Column)
if ns := pair.KeyField.Column.Namespace; ns != "" {
keyColumn = ns + "." + keyColumn
}
joinTag = joinTag.Append(
tags.WithRelLink(pair.ParentField.Name, parentColumn, nil),
tags.WithRefLink(pair.KeyField.Name, keyColumn),
)
}
joinTag = joinTag.Append(
tags.WithRelLink(relation.ParentField.Name, parentColumn, nil),
tags.WithRefLink(relation.KeyField.Name, keyColumn),
)
sqlTag := TagValue{}
if rawSQL := strings.Trim(sqlparser.Stringify(join.With), " )("); rawSQL != "" {
rawSQL = strings.Replace(rawSQL, "("+spec.Table+")", spec.Table, 1)
Expand All @@ -205,6 +215,16 @@ func (t *Tags) buildRelation(spec *Spec, relation *Relation) {
t.Set(tags.SQLTag, sqlTag)
}

func relationColumnName(column *sqlparser.Column) string {
if column == nil {
return ""
}
if column.Name != "" {
return column.Name
}
return column.Alias
}

// Stringify return text representation of struct tag
func (t *Tags) Stringify() string {
if len(t.order) == 0 {
Expand Down
69 changes: 69 additions & 0 deletions internal/inference/tag_relation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package inference

import (
"reflect"
"testing"

"github.com/stretchr/testify/require"
"github.com/viant/datly/view"
vstate "github.com/viant/datly/view/state"
"github.com/viant/datly/view/tags"
"github.com/viant/sqlparser"
"github.com/viant/sqlparser/query"
)

func TestTags_buildRelation_UsesColumnAliasWhenNameIsEmpty(t *testing.T) {
selectQuery, err := sqlparser.ParseQuery("SELECT 'app' AS SITE_TYPE_VALUE")
require.NoError(t, err)

parentColumn := &sqlparser.Column{Name: "SITE_TYPE_VALUES"}
childColumn := &sqlparser.Column{Alias: "SITE_TYPE_VALUE"}

parentField := &Field{
Field: view.Field{Name: "SiteTypeValues", Schema: &vstate.Schema{}},
Column: parentColumn,
}
keyField := &Field{
Field: view.Field{Name: "SiteTypeValue", Schema: &vstate.Schema{}},
Column: childColumn,
}

spec := &Spec{Table: "ignored"}
relation := &Relation{
Name: "siteType",
Join: &query.Join{Alias: "siteType", With: selectQuery},
ParentField: parentField,
KeyField: keyField,
Pairs: []*RelationPair{{
ParentField: parentField,
KeyField: keyField,
}},
}

field := &Field{Field: view.Field{Name: "SiteType", Schema: &vstate.Schema{}}, Tags: Tags{}}
field.Tags.buildRelation(spec, relation)
tagString := field.Tags.Stringify()

parsed, err := tags.Parse(reflect.StructTag(tagString), nil, tags.LinkOnTag)
require.NoError(t, err)
require.Len(t, parsed.LinkOn, 1)

var relField, relColumn, refField, refColumn string
require.NoError(t, parsed.LinkOn.ForEach(func(rf, rc, kf, kc string, include *bool) error {
relField, relColumn, refField, refColumn = rf, rc, kf, kc
return nil
}))

require.Equal(t, "SiteTypeValues", relField)
require.Equal(t, "SITE_TYPE_VALUES", relColumn)
require.Equal(t, "SiteTypeValue", refField)
require.Equal(t, "SITE_TYPE_VALUE", refColumn)
}

func TestType_ByColumn_MatchesAlias(t *testing.T) {
typ := &Type{columnFields: []*Field{{
Field: view.Field{Name: "SiteTypeValue", Schema: &vstate.Schema{}},
Column: &sqlparser.Column{Alias: "SITE_TYPE_VALUE"},
}}}
require.NotNil(t, typ.ByColumn("SITE_TYPE_VALUE"))
}
3 changes: 3 additions & 0 deletions internal/translator/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,9 @@ func (v *View) buildRelations(parentNamespace *Viewlet, rule *Rule) error {
relNamespace.Holder = viewRelation.Holder
refViewName := relNamespace.View.Name
refColumn := relation.KeyField.Column.Name
if refColumn == "" {
refColumn = relation.KeyField.Column.Alias
}
if ns := relation.KeyField.Column.Namespace; ns != "" {
refColumn = ns + "." + refColumn
}
Expand Down
Loading
Loading