Files
ent/dialect/sql/graph.go
Ariel Mashraki 5610d0ade9 dialect/sqlgraph: fix neighbors check with predicates
Summary: Pull Request resolved: https://github.com/facebookincubator/ent/pull/206

Reviewed By: alexsn

Differential Revision: D18729641

fbshipit-source-id: a5d333132ae276691ac8c1b084bc5d2037112529
2019-11-27 23:50:00 -08:00

265 lines
6.8 KiB
Go

// Copyright 2019-present Facebook Inc. All rights reserved.
// This source code is licensed under the Apache 2.0 license found
// in the LICENSE file in the root directory of this source tree.
package sql
// Rel is a relation type of an edge.
type Rel int
// Relation types.
const (
Unk Rel = iota // Unknown.
O2O // One to one / has one.
O2M // One to many / has many.
M2O // Many to one (inverse perspective for O2M).
M2M // Many to many.
)
// String returns the relation name.
func (r Rel) String() (s string) {
switch r {
case O2O:
s = "O2O"
case O2M:
s = "O2M"
case M2O:
s = "M2O"
case M2M:
s = "M2M"
default:
s = "Unknown"
}
return s
}
// A Step provides a path-step information to the traversal functions.
type Step struct {
// From is the source of the step.
From struct {
// V can be either one vertex or set of vertices.
// It can be a pre-processed step (sql.Query) or a simple Go type (integer or string).
V interface{}
// Table holds the table name of V (from).
Table string
// Column to join with. Usually the "id" column.
Column string
}
// Edge holds the edge information for getting the neighbors.
Edge struct {
// Rel of the edge.
Rel Rel
// Table name of where this edge columns reside.
Table string
// Columns of the edge.
// In O2O and M2O, it holds the foreign-key column. Hence, len == 1.
// In M2M, it holds the primary-key columns of the join table. Hence, len == 2.
Columns []string
// Inverse indicates if the edge is an inverse edge.
Inverse bool
}
// To is the dest of the path (the neighbors).
To struct {
// Table holds the table name of the neighbors (to).
Table string
// Column to join with. Usually the "id" column.
Column string
}
}
// StepOption allows configuring Steps using functional options.
type StepOption func(*Step)
// From sets the source of the step.
func From(table, column string, v ...interface{}) StepOption {
return func(s *Step) {
s.From.Table = table
s.From.Column = column
if len(v) > 0 {
s.From.V = v[0]
}
}
}
// To sets the destination of the step.
func To(table, column string) StepOption {
return func(s *Step) {
s.To.Table = table
s.To.Column = column
}
}
// Edge sets the edge info for getting the neighbors.
func Edge(rel Rel, inverse bool, table string, columns ...string) StepOption {
return func(s *Step) {
s.Edge.Rel = rel
s.Edge.Table = table
s.Edge.Columns = columns
s.Edge.Inverse = inverse
}
}
// NewStep gets list of options and returns a configured step.
//
// NewStep(
// From("table", "pk", V),
// To("table", "pk"),
// Edge("name", O2M, "fk"),
// )
//
func NewStep(opts ...StepOption) *Step {
s := &Step{}
for _, opt := range opts {
opt(s)
}
return s
}
// Neighbors returns a Selector for evaluating the path-step
// and getting the neighbors of one vertex.
func Neighbors(dialect string, s *Step) (q *Selector) {
builder := Dialect(dialect)
switch r := s.Edge.Rel; {
case r == M2M:
pk1, pk2 := s.Edge.Columns[1], s.Edge.Columns[0]
if s.Edge.Inverse {
pk1, pk2 = pk2, pk1
}
to := builder.Table(s.To.Table)
join := builder.Table(s.Edge.Table)
match := builder.Select(join.C(pk1)).
From(join).
Where(EQ(join.C(pk2), s.From.V))
q = builder.Select().
From(to).
Join(match).
On(to.C(s.To.Column), match.C(pk1))
case r == M2O || (r == O2O && s.Edge.Inverse):
t1 := builder.Table(s.To.Table)
t2 := builder.Select(s.Edge.Columns[0]).
From(builder.Table(s.Edge.Table)).
Where(EQ(s.From.Column, s.From.V))
q = builder.Select().
From(t1).
Join(t2).
On(t1.C(s.From.Column), t2.C(s.Edge.Columns[0]))
case r == O2M || (r == O2O && !s.Edge.Inverse):
q = builder.Select().
From(builder.Table(s.To.Table)).
Where(EQ(s.Edge.Columns[0], s.From.V))
}
return q
}
// SetNeighbors returns a Selector for evaluating the path-step
// and getting the neighbors of set of vertices.
func SetNeighbors(dialect string, s *Step) (q *Selector) {
set := s.From.V.(*Selector)
builder := Dialect(dialect)
switch r := s.Edge.Rel; {
case r == M2M:
pk1, pk2 := s.Edge.Columns[1], s.Edge.Columns[0]
if s.Edge.Inverse {
pk1, pk2 = pk2, pk1
}
to := builder.Table(s.To.Table)
set.Select(set.C(s.From.Column))
join := builder.Table(s.Edge.Table)
match := builder.Select(join.C(pk1)).
From(join).
Join(set).
On(join.C(pk2), set.C(s.From.Column))
q = builder.Select().
From(to).
Join(match).
On(to.C(s.To.Column), match.C(pk1))
case r == M2O || (r == O2O && s.Edge.Inverse):
t1 := builder.Table(s.To.Table)
set.Select(set.C(s.Edge.Columns[0]))
q = builder.Select().
From(t1).
Join(set).
On(t1.C(s.To.Column), set.C(s.Edge.Columns[0]))
case r == O2M || (r == O2O && !s.Edge.Inverse):
t1 := builder.Table(s.To.Table)
set.Select(set.C(s.From.Column))
q = builder.Select().
From(t1).
Join(set).
On(t1.C(s.Edge.Columns[0]), set.C(s.From.Column))
}
return q
}
// HasNeighbors applies on the given Selector a neighbors check.
func HasNeighbors(q *Selector, s *Step) {
builder := Dialect(q.dialect)
switch r := s.Edge.Rel; {
case r == M2M:
pk1 := s.Edge.Columns[0]
if s.Edge.Inverse {
pk1 = s.Edge.Columns[1]
}
from := q.Table()
join := builder.Table(s.Edge.Table)
q.Where(
In(
from.C(s.From.Column),
builder.Select(join.C(pk1)).From(join),
),
)
case r == M2O || (r == O2O && s.Edge.Inverse):
from := q.Table()
q.Where(NotNull(from.C(s.Edge.Columns[0])))
case r == O2M || (r == O2O && !s.Edge.Inverse):
from := q.Table()
to := builder.Table(s.Edge.Table)
q.Where(
In(
from.C(s.From.Column),
builder.Select(to.C(s.Edge.Columns[0])).
From(to).
Where(NotNull(to.C(s.Edge.Columns[0]))),
),
)
}
}
// HasNeighborsWith applies on the given Selector a neighbors check.
// The given predicate applies its filtering on the selector.
func HasNeighborsWith(q *Selector, s *Step, pred func(*Selector)) {
builder := Dialect(q.dialect)
switch r := s.Edge.Rel; {
case r == M2M:
pk1, pk2 := s.Edge.Columns[1], s.Edge.Columns[0]
if s.Edge.Inverse {
pk1, pk2 = pk2, pk1
}
from := q.Table()
to := builder.Table(s.To.Table)
edge := builder.Table(s.Edge.Table)
join := builder.Select(edge.C(pk2)).
From(edge).
Join(to).
On(edge.C(pk1), to.C(s.To.Column))
matches := builder.Select().From(to)
pred(matches)
join.FromSelect(matches)
q.Where(In(from.C(s.From.Column), join))
case r == M2O || (r == O2O && s.Edge.Inverse):
from := q.Table()
to := builder.Table(s.To.Table)
matches := builder.Select(to.C(s.To.Column)).
From(to)
pred(matches)
q.Where(In(from.C(s.Edge.Columns[0]), matches))
case r == O2M || (r == O2O && !s.Edge.Inverse):
from := q.Table()
to := builder.Table(s.Edge.Table)
matches := builder.Select(to.C(s.Edge.Columns[0])).
From(to)
pred(matches)
q.Where(In(from.C(s.From.Column), matches))
}
}