diff --git a/builder/builder.go b/builder/builder.go index 88af1ff..7047d56 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -28,22 +28,21 @@ func NewBuilder(format token.Format) Builder { } // Build converts a statement into a query and a slice of values. -func (b Builder) Build(stmt statement.Statement) (string, []interface{}) { - - if n, ok := stmt.(statement.Nodeizer); ok { - root := statement.Traverse(n) - tokenizers, values := statement.Nodize(root) - sql := token.Print(tokenizers.Tokenize(0), b.Format) - if len(values) == 0 { - values = nil - } - return sql, values - } - - tokenizer, values := statement.Nodeize(stmt) - query := token.Print(tokenizer.Tokenize(0), b.Format) +func (b Builder) Build(n statement.Statement) (string, []interface{}) { + // if n, ok := stmt.(statement.Nodeizer); ok { + root := statement.Traverse(n) + tokenizers, values := statement.Nodize(root) + sql := token.Generate(tokenizers.Tokenize(0), b.Format) if len(values) == 0 { values = nil } - return query, values + return sql, values + // } + + // tokenizer, values := statement.Nodeize(stmt) + // query := token.Generate(tokenizer.Tokenize(0), b.Format) + // if len(values) == 0 { + // values = nil + // } + // return query, values } diff --git a/builder/builder_test.go b/builder/builder_test.go index 72e2ab7..0af5c8d 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -163,7 +163,8 @@ func TestBuilder(t *testing.T) { > OR title = ? > ) > ) -> UNION ( +> UNION +> ( > SELECT DISTINCT > open_emp_id > FROM diff --git a/statement/assign.go b/statement/assign.go index 474b4ec..12d2cb7 100644 --- a/statement/assign.go +++ b/statement/assign.go @@ -25,9 +25,7 @@ func (a Assign) nodeize() (tokenizer.Tokenizer, []interface{}) { t1, t2, tokenizer.NewLine( - token.Space, token.Word(keyword.Eq), - token.Space, ), ), append(v1, v2...) } diff --git a/statement/case_searched.go b/statement/case_searched.go index 1a630fe..18093d6 100644 --- a/statement/case_searched.go +++ b/statement/case_searched.go @@ -16,14 +16,10 @@ func NewSearchedCase() *SearchedCase { func (c *SearchedCase) When(condition ComparisonOrLogicalOperation) *SearchedWhen { w := NewSearchedWhen(condition) - Link(c, w) + Contract(c, w) return w } -func (c *SearchedCase) separator() token.Tokens { - return nil -} - func (c *SearchedCase) nodeize() (tokenizer.Tokenizer, []interface{}) { return tokenizer.NewContainer( tokenizer.NewLine(token.Word(keyword.Case)), diff --git a/statement/case_simple.go b/statement/case_simple.go index 5786c09..b800dce 100644 --- a/statement/case_simple.go +++ b/statement/case_simple.go @@ -19,18 +19,14 @@ func NewSimpleCase(param ValOrColOrFuncOrSub) *SimpleCase { func (c *SimpleCase) When(param Param) *SimpleWhen { w := NewSimpleWhen(param) - Link(c, w) + Contract(c, w) return w } -func (c *SimpleCase) separator() token.Tokens { - return nil -} - func (c *SimpleCase) nodeize() (tokenizer.Tokenizer, []interface{}) { t, vals := c.param.nodeize() return tokenizer.NewContainer( - t.Prepend(token.Word(keyword.Case), token.Space), + t.Prepend(token.Word(keyword.Case)), ).SetLast( tokenizer.NewLine(token.Word(keyword.End)), ), vals @@ -41,7 +37,7 @@ type SimpleWhen struct { Prev Next param Param - paren Nodeizer + paren Statement } func NewSimpleWhen(param Param) *SimpleWhen { @@ -101,7 +97,7 @@ func (t SimpleThen) nodeize() (tokenizer.Tokenizer, []interface{}) { type Else struct { Next param ValOrColOrFuncOrSub - pre Nodeizer + pre Statement } func NewElse(param ValOrColOrFuncOrSub) *Else { diff --git a/statement/column.go b/statement/column.go index 67c93f6..779053b 100644 --- a/statement/column.go +++ b/statement/column.go @@ -28,7 +28,7 @@ func (c Column) As(alias string) ColumnAs { // Define is used to specify a definition for the column. // This constitutes a part of the table creation. // Returns a new Definition. -func (c Column) Define(definition string) Definition { +func (c Column) Define(definition string) *Definition { d := NewDefinition(definition) d.column = c return d @@ -119,73 +119,73 @@ func (c Column) IsNotNull() NullOperation { return i } -func (c Column) EqAll(params Subquery) NonScalarOperation { +func (c Column) EqAll(params *Subquery) NonScalarOperation { n := NewEqAll(params) n.column = c return n } -func (c Column) NotEqAll(params Subquery) NonScalarOperation { +func (c Column) NotEqAll(params *Subquery) NonScalarOperation { n := NewNotEqAll(params) n.column = c return n } -func (c Column) GtAll(params Subquery) NonScalarOperation { +func (c Column) GtAll(params *Subquery) NonScalarOperation { n := NewGtAll(params) n.column = c return n } -func (c Column) GteAll(params Subquery) NonScalarOperation { +func (c Column) GteAll(params *Subquery) NonScalarOperation { n := NewGteAll(params) n.column = c return n } -func (c Column) LtAll(params Subquery) NonScalarOperation { +func (c Column) LtAll(params *Subquery) NonScalarOperation { n := NewLtAll(params) n.column = c return n } -func (c Column) LteAll(params Subquery) NonScalarOperation { +func (c Column) LteAll(params *Subquery) NonScalarOperation { n := NewLteAll(params) n.column = c return n } -func (c Column) EqAny(params Subquery) NonScalarOperation { +func (c Column) EqAny(params *Subquery) NonScalarOperation { n := NewEqAny(params) n.column = c return n } -func (c Column) NotEqAny(params Subquery) NonScalarOperation { +func (c Column) NotEqAny(params *Subquery) NonScalarOperation { n := NewNotEqAny(params) n.column = c return n } -func (c Column) GtAny(params Subquery) NonScalarOperation { +func (c Column) GtAny(params *Subquery) NonScalarOperation { n := NewGtAny(params) n.column = c return n } -func (c Column) GteAny(params Subquery) NonScalarOperation { +func (c Column) GteAny(params *Subquery) NonScalarOperation { n := NewGteAny(params) n.column = c return n } -func (c Column) LtAny(params Subquery) NonScalarOperation { +func (c Column) LtAny(params *Subquery) NonScalarOperation { n := NewLtAny(params) n.column = c return n } -func (c Column) LteAny(params Subquery) NonScalarOperation { +func (c Column) LteAny(params *Subquery) NonScalarOperation { n := NewLteAny(params) n.column = c return n diff --git a/statement/column_as.go b/statement/column_as.go index dca229b..8172acf 100644 --- a/statement/column_as.go +++ b/statement/column_as.go @@ -21,11 +21,11 @@ func (c ColumnAs) nodeize() (tokenizer.Tokenizer, []interface{}) { if c.column == nil { return tokenizer.NewLine( token.Word(keyword.As), - token.Space, ).Append( - token.Wrap( + token.NewTokens( + token.QuoteStart, token.Word(c.alias), - token.Quote, + token.QuoteEnd, )..., ), nil } @@ -34,15 +34,12 @@ func (c ColumnAs) nodeize() (tokenizer.Tokenizer, []interface{}) { return tokenizer.ConcatTokenizers( t1, tokenizer.NewLine( - token.Wrap( - token.Word(c.alias), - token.Quote, - )..., + token.QuoteStart, + token.Word(c.alias), + token.QuoteEnd, ), tokenizer.NewLine( - token.Space, token.Word(keyword.As), - token.Space, ), ), v1 } diff --git a/statement/create_table.go b/statement/create_table.go index fe5a351..249f0f2 100644 --- a/statement/create_table.go +++ b/statement/create_table.go @@ -7,47 +7,41 @@ import ( ) type CreateTable struct { + Parent ifNotExists bool - joiner Joiner + table *Table } -func NewCreateTable(joiner Joiner) CreateTable { - return CreateTable{ +func NewCreateTable(table *Table) *CreateTable { + return &CreateTable{ ifNotExists: false, - joiner: joiner, + table: table, } } -func NewCreateTableIfNotExists(joiner Joiner) CreateTable { - return CreateTable{ +func NewCreateTableIfNotExists(table *Table) *CreateTable { + return &CreateTable{ ifNotExists: true, - joiner: joiner, + table: table, } } -func (c CreateTable) nodeize() (tokenizer.Tokenizer, []interface{}) { - return c.container() +func (c *CreateTable) Definitions(defs ...*Definition) *Definitions { + ds := NewDefinitions(defs...) + Contract(c, ds) + return ds } -func (c CreateTable) container() (tokenizer.Container, []interface{}) { - line := tokenizer.NewLine(token.Word(keyword.CreateTable)) +func (c *CreateTable) nodeize() (tokenizer.Tokenizer, []interface{}) { + tokens := token.Tokens{ + token.Word(keyword.CreateTable), + } if c.ifNotExists { - line = line.A( - token.Space, + tokens = tokens.Append( token.Word(keyword.IfNotExists), ) } - middle, values := c.joiner.nodeize() - return tokenizer.NewContainer( - line, - ).SetMiddle( - middle, - ), values -} - -func (c CreateTable) Definitions(defs ...Definition) Definitions { - ds := NewDefinitions(defs...) - ds.createTable = c - return ds + t, values := c.table.nodeize() + return tokenizer.NewContainer(t.Prepend(tokens...)), values } diff --git a/statement/create_table_test.go b/statement/create_table_test.go index 09259ff..881d039 100644 --- a/statement/create_table_test.go +++ b/statement/create_table_test.go @@ -24,8 +24,7 @@ func TestCreateTableSQL(t *testing.T) { statement.NewTable("foo"), ), "CREATE TABLE foo", - `> CREATE TABLE -> foo + `> CREATE TABLE foo `, nil, }, @@ -34,8 +33,7 @@ func TestCreateTableSQL(t *testing.T) { statement.NewTable("foo"), ), "CREATE TABLE IF NOT EXISTS foo", - `> CREATE TABLE IF NOT EXISTS -> foo + `> CREATE TABLE IF NOT EXISTS foo `, nil, }, @@ -44,8 +42,8 @@ func TestCreateTableSQL(t *testing.T) { statement.NewTable("foo"), ).Definitions(), "CREATE TABLE foo ()", - `> CREATE TABLE -> foo ( + `> CREATE TABLE foo +> ( > ) `, nil, @@ -57,8 +55,8 @@ func TestCreateTableSQL(t *testing.T) { statement.NewColumn("name").Define("VARCHAR(255)"), ), "CREATE TABLE foo (name VARCHAR(255))", - `> CREATE TABLE -> foo ( + `> CREATE TABLE foo +> ( > name VARCHAR(255) > ) `, @@ -72,8 +70,8 @@ func TestCreateTableSQL(t *testing.T) { statement.NewColumn("gender").Define("ENUM('M', 'F')"), ), "CREATE TABLE foo (name VARCHAR(255), gender ENUM('M', 'F'))", - `> CREATE TABLE -> foo ( + `> CREATE TABLE foo +> ( > name VARCHAR(255) > , gender ENUM('M', 'F') > ) @@ -89,8 +87,8 @@ func TestCreateTableSQL(t *testing.T) { statement.NewColumn("birth_date").Define("DATE"), ), "CREATE TABLE foo (name VARCHAR(255), gender ENUM('M', 'F'), birth_date DATE)", - `> CREATE TABLE -> foo ( + `> CREATE TABLE foo +> ( > name VARCHAR(255) > , gender ENUM('M', 'F') > , birth_date DATE diff --git a/statement/definition.go b/statement/definition.go index e1da4c5..5367e33 100644 --- a/statement/definition.go +++ b/statement/definition.go @@ -6,12 +6,13 @@ import ( ) type Definition struct { + Child column Column definition string } -func NewDefinition(definition string) Definition { - return Definition{ +func NewDefinition(definition string) *Definition { + return &Definition{ definition: definition, } } @@ -20,44 +21,29 @@ func (d Definition) nodeize() (tokenizer.Tokenizer, []interface{}) { t, values := d.column.nodeize() return t. Append( - token.Space, token.Word(d.definition), ), values } type Definitions struct { - createTable CreateTable - definitions []Definition + Child + Parent } -func NewDefinitions(definitions ...Definition) Definitions { - return Definitions{ - definitions: definitions, +func NewDefinitions(defs ...*Definition) *Definitions { + ds := &Definitions{} + for _, d := range defs { + Contract(ds, d) } + return ds } -func (ds Definitions) nodeize() (tokenizer.Tokenizer, []interface{}) { - ts := make(tokenizer.Tokenizers, len(ds.definitions)) - values := []interface{}{} - for i, d := range ds.definitions { - var vals []interface{} - ts[i], vals = d.nodeize() - values = append(values, vals...) - } - ts = ts.Prefix( +func (d Definitions) nodeize() (tokenizer.Tokenizer, []interface{}) { + return tokenizer.NewParentheses(nil), nil +} + +func (d Definitions) separator() token.Tokens { + return token.NewTokens( token.Comma, - token.Space, ) - - c, values := ds.createTable.container() - middle := c.Middle() - def := tokenizer.NewParentheses(ts) - - return c.SetMiddle( - tokenizer.ConcatTokenizers( - middle, - def, - tokenizer.NewLine(token.Space), - ), - ), values } diff --git a/statement/delete.go b/statement/delete.go index 6146a2b..eaf8270 100644 --- a/statement/delete.go +++ b/statement/delete.go @@ -6,28 +6,22 @@ import ( "github.com/minodisk/sqlabble/tokenizer" ) -type Delete struct{} +type Delete struct { + Prev +} -func NewDelete() Delete { - return Delete{} +func NewDelete() *Delete { + return &Delete{} } -func (d Delete) From(t Table) From { +func (d *Delete) From(t *Table) *From { f := NewFrom(t) - f.prev = d + Link(d, f) return f } -func (d Delete) nodeize() (tokenizer.Tokenizer, []interface{}) { - return nodeizeClauses(d) -} - -func (d Delete) self() (tokenizer.Tokenizer, []interface{}) { +func (d *Delete) nodeize() (tokenizer.Tokenizer, []interface{}) { return tokenizer.NewContainer( tokenizer.NewLine(token.Word(keyword.Delete)), ), nil } - -func (d Delete) previous() Clause { - return nil -} diff --git a/statement/from.go b/statement/from.go index 8d84cba..113377d 100644 --- a/statement/from.go +++ b/statement/from.go @@ -7,53 +7,46 @@ import ( ) type From struct { - prev Clause + Prev + Next table TableOrAliasOrJoiner } -func NewFrom(table TableOrAliasOrJoiner) From { - return From{ +func NewFrom(table TableOrAliasOrJoiner) *From { + return &From{ table: table, } } -func (f From) Where(op ComparisonOrLogicalOperation) Where { +func (f *From) Where(op ComparisonOrLogicalOperation) *Where { w := NewWhere(op) - w.prev = f + Link(f, w) return w } -func (f From) GroupBy(col Column, columns ...Column) GroupBy { +func (f *From) GroupBy(col Column, columns ...Column) *GroupBy { g := NewGroupBy(col, columns...) - g.prev = f + Link(f, g) return g } -func (f From) OrderBy(orders ...Order) OrderBy { +func (f *From) OrderBy(orders ...Order) *OrderBy { o := NewOrderBy(orders...) - o.prev = f + Link(f, o) return o } -func (f From) Limit(count int) Limit { +func (f *From) Limit(count int) *Limit { l := NewLimit(count) - l.prev = f + Link(f, l) return l } -func (f From) nodeize() (tokenizer.Tokenizer, []interface{}) { - return nodeizeClauses(f) -} - -func (f From) self() (tokenizer.Tokenizer, []interface{}) { - middle, values := f.table.nodeize() +func (f *From) nodeize() (tokenizer.Tokenizer, []interface{}) { + middle, values := Nodize(f.table) return tokenizer.NewContainer( tokenizer.NewLine(token.Word(keyword.From)), ).SetMiddle( middle, ), values } - -func (f From) previous() Clause { - return f.prev -} diff --git a/statement/from_test.go b/statement/from_test.go index 76bee04..350e851 100644 --- a/statement/from_test.go +++ b/statement/from_test.go @@ -9,13 +9,6 @@ import ( "github.com/minodisk/sqlabble/statement" ) -func TestFromType(t *testing.T) { - f := statement.From{} - if _, ok := interface{}(f).(statement.Clause); !ok { - t.Errorf("%T should implement statement.Clause", f) - } -} - func TestFromSQL(t *testing.T) { for i, c := range []struct { statement statement.Statement @@ -51,7 +44,21 @@ func TestFromSQL(t *testing.T) { `FROM foo AS "a" JOIN bar USING id`, `> FROM > foo AS "a" -> JOIN bar USING id +> JOIN bar +> USING id +`, + nil, + }, + { + statement.NewFrom( + statement.NewTable("foo").As("a"). + Join(statement.NewTable("bar")).On(statement.NewColumn("a.id"), statement.NewColumn("bar.id")), + ), + `FROM foo AS "a" JOIN bar ON a.id = bar.id`, + `> FROM +> foo AS "a" +> JOIN bar +> ON a.id = bar.id `, nil, }, diff --git a/statement/func.go b/statement/func.go index cc16a39..fd26293 100644 --- a/statement/func.go +++ b/statement/func.go @@ -10,7 +10,7 @@ type Args []Statement func (ps Args) nodeize() (tokenizer.Tokenizer, []interface{}) { if len(ps) == 0 { - return tokenizer.NewLine(token.ParenthesesStart, token.ParenthesesEnd), nil + return tokenizer.NewLine(token.FuncParenStart, token.FuncParenEnd), nil } var tk tokenizer.Tokenizer @@ -22,10 +22,10 @@ func (ps Args) nodeize() (tokenizer.Tokenizer, []interface{}) { values = append(values, vals...) continue } - tk = tokenizer.ConcatTokenizers(tk, t, tokenizer.NewLine(token.Comma, token.Space)) + tk = tokenizer.ConcatTokenizers(tk, t, tokenizer.NewLine(token.Comma)) values = append(values, vals...) } - return tk.Prepend(token.ParenthesesStart).Append(token.ParenthesesEnd), values + return tk.Prepend(token.FuncParenStart).Append(token.FuncParenEnd), values } type ValOrColOrFuncOrSubs []ValOrColOrFuncOrSub @@ -109,73 +109,73 @@ func (c Func) NotIn(params ValsOrSub) ContainingOperation { return n } -func (c Func) EqAll(params Subquery) NonScalarOperation { +func (c Func) EqAll(params *Subquery) NonScalarOperation { n := NewEqAll(params) n.column = c return n } -func (c Func) NotEqAll(params Subquery) NonScalarOperation { +func (c Func) NotEqAll(params *Subquery) NonScalarOperation { n := NewNotEqAll(params) n.column = c return n } -func (c Func) GtAll(params Subquery) NonScalarOperation { +func (c Func) GtAll(params *Subquery) NonScalarOperation { n := NewGtAll(params) n.column = c return n } -func (c Func) GteAll(params Subquery) NonScalarOperation { +func (c Func) GteAll(params *Subquery) NonScalarOperation { n := NewGteAll(params) n.column = c return n } -func (c Func) LtAll(params Subquery) NonScalarOperation { +func (c Func) LtAll(params *Subquery) NonScalarOperation { n := NewLtAll(params) n.column = c return n } -func (c Func) LteAll(params Subquery) NonScalarOperation { +func (c Func) LteAll(params *Subquery) NonScalarOperation { n := NewLteAll(params) n.column = c return n } -func (c Func) EqAny(params Subquery) NonScalarOperation { +func (c Func) EqAny(params *Subquery) NonScalarOperation { n := NewEqAny(params) n.column = c return n } -func (c Func) NotEqAny(params Subquery) NonScalarOperation { +func (c Func) NotEqAny(params *Subquery) NonScalarOperation { n := NewNotEqAny(params) n.column = c return n } -func (c Func) GtAny(params Subquery) NonScalarOperation { +func (c Func) GtAny(params *Subquery) NonScalarOperation { n := NewGtAny(params) n.column = c return n } -func (c Func) GteAny(params Subquery) NonScalarOperation { +func (c Func) GteAny(params *Subquery) NonScalarOperation { n := NewGteAny(params) n.column = c return n } -func (c Func) LtAny(params Subquery) NonScalarOperation { +func (c Func) LtAny(params *Subquery) NonScalarOperation { n := NewLtAny(params) n.column = c return n } -func (c Func) LteAny(params Subquery) NonScalarOperation { +func (c Func) LteAny(params *Subquery) NonScalarOperation { n := NewLteAny(params) n.column = c return n diff --git a/statement/func_date.go b/statement/func_date.go index 8177c2b..ca9c59b 100644 --- a/statement/func_date.go +++ b/statement/func_date.go @@ -235,7 +235,6 @@ func (i Interval) nodeize() (tokenizer.Tokenizer, []interface{}) { t, values := i.duration.nodeize() return t.Prepend( token.Word("INTERVAL"), - token.Space, ), values } @@ -247,7 +246,6 @@ type IntervalUnit struct { func (i IntervalUnit) nodeize() (tokenizer.Tokenizer, []interface{}) { t, values := i.interval.nodeize() return t.Append( - token.Space, token.Word(i.unit), ), values } diff --git a/statement/group_by.go b/statement/group_by.go index b6a6cd5..fff142a 100644 --- a/statement/group_by.go +++ b/statement/group_by.go @@ -7,21 +7,37 @@ import ( ) type GroupBy struct { - prev Clause + Prev + Next columns []Column } -func NewGroupBy(column Column, columns ...Column) GroupBy { - return GroupBy{ - columns: append([]Column{column}, columns...), +func NewGroupBy(column Column, columns ...Column) *GroupBy { + cols := append([]Column{column}, columns...) + return &GroupBy{ + columns: cols, } } -func (g GroupBy) nodeize() (tokenizer.Tokenizer, []interface{}) { - return nodeizeClauses(g) +func (g *GroupBy) Having(operation ComparisonOrLogicalOperation) *Having { + l := NewHaving(operation) + Link(g, l) + return l +} + +func (g *GroupBy) OrderBy(orders ...Order) *OrderBy { + o := NewOrderBy(orders...) + Link(g, o) + return o } -func (g GroupBy) self() (tokenizer.Tokenizer, []interface{}) { +func (g *GroupBy) Limit(count int) *Limit { + l := NewLimit(count) + Link(g, l) + return l +} + +func (g *GroupBy) nodeize() (tokenizer.Tokenizer, []interface{}) { ts := make(tokenizer.Tokenizers, len(g.columns)) values := []interface{}{} for i, c := range g.columns { @@ -34,29 +50,6 @@ func (g GroupBy) self() (tokenizer.Tokenizer, []interface{}) { ).SetMiddle( ts.Prefix( token.Comma, - token.Space, ), ), values } - -func (g GroupBy) previous() Clause { - return g.prev -} - -func (g GroupBy) Having(operation ComparisonOrLogicalOperation) Having { - l := NewHaving(operation) - l.prev = g - return l -} - -func (g GroupBy) OrderBy(orders ...Order) OrderBy { - o := NewOrderBy(orders...) - o.prev = g - return o -} - -func (g GroupBy) Limit(count int) Limit { - l := NewLimit(count) - l.prev = g - return l -} diff --git a/statement/group_by_test.go b/statement/group_by_test.go index e76973f..37994cd 100644 --- a/statement/group_by_test.go +++ b/statement/group_by_test.go @@ -9,13 +9,6 @@ import ( "github.com/minodisk/sqlabble/statement" ) -func TestGroupByType(t *testing.T) { - g := statement.GroupBy{} - if _, ok := interface{}(g).(statement.Clause); !ok { - t.Errorf("%T doesn't implement statement.Clause", g) - } -} - func TestGroupBySQL(t *testing.T) { for i, c := range []struct { statement statement.Statement diff --git a/statement/having.go b/statement/having.go index 0eeb04c..e8b81c6 100644 --- a/statement/having.go +++ b/statement/having.go @@ -7,21 +7,30 @@ import ( ) type Having struct { - prev Clause + Prev + Next operation ComparisonOrLogicalOperation } -func NewHaving(operation ComparisonOrLogicalOperation) Having { - return Having{ +func NewHaving(operation ComparisonOrLogicalOperation) *Having { + return &Having{ operation: operation, } } -func (h Having) nodeize() (tokenizer.Tokenizer, []interface{}) { - return nodeizeClauses(h) +func (h *Having) OrderBy(orders ...Order) *OrderBy { + o := NewOrderBy(orders...) + Link(h, o) + return o +} + +func (h *Having) Limit(count int) *Limit { + l := NewLimit(count) + Link(h, l) + return l } -func (h Having) self() (tokenizer.Tokenizer, []interface{}) { +func (h *Having) nodeize() (tokenizer.Tokenizer, []interface{}) { middle, values := h.operation.nodeize() return tokenizer.NewContainer( tokenizer.NewLine(token.Word(keyword.Having)), @@ -29,19 +38,3 @@ func (h Having) self() (tokenizer.Tokenizer, []interface{}) { middle, ), values } - -func (h Having) previous() Clause { - return h.prev -} - -func (h Having) OrderBy(orders ...Order) OrderBy { - o := NewOrderBy(orders...) - o.prev = h - return o -} - -func (h Having) Limit(count int) Limit { - l := NewLimit(count) - l.prev = h - return l -} diff --git a/statement/having_test.go b/statement/having_test.go index fe187ea..a3e1a6b 100644 --- a/statement/having_test.go +++ b/statement/having_test.go @@ -9,13 +9,6 @@ import ( "github.com/minodisk/sqlabble/statement" ) -func TestHavingType(t *testing.T) { - h := statement.Having{} - if _, ok := interface{}(h).(statement.Clause); !ok { - t.Errorf("%T should implement statement.Clause", h) - } -} - func TestHavingSQL(t *testing.T) { for i, c := range []struct { statement statement.Statement diff --git a/statement/helpers.go b/statement/helpers.go index 5d390bf..75d4f68 100644 --- a/statement/helpers.go +++ b/statement/helpers.go @@ -1,49 +1,5 @@ package statement -import "github.com/minodisk/sqlabble/tokenizer" - -func Nodeize(stmt Statement) (tokenizer.Tokenizer, []interface{}) { - return stmt.nodeize() -} - -func nodeizeClauses(c Clause) (tokenizer.Tokenizer, []interface{}) { - clauses := collectClauses(c) - ts := make(tokenizer.Tokenizers, len(clauses)) - values := []interface{}{} - for i, c := range clauses { - var vals []interface{} - ts[i], vals = c.self() - values = append(values, vals...) - } - return ts, values -} - -func collectClauses(c Clause) []Clause { - cs := []Clause{} - for c != nil { - cs = append([]Clause{c}, cs...) - c = c.previous() - } - return cs -} - -func nodeizeJoiners(j Joiner) (tokenizer.Tokenizer, []interface{}) { - joiners := collectJoiners(j) - ts := make(tokenizer.Tokenizers, len(joiners)) - values := []interface{}{} - for i, j := range joiners { - var vals []interface{} - ts[i], vals = j.self() - values = append(values, vals...) - } - return ts, values -} - -func collectJoiners(c Joiner) []Joiner { - cs := []Joiner{} - for c != nil { - cs = append([]Joiner{c}, cs...) - c = c.previous() - } - return cs -} +// func Nodeize(stmt Statement) (tokenizer.Tokenizer, []interface{}) { +// return stmt.nodeize() +// } diff --git a/statement/insert.go b/statement/insert.go index b2f9d1e..122566d 100644 --- a/statement/insert.go +++ b/statement/insert.go @@ -7,22 +7,31 @@ import ( ) type InsertInto struct { - table Table + Prev + table *Table columns []Column } -func NewInsertInto(table Table, columns ...Column) InsertInto { - return InsertInto{ +func NewInsertInto(table *Table, columns ...Column) *InsertInto { + return &InsertInto{ table: table, columns: columns, } } -func (i InsertInto) nodeize() (tokenizer.Tokenizer, []interface{}) { - return nodeizeClauses(i) +func (i *InsertInto) Values(paramses ...Params) *Values { + v := NewValues(paramses...) + Link(i, v) + return v +} + +func (i *InsertInto) DefaultValues() *DefaultValues { + v := NewDefaultValues() + Link(i, v) + return v } -func (i InsertInto) self() (tokenizer.Tokenizer, []interface{}) { +func (i *InsertInto) nodeize() (tokenizer.Tokenizer, []interface{}) { tableTokenizer, values := i.table.nodeize() ts := make(tokenizer.Tokenizers, len(i.columns)) for j, c := range i.columns { @@ -38,26 +47,9 @@ func (i InsertInto) self() (tokenizer.Tokenizer, []interface{}) { tokenizer.NewParentheses( ts.Prefix( token.Comma, - token.Space, ), ), - tokenizer.NewLine(token.Space), + tokenizer.EmptyLine, ), ), values } - -func (i InsertInto) previous() Clause { - return nil -} - -func (i InsertInto) Values(paramses ...Params) Values { - v := NewValues(paramses...) - v.prev = i - return v -} - -func (i InsertInto) DefaultValues() DefaultValues { - v := NewDefaultValues() - v.prev = i - return v -} diff --git a/statement/interfaces.go b/statement/interfaces.go index 8dc1192..ad1c910 100644 --- a/statement/interfaces.go +++ b/statement/interfaces.go @@ -13,20 +13,12 @@ type Statement interface { nodeize() (tokenizer.Tokenizer, []interface{}) } -type Clause interface { - Statement - self() (tokenizer.Tokenizer, []interface{}) - previous() Clause -} - type Joiner interface { Statement - Join(TableOrAlias) Join - InnerJoin(TableOrAlias) Join - LeftJoin(TableOrAlias) Join - RightJoin(TableOrAlias) Join - self() (tokenizer.Tokenizer, []interface{}) - previous() Joiner + Join(TableOrAlias) *Join + InnerJoin(TableOrAlias) *Join + LeftJoin(TableOrAlias) *Join + RightJoin(TableOrAlias) *Join } type ValOrFuncOrSub interface { diff --git a/statement/join.go b/statement/join.go index 46e684d..e57477a 100644 --- a/statement/join.go +++ b/statement/join.go @@ -7,93 +7,82 @@ import ( ) type Join struct { + Prev + Next joinType string table TableOrAlias - prev Joiner } -func NewJoin(table TableOrAlias) Join { - return Join{ +func NewJoin(table TableOrAlias) *Join { + return &Join{ joinType: keyword.Join, table: table, } } -func NewInnerJoin(table TableOrAlias) Join { - return Join{ +func NewInnerJoin(table TableOrAlias) *Join { + return &Join{ joinType: keyword.InnerJoin, table: table, } } -func NewLeftJoin(table TableOrAlias) Join { - return Join{ +func NewLeftJoin(table TableOrAlias) *Join { + return &Join{ joinType: keyword.LeftJoin, table: table, } } -func NewRightJoin(table TableOrAlias) Join { - return Join{ +func NewRightJoin(table TableOrAlias) *Join { + return &Join{ joinType: keyword.RightJoin, table: table, } } -func (j Join) Join(table TableOrAlias) Join { - j1 := NewJoin(table) - j1.prev = j - return j1 +func (j *Join) Join(table TableOrAlias) *Join { + nj := NewJoin(table) + Link(j, nj) + return nj } -func (j Join) InnerJoin(table TableOrAlias) Join { - j1 := NewInnerJoin(table) - j1.prev = j - return j1 +func (j *Join) InnerJoin(table TableOrAlias) *Join { + nj := NewInnerJoin(table) + Link(j, nj) + return nj } -func (j Join) LeftJoin(table TableOrAlias) Join { - j1 := NewLeftJoin(table) - j1.prev = j - return j1 +func (j *Join) LeftJoin(table TableOrAlias) *Join { + nj := NewLeftJoin(table) + Link(j, nj) + return nj } -func (j Join) RightJoin(table TableOrAlias) Join { - j1 := NewRightJoin(table) - j1.prev = j - return j1 +func (j *Join) RightJoin(table TableOrAlias) *Join { + nj := NewRightJoin(table) + Link(j, nj) + return nj } -func (j Join) On(column1, column2 Column) On { +func (j *Join) On(column1, column2 Column) *On { o := NewOn(column1, column2) - o.join = j + Link(j, o) return o } -func (j Join) Using(col Column) Using { - o := NewUsing(col) - o.join = j - return o -} - -func (j Join) nodeize() (tokenizer.Tokenizer, []interface{}) { - return nodeizeJoiners(j) +func (j *Join) Using(col Column) *Using { + u := NewUsing(col) + Link(j, u) + return u } -func (j Join) self() (tokenizer.Tokenizer, []interface{}) { +func (j *Join) nodeize() (tokenizer.Tokenizer, []interface{}) { if j.table == nil { return nil, nil } t, v := j.table.nodeize() return t.Prepend( token.Word(j.joinType), - token.Space, ), v } - -func (j Join) previous() Joiner { - if j.prev == nil { - return nil - } - return j.prev -} diff --git a/statement/join_test.go b/statement/join_test.go index 4d55def..5d3a7b2 100644 --- a/statement/join_test.go +++ b/statement/join_test.go @@ -11,7 +11,7 @@ import ( func TestJoinType(t *testing.T) { for _, c := range []interface{}{ - statement.Join{}, + &statement.Join{}, } { t.Run(fmt.Sprintf("%T", c), func(t *testing.T) { if _, ok := c.(statement.Joiner); !ok { @@ -122,7 +122,8 @@ func TestJoin(t *testing.T) { statement.NewColumn("bar.id"), ), `JOIN foo ON foo.id = bar.id`, - `> JOIN foo ON foo.id = bar.id + `> JOIN foo +> ON foo.id = bar.id `, nil, }, @@ -134,7 +135,8 @@ func TestJoin(t *testing.T) { statement.NewColumn("b.id"), ), `JOIN foo AS "f" ON f.id = b.id`, - `> JOIN foo AS "f" ON f.id = b.id + `> JOIN foo AS "f" +> ON f.id = b.id `, nil, }, @@ -145,7 +147,8 @@ func TestJoin(t *testing.T) { statement.NewColumn("id"), ), `JOIN foo USING id`, - `> JOIN foo USING id + `> JOIN foo +> USING id `, nil, }, @@ -156,7 +159,8 @@ func TestJoin(t *testing.T) { statement.NewColumn("id"), ), `JOIN foo AS "f" USING id`, - `> JOIN foo AS "f" USING id + `> JOIN foo AS "f" +> USING id `, nil, }, @@ -237,7 +241,8 @@ func TestInnerJoin(t *testing.T) { statement.NewColumn("bar.id"), ), `INNER JOIN foo ON foo.id = bar.id`, - `> INNER JOIN foo ON foo.id = bar.id + `> INNER JOIN foo +> ON foo.id = bar.id `, nil, }, @@ -250,7 +255,8 @@ func TestInnerJoin(t *testing.T) { statement.NewColumn("b.id"), ), `INNER JOIN foo AS "f" ON f.id = b.id`, - `> INNER JOIN foo AS "f" ON f.id = b.id + `> INNER JOIN foo AS "f" +> ON f.id = b.id `, nil, }, @@ -262,7 +268,8 @@ func TestInnerJoin(t *testing.T) { statement.NewColumn("id"), ), `INNER JOIN foo USING id`, - `> INNER JOIN foo USING id + `> INNER JOIN foo +> USING id `, nil, }, @@ -274,7 +281,8 @@ func TestInnerJoin(t *testing.T) { statement.NewColumn("id"), ), `INNER JOIN foo AS "f" USING id`, - `> INNER JOIN foo AS "f" USING id + `> INNER JOIN foo AS "f" +> USING id `, nil, }, @@ -353,7 +361,8 @@ func TestLeftJoin(t *testing.T) { statement.NewColumn("bar.id"), ), `LEFT JOIN foo ON foo.id = bar.id`, - `> LEFT JOIN foo ON foo.id = bar.id + `> LEFT JOIN foo +> ON foo.id = bar.id `, nil, }, @@ -365,7 +374,8 @@ func TestLeftJoin(t *testing.T) { statement.NewColumn("b.id"), ), `LEFT JOIN foo AS "f" ON f.id = b.id`, - `> LEFT JOIN foo AS "f" ON f.id = b.id + `> LEFT JOIN foo AS "f" +> ON f.id = b.id `, nil, }, @@ -376,7 +386,8 @@ func TestLeftJoin(t *testing.T) { statement.NewColumn("id"), ), `LEFT JOIN foo USING id`, - `> LEFT JOIN foo USING id + `> LEFT JOIN foo +> USING id `, nil, }, @@ -387,7 +398,8 @@ func TestLeftJoin(t *testing.T) { statement.NewColumn("id"), ), `LEFT JOIN foo AS "f" USING id`, - `> LEFT JOIN foo AS "f" USING id + `> LEFT JOIN foo AS "f" +> USING id `, nil, }, @@ -466,7 +478,8 @@ func TestRightJoin(t *testing.T) { statement.NewColumn("bar.id"), ), `RIGHT JOIN foo ON foo.id = bar.id`, - `> RIGHT JOIN foo ON foo.id = bar.id + `> RIGHT JOIN foo +> ON foo.id = bar.id `, nil, }, @@ -478,7 +491,8 @@ func TestRightJoin(t *testing.T) { statement.NewColumn("b.id"), ), `RIGHT JOIN foo AS "f" ON f.id = b.id`, - `> RIGHT JOIN foo AS "f" ON f.id = b.id + `> RIGHT JOIN foo AS "f" +> ON f.id = b.id `, nil, }, @@ -489,7 +503,8 @@ func TestRightJoin(t *testing.T) { statement.NewColumn("id"), ), `RIGHT JOIN foo USING id`, - `> RIGHT JOIN foo USING id + `> RIGHT JOIN foo +> USING id `, nil, }, @@ -500,7 +515,8 @@ func TestRightJoin(t *testing.T) { statement.NewColumn("id"), ), `RIGHT JOIN foo AS "f" USING id`, - `> RIGHT JOIN foo AS "f" USING id + `> RIGHT JOIN foo AS "f" +> USING id `, nil, }, diff --git a/statement/limit.go b/statement/limit.go index d376363..1c684d5 100644 --- a/statement/limit.go +++ b/statement/limit.go @@ -7,27 +7,24 @@ import ( ) type Limit struct { - prev Clause + Next + Prev count int } -func NewLimit(count int) Limit { - return Limit{ +func NewLimit(count int) *Limit { + return &Limit{ count: count, } } -func (l Limit) Offset(count int) Offset { +func (l *Limit) Offset(count int) *Offset { o := NewOffset(count) - o.prev = l + Link(l, o) return o } -func (l Limit) nodeize() (tokenizer.Tokenizer, []interface{}) { - return nodeizeClauses(l) -} - -func (l Limit) self() (tokenizer.Tokenizer, []interface{}) { +func (l *Limit) nodeize() (tokenizer.Tokenizer, []interface{}) { line, values := tokenizer.ParamsToLine(l.count) return tokenizer.NewContainer( tokenizer.NewLine(token.Word(keyword.Limit)), @@ -35,7 +32,3 @@ func (l Limit) self() (tokenizer.Tokenizer, []interface{}) { line, ), values } - -func (l Limit) previous() Clause { - return l.prev -} diff --git a/statement/limit_test.go b/statement/limit_test.go index 37d6c19..5e47c6c 100644 --- a/statement/limit_test.go +++ b/statement/limit_test.go @@ -9,13 +9,6 @@ import ( "github.com/minodisk/sqlabble/statement" ) -func TestLimitType(t *testing.T) { - l := statement.Limit{} - if _, ok := interface{}(l).(statement.Clause); !ok { - t.Errorf("%T should implement statement.Clause", l) - } -} - func TestLimitSQL(t *testing.T) { for i, c := range []struct { statement statement.Statement diff --git a/statement/nodeizer.go b/statement/nodeizer.go new file mode 100644 index 0000000..896b598 --- /dev/null +++ b/statement/nodeizer.go @@ -0,0 +1,81 @@ +package statement + +import "github.com/minodisk/sqlabble/tokenizer" + +func Nodize(stmt Statement) (tokenizer.Tokenizers, []interface{}) { + var tokenizers tokenizer.Tokenizers + values := []interface{}{} + + ns := []Statement{} + + { + p := stmt + for { + prever, ok := p.(Prever) + if !ok { + break + } + p = prever.prev() + if p == nil { + break + } + ns = append([]Statement{p}, ns...) + } + } + + if c, ok := stmt.(Lister); ok { + ns = append(ns, c.list()...) + } else { + ns = append(ns, stmt) + } + + { + p := stmt + for { + nexter, ok := p.(Nexter) + if !ok { + break + } + p = nexter.next() + if p == nil { + break + } + ns = append(ns, p) + } + } + + for _, n := range ns { + if childer, ok := n.(Childer); ok { + t1, vals1 := n.nodeize() + values = append(values, vals1...) + first, _ := t1.FirstLine() + _, last := t1.LastLine() + + children := childer.children() + ts := make(tokenizer.Tokenizers, len(children)) + for i, child := range children { + var vals []interface{} + ts[i], vals = Nodize(child) + values = append(values, vals...) + } + + if separatorer, ok := childer.(Separatorer); ok { + seps := separatorer.separator() + ts = ts.Prefix(seps...) + } + + t12 := tokenizer. + NewContainer(first). + SetMiddle(ts). + SetLast(last) + tokenizers = append(tokenizers, t12) + continue + } + + t1, values1 := n.nodeize() + tokenizers = append(tokenizers, t1) + values = append(values, values1...) + } + + return tokenizers, values +} diff --git a/statement/offset.go b/statement/offset.go index 8901142..4996bc9 100644 --- a/statement/offset.go +++ b/statement/offset.go @@ -8,22 +8,18 @@ import ( // Offset skips specified rows before beginning to return rows. type Offset struct { - prev Clause + Next count int } // NewOffset return a new Offset. -func NewOffset(count int) Offset { - return Offset{ +func NewOffset(count int) *Offset { + return &Offset{ count: count, } } func (o Offset) nodeize() (tokenizer.Tokenizer, []interface{}) { - return nodeizeClauses(o) -} - -func (o Offset) self() (tokenizer.Tokenizer, []interface{}) { line, values := tokenizer.ParamsToLine(o.count) return tokenizer.NewContainer( tokenizer.NewLine(token.Word(keyword.Offset)), @@ -31,7 +27,3 @@ func (o Offset) self() (tokenizer.Tokenizer, []interface{}) { line, ), values } - -func (o Offset) previous() Clause { - return o.prev -} diff --git a/statement/offset_test.go b/statement/offset_test.go index b1c17da..0060fbe 100644 --- a/statement/offset_test.go +++ b/statement/offset_test.go @@ -9,13 +9,6 @@ import ( "github.com/minodisk/sqlabble/statement" ) -func TestOffsetType(t *testing.T) { - o := statement.Offset{} - if _, ok := interface{}(o).(statement.Clause); !ok { - t.Errorf("%T should implement statement.Clause", o) - } -} - func TestOffset(t *testing.T) { for i, c := range []struct { statement statement.Statement diff --git a/statement/on.go b/statement/on.go index b10e79c..1da452c 100644 --- a/statement/on.go +++ b/statement/on.go @@ -7,75 +7,58 @@ import ( ) type On struct { - join Join + Prev + Next column1, column2 Column } -func NewOn(column1, column2 Column) On { - return On{ +func NewOn(column1, column2 Column) *On { + return &On{ column1: column1, column2: column2, } } -func (o On) Join(table TableOrAlias) Join { +func (o *On) Join(table TableOrAlias) *Join { j := NewJoin(table) - j.prev = o + Link(o, j) return j } -func (o On) InnerJoin(table TableOrAlias) Join { +func (o *On) InnerJoin(table TableOrAlias) *Join { j := NewInnerJoin(table) - j.prev = o + Link(o, j) return j } -func (o On) LeftJoin(table TableOrAlias) Join { +func (o *On) LeftJoin(table TableOrAlias) *Join { j := NewLeftJoin(table) - j.prev = o + Link(o, j) return j } -func (o On) RightJoin(table TableOrAlias) Join { +func (o *On) RightJoin(table TableOrAlias) *Join { j := NewRightJoin(table) - j.prev = o + Link(o, j) return j } -func (o On) nodeize() (tokenizer.Tokenizer, []interface{}) { - return nodeizeJoiners(o) -} - -func (o On) self() (tokenizer.Tokenizer, []interface{}) { - t0, v0 := o.join.self() +func (o *On) nodeize() (tokenizer.Tokenizer, []interface{}) { t1, v1 := o.column1.nodeize() t2, v2 := o.column2.nodeize() return tokenizer.ConcatTokenizers( - t0, - tokenizer.ConcatTokenizers( t1, t2, tokenizer.NewLine( - token.Space, token.Word(keyword.Eq), - token.Space, ), - ), - tokenizer.NewLine( - token.Space, - token.Word(keyword.On), - token.Space, - ), - ), append(append(v0, v1...), v2...) -} - -func (o On) previous() Joiner { - return o.join.previous() + ).Prepend(token.Word(keyword.On)), + append(v1, v2...) } // isTableOrAliasOrJoiner always returns true. // This method exists only to implement the interface TableOrAliasOrJoiner. // This is a shit of duck typing, but anyway it works. -func (o On) isTableOrAliasOrJoiner() bool { +func (o *On) isTableOrAliasOrJoiner() bool { return true } diff --git a/statement/on_test.go b/statement/on_test.go index 4a2eb30..1187ece 100644 --- a/statement/on_test.go +++ b/statement/on_test.go @@ -21,8 +21,8 @@ func TestOn(t *testing.T) { statement.NewColumn("f.id"), statement.NewColumn("b.id"), ), - " ON f.id = b.id", - `> ON f.id = b.id + "ON f.id = b.id", + `> ON f.id = b.id `, nil, }, @@ -33,8 +33,8 @@ func TestOn(t *testing.T) { ).Join( statement.NewTable("bar"), ), - " ON f.id = b.id JOIN bar", - `> ON f.id = b.id + "ON f.id = b.id JOIN bar", + `> ON f.id = b.id > JOIN bar `, nil, @@ -46,8 +46,8 @@ func TestOn(t *testing.T) { ).InnerJoin( statement.NewTable("bar"), ), - " ON f.id = b.id INNER JOIN bar", - `> ON f.id = b.id + "ON f.id = b.id INNER JOIN bar", + `> ON f.id = b.id > INNER JOIN bar `, nil, @@ -59,8 +59,8 @@ func TestOn(t *testing.T) { ).LeftJoin( statement.NewTable("bar"), ), - " ON f.id = b.id LEFT JOIN bar", - `> ON f.id = b.id + "ON f.id = b.id LEFT JOIN bar", + `> ON f.id = b.id > LEFT JOIN bar `, nil, @@ -72,8 +72,8 @@ func TestOn(t *testing.T) { ).RightJoin( statement.NewTable("bar"), ), - " ON f.id = b.id RIGHT JOIN bar", - `> ON f.id = b.id + "ON f.id = b.id RIGHT JOIN bar", + `> ON f.id = b.id > RIGHT JOIN bar `, nil, @@ -86,7 +86,8 @@ func TestOn(t *testing.T) { ), `foo AS "a" JOIN bar ON a.id = bar.id`, `> foo AS "a" -> JOIN bar ON a.id = bar.id +> JOIN bar +> ON a.id = bar.id `, nil, }, diff --git a/statement/op.go b/statement/op.go index 605965b..da928f6 100644 --- a/statement/op.go +++ b/statement/op.go @@ -38,7 +38,6 @@ func (o JoinOperation) nodeize() (tokenizer.Tokenizer, []interface{}) { } return tokenizer.NewTokenizers(ts...).Prefix( token.Word(o.keyword()), - token.Space, ), values } @@ -64,7 +63,6 @@ func (o Not) nodeize() (tokenizer.Tokenizer, []interface{}) { middle, ).Prepend( token.Word(o.keyword()), - token.Space, ), values } @@ -145,9 +143,7 @@ func (o ComparisonOperation) nodeize() (tokenizer.Tokenizer, []interface{}) { t1, t2, tokenizer.NewLine( - token.Space, token.Word(o.keyword()), - token.Space, ), ), append(v1, v2...) } @@ -177,16 +173,12 @@ func (o Between) nodeize() (tokenizer.Tokenizer, []interface{}) { t1, t2, tokenizer.NewLine( - token.Space, token.Word(o.keyword()), - token.Space, ), ), t3, tokenizer.NewLine( - token.Space, token.Word(keyword.And), - token.Space, ), ), append(append(v1, v2...), v3...) } @@ -222,9 +214,7 @@ func (o ContainingOperation) nodeize() (tokenizer.Tokenizer, []interface{}) { t1, t2, tokenizer.NewLine( - token.Space, token.Word(o.keyword()), - token.Space, ), ), append(v1, v2...) } @@ -256,9 +246,7 @@ func (o NullOperation) nodeize() (tokenizer.Tokenizer, []interface{}) { t1, tokenizer.NewLine(token.Word(keyword.Null)), tokenizer.NewLine( - token.Space, token.Word(o.keyword()), - token.Space, ), ), v1 } diff --git a/statement/op_nonscalar.go b/statement/op_nonscalar.go index c97f806..dec314a 100644 --- a/statement/op_nonscalar.go +++ b/statement/op_nonscalar.go @@ -9,87 +9,87 @@ import ( type NonScalarOperation struct { op keyword.Operator column ValOrColOrFuncOrSub - params Subquery + params *Subquery } -func NewEqAll(params Subquery) NonScalarOperation { +func NewEqAll(params *Subquery) NonScalarOperation { return NonScalarOperation{ op: keyword.EqAll, params: params, } } -func NewNotEqAll(params Subquery) NonScalarOperation { +func NewNotEqAll(params *Subquery) NonScalarOperation { return NonScalarOperation{ op: keyword.NotEqAll, params: params, } } -func NewGtAll(params Subquery) NonScalarOperation { +func NewGtAll(params *Subquery) NonScalarOperation { return NonScalarOperation{ op: keyword.GtAll, params: params, } } -func NewGteAll(params Subquery) NonScalarOperation { +func NewGteAll(params *Subquery) NonScalarOperation { return NonScalarOperation{ op: keyword.GteAll, params: params, } } -func NewLtAll(params Subquery) NonScalarOperation { +func NewLtAll(params *Subquery) NonScalarOperation { return NonScalarOperation{ op: keyword.LtAll, params: params, } } -func NewLteAll(params Subquery) NonScalarOperation { +func NewLteAll(params *Subquery) NonScalarOperation { return NonScalarOperation{ op: keyword.LteAll, params: params, } } -func NewEqAny(params Subquery) NonScalarOperation { +func NewEqAny(params *Subquery) NonScalarOperation { return NonScalarOperation{ op: keyword.EqAny, params: params, } } -func NewNotEqAny(params Subquery) NonScalarOperation { +func NewNotEqAny(params *Subquery) NonScalarOperation { return NonScalarOperation{ op: keyword.NotEqAny, params: params, } } -func NewGtAny(params Subquery) NonScalarOperation { +func NewGtAny(params *Subquery) NonScalarOperation { return NonScalarOperation{ op: keyword.GtAny, params: params, } } -func NewGteAny(params Subquery) NonScalarOperation { +func NewGteAny(params *Subquery) NonScalarOperation { return NonScalarOperation{ op: keyword.GteAny, params: params, } } -func NewLtAny(params Subquery) NonScalarOperation { +func NewLtAny(params *Subquery) NonScalarOperation { return NonScalarOperation{ op: keyword.LtAny, params: params, } } -func NewLteAny(params Subquery) NonScalarOperation { +func NewLteAny(params *Subquery) NonScalarOperation { return NonScalarOperation{ op: keyword.LteAny, params: params, @@ -101,7 +101,6 @@ func (n NonScalarOperation) nodeize() (tokenizer.Tokenizer, []interface{}) { if n.column == nil { return t2.Prepend( token.Word(n.keyword()), - token.Space, ), v2 } @@ -110,9 +109,7 @@ func (n NonScalarOperation) nodeize() (tokenizer.Tokenizer, []interface{}) { t1, t2, tokenizer.NewLine( - token.Space, token.Word(n.keyword()), - token.Space, ), ), append(v1, v2...) } @@ -123,17 +120,17 @@ func (n NonScalarOperation) keyword() keyword.Operator { type ExistanceOperation struct { op keyword.Operator - params Subquery + params *Subquery } -func NewExists(params Subquery) ExistanceOperation { +func NewExists(params *Subquery) ExistanceOperation { return ExistanceOperation{ op: keyword.Exists, params: params, } } -func NewNotExists(params Subquery) ExistanceOperation { +func NewNotExists(params *Subquery) ExistanceOperation { return ExistanceOperation{ op: keyword.NotExists, params: params, @@ -144,7 +141,6 @@ func (e ExistanceOperation) nodeize() (tokenizer.Tokenizer, []interface{}) { t2, v2 := e.params.nodeize() return t2.Prepend( token.Word(e.keyword()), - token.Space, ), v2 } diff --git a/statement/order.go b/statement/order.go index 46fd9af..4541818 100644 --- a/statement/order.go +++ b/statement/order.go @@ -26,7 +26,6 @@ func NewDesc() Order { func (o Order) nodeize() (tokenizer.Tokenizer, []interface{}) { t1, v1 := o.column.nodeize() return t1.Append( - token.Space, token.Word(o.keyword()), ), v1 } diff --git a/statement/order_by.go b/statement/order_by.go index ba95d50..9abc8b3 100644 --- a/statement/order_by.go +++ b/statement/order_by.go @@ -7,19 +7,24 @@ import ( ) type OrderBy struct { - prev Clause + Prev + Next orders []Order } -func NewOrderBy(os ...Order) OrderBy { - return OrderBy{orders: os} +func NewOrderBy(orders ...Order) *OrderBy { + return &OrderBy{ + orders: orders, + } } -func (o OrderBy) nodeize() (tokenizer.Tokenizer, []interface{}) { - return nodeizeClauses(o) +func (o *OrderBy) Limit(count int) *Limit { + l := NewLimit(count) + Link(o, l) + return l } -func (o OrderBy) self() (tokenizer.Tokenizer, []interface{}) { +func (o *OrderBy) nodeize() (tokenizer.Tokenizer, []interface{}) { tokenizers := make(tokenizer.Tokenizers, len(o.orders)) values := []interface{}{} for i, o := range o.orders { @@ -30,16 +35,6 @@ func (o OrderBy) self() (tokenizer.Tokenizer, []interface{}) { return tokenizer.NewContainer( tokenizer.NewLine(token.Word(keyword.OrderBy)), ).SetMiddle( - tokenizers.Prefix(token.Comma, token.Space), + tokenizers.Prefix(token.Comma), ), values } - -func (o OrderBy) previous() Clause { - return o.prev -} - -func (o OrderBy) Limit(count int) Limit { - l := NewLimit(count) - l.prev = o - return l -} diff --git a/statement/params.go b/statement/params.go index ab02b67..b60cd0e 100644 --- a/statement/params.go +++ b/statement/params.go @@ -50,8 +50,8 @@ func (ps Params) nodeize() (tokenizer.Tokenizer, []interface{}) { } line, values := tokenizer.ParamsToLine(values...) return line. - Prepend(token.ParenthesesStart). - Append(token.ParenthesesEnd), values + Prepend(token.ParenStart). + Append(token.ParenEnd), values } // isParamsOrSubquery always returns true. diff --git a/statement/select.go b/statement/select.go index 90637dd..99a9ca4 100644 --- a/statement/select.go +++ b/statement/select.go @@ -7,35 +7,32 @@ import ( ) type Select struct { + Prev distinct bool columns []ColOrAliasOrFuncOrSub } -func NewSelect(columns ...ColOrAliasOrFuncOrSub) Select { - return Select{ +func NewSelect(columns ...ColOrAliasOrFuncOrSub) *Select { + return &Select{ distinct: false, columns: columns, } } -func NewSelectDistinct(columns ...ColOrAliasOrFuncOrSub) Select { - return Select{ +func NewSelectDistinct(columns ...ColOrAliasOrFuncOrSub) *Select { + return &Select{ distinct: true, columns: columns, } } -func (s Select) From(t TableOrAliasOrJoiner) From { +func (s *Select) From(t TableOrAliasOrJoiner) *From { f := NewFrom(t) - f.prev = s + Link(s, f) return f } -func (s Select) nodeize() (tokenizer.Tokenizer, []interface{}) { - return nodeizeClauses(s) -} - -func (s Select) self() (tokenizer.Tokenizer, []interface{}) { +func (s *Select) nodeize() (tokenizer.Tokenizer, []interface{}) { tokenizers := make(tokenizer.Tokenizers, len(s.columns)) values := []interface{}{} for i, c := range s.columns { @@ -46,17 +43,12 @@ func (s Select) self() (tokenizer.Tokenizer, []interface{}) { tokens := token.NewTokens(token.Word(keyword.Select)) if s.distinct { tokens = tokens.Append( - token.Space, token.Word(keyword.Distinct), ) } return tokenizer.NewContainer( tokenizer.NewLine(tokens...), ).SetMiddle( - tokenizers.Prefix(token.Comma, token.Space), + tokenizers.Prefix(token.Comma), ), values } - -func (s Select) previous() Clause { - return nil -} diff --git a/statement/select_test.go b/statement/select_test.go index af69187..ca14c1c 100644 --- a/statement/select_test.go +++ b/statement/select_test.go @@ -9,13 +9,6 @@ import ( "github.com/minodisk/sqlabble/statement" ) -func TestSelectType(t *testing.T) { - s := statement.Select{} - if _, ok := interface{}(s).(statement.Clause); !ok { - t.Errorf("%T should implement statement.Clause", s) - } -} - func TestSelectSQL(t *testing.T) { for i, c := range []struct { statement statement.Statement diff --git a/statement/set.go b/statement/set.go index bde8854..c2e488d 100644 --- a/statement/set.go +++ b/statement/set.go @@ -7,21 +7,24 @@ import ( ) type Set struct { - prev Clause + Prev + Next assigns []Assign } -func NewSet(assigns ...Assign) Set { - return Set{ +func NewSet(assigns ...Assign) *Set { + return &Set{ assigns: assigns, } } -func (s Set) nodeize() (tokenizer.Tokenizer, []interface{}) { - return nodeizeClauses(s) +func (s *Set) Where(operation ComparisonOrLogicalOperation) *Where { + w := NewWhere(operation) + Link(s, w) + return w } -func (s Set) self() (tokenizer.Tokenizer, []interface{}) { +func (s *Set) nodeize() (tokenizer.Tokenizer, []interface{}) { tokenizers := make(tokenizer.Tokenizers, len(s.assigns)) values := []interface{}{} for i, a := range s.assigns { @@ -34,16 +37,6 @@ func (s Set) self() (tokenizer.Tokenizer, []interface{}) { token.Word(keyword.Set), ), ).SetMiddle( - tokenizers.Prefix(token.Comma, token.Space), + tokenizers.Prefix(token.Comma), ), values } - -func (s Set) previous() Clause { - return s.prev -} - -func (s Set) Where(operation ComparisonOrLogicalOperation) Where { - w := NewWhere(operation) - w.prev = s - return w -} diff --git a/statement/set_operation.go b/statement/set_operation.go index 896b69e..5a7c9b5 100644 --- a/statement/set_operation.go +++ b/statement/set_operation.go @@ -7,84 +7,65 @@ import ( ) type SetOperation struct { + Prev + Next op keyword.Operator statements []Statement } -func NewUnion(statements ...Statement) SetOperation { - return SetOperation{ - op: keyword.Union, - statements: statements, - } +func NewUnion(statements ...Statement) *SetOperation { + return NewSetOperation(keyword.Union, statements...) } -func NewUnionAll(statements ...Statement) SetOperation { - return SetOperation{ - op: keyword.UnionAll, - statements: statements, - } +func NewUnionAll(statements ...Statement) *SetOperation { + return NewSetOperation(keyword.UnionAll, statements...) } -func NewIntersect(statements ...Statement) SetOperation { - return SetOperation{ - op: keyword.Intersect, - statements: statements, - } +func NewIntersect(statements ...Statement) *SetOperation { + return NewSetOperation(keyword.Intersect, statements...) } -func NewIntersectAll(statements ...Statement) SetOperation { - return SetOperation{ - op: keyword.IntersectAll, - statements: statements, - } +func NewIntersectAll(statements ...Statement) *SetOperation { + return NewSetOperation(keyword.IntersectAll, statements...) } -func NewExcept(statements ...Statement) SetOperation { - return SetOperation{ - op: keyword.Except, - statements: statements, +func NewExcept(statements ...Statement) *SetOperation { + return NewSetOperation(keyword.Except, statements...) +} + +func NewSetOperation(op keyword.Operator, stmts ...Statement) *SetOperation { + s := &SetOperation{ + op: op, + statements: stmts, } + return s } -func NewExceptAll(statements ...Statement) SetOperation { - return SetOperation{ +func NewExceptAll(statements ...Statement) *SetOperation { + return &SetOperation{ op: keyword.ExceptAll, statements: statements, } } -func (u SetOperation) nodeize() (tokenizer.Tokenizer, []interface{}) { - return u.self() +func (u *SetOperation) OrderBy(os ...Order) *OrderBy { + o := NewOrderBy(os...) + Link(u, o) + return o +} + +func (u *SetOperation) nodeize() (tokenizer.Tokenizer, []interface{}) { + return tokenizer.NewLine(token.Word(u.op)), nil } -func (u SetOperation) self() (tokenizer.Tokenizer, []interface{}) { - tokenizers := make(tokenizer.Tokenizers, len(u.statements)) - values := []interface{}{} +func (u *SetOperation) list() []Statement { + ns := []Statement{} for i, s := range u.statements { - t, vals := s.nodeize() - t = tokenizer.NewParentheses(t) - if i != 0 { - t = t.Prepend( - token.Word(u.keyword()), - token.Space, - ) + if i == 0 { + ns = append(ns, NewSubquery(s)) + continue } - tokenizers[i] = t - values = append(values, vals...) + ns = append(ns, u, NewSubquery(s)) } - return tokenizers, values -} - -func (u SetOperation) previous() Clause { - return nil -} - -func (u SetOperation) keyword() keyword.Operator { - return u.op -} - -func (u SetOperation) OrderBy(os ...Order) OrderBy { - o := NewOrderBy(os...) - o.prev = u - return o + return ns } diff --git a/statement/set_operation_test.go b/statement/set_operation_test.go index bd8b463..e37a06f 100644 --- a/statement/set_operation_test.go +++ b/statement/set_operation_test.go @@ -9,16 +9,6 @@ import ( "github.com/minodisk/sqlabble/statement" ) -func TestSetOperationType(t *testing.T) { - for _, s := range []interface{}{ - statement.SetOperation{}, - } { - if _, ok := s.(statement.Clause); !ok { - t.Errorf("%T should implement statement.Clause", s) - } - } -} - func TestUnionSQL(t *testing.T) { for i, c := range []struct { statement statement.Statement @@ -28,17 +18,20 @@ func TestUnionSQL(t *testing.T) { }{ { statement.NewUnion( - statement.NewSelect(statement.NewColumn("a")), + statement.NewSelect(statement.NewColumn("a")).From(statement.NewTable("aaa")), statement.NewSelect(statement.NewColumn("b")), ).OrderBy( statement.NewColumn("foo").Asc(), ), - "(SELECT a) UNION (SELECT b) ORDER BY foo ASC", + "(SELECT a FROM aaa) UNION (SELECT b) ORDER BY foo ASC", `> ( > SELECT > a +> FROM +> aaa > ) -> UNION ( +> UNION +> ( > SELECT > b > ) @@ -64,22 +57,26 @@ func TestUnionSQL(t *testing.T) { > SELECT > a > ) -> UNION ( +> UNION +> ( > ( > ( > SELECT > b > ) -> UNION ( +> UNION +> ( > SELECT > c > ) > ) -> UNION ( +> UNION +> ( > SELECT > d > ) -> UNION ( +> UNION +> ( > SELECT > e > ) @@ -133,22 +130,26 @@ func TestUnionAllSQL(t *testing.T) { > SELECT > a > ) -> UNION ALL ( +> UNION ALL +> ( > ( > ( > SELECT > b > ) -> UNION ALL ( +> UNION ALL +> ( > SELECT > c > ) > ) -> UNION ALL ( +> UNION ALL +> ( > SELECT > d > ) -> UNION ALL ( +> UNION ALL +> ( > SELECT > e > ) @@ -202,22 +203,26 @@ func TestIntersectSQL(t *testing.T) { > SELECT > a > ) -> INTERSECT ( +> INTERSECT +> ( > ( > ( > SELECT > b > ) -> INTERSECT ( +> INTERSECT +> ( > SELECT > c > ) > ) -> INTERSECT ( +> INTERSECT +> ( > SELECT > d > ) -> INTERSECT ( +> INTERSECT +> ( > SELECT > e > ) @@ -271,22 +276,26 @@ func TestIntersectAllSQL(t *testing.T) { > SELECT > a > ) -> INTERSECT ALL ( +> INTERSECT ALL +> ( > ( > ( > SELECT > b > ) -> INTERSECT ALL ( +> INTERSECT ALL +> ( > SELECT > c > ) > ) -> INTERSECT ALL ( +> INTERSECT ALL +> ( > SELECT > d > ) -> INTERSECT ALL ( +> INTERSECT ALL +> ( > SELECT > e > ) @@ -340,22 +349,26 @@ func TestExceptSQL(t *testing.T) { > SELECT > a > ) -> EXCEPT ( +> EXCEPT +> ( > ( > ( > SELECT > b > ) -> EXCEPT ( +> EXCEPT +> ( > SELECT > c > ) > ) -> EXCEPT ( +> EXCEPT +> ( > SELECT > d > ) -> EXCEPT ( +> EXCEPT +> ( > SELECT > e > ) @@ -409,22 +422,26 @@ func TestExceptAllSQL(t *testing.T) { > SELECT > a > ) -> EXCEPT ALL ( +> EXCEPT ALL +> ( > ( > ( > SELECT > b > ) -> EXCEPT ALL ( +> EXCEPT ALL +> ( > SELECT > c > ) > ) -> EXCEPT ALL ( +> EXCEPT ALL +> ( > SELECT > d > ) -> EXCEPT ALL ( +> EXCEPT ALL +> ( > SELECT > e > ) diff --git a/statement/subquery.go b/statement/subquery.go index 0463edc..aabc6b6 100644 --- a/statement/subquery.go +++ b/statement/subquery.go @@ -3,142 +3,143 @@ package statement import "github.com/minodisk/sqlabble/tokenizer" type Subquery struct { + Child statement Statement } -func NewSubquery(statement Statement) Subquery { - return Subquery{ +func NewSubquery(statement Statement) *Subquery { + return &Subquery{ statement: statement, } } -func (s Subquery) As(alias string) SubqueryAs { +func (s *Subquery) As(alias string) SubqueryAs { a := NewSubqueryAs(alias) a.subquery = s return a } -func (s Subquery) Eq(value ValOrColOrFuncOrSub) ComparisonOperation { +func (s *Subquery) Eq(value ValOrColOrFuncOrSub) ComparisonOperation { e := NewEq(value) e.column = s return e } -func (s Subquery) NotEq(value ValOrColOrFuncOrSub) ComparisonOperation { +func (s *Subquery) NotEq(value ValOrColOrFuncOrSub) ComparisonOperation { n := NewNotEq(value) n.column = s return n } -func (s Subquery) Gt(value ValOrColOrFuncOrSub) ComparisonOperation { +func (s *Subquery) Gt(value ValOrColOrFuncOrSub) ComparisonOperation { g := NewGt(value) g.column = s return g } -func (s Subquery) Gte(value ValOrColOrFuncOrSub) ComparisonOperation { +func (s *Subquery) Gte(value ValOrColOrFuncOrSub) ComparisonOperation { g := NewGte(value) g.column = s return g } -func (s Subquery) Lt(value ValOrColOrFuncOrSub) ComparisonOperation { +func (s *Subquery) Lt(value ValOrColOrFuncOrSub) ComparisonOperation { l := NewLt(value) l.column = s return l } -func (s Subquery) Lte(value ValOrColOrFuncOrSub) ComparisonOperation { +func (s *Subquery) Lte(value ValOrColOrFuncOrSub) ComparisonOperation { l := NewLte(value) l.column = s return l } -func (s Subquery) Like(value ValOrColOrFuncOrSub) ComparisonOperation { +func (s *Subquery) Like(value ValOrColOrFuncOrSub) ComparisonOperation { l := NewLike(value) l.column = s return l } -func (s Subquery) RegExp(value ValOrColOrFuncOrSub) ComparisonOperation { +func (s *Subquery) RegExp(value ValOrColOrFuncOrSub) ComparisonOperation { r := NewRegExp(value) r.column = s return r } -func (s Subquery) Between(from, to ValOrColOrFuncOrSub) Between { +func (s *Subquery) Between(from, to ValOrColOrFuncOrSub) Between { b := NewBetween(from, to) b.column = s return b } -func (s Subquery) In(params ValsOrSub) ContainingOperation { +func (s *Subquery) In(params ValsOrSub) ContainingOperation { i := NewIn(params) i.column = s return i } -func (s Subquery) NotIn(params ValsOrSub) ContainingOperation { +func (s *Subquery) NotIn(params ValsOrSub) ContainingOperation { n := NewNotIn(params) n.column = s return n } -func (s Subquery) IsNull() NullOperation { +func (s *Subquery) IsNull() NullOperation { i := NewIsNull() i.column = s return i } -func (s Subquery) IsNotNull() NullOperation { +func (s *Subquery) IsNotNull() NullOperation { i := NewIsNotNull() i.column = s return i } -func (s Subquery) nodeize() (tokenizer.Tokenizer, []interface{}) { - t, values := s.statement.nodeize() +func (s *Subquery) nodeize() (tokenizer.Tokenizer, []interface{}) { + t, values := Nodize(s.statement) return tokenizer.NewParentheses(t), values } // isValsOrSub always returns true. // This method exists only to implement the interface isValsOrSub. // This is a shit of duck typing, but anyway it works. -func (s Subquery) isValsOrSub() bool { +func (s *Subquery) isValsOrSub() bool { return true } // isValOrFuncOrSub always returns true. // This method exists only to implement the interface ValOrFuncOrSub. // This is a shit of duck typing, but anyway it works. -func (s Subquery) isValOrFuncOrSub() bool { +func (s *Subquery) isValOrFuncOrSub() bool { return true } // isValOrColOrFuncOrSub always returns true. // This method exists only to implement the interface ValOrColOrFuncOrSub. // This is a shit of duck typing, but anyway it works. -func (s Subquery) isValOrColOrFuncOrSub() bool { +func (s *Subquery) isValOrColOrFuncOrSub() bool { return true } // isColOrSub always returns true. // This method exists only to implement the interface ColOrSub. // This is a shit of duck typing, but anyway it works. -func (s Subquery) isColOrSub() bool { +func (s *Subquery) isColOrSub() bool { return true } // isColOrAliasOrSub always returns true. // This method exists only to implement the interface ColOrAliasOrSub. // This is a shit of duck typing, but anyway it works. -func (s Subquery) isColOrAliasOrSub() bool { +func (s *Subquery) isColOrAliasOrSub() bool { return true } // isColOrAliasOrFuncOrSub always returns true. // This method exists only to implement the interface ColOrAliasOrFuncOrSub. // This is a shit of duck typing, but anyway it works. -func (s Subquery) isColOrAliasOrFuncOrSub() bool { +func (s *Subquery) isColOrAliasOrFuncOrSub() bool { return true } diff --git a/statement/subquery_as.go b/statement/subquery_as.go index 000fa01..c639ee9 100644 --- a/statement/subquery_as.go +++ b/statement/subquery_as.go @@ -7,7 +7,7 @@ import ( ) type SubqueryAs struct { - subquery Subquery + subquery *Subquery alias string } @@ -24,9 +24,7 @@ func (a SubqueryAs) nodeize() (tokenizer.Tokenizer, []interface{}) { t1, t2, tokenizer.NewLine( - token.Space, token.Word(keyword.As), - token.Space, ), ), v1 } diff --git a/statement/table.go b/statement/table.go index 08cbe7c..01ac9ff 100644 --- a/statement/table.go +++ b/statement/table.go @@ -6,71 +6,68 @@ import ( ) type Table struct { + Prev + Next name string } -func NewTable(name string) Table { - return Table{ +func NewTable(name string) *Table { + return &Table{ name: name, } } -func (t Table) As(alias string) TableAs { - return TableAs{ - table: t, - alias: alias, - } -} - -func (t Table) Join(table TableOrAlias) Join { - nj := NewJoin(table) - nj.prev = t - return nj +func (t *Table) As(alias string) *TableAs { + a := NewTableAs(alias) + a.table = t + return a } -func (t Table) InnerJoin(table TableOrAlias) Join { - ij := NewInnerJoin(table) - ij.prev = t - return ij +func (t *Table) Join(table TableOrAlias) *Join { + j := NewJoin(table) + Link(t, j) + return j } -func (t Table) LeftJoin(table TableOrAlias) Join { - lj := NewLeftJoin(table) - lj.prev = t - return lj +func (t *Table) InnerJoin(table TableOrAlias) *Join { + j := NewInnerJoin(table) + Link(t, j) + return j } -func (t Table) RightJoin(table TableOrAlias) Join { - rj := NewRightJoin(table) - rj.prev = t - return rj +func (t *Table) LeftJoin(table TableOrAlias) *Join { + j := NewLeftJoin(table) + Link(t, j) + return j } -func (t Table) nodeize() (tokenizer.Tokenizer, []interface{}) { - return nodeizeJoiners(t) +func (t *Table) RightJoin(table TableOrAlias) *Join { + j := NewRightJoin(table) + Link(t, j) + return j } -func (t Table) self() (tokenizer.Tokenizer, []interface{}) { +func (t *Table) nodeize() (tokenizer.Tokenizer, []interface{}) { if t.name == "" { return nil, nil } return tokenizer.NewLine(token.Word(t.name)), nil } -func (t Table) previous() Joiner { +func (t *Table) previous() Joiner { return nil } // isTableOrAlias always returns true. // This method exists only to implement the interface TableOrAlias. // This is a shit of duck typing, but anyway it works. -func (t Table) isTableOrAlias() bool { +func (t *Table) isTableOrAlias() bool { return true } // isTableOrAliasOrJoiner always returns true. // This method exists only to implement the interface TableOrAliasOrJoiner. // This is a shit of duck typing, but anyway it works. -func (t Table) isTableOrAliasOrJoiner() bool { +func (t *Table) isTableOrAliasOrJoiner() bool { return true } diff --git a/statement/table_as.go b/statement/table_as.go index 09d56f4..7823669 100644 --- a/statement/table_as.go +++ b/statement/table_as.go @@ -7,72 +7,73 @@ import ( ) type TableAs struct { - table Table + Prev + Next + table *Table alias string } -func (t TableAs) Join(table TableOrAlias) Join { - nj := NewJoin(table) - nj.prev = t - return nj +func NewTableAs(alias string) *TableAs { + return &TableAs{ + alias: alias, + } } -func (t TableAs) InnerJoin(table TableOrAlias) Join { - ij := NewInnerJoin(table) - ij.prev = t - return ij +func (t *TableAs) Join(table TableOrAlias) *Join { + j := NewJoin(table) + Link(t, j) + return j } -func (t TableAs) LeftJoin(table TableOrAlias) Join { - lj := NewLeftJoin(table) - lj.prev = t - return lj +func (t *TableAs) InnerJoin(table TableOrAlias) *Join { + j := NewInnerJoin(table) + Link(t, j) + return j } -func (t TableAs) RightJoin(table TableOrAlias) Join { - rj := NewRightJoin(table) - rj.prev = t - return rj +func (t *TableAs) LeftJoin(table TableOrAlias) *Join { + j := NewLeftJoin(table) + Link(t, j) + return j } -func (t TableAs) nodeize() (tokenizer.Tokenizer, []interface{}) { +func (t *TableAs) RightJoin(table TableOrAlias) *Join { + j := NewRightJoin(table) + Link(t, j) + return j +} + +func (t *TableAs) nodeize() (tokenizer.Tokenizer, []interface{}) { return t.self() } -func (t TableAs) self() (tokenizer.Tokenizer, []interface{}) { +func (t *TableAs) self() (tokenizer.Tokenizer, []interface{}) { t1, v1 := t.table.nodeize() t2 := tokenizer.NewLine( - token.Wrap( - token.Word(t.alias), - token.Quote, - )..., + token.QuoteStart, + token.Word(t.alias), + token.QuoteEnd, ) return tokenizer.ConcatTokenizers( t1, t2, tokenizer.NewLine( - token.Space, token.Word(keyword.As), - token.Space, ), ), v1 } -func (t TableAs) previous() Joiner { - return nil -} - // isTableOrAlias always returns true. // This method exists only to implement the interface TableOrAlias. // This is a shit of duck typing, but anyway it works. -func (t TableAs) isTableOrAlias() bool { +func (t *TableAs) isTableOrAlias() bool { return true } // isTableOrAliasOrJoiner always returns true. // This method exists only to implement the interface TableOrAliasOrJoiner. // This is a shit of duck typing, but anyway it works. -func (t TableAs) isTableOrAliasOrJoiner() bool { +func (t *TableAs) isTableOrAliasOrJoiner() bool { return true } diff --git a/statement/table_as_test.go b/statement/table_as_test.go index 07183b6..77084c8 100644 --- a/statement/table_as_test.go +++ b/statement/table_as_test.go @@ -11,7 +11,7 @@ import ( func TestTableAsType(t *testing.T) { for _, c := range []interface{}{ - statement.TableAs{}, + &statement.TableAs{}, } { t.Run(fmt.Sprintf("%T", c), func(t *testing.T) { if _, ok := c.(statement.Joiner); !ok { diff --git a/statement/table_test.go b/statement/table_test.go index 86f4449..d2cf1af 100644 --- a/statement/table_test.go +++ b/statement/table_test.go @@ -11,7 +11,7 @@ import ( func TestTableType(t *testing.T) { for _, c := range []interface{}{ - statement.Table{}, + &statement.Table{}, } { t.Run(fmt.Sprintf("%T", c), func(t *testing.T) { if _, ok := c.(statement.Joiner); !ok { diff --git a/statement/traverser.go b/statement/traverser.go index d6611cc..cd43092 100644 --- a/statement/traverser.go +++ b/statement/traverser.go @@ -1,52 +1,42 @@ package statement -import ( - "github.com/minodisk/sqlabble/token" - "github.com/minodisk/sqlabble/tokenizer" -) - -func Link(n1, n2 Nodeizer) { - switch t1 := n1.(type) { - case Childer: - switch t2 := n2.(type) { - case Parenter: - t1.setChild(t2) - t2.setParent(t1) - } - case Nexter: - switch t2 := n2.(type) { - case Prever: - t1.setNext(t2) - t2.setPrev(t1) - } - } +import "github.com/minodisk/sqlabble/token" + +func Contract(t1 Childer, t2 Parenter) { + t1.setChild(t2) + t2.setParent(t1) } -type Nodeizer interface { - nodeize() (tokenizer.Tokenizer, []interface{}) +func Link(t1 Nexter, t2 Prever) { + t1.setNext(t2) + t2.setPrev(t1) } type Childer interface { - Nodeizer - child() Parenter + Statement + children() []Parenter setChild(Parenter) +} + +type Separatorer interface { + Childer separator() token.Tokens } type Parent struct { - c Parenter + c []Parenter } -func (p *Parent) child() Parenter { +func (p *Parent) children() []Parenter { return p.c } func (p *Parent) setChild(c Parenter) { - p.c = c + p.c = append(p.c, c) } type Parenter interface { - Nodeizer + Statement parent() Childer setParent(Childer) } @@ -64,7 +54,7 @@ func (c *Child) setParent(p Childer) { } type Prever interface { - Nodeizer + Statement prev() Nexter setPrev(Nexter) } @@ -82,7 +72,7 @@ func (n *Next) setPrev(p Nexter) { } type Nexter interface { - Nodeizer + Statement next() Prever setNext(Prever) } @@ -99,7 +89,7 @@ func (p *Prev) setNext(n Prever) { p.n = n } -func Traverse(t Nodeizer) Nodeizer { +func Traverse(t Statement) Statement { { for p := t; p != nil; { t = p @@ -123,43 +113,7 @@ func Traverse(t Nodeizer) Nodeizer { return t } -func Nodize(n Nodeizer) (tokenizer.Tokenizers, []interface{}) { - if n == nil { - return nil, nil - } - - var tokenizers tokenizer.Tokenizers - values := []interface{}{} - - if childer, ok := n.(Childer); ok { - if child := childer.child(); child != nil { - t1, vals1 := n.nodeize() - t2, vals2 := Nodize(child) - first, _ := t1.FirstLine() - _, last := t1.LastLine() - t12 := tokenizer. - NewContainer(first). - SetMiddle(t2.Prefix(childer.separator()...)). - SetLast(last) - - tokenizers = tokenizer.NewTokenizers(t12) - values = append(append(values, vals1...), vals2...) - } - } - - if tokenizers == nil { - t, vals := n.nodeize() - tokenizers = tokenizer.NewTokenizers(t) - values = vals - } - - if nexter, ok := n.(Nexter); ok { - if next := nexter.next(); next != nil { - ts, vals := Nodize(next) - tokenizers = append(tokenizers, ts...) - values = append(values, vals...) - } - } - - return tokenizers, values +type Lister interface { + Statement + list() []Statement } diff --git a/statement/update.go b/statement/update.go index 8744ad2..1e1909e 100644 --- a/statement/update.go +++ b/statement/update.go @@ -7,21 +7,23 @@ import ( ) type Update struct { - prev Clause - table Table + Prev + table *Table } -func NewUpdate(table Table) Update { - return Update{ +func NewUpdate(table *Table) *Update { + return &Update{ table: table, } } -func (u Update) nodeize() (tokenizer.Tokenizer, []interface{}) { - return nodeizeClauses(u) +func (u *Update) Set(assigns ...Assign) *Set { + s := NewSet(assigns...) + Link(u, s) + return s } -func (u Update) self() (tokenizer.Tokenizer, []interface{}) { +func (u *Update) nodeize() (tokenizer.Tokenizer, []interface{}) { middle, values := u.table.nodeize() return tokenizer.NewContainer( tokenizer.NewLine(token.Word(keyword.Update)), @@ -29,13 +31,3 @@ func (u Update) self() (tokenizer.Tokenizer, []interface{}) { middle, ), values } - -func (u Update) previous() Clause { - return u.prev -} - -func (u Update) Set(assigns ...Assign) Set { - s := NewSet(assigns...) - s.prev = u - return s -} diff --git a/statement/using.go b/statement/using.go index d1dca91..73537fb 100644 --- a/statement/using.go +++ b/statement/using.go @@ -7,65 +7,49 @@ import ( ) type Using struct { - join Join + Prev + Next column Column } -func NewUsing(column Column) Using { - return Using{ +func NewUsing(column Column) *Using { + return &Using{ column: column, } } -func (u Using) Join(table TableOrAlias) Join { +func (u *Using) Join(table TableOrAlias) *Join { j := NewJoin(table) - j.prev = u + Link(u, j) return j } -func (u Using) InnerJoin(table TableOrAlias) Join { +func (u *Using) InnerJoin(table TableOrAlias) *Join { j := NewInnerJoin(table) - j.prev = u + Link(u, j) return j } -func (u Using) LeftJoin(table TableOrAlias) Join { +func (u *Using) LeftJoin(table TableOrAlias) *Join { j := NewLeftJoin(table) - j.prev = u + Link(u, j) return j } -func (u Using) RightJoin(table TableOrAlias) Join { +func (u *Using) RightJoin(table TableOrAlias) *Join { j := NewRightJoin(table) - j.prev = u + Link(u, j) return j } -func (u Using) nodeize() (tokenizer.Tokenizer, []interface{}) { - return nodeizeJoiners(u) -} - -func (u Using) self() (tokenizer.Tokenizer, []interface{}) { - t1, v1 := u.join.self() - t2, v2 := u.column.nodeize() - return tokenizer.ConcatTokenizers( - t1, - t2, - tokenizer.NewLine( - token.Space, - token.Word(keyword.Using), - token.Space, - ), - ), append(v1, v2...) -} - -func (u Using) previous() Joiner { - return u.join.previous() +func (u *Using) nodeize() (tokenizer.Tokenizer, []interface{}) { + t1, v1 := u.column.nodeize() + return t1.Prepend(token.Word(keyword.Using)), v1 } // isTableOrAliasOrJoiner always returns true. // This method exists only to implement the interface TableOrAliasOrJoiner. // This is a shit of duck typing, but anyway it works. -func (u Using) isTableOrAliasOrJoiner() bool { +func (u *Using) isTableOrAliasOrJoiner() bool { return true } diff --git a/statement/using_test.go b/statement/using_test.go index 6bd406a..1b35497 100644 --- a/statement/using_test.go +++ b/statement/using_test.go @@ -20,8 +20,8 @@ func TestUsing(t *testing.T) { statement.NewUsing( statement.NewColumn("id"), ), - " USING id", - `> USING id + "USING id", + `> USING id `, nil, }, @@ -31,8 +31,8 @@ func TestUsing(t *testing.T) { ).Join( statement.NewTable("bar"), ), - " USING id JOIN bar", - `> USING id + "USING id JOIN bar", + `> USING id > JOIN bar `, nil, @@ -43,8 +43,8 @@ func TestUsing(t *testing.T) { ).InnerJoin( statement.NewTable("bar"), ), - " USING id INNER JOIN bar", - `> USING id + "USING id INNER JOIN bar", + `> USING id > INNER JOIN bar `, nil, @@ -55,8 +55,8 @@ func TestUsing(t *testing.T) { ).LeftJoin( statement.NewTable("bar"), ), - " USING id LEFT JOIN bar", - `> USING id + "USING id LEFT JOIN bar", + `> USING id > LEFT JOIN bar `, nil, @@ -67,8 +67,8 @@ func TestUsing(t *testing.T) { ).RightJoin( statement.NewTable("bar"), ), - " USING id RIGHT JOIN bar", - `> USING id + "USING id RIGHT JOIN bar", + `> USING id > RIGHT JOIN bar `, nil, @@ -78,7 +78,8 @@ func TestUsing(t *testing.T) { Join(statement.NewTable("bar")).Using(statement.NewColumn("id")), `foo AS "a" JOIN bar USING id`, `> foo AS "a" -> JOIN bar USING id +> JOIN bar +> USING id `, nil, }, diff --git a/statement/values.go b/statement/values.go index 1c89a4c..2cd6ffd 100644 --- a/statement/values.go +++ b/statement/values.go @@ -7,21 +7,17 @@ import ( ) type Values struct { - prev Clause + Next paramses []Params } -func NewValues(paramses ...Params) Values { - return Values{ +func NewValues(paramses ...Params) *Values { + return &Values{ paramses: paramses, } } -func (v Values) nodeize() (tokenizer.Tokenizer, []interface{}) { - return nodeizeClauses(v) -} - -func (v Values) self() (tokenizer.Tokenizer, []interface{}) { +func (v *Values) nodeize() (tokenizer.Tokenizer, []interface{}) { tokenizers := make(tokenizer.Tokenizers, len(v.paramses)) values := []interface{}{} for i, p := range v.paramses { @@ -35,33 +31,20 @@ func (v Values) self() (tokenizer.Tokenizer, []interface{}) { ).SetMiddle( tokenizer.NewTokenizers(tokenizers...).Prefix( token.Comma, - token.Space, ), ), values } -func (v Values) previous() Clause { - return v.prev -} - type DefaultValues struct { - prev Clause + Next } -func NewDefaultValues() DefaultValues { - return DefaultValues{} +func NewDefaultValues() *DefaultValues { + return &DefaultValues{} } -func (v DefaultValues) nodeize() (tokenizer.Tokenizer, []interface{}) { - return nodeizeClauses(v) -} - -func (v DefaultValues) self() (tokenizer.Tokenizer, []interface{}) { +func (v *DefaultValues) nodeize() (tokenizer.Tokenizer, []interface{}) { return tokenizer.NewContainer( tokenizer.NewLine(token.Word(keyword.DefaultValues)), ), nil } - -func (v DefaultValues) previous() Clause { - return v.prev -} diff --git a/statement/where.go b/statement/where.go index 4cef14d..9c79333 100644 --- a/statement/where.go +++ b/statement/where.go @@ -7,47 +7,40 @@ import ( ) type Where struct { - prev Clause + Prev + Next operation ComparisonOrLogicalOperation } -func NewWhere(operation ComparisonOrLogicalOperation) Where { - return Where{ +func NewWhere(operation ComparisonOrLogicalOperation) *Where { + return &Where{ operation: operation, } } -func (w Where) nodeize() (tokenizer.Tokenizer, []interface{}) { - return nodeizeClauses(w) -} - -func (w Where) self() (tokenizer.Tokenizer, []interface{}) { - middle, values := w.operation.nodeize() - return tokenizer.NewContainer( - tokenizer.NewLine(token.Word(keyword.Where)), - ).SetMiddle( - middle, - ), values -} - -func (w Where) previous() Clause { - return w.prev -} - -func (w Where) GroupBy(column Column, columns ...Column) GroupBy { +func (w *Where) GroupBy(column Column, columns ...Column) *GroupBy { g := NewGroupBy(column, columns...) - g.prev = w + Link(w, g) return g } -func (w Where) OrderBy(orders ...Order) OrderBy { +func (w *Where) OrderBy(orders ...Order) *OrderBy { o := NewOrderBy(orders...) - o.prev = w + Link(w, o) return o } -func (w Where) Limit(count int) Limit { +func (w *Where) Limit(count int) *Limit { l := NewLimit(count) - l.prev = w + Link(w, l) return l } + +func (w *Where) nodeize() (tokenizer.Tokenizer, []interface{}) { + middle, values := w.operation.nodeize() + return tokenizer.NewContainer( + tokenizer.NewLine(token.Word(keyword.Where)), + ).SetMiddle( + middle, + ), values +} diff --git a/statement/where_test.go b/statement/where_test.go index 5137dbd..c2d0416 100644 --- a/statement/where_test.go +++ b/statement/where_test.go @@ -9,13 +9,6 @@ import ( "github.com/minodisk/sqlabble/statement" ) -func TestWhereType(t *testing.T) { - w := statement.Where{} - if _, ok := interface{}(w).(statement.Clause); !ok { - t.Errorf("%s should implement statement.Clause", w) - } -} - func TestWhereOperation(t *testing.T) { for i, c := range []struct { statement statement.Statement diff --git a/token/debugger.go b/token/debugger.go new file mode 100644 index 0000000..2b0bfd3 --- /dev/null +++ b/token/debugger.go @@ -0,0 +1,14 @@ +package token + +import ( + "fmt" + "strings" +) + +func Dump(tokens Tokens) string { + strs := make([]string, len(tokens)) + for i, t := range tokens { + strs[i] = fmt.Sprintf("[%s]", t.Debug()) + } + return strings.Join(strs, "") +} diff --git a/token/generator.go b/token/generator.go new file mode 100644 index 0000000..299207c --- /dev/null +++ b/token/generator.go @@ -0,0 +1,77 @@ +package token + +// Generate converts the tokens to text according to format. +func Generate(tokens Tokens, format Format) string { + if format.IsBreaking { + return generateWithLineBreak(tokens, format).String(format) + } + return generateWithoutLineBreak(tokens, format).String(format) +} + +func generateWithLineBreak(tokens Tokens, format Format) Tokens { + ts := Tokens{} + last := len(tokens) - 1 + for i, t := range tokens { + var next Token + if i != last { + next = tokens[i+1] + } + switch t { + case LineStart, Indent, LineEnd, QuoteStart, ParenStart, FuncParenStart: + ts = append(ts, t) + continue + default: + switch next { + case LineEnd, Comma, QuoteEnd, ParenEnd, FuncParenStart, FuncParenEnd: + ts = append(ts, t) + continue + default: + ts = append(ts, t, Space) + continue + } + } + ts = append(ts, t) + } + return ts +} + +func generateWithoutLineBreak(tokens Tokens, format Format) Tokens { + ts1 := Tokens{} + for _, t := range tokens { + switch t { + case LineStart, LineEnd, Indent: + continue + default: + ts1 = append(ts1, t) + } + } + + ts2 := Tokens{} + last := len(ts1) - 1 + for i, t := range ts1 { + var next Token + if i != last { + next = ts1[i+1] + } + switch t { + case QuoteStart, ParenStart, FuncParenStart: + ts2 = append(ts2, t) + continue + default: + if i == last { + ts2 = append(ts2, t) + continue + } + switch next { + case Comma, QuoteEnd, ParenEnd, FuncParenStart, FuncParenEnd: + ts2 = append(ts2, t) + continue + default: + ts2 = append(ts2, t, Space) + continue + } + } + } + + return ts2 +} diff --git a/token/interfaces.go b/token/interfaces.go index ef6ab80..d73ee2d 100644 --- a/token/interfaces.go +++ b/token/interfaces.go @@ -2,4 +2,5 @@ package token type Token interface { String(Format) string + Debug() string } diff --git a/token/printer.go b/token/printer.go deleted file mode 100644 index c6189e3..0000000 --- a/token/printer.go +++ /dev/null @@ -1,40 +0,0 @@ -package token - -// Print converts the tokens to text according to format. -// Print behaves in contrast to the scan in -// the implementation of a parser. -func Print(tokens Tokens, format Format) string { - if format.IsBreaking { - return tokens.String(format) - } - - ts := Tokens{} - var prev Token - for i, t := range tokens { - switch t { - case Indent: - continue - case LineEnd: - continue - case LineStart: - if i == 0 || prev == ParenthesesStart { - continue - } - prev = Space - ts = append(ts, prev) - continue - case ParenthesesEnd: - if prev == Space { - ts = ts[:len(ts)-1] - } - case Comma: - if prev == Space { - ts = ts[:len(ts)-1] - } - } - ts = append(ts, t) - prev = t - } - - return ts.String(format) -} diff --git a/token/token.go b/token/token.go index 1c2fe38..f5bf170 100644 --- a/token/token.go +++ b/token/token.go @@ -2,10 +2,11 @@ package token // Tokens that change behavior by setting Format. var ( - Quote = new(quote) - LineStart = new(lineStart) - LineEnd = new(lineEnd) - Indent = new(indent) + QuoteStart = new(quote) + QuoteEnd = new(quote) + LineStart = new(lineStart) + LineEnd = new(lineEnd) + Indent = new(indent) ) type quote string @@ -14,20 +15,36 @@ func (q quote) String(format Format) string { return format.Quote } +func (q quote) Debug() string { + return "Quote" +} + type lineStart string func (l lineStart) String(format Format) string { return format.Prefix } +func (l lineStart) Debug() string { + return "LineStart" +} + type lineEnd string func (l lineEnd) String(format Format) string { return format.LineEnd } +func (l lineEnd) Debug() string { + return "LineEnd" +} + type indent string func (i indent) String(format Format) string { return format.Indent } + +func (l indent) Debug() string { + return "Indent" +} diff --git a/token/tokens.go b/token/tokens.go index f195447..e1e0046 100644 --- a/token/tokens.go +++ b/token/tokens.go @@ -16,7 +16,7 @@ func PlaceholderTokens(i int) Tokens { tokens := Tokens{} for ; i > 0; i-- { if i > 1 { - tokens = append(tokens, Placeholder, Comma, Space) + tokens = append(tokens, Placeholder, Comma) continue } tokens = append(tokens, Placeholder) diff --git a/token/tokens_test.go b/token/tokens_test.go index 2fa1190..347e5db 100644 --- a/token/tokens_test.go +++ b/token/tokens_test.go @@ -16,9 +16,9 @@ func TestToken(t *testing.T) { }{ { token.Tokens{ - token.Quote, + token.QuoteStart, token.Word("foo"), - token.Quote, + token.QuoteEnd, }, `"foo"`, "`foo`", diff --git a/token/word.go b/token/word.go index 60761ad..b7c26ed 100644 --- a/token/word.go +++ b/token/word.go @@ -1,12 +1,16 @@ package token +import "fmt" + // Tokens that don't change behavior according to Format setting. -const ( - Placeholder = Word("?") - Space = Word(" ") - Comma = Word(",") - ParenthesesStart = Word("(") - ParenthesesEnd = Word(")") +var ( + Space = NewWord(" ") + Placeholder = NewWord("?") + Comma = NewWord(",") + ParenStart = NewWord("(") + ParenEnd = NewWord(")") + FuncParenStart = NewWord("(") + FuncParenEnd = NewWord(")") ) type Word string @@ -20,6 +24,10 @@ func (w Word) String(format Format) string { return string(w) } +func (w Word) Debug() string { + return fmt.Sprintf("Word(%s)", w) +} + func (w Word) Append(tokens ...Token) []Token { return append([]Token{w}, tokens...) } diff --git a/tokenizer/container.go b/tokenizer/container.go index 4ebb31f..9322e62 100644 --- a/tokenizer/container.go +++ b/tokenizer/container.go @@ -10,11 +10,11 @@ type Container struct { func WrapParenthesesLines(lines ...Line) Container { return NewContainer( - NewLine(token.ParenthesesStart), + NewLine(token.ParenStart), ).SetMiddle( NewLines(lines...), ).SetLast( - NewLine(token.ParenthesesEnd), + NewLine(token.ParenEnd), ) } @@ -26,9 +26,9 @@ func NewContainer(first Tokenizer) Container { func NewParentheses(middle Tokenizer) Container { return Container{ - first: NewLine(token.ParenthesesStart), + first: NewLine(token.ParenStart), middle: middle, - last: NewLine(token.ParenthesesEnd), + last: NewLine(token.ParenEnd), } } @@ -81,6 +81,9 @@ func (c Container) FirstLine() (Line, Tokenizer) { } func (c Container) LastLine() (Tokenizer, Line) { + if c.last == nil { + return nil, EmptyLine + } t, l := c.last.LastLine() return NewContainer( c.first,