Skip to content

Commit

Permalink
Merge pull request #95 from ulule/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
edouardparis authored Apr 24, 2020
2 parents dd0e97b + afe2283 commit fa07156
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 76 deletions.
17 changes: 17 additions & 0 deletions builder/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,23 @@ func NewSelect() Select {
return Select{}
}

// DistinctOn adds a DISTINCT ON clause to the query.
func (b Select) DistinctOn(args ...interface{}) Select {
if !b.query.GroupBy.IsEmpty() {
panic("loukoum: select builder has distinct on clause already defined")
}

columns := ToColumns(args)
distinctOn := stmt.NewDistinctOn(columns)
if distinctOn.IsEmpty() {
panic("loukoum: given distinct on clause is undefined")
}

b.query.DistinctOn = distinctOn

return b
}

// Distinct adds a DISTINCT clause to the query.
func (b Select) Distinct() Select {
b.query.Distinct = true
Expand Down
59 changes: 59 additions & 0 deletions builder/select_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,65 @@ func TestSelect_Columns(t *testing.T) {
})
}

func TestSelect_DistinctOn(t *testing.T) {
RunBuilderTests(t, []BuilderTest{
{
Name: "One column",
Builders: []builder.Builder{
loukoum.
Select("date").
DistinctOn("id").
From("record").
Where(loukoum.Condition("disabled").IsNull(false)),
loukoum.
Select("date").
DistinctOn(loukoum.Column("id")).
From("record").
Where(loukoum.Condition("disabled").IsNull(false)),
},
SameQuery: "SELECT DISTINCT ON (id) date FROM record WHERE (disabled IS NOT NULL)",
},
{
Name: "Two columns",
Builders: []builder.Builder{
loukoum.
Select("date").
DistinctOn("id", "user_id").
From("record").
Where(loukoum.Condition("disabled").IsNull(false)),
loukoum.
Select("date").
DistinctOn(loukoum.Column("id"), loukoum.Column("user_id")).
From("record").
Where(loukoum.Condition("disabled").IsNull(false)),
},
SameQuery: fmt.Sprint(
"SELECT DISTINCT ON (id, user_id) date FROM record ",
"WHERE (disabled IS NOT NULL)",
),
},
{
Name: "Three columns",
Builders: []builder.Builder{
loukoum.
Select("date").
DistinctOn("id", "user_id", "project_id").
From("record").
Where(loukoum.Condition("disabled").IsNull(false)),
loukoum.
Select("date").
DistinctOn(loukoum.Column("id"), loukoum.Column("user_id"), loukoum.Column("project_id")).
From("record").
Where(loukoum.Condition("disabled").IsNull(false)),
},
SameQuery: fmt.Sprint(
"SELECT DISTINCT ON (id, user_id, project_id) date FROM record ",
"WHERE (disabled IS NOT NULL)",
),
},
})
}

