mirror of
https://github.com/ent/ent.git
synced 2026-05-22 09:31:45 +03:00
dialect/sql: move order-by options to top-level driver package (#3446)
This commit is contained in:
@@ -186,3 +186,79 @@ func NewColumnCheck(checks map[string]func(string) bool) ColumnCheck {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
// OrderFieldTerm represents an ordering by a field.
|
||||
OrderFieldTerm struct {
|
||||
OrderTermOptions
|
||||
Field string // Field name.
|
||||
}
|
||||
// OrderExprTerm represents an ordering by an expression.
|
||||
OrderExprTerm struct {
|
||||
OrderTermOptions
|
||||
Expr Querier // Expression.
|
||||
}
|
||||
// OrderTerm represents an ordering by a term.
|
||||
OrderTerm interface {
|
||||
term()
|
||||
}
|
||||
// OrderTermOptions represents options for ordering by a term.
|
||||
OrderTermOptions struct {
|
||||
Desc bool // Whether to sort in descending order.
|
||||
As string // Optional alias.
|
||||
Selected bool // Whether the term should be selected.
|
||||
}
|
||||
// OrderTermOption is an option for ordering by a term.
|
||||
OrderTermOption func(*OrderTermOptions)
|
||||
)
|
||||
|
||||
// OrderDesc returns an option to sort in descending order.
|
||||
func OrderDesc() OrderTermOption {
|
||||
return func(o *OrderTermOptions) {
|
||||
o.Desc = true
|
||||
}
|
||||
}
|
||||
|
||||
// OrderAs returns an option to set the alias for the ordering.
|
||||
func OrderAs(as string) OrderTermOption {
|
||||
return func(o *OrderTermOptions) {
|
||||
o.As = as
|
||||
}
|
||||
}
|
||||
|
||||
// OrderSelected returns an option to select the ordering term.
|
||||
func OrderSelected() OrderTermOption {
|
||||
return func(o *OrderTermOptions) {
|
||||
o.Selected = true
|
||||
}
|
||||
}
|
||||
|
||||
// OrderSelectAs returns an option to set and select the alias for the ordering.
|
||||
func OrderSelectAs(as string) OrderTermOption {
|
||||
return func(o *OrderTermOptions) {
|
||||
o.As = as
|
||||
o.Selected = true
|
||||
}
|
||||
}
|
||||
|
||||
// NewOrderTermOptions returns a new OrderTermOptions from the given options.
|
||||
func NewOrderTermOptions(opts ...OrderTermOption) *OrderTermOptions {
|
||||
o := &OrderTermOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(o)
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// OrderByField returns an ordering by the given field.
|
||||
func OrderByField(name string, opts ...OrderTermOption) *OrderFieldTerm {
|
||||
return &OrderFieldTerm{Field: name, OrderTermOptions: *NewOrderTermOptions(opts...)}
|
||||
}
|
||||
|
||||
// OrderByExpr returns an ordering by the given expression.
|
||||
func OrderByExpr(x Querier, opts ...OrderTermOption) *OrderExprTerm {
|
||||
return &OrderExprTerm{Expr: x, OrderTermOptions: *NewOrderTermOptions(opts...)}
|
||||
}
|
||||
|
||||
func (OrderFieldTerm) term() {}
|
||||
func (OrderExprTerm) term() {}
|
||||
|
||||
@@ -304,126 +304,16 @@ func HasNeighborsWith(q *sql.Selector, s *Step, pred func(*sql.Selector)) {
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
// OrderByOptions holds the information needed to order a query by an edge.
|
||||
OrderByOptions struct {
|
||||
// Step to get the edge to order by its count.
|
||||
Step *Step
|
||||
// Terms used for non-aggregation ordering.
|
||||
// See, OrderByNeighborTerms for more info.
|
||||
Terms []OrderByTerm
|
||||
// Selected indicates that the order terms
|
||||
// should be appended to the query selection.
|
||||
Selected bool
|
||||
}
|
||||
// OrderByInfo holds the information done by the OrderBy functions.
|
||||
OrderByInfo struct {
|
||||
Terms []OrderByTerm
|
||||
}
|
||||
// OrderByTerm holds the terms of an order by clause.
|
||||
OrderByTerm struct {
|
||||
Column string // Column name. If empty, an expression is used.
|
||||
Expr sql.Querier // Expression. If nil, the column is used.
|
||||
As string // Optional alias.
|
||||
Type field.Type // Term type.
|
||||
Desc bool // Descending order.
|
||||
}
|
||||
// OrderByOption allows configuring OrderByOptions using functional options.
|
||||
OrderByOption func(*OrderByOptions)
|
||||
)
|
||||
|
||||
// OrderDesc sets the latest order by term as descending order,
|
||||
// or add a new descending order term if no terms are present.
|
||||
func OrderDesc() OrderByOption {
|
||||
return func(opts *OrderByOptions) {
|
||||
if len(opts.Terms) > 0 {
|
||||
opts.Terms[len(opts.Terms)-1].Desc = true
|
||||
} else {
|
||||
opts.Terms = append(opts.Terms, OrderByTerm{
|
||||
Desc: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OrderByExpr appends an expression to the order by clause.
|
||||
func OrderByExpr(x sql.Querier, as string) OrderByOption {
|
||||
return func(opts *OrderByOptions) {
|
||||
opts.Terms = append(opts.Terms, OrderByTerm{
|
||||
Expr: x,
|
||||
As: as,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// OrderByExprDesc appends an expression to the order by clause in descending order.
|
||||
func OrderByExprDesc(x sql.Querier, as string) OrderByOption {
|
||||
return func(opts *OrderByOptions) {
|
||||
opts.Terms = append(opts.Terms, OrderByTerm{
|
||||
Expr: x,
|
||||
As: as,
|
||||
Desc: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// OrderByColumn appends a column to the order by clause.
|
||||
func OrderByColumn(c string) OrderByOption {
|
||||
return func(opts *OrderByOptions) {
|
||||
opts.Terms = append(opts.Terms, OrderByTerm{
|
||||
Column: c,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// OrderByColumnDesc appends a column to the order by clause in descending order.
|
||||
func OrderByColumnDesc(c string) OrderByOption {
|
||||
return func(opts *OrderByOptions) {
|
||||
opts.Terms = append(opts.Terms, OrderByTerm{
|
||||
Column: c,
|
||||
Desc: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// OrderBySelected appends the ordered columns or terms with aliases to the query selection.
|
||||
func OrderBySelected() OrderByOption {
|
||||
return func(opts *OrderByOptions) {
|
||||
opts.Selected = true
|
||||
}
|
||||
}
|
||||
|
||||
// NewOrderBy gets list of options and returns a configured order-by.
|
||||
//
|
||||
// NewOrderBy(
|
||||
// sqlgraph.NewStep(
|
||||
// sqlgraph.From(user.Table, user.FieldID),
|
||||
// sqlgraph.To(group.Table, group.FieldID),
|
||||
// sqlgraph.Edge(sqlgraph.M2M, false, user.GroupsTable, user.GroupsPrimaryKey...),
|
||||
// ),
|
||||
// OrderByExpr(
|
||||
// sql.Expr("SUM(age)"),
|
||||
// "sum_age",
|
||||
// ),
|
||||
// )
|
||||
func NewOrderBy(s *Step, opts ...OrderByOption) *OrderByOptions {
|
||||
r := &OrderByOptions{Step: s}
|
||||
for _, opt := range opts {
|
||||
opt(r)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// countAlias returns the alias to use for the count column.
|
||||
func countAlias(q *sql.Selector, opts *OrderByOptions) string {
|
||||
if len(opts.Terms) == 1 && opts.Terms[0].As != "" {
|
||||
return opts.Terms[0].As
|
||||
func countAlias(q *sql.Selector, s *Step, opt *sql.OrderTermOptions) string {
|
||||
if opt.As != "" {
|
||||
return opt.As
|
||||
}
|
||||
selected := make(map[string]struct{})
|
||||
for _, c := range q.SelectedColumns() {
|
||||
selected[c] = struct{}{}
|
||||
}
|
||||
column := fmt.Sprintf("count_%s", opts.Step.To.Table)
|
||||
column := fmt.Sprintf("count_%s", s.To.Table)
|
||||
// If the column was already selected,
|
||||
// try to find a free alias.
|
||||
if _, ok := selected[column]; ok {
|
||||
@@ -439,22 +329,19 @@ func countAlias(q *sql.Selector, opts *OrderByOptions) string {
|
||||
|
||||
// OrderByNeighborsCount appends ordering based on the number of neighbors.
|
||||
// For example, order users by their number of posts.
|
||||
func OrderByNeighborsCount(q *sql.Selector, opts *OrderByOptions) {
|
||||
func OrderByNeighborsCount(q *sql.Selector, s *Step, opts ...sql.OrderTermOption) {
|
||||
var (
|
||||
desc bool
|
||||
join *sql.Selector
|
||||
opt = sql.NewOrderTermOptions(opts...)
|
||||
build = sql.Dialect(q.Dialect())
|
||||
)
|
||||
if len(opts.Terms) == 1 {
|
||||
desc = opts.Terms[0].Desc
|
||||
}
|
||||
switch s := opts.Step; {
|
||||
switch {
|
||||
case s.FromEdgeOwner():
|
||||
// For M2O and O2O inverse, the FK resides in the same table.
|
||||
// Hence, the order by is on the nullability of the column.
|
||||
x := func(b *sql.Builder) {
|
||||
b.Ident(s.From.Column)
|
||||
if desc {
|
||||
if opt.Desc {
|
||||
b.WriteOp(sql.OpNotNull)
|
||||
} else {
|
||||
b.WriteOp(sql.OpIsNull)
|
||||
@@ -462,7 +349,7 @@ func OrderByNeighborsCount(q *sql.Selector, opts *OrderByOptions) {
|
||||
}
|
||||
q.OrderExpr(build.Expr(x))
|
||||
case s.ThroughEdgeTable():
|
||||
countC := countAlias(q, opts)
|
||||
countC := countAlias(q, s, opt)
|
||||
pk1 := s.Edge.Columns[0]
|
||||
if s.Edge.Inverse {
|
||||
pk1 = s.Edge.Columns[1]
|
||||
@@ -479,11 +366,11 @@ func OrderByNeighborsCount(q *sql.Selector, opts *OrderByOptions) {
|
||||
q.C(s.From.Column),
|
||||
join.C(pk1),
|
||||
)
|
||||
opts.Terms = []OrderByTerm{{As: countC, Type: field.TypeInt, Desc: desc}}
|
||||
orderTerms(q, join, opts.Terms)
|
||||
appendTerms(q, opts)
|
||||
orderTerms(q, join, []sql.OrderTerm{
|
||||
sql.OrderByExpr(nil, append(opts, sql.OrderAs(countC))...),
|
||||
})
|
||||
case s.ToEdgeOwner():
|
||||
countC := countAlias(q, opts)
|
||||
countC := countAlias(q, s, opt)
|
||||
edgeT := build.Table(s.Edge.Table).Schema(s.Edge.Schema)
|
||||
join = build.Select(
|
||||
edgeT.C(s.Edge.Columns[0]),
|
||||
@@ -496,78 +383,80 @@ func OrderByNeighborsCount(q *sql.Selector, opts *OrderByOptions) {
|
||||
q.C(s.From.Column),
|
||||
join.C(s.Edge.Columns[0]),
|
||||
)
|
||||
opts.Terms = []OrderByTerm{{As: countC, Type: field.TypeInt, Desc: desc}}
|
||||
orderTerms(q, join, opts.Terms)
|
||||
appendTerms(q, opts)
|
||||
orderTerms(q, join, []sql.OrderTerm{
|
||||
sql.OrderByExpr(nil, append(opts, sql.OrderAs(countC))...),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func orderTerms(q, join *sql.Selector, ts []OrderByTerm) {
|
||||
func orderTerms(q, join *sql.Selector, ts []sql.OrderTerm) {
|
||||
for _, t := range ts {
|
||||
t := t
|
||||
q.OrderExprFunc(func(b *sql.Builder) {
|
||||
switch {
|
||||
case t.As != "":
|
||||
b.WriteString(join.C(t.As))
|
||||
case t.Column != "":
|
||||
b.WriteString(join.C(t.Column))
|
||||
case t.Expr != nil:
|
||||
b.Join(t.Expr)
|
||||
var desc bool
|
||||
switch t := t.(type) {
|
||||
case *sql.OrderFieldTerm:
|
||||
f := t.Field
|
||||
if t.As != "" {
|
||||
f = t.As
|
||||
}
|
||||
c := join.C(f)
|
||||
b.WriteString(c)
|
||||
if t.Selected {
|
||||
q.AppendSelect(c)
|
||||
}
|
||||
desc = t.Desc
|
||||
case *sql.OrderExprTerm:
|
||||
if t.As != "" {
|
||||
c := join.C(t.As)
|
||||
b.WriteString(c)
|
||||
if t.Selected {
|
||||
q.AppendSelect(c)
|
||||
}
|
||||
} else {
|
||||
b.Join(t.Expr)
|
||||
}
|
||||
desc = t.Desc
|
||||
default:
|
||||
return
|
||||
}
|
||||
// Unlike MySQL and SQLite, NULL values sort as if larger than any other value.
|
||||
// Therefore, we need to explicitly order NULLs first on ASC and last on DESC.
|
||||
switch pg := b.Dialect() == dialect.Postgres; {
|
||||
case pg && t.Desc:
|
||||
case pg && desc:
|
||||
b.WriteString(" DESC NULLS LAST")
|
||||
case pg:
|
||||
b.WriteString(" NULLS FIRST")
|
||||
case t.Desc:
|
||||
case desc:
|
||||
b.WriteString(" DESC")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func selectTerms(q *sql.Selector, ts []OrderByTerm) {
|
||||
func selectTerms(q *sql.Selector, ts []sql.OrderTerm) {
|
||||
for _, t := range ts {
|
||||
switch {
|
||||
case t.Column != "" && t.As != "":
|
||||
q.AppendSelect(q.C(t.Column), t.As)
|
||||
case t.Column != "":
|
||||
q.AppendSelect(q.C(t.Column))
|
||||
case t.Expr != nil:
|
||||
switch t := t.(type) {
|
||||
case *sql.OrderFieldTerm:
|
||||
q.AppendSelect(q.C(t.Field))
|
||||
case *sql.OrderExprTerm:
|
||||
q.AppendSelectExprAs(t.Expr, t.As)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func appendTerms(q *sql.Selector, opts *OrderByOptions) {
|
||||
if !opts.Selected {
|
||||
return
|
||||
}
|
||||
for _, t := range opts.Terms {
|
||||
switch {
|
||||
case t.As != "":
|
||||
q.AppendSelect(t.As)
|
||||
case t.Column != "":
|
||||
q.AppendSelect(q.C(t.Column))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OrderByNeighborTerms appends ordering based on the number of neighbors.
|
||||
// For example, order users by their number of posts.
|
||||
func OrderByNeighborTerms(q *sql.Selector, opts *OrderByOptions) {
|
||||
func OrderByNeighborTerms(q *sql.Selector, s *Step, opts ...sql.OrderTerm) {
|
||||
var (
|
||||
join *sql.Selector
|
||||
build = sql.Dialect(q.Dialect())
|
||||
)
|
||||
switch s := opts.Step; {
|
||||
switch {
|
||||
case s.FromEdgeOwner():
|
||||
toT := build.Table(s.To.Table).Schema(s.To.Schema)
|
||||
join = build.Select(toT.C(s.To.Column)).
|
||||
From(toT)
|
||||
selectTerms(join, opts.Terms)
|
||||
selectTerms(join, opts)
|
||||
q.LeftJoin(join).
|
||||
On(q.C(s.Edge.Columns[0]), join.C(s.To.Column))
|
||||
case s.ThroughEdgeTable():
|
||||
@@ -582,7 +471,7 @@ func OrderByNeighborTerms(q *sql.Selector, opts *OrderByOptions) {
|
||||
Join(joinT).
|
||||
On(toT.C(s.To.Column), joinT.C(pk1)).
|
||||
GroupBy(pk2)
|
||||
selectTerms(join, opts.Terms)
|
||||
selectTerms(join, opts)
|
||||
q.LeftJoin(join).
|
||||
On(q.C(s.From.Column), join.C(pk2))
|
||||
case s.ToEdgeOwner():
|
||||
@@ -590,12 +479,11 @@ func OrderByNeighborTerms(q *sql.Selector, opts *OrderByOptions) {
|
||||
join = build.Select(toT.C(s.Edge.Columns[0])).
|
||||
From(toT).
|
||||
GroupBy(toT.C(s.Edge.Columns[0]))
|
||||
selectTerms(join, opts.Terms)
|
||||
selectTerms(join, opts)
|
||||
q.LeftJoin(join).
|
||||
On(q.C(s.From.Column), join.C(s.Edge.Columns[0]))
|
||||
}
|
||||
orderTerms(q, join, opts.Terms)
|
||||
appendTerms(q, opts)
|
||||
orderTerms(q, join, opts)
|
||||
}
|
||||
|
||||
type (
|
||||
|
||||
@@ -918,27 +918,43 @@ func TestOrderByNeighborsCount(t *testing.T) {
|
||||
From(t1)
|
||||
t.Run("O2M", func(t *testing.T) {
|
||||
s := s.Clone()
|
||||
OrderByNeighborsCount(s, NewOrderBy(
|
||||
OrderByNeighborsCount(s,
|
||||
NewStep(
|
||||
From("users", "id"),
|
||||
To("pets", "owner_id"),
|
||||
Edge(O2M, false, "pets", "owner_id"),
|
||||
),
|
||||
OrderByExprDesc(nil, "count_pets"),
|
||||
))
|
||||
sql.OrderDesc(),
|
||||
sql.OrderAs("count_pets"),
|
||||
)
|
||||
query, args := s.Query()
|
||||
require.Empty(t, args)
|
||||
require.Equal(t, `SELECT "users"."name" FROM "users" LEFT JOIN (SELECT "pets"."owner_id", COUNT(*) AS "count_pets" FROM "pets" GROUP BY "pets"."owner_id") AS "t1" ON "users"."id" = "t1"."owner_id" ORDER BY "t1"."count_pets" DESC NULLS LAST`, query)
|
||||
})
|
||||
t.Run("O2M/Selected", func(t *testing.T) {
|
||||
s := s.Clone()
|
||||
OrderByNeighborsCount(s,
|
||||
NewStep(
|
||||
From("users", "id"),
|
||||
To("pets", "owner_id"),
|
||||
Edge(O2M, false, "pets", "owner_id"),
|
||||
),
|
||||
sql.OrderDesc(),
|
||||
sql.OrderSelectAs("count_pets"),
|
||||
)
|
||||
query, args := s.Query()
|
||||
require.Empty(t, args)
|
||||
require.Equal(t, `SELECT "users"."name", "t1"."count_pets" FROM "users" LEFT JOIN (SELECT "pets"."owner_id", COUNT(*) AS "count_pets" FROM "pets" GROUP BY "pets"."owner_id") AS "t1" ON "users"."id" = "t1"."owner_id" ORDER BY "t1"."count_pets" DESC NULLS LAST`, query)
|
||||
})
|
||||
t.Run("M2M", func(t *testing.T) {
|
||||
s := s.Clone()
|
||||
OrderByNeighborsCount(s, &OrderByOptions{
|
||||
Step: NewStep(
|
||||
OrderByNeighborsCount(s,
|
||||
NewStep(
|
||||
From("users", "id"),
|
||||
To("groups", "id"),
|
||||
Edge(M2M, false, "user_groups", "user_id", "group_id"),
|
||||
),
|
||||
})
|
||||
)
|
||||
query, args := s.Query()
|
||||
require.Empty(t, args)
|
||||
require.Equal(t, `SELECT "users"."name" FROM "users" LEFT JOIN (SELECT "user_groups"."user_id", COUNT(*) AS "count_groups" FROM "user_groups" GROUP BY "user_groups"."user_id") AS "t1" ON "users"."id" = "t1"."user_id" ORDER BY "t1"."count_groups" NULLS FIRST`, query)
|
||||
@@ -946,44 +962,29 @@ func TestOrderByNeighborsCount(t *testing.T) {
|
||||
// Zero or one.
|
||||
t.Run("M2O", func(t *testing.T) {
|
||||
s1, s2 := s.Clone(), s.Clone()
|
||||
OrderByNeighborsCount(s1, NewOrderBy(
|
||||
OrderByNeighborsCount(s1,
|
||||
NewStep(
|
||||
From("pets", "owner_id"),
|
||||
To("users", "id"),
|
||||
Edge(M2O, true, "pets", "owner_id"),
|
||||
),
|
||||
))
|
||||
)
|
||||
query, args := s1.Query()
|
||||
require.Empty(t, args)
|
||||
require.Equal(t, `SELECT "users"."name" FROM "users" ORDER BY "owner_id" IS NULL`, query)
|
||||
|
||||
OrderByNeighborsCount(s2, NewOrderBy(
|
||||
OrderByNeighborsCount(s2,
|
||||
NewStep(
|
||||
From("pets", "owner_id"),
|
||||
To("users", "id"),
|
||||
Edge(M2O, true, "pets", "owner_id"),
|
||||
),
|
||||
OrderDesc(),
|
||||
))
|
||||
sql.OrderDesc(),
|
||||
)
|
||||
query, args = s2.Query()
|
||||
require.Empty(t, args)
|
||||
require.Equal(t, `SELECT "users"."name" FROM "users" ORDER BY "owner_id" IS NOT NULL`, query)
|
||||
})
|
||||
t.Run("Selected", func(t *testing.T) {
|
||||
s := s.Clone()
|
||||
OrderByNeighborsCount(s, NewOrderBy(
|
||||
NewStep(
|
||||
From("users", "id"),
|
||||
To("pets", "owner_id"),
|
||||
Edge(O2M, false, "pets", "owner_id"),
|
||||
),
|
||||
OrderByExprDesc(nil, "count_pets"),
|
||||
OrderBySelected(),
|
||||
))
|
||||
query, args := s.Query()
|
||||
require.Empty(t, args)
|
||||
require.Equal(t, `SELECT "users"."name", "count_pets" FROM "users" LEFT JOIN (SELECT "pets"."owner_id", COUNT(*) AS "count_pets" FROM "pets" GROUP BY "pets"."owner_id") AS "t1" ON "users"."id" = "t1"."owner_id" ORDER BY "t1"."count_pets" DESC NULLS LAST`, query)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOrderByNeighborTerms(t *testing.T) {
|
||||
@@ -993,77 +994,55 @@ func TestOrderByNeighborTerms(t *testing.T) {
|
||||
From(t1)
|
||||
t.Run("M2O", func(t *testing.T) {
|
||||
s := s.Clone()
|
||||
OrderByNeighborTerms(s, NewOrderBy(
|
||||
OrderByNeighborTerms(s,
|
||||
NewStep(
|
||||
From("users", "id"),
|
||||
To("workplace", "id"),
|
||||
Edge(M2O, true, "users", "workplace_id"),
|
||||
),
|
||||
OrderByColumn("name"),
|
||||
))
|
||||
sql.OrderByField("name"),
|
||||
)
|
||||
query, args := s.Query()
|
||||
require.Empty(t, args)
|
||||
require.Equal(t, `SELECT "users"."name" FROM "users" LEFT JOIN (SELECT "workplace"."id", "workplace"."name" FROM "workplace") AS "t1" ON "users"."workplace_id" = "t1"."id" ORDER BY "t1"."name" NULLS FIRST`, query)
|
||||
})
|
||||
t.Run("O2M", func(t *testing.T) {
|
||||
s := s.Clone()
|
||||
OrderByNeighborTerms(s, NewOrderBy(
|
||||
OrderByNeighborTerms(s,
|
||||
NewStep(
|
||||
From("users", "id"),
|
||||
To("repos", "id"),
|
||||
Edge(O2M, false, "repo", "owner_id"),
|
||||
),
|
||||
OrderByExpr(
|
||||
sql.OrderByExpr(
|
||||
sql.ExprFunc(func(b *sql.Builder) {
|
||||
b.S("SUM(").Ident("num_stars").S(")")
|
||||
}),
|
||||
"total_stars",
|
||||
sql.OrderSelectAs("total_stars"),
|
||||
),
|
||||
))
|
||||
)
|
||||
query, args := s.Query()
|
||||
require.Empty(t, args)
|
||||
require.Equal(t, `SELECT "users"."name" FROM "users" LEFT JOIN (SELECT "repo"."owner_id", (SUM("num_stars")) AS "total_stars" FROM "repo" GROUP BY "repo"."owner_id") AS "t1" ON "users"."id" = "t1"."owner_id" ORDER BY "t1"."total_stars" NULLS FIRST`, query)
|
||||
require.Equal(t, `SELECT "users"."name", "t1"."total_stars" FROM "users" LEFT JOIN (SELECT "repo"."owner_id", (SUM("num_stars")) AS "total_stars" FROM "repo" GROUP BY "repo"."owner_id") AS "t1" ON "users"."id" = "t1"."owner_id" ORDER BY "t1"."total_stars" NULLS FIRST`, query)
|
||||
})
|
||||
t.Run("M2M", func(t *testing.T) {
|
||||
s := s.Clone()
|
||||
OrderByNeighborTerms(s, NewOrderBy(
|
||||
OrderByNeighborTerms(s,
|
||||
NewStep(
|
||||
From("users", "id"),
|
||||
To("group", "id"),
|
||||
Edge(M2M, false, "user_groups", "user_id", "group_id"),
|
||||
),
|
||||
OrderByExpr(
|
||||
sql.OrderByExpr(
|
||||
sql.ExprFunc(func(b *sql.Builder) {
|
||||
b.S("SUM(").Ident("num_users").S(")")
|
||||
}),
|
||||
"total_users",
|
||||
sql.OrderSelectAs("total_users"),
|
||||
),
|
||||
))
|
||||
)
|
||||
query, args := s.Query()
|
||||
require.Empty(t, args)
|
||||
require.Equal(t, `SELECT "users"."name" FROM "users" LEFT JOIN (SELECT "user_id", (SUM("num_users")) AS "total_users" FROM "group" JOIN "user_groups" AS "t1" ON "group"."id" = "t1"."group_id" GROUP BY "user_id") AS "t1" ON "users"."id" = "t1"."user_id" ORDER BY "t1"."total_users" NULLS FIRST`, query)
|
||||
})
|
||||
t.Run("Selected", func(t *testing.T) {
|
||||
t1 := build.Table("users")
|
||||
s := build.Select(t1.C("name")).
|
||||
From(t1)
|
||||
OrderByNeighborTerms(s, NewOrderBy(
|
||||
NewStep(
|
||||
From("users", "id"),
|
||||
To("group", "id"),
|
||||
Edge(M2M, false, "user_groups", "user_id", "group_id"),
|
||||
),
|
||||
OrderByExpr(
|
||||
sql.ExprFunc(func(b *sql.Builder) {
|
||||
b.S("SUM(").Ident("num_users").S(")")
|
||||
}),
|
||||
"total_users",
|
||||
),
|
||||
OrderBySelected(),
|
||||
))
|
||||
query, args := s.Query()
|
||||
require.Empty(t, args)
|
||||
require.Equal(t, `SELECT "users"."name", "total_users" FROM "users" LEFT JOIN (SELECT "user_id", (SUM("num_users")) AS "total_users" FROM "group" JOIN "user_groups" AS "t1" ON "group"."id" = "t1"."group_id" GROUP BY "user_id") AS "t1" ON "users"."id" = "t1"."user_id" ORDER BY "t1"."total_users" NULLS FIRST`, query)
|
||||
require.Equal(t, `SELECT "users"."name", "t1"."total_users" FROM "users" LEFT JOIN (SELECT "user_id", (SUM("num_users")) AS "total_users" FROM "group" JOIN "user_groups" AS "t1" ON "group"."id" = "t1"."group_id" GROUP BY "user_id") AS "t1" ON "users"."id" = "t1"."user_id" ORDER BY "t1"."total_users" NULLS FIRST`, query)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user