diff --git a/builder/builder.go b/builder/builder.go index 60850c8..e449873 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -199,20 +199,26 @@ func ToTables(values []interface{}) []stmt.Table { } // ToFrom takes an empty interfaces and returns a From instance. -func ToFrom(arg interface{}) stmt.From { +func ToFrom(args ...interface{}) stmt.From { from := stmt.From{} - switch value := arg.(type) { - case string: - from = stmt.NewFrom(stmt.NewTable(value), false) - case stmt.From: - from = value - case stmt.Table: - from = stmt.NewFrom(value, false) - default: - panic(fmt.Sprintf("loukoum: cannot use %T as from clause", arg)) + tables := make([]stmt.Statement, len(args)) + for i := range args { + switch value := args[i].(type) { + case string: + tables[i] = stmt.NewTable(value) + case stmt.From: + from = value + case stmt.Table: + tables[i] = value + case stmt.Raw: + tables[i] = value + default: + panic(fmt.Sprintf("loukoum: cannot use %T as from clause", args[i])) + } } + from = stmt.NewFrom(tables) if from.IsEmpty() { panic("loukoum: given from clause is undefined") } diff --git a/builder/delete.go b/builder/delete.go index 57c7e09..b38c15d 100644 --- a/builder/delete.go +++ b/builder/delete.go @@ -16,23 +16,13 @@ func NewDelete() Delete { } // From sets the FROM clause of the query. -func (b Delete) From(arg interface{}) Delete { +func (b Delete) From(arg ...interface{}) Delete { if !b.query.From.IsEmpty() { panic("loukoum: delete builder has from clause already defined") } - only := b.query.From.Only - b.query.From = ToFrom(arg) - if only { - b.query.From.Only = only - } - - return b -} + b.query.From = ToFrom(arg...) -// Only adds a ONLY clause to the query. -func (b Delete) Only() Delete { - b.query.From.Only = true return b } diff --git a/builder/delete_test.go b/builder/delete_test.go index 680465d..5321ebc 100644 --- a/builder/delete_test.go +++ b/builder/delete_test.go @@ -21,8 +21,7 @@ func TestDelete(t *testing.T) { { Name: "Only", Builders: []builder.Builder{ - loukoum.Delete("table").Only(), - loukoum.Delete(loukoum.Table("table")).Only(), + loukoum.Delete(loukoum.Table("table").Only()), }, SameQuery: "DELETE FROM ONLY \"table\"", }, @@ -33,7 +32,7 @@ func TestDelete(t *testing.T) { }, { Name: "As only", - Builder: loukoum.Delete(loukoum.Table("table").As("foobar")).Only(), + Builder: loukoum.Delete(loukoum.Table("table").As("foobar").Only()), SameQuery: "DELETE FROM ONLY \"table\" AS \"foobar\"", }, }) diff --git a/builder/select.go b/builder/select.go index c785a45..87eb490 100644 --- a/builder/select.go +++ b/builder/select.go @@ -56,12 +56,12 @@ func (b Select) Columns(args ...interface{}) Select { } // From sets the FROM clause of the query. -func (b Select) From(arg interface{}) Select { +func (b Select) From(arg ...interface{}) Select { if !b.query.From.IsEmpty() { panic("loukoum: select builder has from clause already defined") } - b.query.From = ToFrom(arg) + b.query.From = ToFrom(arg...) return b } diff --git a/builder/select_test.go b/builder/select_test.go index 78e1cb4..b0c9e32 100644 --- a/builder/select_test.go +++ b/builder/select_test.go @@ -304,6 +304,26 @@ func TestSelect_From(t *testing.T) { Builder: loukoum.Select("a").From(loukoum.Table("foobar").As("example")), SameQuery: "SELECT \"a\" FROM \"foobar\" AS \"example\"", }, + { + Name: "Multiple", + Builder: loukoum.Select("a").From(loukoum.Table("foobar").As("example"), loukoum.Table("foobar").As("example2")), + SameQuery: "SELECT \"a\" FROM \"foobar\" AS \"example\", \"foobar\" AS \"example2\"", + }, + { + Name: "Multiple strings", + Builder: loukoum.Select("a").From("example", "example2"), + SameQuery: "SELECT \"a\" FROM \"example\", \"example2\"", + }, + { + Name: "Multiple raws", + Builder: loukoum.Select("a").From(loukoum.Raw("example1 ex1"), loukoum.Raw("example2 ex2")), + SameQuery: "SELECT \"a\" FROM example1 ex1, example2 ex2", + }, + { + Name: "Multiple raw", + Builder: loukoum.Select("a").From(loukoum.Raw("example1 ex1, example2 ex2")), + SameQuery: "SELECT \"a\" FROM example1 ex1, example2 ex2", + }, }) } diff --git a/stmt/from.go b/stmt/from.go index 886fee3..1fcfef2 100644 --- a/stmt/from.go +++ b/stmt/from.go @@ -7,32 +7,33 @@ import ( // From is a FROM clause. type From struct { - Only bool - Table Table + Tables []Statement } // NewFrom returns a new From instance. -func NewFrom(table Table, only bool) From { +func NewFrom(tables []Statement) From { return From{ - Only: only, - Table: table, + Tables: tables, } } // Write exposes statement as a SQL query. func (from From) Write(ctx types.Context) { ctx.Write(token.From.String()) - if from.Only { - ctx.Write(" ") - ctx.Write(token.Only.String()) - } ctx.Write(" ") - from.Table.Write(ctx) + + for i := range from.Tables { + from.Tables[i].Write(ctx) + + if i != len(from.Tables)-1 { + ctx.Write(", ") + } + } } // IsEmpty returns true if statement is undefined. func (from From) IsEmpty() bool { - return from.Table.IsEmpty() + return len(from.Tables) == 0 } // Ensure that From is a Statement diff --git a/stmt/table.go b/stmt/table.go index f9529db..8e57c83 100644 --- a/stmt/table.go +++ b/stmt/table.go @@ -7,8 +7,9 @@ import ( // Table is a table identifier. type Table struct { - Name string Alias string + Name string + only bool } // NewTable returns a new Table instance. @@ -16,6 +17,12 @@ func NewTable(name string) Table { return NewTableAlias(name, "") } +// Only sets ONLY clause to the table. +func (table Table) Only() Table { + table.only = true + return table +} + // NewTableAlias returns a new Table instance with an alias. func NewTableAlias(name, alias string) Table { return Table{ @@ -32,6 +39,10 @@ func (table Table) As(alias string) Table { // Write exposes statement as a SQL query. func (table Table) Write(ctx types.Context) { + if table.only { + ctx.Write(token.Only.String()) + ctx.Write(" ") + } ctx.Write(quote(table.Name)) if table.Alias != "" { ctx.Write(" ")