func TestSelect_From(t *testing.T) {
RunBuilderTests(t, []BuilderTest{
{
Expand Down
42 changes: 42 additions & 0 deletions stmt/distinct_on.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package stmt

import (
"github.com/ulule/loukoum/v3/token"
"github.com/ulule/loukoum/v3/types"
)

// DistinctOn is a distinct on expression.
type DistinctOn struct {
Columns []Column
}

// NewDistinctOn returns a new DistinctOn instance.
func NewDistinctOn(columns []Column) DistinctOn {
return DistinctOn{
Columns: columns,
}
}

// Write exposes statement as a SQL query.
func (distinctOn DistinctOn) Write(ctx types.Context) {
if distinctOn.IsEmpty() {
return
}
ctx.Write(token.DistinctOn.String())
ctx.Write(" (")
for i := range distinctOn.Columns {
if i != 0 {
ctx.Write(", ")
}
distinctOn.Columns[i].Write(ctx)
}
ctx.Write(")")
}

// IsEmpty returns true if statement is undefined.
func (distinctOn DistinctOn) IsEmpty() bool {
return len(distinctOn.Columns) == 0
}

// Ensure that DistinctOn is a Statement
var _ Statement = DistinctOn{}
6 changes: 6 additions & 0 deletions stmt/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Select struct {
Prefix Prefix
With With
Distinct bool
DistinctOn DistinctOn
Expressions []SelectExpression
From From
Joins []Join
Expand Down Expand Up @@ -58,6 +59,11 @@ func (selekt Select) writeHead(ctx types.Context) {

ctx.Write(token.Select.String())

if !selekt.DistinctOn.IsEmpty() {
ctx.Write(" ")
selekt.DistinctOn.Write(ctx)
}

if selekt.Distinct {
ctx.Write(" ")
ctx.Write(token.Distinct.String())
Expand Down
154 changes: 78 additions & 76 deletions token/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,44 +37,45 @@ const (

// Keywords token types.
const (
Select = Type("SELECT")
Update = Type("UPDATE")
Insert = Type("INSERT")
Delete = Type("DELETE")
From = Type("FROM")
Where = Type("WHERE")
And = Type("AND")
Or = Type("OR")
Limit = Type("LIMIT")
Offset = Type("OFFSET")
Set = Type("SET")
As = Type("AS")
Inner = Type("INNER")
Cross = Type("CROSS")
Left = Type("LEFT")
Right = Type("RIGHT")
Join = Type("JOIN")
On = Type("ON")
Group = Type("GROUP")
By = Type("BY")
Having = Type("HAVING")
Order = Type("ORDER")
Distinct = Type("DISTINCT")
Only = Type("ONLY")
Using = Type("USING")
Returning = Type("RETURNING")
Values = Type("VALUES")
Into = Type("INTO")
Conflict = Type("CONFLICT")
Do = Type("DO")
Nothing = Type("NOTHING")
With = Type("WITH")
Not = Type("NOT")
Exists = Type("EXISTS")
Count = Type("COUNT")
Max = Type("MAX")
Min = Type("MIN")
Sum = Type("SUM")
Select = Type("SELECT")
Update = Type("UPDATE")
Insert = Type("INSERT")
Delete = Type("DELETE")
From = Type("FROM")
Where = Type("WHERE")
And = Type("AND")
Or = Type("OR")
Limit = Type("LIMIT")
Offset = Type("OFFSET")
Set = Type("SET")
As = Type("AS")
Inner = Type("INNER")
Cross = Type("CROSS")
Left = Type("LEFT")
Right = Type("RIGHT")
Join = Type("JOIN")
On = Type("ON")
Group = Type("GROUP")
By = Type("BY")
Having = Type("HAVING")
Order = Type("ORDER")
Distinct = Type("DISTINCT")
DistinctOn = Type("DISTINCT ON")
Only = Type("ONLY")
Using = Type("USING")
Returning = Type("RETURNING")
Values = Type("VALUES")
Into = Type("INTO")
Conflict = Type("CONFLICT")
Do = Type("DO")
Nothing = Type("NOTHING")
With = Type("WITH")
Not = Type("NOT")
Exists = Type("EXISTS")
Count = Type("COUNT")
Max = Type("MAX")
Min = Type("MIN")
Sum = Type("SUM")
)

// A Token is defined by its type and a value.
Expand All @@ -88,44 +89,45 @@ func (t *Token) String() string {
}

var keywords = map[string]Type{
"SELECT": Select,
"UPDATE": Update,
"INSERT": Insert,
"DELETE": Delete,
"FROM": From,
"WHERE": Where,
"AND": And,
"OR": Or,
"LIMIT": Limit,
"OFFSET": Offset,
"SET": Set,
"AS": As,
"INNER": Inner,
"CROSS": Cross,
"LEFT": Left,
"RIGHT": Right,
"JOIN": Join,
"ON": On,
"GROUP": Group,
"BY": By,
"HAVING": Having,
"ORDER": Order,
"DISTINCT": Distinct,
"ONLY": Only,
"USING": Using,
"RETURNING": Returning,
"VALUES": Values,
"INTO": Into,
"CONFLICT": Conflict,
"DO": Do,
"NOTHING": Nothing,
"WITH": With,
"NOT": Not,
"EXISTS": Exists,
"COUNT": Count,
"MAX": Max,
"MIN": Min,
"SUM": Sum,
"SELECT": Select,
"UPDATE": Update,
"INSERT": Insert,
"DELETE": Delete,
"FROM": From,
"WHERE": Where,
"AND": And,
"OR": Or,
"LIMIT": Limit,
"OFFSET": Offset,
"SET": Set,
"AS": As,
"INNER": Inner,
"CROSS": Cross,
"LEFT": Left,
"RIGHT": Right,
"JOIN": Join,
"ON": On,
"GROUP": Group,
"BY": By,
"HAVING": Having,
"ORDER": Order,
"DISTINCT": Distinct,
"DISTINCT ON": DistinctOn,
"ONLY": Only,
"USING": Using,
"RETURNING": Returning,
"VALUES": Values,
"INTO": Into,
"CONFLICT": Conflict,
"DO": Do,
"NOTHING": Nothing,
"WITH": With,
"NOT": Not,
"EXISTS": Exists,
"COUNT": Count,
"MAX": Max,
"MIN": Min,
"SUM": Sum,
}

// Lookup will try to map a statement to a keyword.
Expand Down

0 comments on commit fa07156

Please sign in to comment.