Skip to content

Commit

Permalink
Simplified Columnar interface
Browse files Browse the repository at this point in the history
  • Loading branch information
aodin committed May 19, 2016
1 parent 932937f commit 146c6cd
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 76 deletions.
32 changes: 19 additions & 13 deletions column.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@ import (
"github.com/aodin/sol/types"
)

// Tabular is the interface that all dialects of a SQL column must implement
type Columnar interface {
// Two methods for neutral SQL element interfaces:
// (1) Require the interface to return the neutral implementation
// (2) Enumerate all the methods an implmentation would require
// Columnar and Tabular both use method (1)
// Name has been left as a legacy shortcut but may be removed
Compiles
Selectable
AddOperator(string) ColumnElem // TODO or Columnar?
Alias() string
As(string) Columnar
FullName() string
IsInvalid() bool
Name() string
Table() Tabular
Type() types.Type
Column() ColumnElem
Table() *TableElem
}

// ColumnElem is a dialect neutral implementation of a SQL column
Expand All @@ -43,16 +44,22 @@ func (col ColumnElem) Alias() string {
return col.alias
}

// As sets an alias for this ColumnElem
func (col ColumnElem) As(alias string) Columnar {
// As sets an alias and returns a copy of the ColumnElem
func (col ColumnElem) As(alias string) ColumnElem {
col.alias = alias
return col
}

// Column returns a copy of its receiver Column. This method implements
// the Columnar interface.
func (col ColumnElem) Column() ColumnElem {
return col
}

// Columns returns the ColumnElem itself in a slice of ColumnElem. This
// method implements the Selectable interface.
func (col ColumnElem) Columns() []Columnar {
return []Columnar{col}
func (col ColumnElem) Columns() []ColumnElem {
return []ColumnElem{col}
}

// Compile produces the dialect specific SQL and adds any parameters
Expand Down Expand Up @@ -127,8 +134,7 @@ func (col ColumnElem) Modify(tabular Tabular) error {
}

// Table returns the column's Table
func (col ColumnElem) Table() Tabular {
// TODO should it return Tabular of *TableElem
func (col ColumnElem) Table() *TableElem {
return col.table
}

Expand Down
20 changes: 13 additions & 7 deletions columns.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
// Columns maps column to name to a ColumnElem. It also maintains an order
// of columns.
type Columns struct {
order []Columnar
c map[string]Columnar
order []ColumnElem
c map[string]ColumnElem
}

func (columns *Columns) add(column Columnar) error {
Expand All @@ -21,18 +21,24 @@ func (columns *Columns) add(column Columnar) error {
column.Name(),
)
}
columns.order = append(columns.order, column)
columns.c[column.Name()] = column
columns.order = append(columns.order, column.Column())
columns.c[column.Name()] = column.Column()
return nil
}

// All returns all columns as ColumnElems in their default order
func (columns Columns) All() []Columnar {
func (columns Columns) All() []ColumnElem {
return columns.order
}

func (columns Columns) Get(name string) Columnar {
return columns.c[name]
// Get returns a ColumnElem - or an invalid ColumnElem if a column
// with the given name does not exist in Columns
func (columns Columns) Get(name string) ColumnElem {
col, ok := columns.c[name]
if !ok {
return InvalidColumn(name, nil)
}
return col
}

// Has returns true if there is a column with the given name in Columns
Expand Down
54 changes: 20 additions & 34 deletions foreignkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ var _ types.Type = FKElem{}
// types.Type interface so it can be used in CREATE TABLE statements.
type FKElem struct {
name string
col Columnar
col ColumnElem
datatype types.Type
table *TableElem // the parent table of the key
references *TableElem // the table the key references
Expand Down Expand Up @@ -125,62 +125,48 @@ func (fk FKElem) OnUpdate(b fkAction) FKElem {

// ForeignKey creates a FKElem from the given name and column/table.
// If given a column, it must already have its table assigned.
// If given a table, it must have one and only one primary key
func ForeignKey(name string, fk Selectable, datatypes ...types.Type) FKElem {
var col Columnar
var table *TableElem
if fk == nil {
log.Panic("sol: inline foreign key was given a nil Selectable")
}
columns := fk.Columns()

// TODO use a switch with a fallthrough to fix repeated logic?
if len(columns) == 0 {
log.Panic(
"sol: inline foreign key Selectable must have at least one column",
)
} else if len(columns) == 1 {
col = columns[0]
if col.Table() == nil || col.Table().Table() == nil {
log.Panic(
"sol: inline foreign key columns must have their table assigned before creation",
)
}
table = col.Table().Table()
} else {
// Simply use the table of the first column
// TODO This is a strange decision that will error silently
// It also needs to call Table() twice to get the dialect neutral
// table implementation
if columns[0].Table() == nil || columns[0].Table().Table() == nil {
log.Panic(
"sol: inline foreign key columns must have their table assigned before creation",
)
}
table = columns[0].Table().Table()
pk := table.PrimaryKey()
var col ColumnElem
switch t := fk.(type) {
case Columnar:
col = t.Column()
case Tabular:
// If the given Selectable was a Table - use its primary key
// TODO Can foreign keys reference multiple columns?
pk := t.Table().PrimaryKey()
if len(pk) != 1 {
log.Panic(
"sol: inline foreign key tables must have one and only one primary key column",
"sol: a table used directly in ForeignKey must have one and only one primary key column",
)
}
col = table.C(pk[0])
col = t.Table().C(pk[0])
default:
log.Panicf("sol: unknown Selectable %T used in ForeignKey", t)
}

if col.IsInvalid() {
log.Panic("sol: referenced column does not exist")
if col.Table() == nil {
log.Panic(
"sol: a column must have a table before being used in ForeignKey",
)
}

// Allow an overriding datatype
datatype := col.Type()
if len(datatypes) > 0 {
// TODO what should happen if multiple datatypes are given?
datatype = datatypes[0]
}

return FKElem{
name: name,
col: col,
datatype: datatype,
references: table,
references: col.Table(),
}
}

Expand Down
2 changes: 1 addition & 1 deletion function.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package sol

func Function(name string, col Columnar) ColumnElem {
return col.AddOperator(name)
return col.Column().AddOperator(name)
}

func Avg(expression Columnar) ColumnElem {
Expand Down
7 changes: 4 additions & 3 deletions insert.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ var (
type InsertStmt struct {
Stmt
table Tabular
columns []Columnar
columns []ColumnElem // TODO Columns
args []interface{}
fields fields
}
Expand Down Expand Up @@ -343,7 +343,8 @@ func isEmptyValue(v reflect.Value) bool {
}

// TODO error if there was no match?
func removeColumn(columns []Columnar, name string) []Columnar {
// TODO should be a method of ColumnMap
func removeColumn(columns []ColumnElem, name string) []ColumnElem {
for i, col := range columns {
if col.Name() == name {
return append(columns[:i], columns[i+1:]...)
Expand Down Expand Up @@ -373,7 +374,7 @@ func valuesMap(stmt InsertStmt, values Values) (fields, error) {
// Insert creates an INSERT statement for the given columns. There must be at
// least one column and all columns must belong to the same table.
func Insert(selections ...Selectable) (stmt InsertStmt) {
var columns []Columnar
var columns []ColumnElem
for _, selection := range selections {
if selection == nil {
stmt.AddMeta("sol: INSERT received a nil selectable - do the columns or tables you selected exist?")
Expand Down
3 changes: 3 additions & 0 deletions postgres/column.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ const (
Difference = "-"
)

// ColumnElem is the postgres dialect's implementation of a SQL column
type ColumnElem struct {
sol.ColumnElem
}

var _ sol.Columnar = ColumnElem{}

func (col ColumnElem) operator(op string, param interface{}) sol.BinaryClause {
return sol.BinaryClause{
Pre: col,
Expand Down
2 changes: 1 addition & 1 deletion postgres/insert.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type InsertStmt struct {
conflictTargets []string
values sol.Values
where sol.Clause
returning []sol.Columnar
returning []sol.ColumnElem // TODO ColumnMap
}

// String outputs the parameter-less INSERT ... RETURNING statement in the
Expand Down
4 changes: 3 additions & 1 deletion postgres/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ type TableElem struct {
*sol.TableElem
}

var _ sol.Tabular = &TableElem{}

// Column will return a postgres specific ColumnElem rather than a generic
// ColumnElem
func (table TableElem) Column(name string) ColumnElem {
Expand All @@ -22,7 +24,7 @@ func (table TableElem) Column(name string) ColumnElem {
}
// TODO invalid column? Prevent the mixing of column types?
}
return ColumnElem{ColumnElem: sol.InvalidColumn(name, table.TableElem)}
return ColumnElem{ColumnElem: sol.InvalidColumn(name, table)}
}

// C is an alias for Column
Expand Down
21 changes: 13 additions & 8 deletions select.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ import (
// Selectable is an interface that allows both tables and columns to be
// selected. It is implemented by TableElem and ColumnElem.
type Selectable interface {
Columns() []Columnar
Columns() []ColumnElem
}

// SelectStmt is the internal representation of an SQL SELECT statement.
type SelectStmt struct {
ConditionalStmt
tables []Tabular
columns []Columnar
columns []ColumnElem // TODO Columns
joins []JoinClause
groupBy []Columnar
groupBy []ColumnElem // TODO Columns
having Clause
orderBy []OrderedColumn
isDistinct bool
distincts []Columnar
distincts []ColumnElem // TODO Columns
limit int
offset int
}
Expand All @@ -35,7 +35,7 @@ func (stmt SelectStmt) String() string {
}

// TODO where should this function live? Also used in postgres.InsertStmt
func CompileColumns(columns []Columnar) []string {
func CompileColumns(columns []ColumnElem) []string {
names := make([]string, len(columns))
for i, col := range columns {
// Ignore dialect, parameters and error?
Expand Down Expand Up @@ -163,7 +163,10 @@ func (stmt SelectStmt) All() SelectStmt {
// column are provided, the clause will be compiled as a DISTINCT ON.
func (stmt SelectStmt) Distinct(columns ...Columnar) SelectStmt {
stmt.isDistinct = true
stmt.distincts = columns
// TODO ColumnMap method
for _, column := range columns {
stmt.distincts = append(stmt.distincts, column.Column())
}
return stmt
}

Expand Down Expand Up @@ -228,7 +231,9 @@ func (stmt SelectStmt) Where(conditions ...Clause) SelectStmt {
// is allowed per statement. Additional calls to GroupBy will overwrite the
// existing GROUP BY clause.
func (stmt SelectStmt) GroupBy(columns ...Columnar) SelectStmt {
stmt.groupBy = columns
for _, column := range columns {
stmt.groupBy = append(stmt.groupBy, column.Column())
}
return stmt
}

Expand Down Expand Up @@ -307,7 +312,7 @@ func SelectTable(table Tabular, selects ...Selectable) (stmt SelectStmt) {

// Select generates a new SELECT statement from the given columns and tables.
func Select(selections ...Selectable) (stmt SelectStmt) {
columns := make([]Columnar, 0)
columns := []ColumnElem{}
for _, selection := range selections {
if selection == nil {
stmt.AddMeta("sol: received a nil selectable in Select()")
Expand Down
16 changes: 8 additions & 8 deletions table.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import (
"github.com/aodin/sol/types"
)

// Tabular is the interface that all dialects of SQL table must implement
// Tabular is the interface that all dialects of a SQL table must implement
type Tabular interface {
// Two methods for neutral SQL element interfaces:
// (1) Require the interface to return the neutral implementation
// (2) Enumerate all the methods an implmentation would require
// Columnar and Tabular both use method (1)
// Name has been left as a legacy shortcut but may be removed
Selectable
Name() string
// TODO There are two choices for neutral SQL element interfaces:
// 1. Require the interface to return the neutral implementation
// 2. Enumerate all the methods an implmentation would require
// For now, Tabular uses (1) and Columnar uses (2) - this should
// be standardized
Table() *TableElem
}

Expand Down Expand Up @@ -53,7 +53,7 @@ func (table TableElem) C(name string) ColumnElem {
}

// Columns returns all the table columns in the original schema order
func (table TableElem) Columns() []Columnar {
func (table TableElem) Columns() []ColumnElem {
return table.columns.order
}

Expand Down Expand Up @@ -132,7 +132,7 @@ func Table(name string, modifiers ...Modifier) *TableElem {
}
table := &TableElem{
name: name,
columns: Columns{c: make(map[string]Columnar)},
columns: Columns{c: make(map[string]ColumnElem)}, // TODO ColumnMap
}
for _, modifier := range modifiers {
if err := modifier.Modify(table); err != nil {
Expand Down

0 comments on commit 146c6cd

Please sign in to comment.