2019-03-02 12:34:08 +01:00
|
|
|
// Modeling of tables. This is where query preparation starts
|
|
|
|
|
|
|
|
|
|
package sqlbuilder
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"fmt"
|
|
|
|
|
"github.com/dropbox/godropbox/errors"
|
|
|
|
|
)
|
|
|
|
|
|
2019-03-30 10:17:32 +01:00
|
|
|
// The sql tableName read interface. NOTE: NATURAL JOINs, and join "USING" clause
|
2019-03-02 12:34:08 +01:00
|
|
|
// are not supported.
|
|
|
|
|
type ReadableTable interface {
|
2019-03-30 10:17:32 +01:00
|
|
|
// Returns the list of columns that are in the current tableName expression.
|
2019-03-31 14:07:58 +02:00
|
|
|
Columns() []Column
|
2019-03-02 12:34:08 +01:00
|
|
|
|
2019-04-03 11:03:07 +02:00
|
|
|
//Column(name string) Column
|
2019-03-30 10:17:32 +01:00
|
|
|
|
|
|
|
|
// Generates the sql string for the current tableName expression. Note: the
|
2019-03-02 12:34:08 +01:00
|
|
|
// generated string may not be a valid/executable sql statement.
|
2019-03-09 09:52:03 +01:00
|
|
|
SerializeSql(out *bytes.Buffer) error
|
2019-03-02 12:34:08 +01:00
|
|
|
|
2019-03-30 10:17:32 +01:00
|
|
|
// Generates a select query on the current tableName.
|
2019-04-03 11:03:07 +02:00
|
|
|
SELECT(projections ...Projection) SelectStatement
|
2019-03-02 12:34:08 +01:00
|
|
|
|
2019-03-30 10:17:32 +01:00
|
|
|
// Creates a inner join tableName expression using onCondition.
|
2019-04-03 11:03:07 +02:00
|
|
|
INNER_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable
|
2019-03-02 12:34:08 +01:00
|
|
|
|
2019-03-31 14:07:58 +02:00
|
|
|
//InnerJoinUsing(table ReadableTable, col1 Column, col2 Column) ReadableTable
|
2019-03-15 21:45:10 +01:00
|
|
|
|
2019-03-30 10:17:32 +01:00
|
|
|
// Creates a left join tableName expression using onCondition.
|
2019-03-02 12:34:08 +01:00
|
|
|
LeftJoinOn(table ReadableTable, onCondition BoolExpression) ReadableTable
|
|
|
|
|
|
2019-03-30 10:17:32 +01:00
|
|
|
// Creates a right join tableName expression using onCondition.
|
2019-03-02 12:34:08 +01:00
|
|
|
RightJoinOn(table ReadableTable, onCondition BoolExpression) ReadableTable
|
2019-03-16 14:02:45 +01:00
|
|
|
|
2019-04-03 11:03:07 +02:00
|
|
|
FULL_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable
|
2019-03-16 14:02:45 +01:00
|
|
|
|
|
|
|
|
CrossJoin(table ReadableTable) ReadableTable
|
2019-03-02 12:34:08 +01:00
|
|
|
}
|
|
|
|
|
|
2019-03-30 10:17:32 +01:00
|
|
|
// The sql tableName write interface.
|
2019-03-02 12:34:08 +01:00
|
|
|
type WritableTable interface {
|
2019-03-30 10:17:32 +01:00
|
|
|
// Returns the list of columns that are in the tableName.
|
2019-03-31 14:07:58 +02:00
|
|
|
Columns() []Column
|
2019-03-02 12:34:08 +01:00
|
|
|
|
2019-03-30 10:17:32 +01:00
|
|
|
// Generates the sql string for the current tableName expression. Note: the
|
2019-03-02 12:34:08 +01:00
|
|
|
// generated string may not be a valid/executable sql statement.
|
2019-03-30 10:17:32 +01:00
|
|
|
// The database is the name of the database the tableName is on
|
2019-03-09 09:52:03 +01:00
|
|
|
SerializeSql(out *bytes.Buffer) error
|
2019-03-02 12:34:08 +01:00
|
|
|
|
2019-03-31 14:07:58 +02:00
|
|
|
Insert(columns ...Column) InsertStatement
|
2019-03-02 12:34:08 +01:00
|
|
|
Update() UpdateStatement
|
|
|
|
|
Delete() DeleteStatement
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-30 10:17:32 +01:00
|
|
|
// Defines a physical tableName in the database that is both readable and writable.
|
2019-03-02 12:34:08 +01:00
|
|
|
// This function will panic if name is not valid
|
2019-03-31 14:07:58 +02:00
|
|
|
func NewTable(schemaName, name string, columns ...Column) *Table {
|
2019-03-02 12:34:08 +01:00
|
|
|
if !validIdentifierName(name) {
|
2019-03-30 10:17:32 +01:00
|
|
|
panic("Invalid tableName name")
|
2019-03-02 12:34:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t := &Table{
|
2019-03-09 09:52:03 +01:00
|
|
|
schemaName: schemaName,
|
2019-03-02 12:34:08 +01:00
|
|
|
name: name,
|
|
|
|
|
columns: columns,
|
2019-03-31 14:07:58 +02:00
|
|
|
columnLookup: make(map[string]Column),
|
2019-03-02 12:34:08 +01:00
|
|
|
}
|
|
|
|
|
for _, c := range columns {
|
|
|
|
|
err := c.setTableName(name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
t.columnLookup[c.Name()] = c
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(columns) == 0 {
|
|
|
|
|
panic(fmt.Sprintf("Table %s has no columns", name))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return t
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Table struct {
|
2019-03-09 09:52:03 +01:00
|
|
|
schemaName string
|
2019-03-02 12:34:08 +01:00
|
|
|
name string
|
2019-03-16 20:41:06 +01:00
|
|
|
alias string
|
2019-03-31 14:07:58 +02:00
|
|
|
columns []Column
|
|
|
|
|
columnLookup map[string]Column
|
2019-03-02 12:34:08 +01:00
|
|
|
// If not empty, the name of the index to force
|
|
|
|
|
forcedIndex string
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-30 10:17:32 +01:00
|
|
|
// Returns the specified column, or errors if it doesn't exist in the tableName
|
2019-03-31 14:07:58 +02:00
|
|
|
func (t *Table) getColumn(name string) (Column, error) {
|
2019-03-02 12:34:08 +01:00
|
|
|
if c, ok := t.columnLookup[name]; ok {
|
|
|
|
|
return c, nil
|
|
|
|
|
}
|
2019-03-30 10:17:32 +01:00
|
|
|
return nil, errors.Newf("No such column '%s' in tableName '%s'", name, t.name)
|
2019-03-02 12:34:08 +01:00
|
|
|
}
|
|
|
|
|
|
2019-03-31 14:07:58 +02:00
|
|
|
func (t *Table) Column(name string) Column {
|
2019-03-30 10:17:32 +01:00
|
|
|
return &baseColumn{
|
|
|
|
|
name: name,
|
|
|
|
|
nullable: NotNullable,
|
|
|
|
|
tableName: t.name,
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-03-02 12:34:08 +01:00
|
|
|
|
2019-03-31 14:07:58 +02:00
|
|
|
// Returns all expresssion for a tableName as a slice of projections
|
|
|
|
|
func (t *Table) Projections() []Expression {
|
|
|
|
|
result := make([]Expression, 0)
|
2019-03-02 12:34:08 +01:00
|
|
|
|
|
|
|
|
for _, col := range t.columns {
|
2019-03-30 10:17:32 +01:00
|
|
|
col.Asc()
|
2019-03-02 12:34:08 +01:00
|
|
|
result = append(result, col)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-16 20:41:06 +01:00
|
|
|
func (t *Table) SetAlias(alias string) {
|
|
|
|
|
t.alias = alias
|
|
|
|
|
|
|
|
|
|
for _, c := range t.columns {
|
|
|
|
|
err := c.setTableName(alias)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-30 10:17:32 +01:00
|
|
|
// Returns the tableName's name in the database
|
2019-03-02 12:34:08 +01:00
|
|
|
func (t *Table) Name() string {
|
|
|
|
|
return t.name
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-09 09:52:03 +01:00
|
|
|
func (t *Table) SchemaName() string {
|
|
|
|
|
return t.schemaName
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-30 10:17:32 +01:00
|
|
|
// Returns a list of the tableName's columns
|
2019-03-31 14:07:58 +02:00
|
|
|
func (t *Table) Columns() []Column {
|
2019-03-02 12:34:08 +01:00
|
|
|
return t.columns
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-30 10:17:32 +01:00
|
|
|
// Returns a copy of this tableName, but with the specified index forced.
|
2019-03-02 12:34:08 +01:00
|
|
|
func (t *Table) ForceIndex(index string) *Table {
|
|
|
|
|
newTable := *t
|
|
|
|
|
newTable.forcedIndex = index
|
|
|
|
|
return &newTable
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-30 10:17:32 +01:00
|
|
|
// Generates the sql string for the current tableName expression. Note: the
|
2019-03-02 12:34:08 +01:00
|
|
|
// generated string may not be a valid/executable sql statement.
|
2019-03-09 09:52:03 +01:00
|
|
|
func (t *Table) SerializeSql(out *bytes.Buffer) error {
|
|
|
|
|
if !validIdentifierName(t.schemaName) {
|
|
|
|
|
return errors.New("Invalid database name specified")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, _ = out.WriteString(t.schemaName)
|
2019-03-03 17:54:43 +01:00
|
|
|
_, _ = out.WriteString(".")
|
2019-03-02 12:34:08 +01:00
|
|
|
_, _ = out.WriteString(t.Name())
|
|
|
|
|
|
2019-03-16 20:41:06 +01:00
|
|
|
if len(t.alias) > 0 {
|
|
|
|
|
out.WriteString(" AS ")
|
|
|
|
|
out.WriteString(t.alias)
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-02 12:34:08 +01:00
|
|
|
if t.forcedIndex != "" {
|
|
|
|
|
if !validIdentifierName(t.forcedIndex) {
|
|
|
|
|
return errors.Newf("'%s' is not a valid identifier for an index", t.forcedIndex)
|
|
|
|
|
}
|
2019-03-09 09:52:03 +01:00
|
|
|
_, _ = out.WriteString(" FORCE INDEX (")
|
2019-03-02 12:34:08 +01:00
|
|
|
_, _ = out.WriteString(t.forcedIndex)
|
2019-03-09 09:52:03 +01:00
|
|
|
_, _ = out.WriteString(")")
|
2019-03-02 12:34:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-30 10:17:32 +01:00
|
|
|
// Generates a select query on the current tableName.
|
2019-04-03 11:03:07 +02:00
|
|
|
func (t *Table) SELECT(projections ...Projection) SelectStatement {
|
2019-03-02 12:34:08 +01:00
|
|
|
return newSelectStatement(t, projections)
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-30 10:17:32 +01:00
|
|
|
// Creates a inner join tableName expression using onCondition.
|
2019-04-03 11:03:07 +02:00
|
|
|
func (t *Table) INNER_JOIN(
|
2019-03-02 12:34:08 +01:00
|
|
|
table ReadableTable,
|
|
|
|
|
onCondition BoolExpression) ReadableTable {
|
|
|
|
|
|
|
|
|
|
return InnerJoinOn(t, table, onCondition)
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-31 14:07:58 +02:00
|
|
|
//func (t *Table) InnerJoinUsing(
|
|
|
|
|
// table ReadableTable,
|
|
|
|
|
// col1 Column,
|
|
|
|
|
// col2 Column) ReadableTable {
|
|
|
|
|
//
|
2019-04-03 11:03:07 +02:00
|
|
|
// return INNER_JOIN(t, table, col1.Eq(col2))
|
2019-03-31 14:07:58 +02:00
|
|
|
//}
|
2019-03-15 21:45:10 +01:00
|
|
|
|
2019-03-30 10:17:32 +01:00
|
|
|
// Creates a left join tableName expression using onCondition.
|
2019-03-02 12:34:08 +01:00
|
|
|
func (t *Table) LeftJoinOn(
|
|
|
|
|
table ReadableTable,
|
|
|
|
|
onCondition BoolExpression) ReadableTable {
|
|
|
|
|
|
|
|
|
|
return LeftJoinOn(t, table, onCondition)
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-30 10:17:32 +01:00
|
|
|
// Creates a right join tableName expression using onCondition.
|
2019-03-02 12:34:08 +01:00
|
|
|
func (t *Table) RightJoinOn(
|
|
|
|
|
table ReadableTable,
|
|
|
|
|
onCondition BoolExpression) ReadableTable {
|
|
|
|
|
|
|
|
|
|
return RightJoinOn(t, table, onCondition)
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-03 11:03:07 +02:00
|
|
|
func (t *Table) FULL_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable {
|
2019-03-31 14:07:58 +02:00
|
|
|
return FullJoin(t, table, onCondition)
|
2019-03-16 14:02:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Table) CrossJoin(table ReadableTable) ReadableTable {
|
|
|
|
|
return CrossJoin(t, table)
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-31 14:07:58 +02:00
|
|
|
func (t *Table) Insert(columns ...Column) InsertStatement {
|
2019-03-02 12:34:08 +01:00
|
|
|
return newInsertStatement(t, columns...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Table) Update() UpdateStatement {
|
|
|
|
|
return newUpdateStatement(t)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Table) Delete() DeleteStatement {
|
|
|
|
|
return newDeleteStatement(t)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type joinType int
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
INNER_JOIN joinType = iota
|
|
|
|
|
LEFT_JOIN
|
|
|
|
|
RIGHT_JOIN
|
2019-03-16 14:02:45 +01:00
|
|
|
FULL_JOIN
|
|
|
|
|
CROSS_JOIN
|
2019-03-02 12:34:08 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Join expressions are pseudo readable tables.
|
|
|
|
|
type joinTable struct {
|
|
|
|
|
lhs ReadableTable
|
|
|
|
|
rhs ReadableTable
|
|
|
|
|
join_type joinType
|
|
|
|
|
onCondition BoolExpression
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newJoinTable(
|
|
|
|
|
lhs ReadableTable,
|
|
|
|
|
rhs ReadableTable,
|
|
|
|
|
join_type joinType,
|
|
|
|
|
onCondition BoolExpression) ReadableTable {
|
|
|
|
|
|
|
|
|
|
return &joinTable{
|
|
|
|
|
lhs: lhs,
|
|
|
|
|
rhs: rhs,
|
|
|
|
|
join_type: join_type,
|
|
|
|
|
onCondition: onCondition,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func InnerJoinOn(
|
|
|
|
|
lhs ReadableTable,
|
|
|
|
|
rhs ReadableTable,
|
|
|
|
|
onCondition BoolExpression) ReadableTable {
|
|
|
|
|
|
|
|
|
|
return newJoinTable(lhs, rhs, INNER_JOIN, onCondition)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func LeftJoinOn(
|
|
|
|
|
lhs ReadableTable,
|
|
|
|
|
rhs ReadableTable,
|
|
|
|
|
onCondition BoolExpression) ReadableTable {
|
|
|
|
|
|
|
|
|
|
return newJoinTable(lhs, rhs, LEFT_JOIN, onCondition)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func RightJoinOn(
|
|
|
|
|
lhs ReadableTable,
|
|
|
|
|
rhs ReadableTable,
|
|
|
|
|
onCondition BoolExpression) ReadableTable {
|
|
|
|
|
|
|
|
|
|
return newJoinTable(lhs, rhs, RIGHT_JOIN, onCondition)
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-16 14:02:45 +01:00
|
|
|
func FullJoin(
|
|
|
|
|
lhs ReadableTable,
|
|
|
|
|
rhs ReadableTable,
|
|
|
|
|
onCondition BoolExpression) ReadableTable {
|
|
|
|
|
|
|
|
|
|
return newJoinTable(lhs, rhs, FULL_JOIN, onCondition)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func CrossJoin(
|
|
|
|
|
lhs ReadableTable,
|
|
|
|
|
rhs ReadableTable) ReadableTable {
|
|
|
|
|
|
|
|
|
|
return newJoinTable(lhs, rhs, CROSS_JOIN, nil)
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-31 14:07:58 +02:00
|
|
|
func (t *joinTable) Columns() []Column {
|
|
|
|
|
columns := make([]Column, 0)
|
2019-03-02 12:34:08 +01:00
|
|
|
columns = append(columns, t.lhs.Columns()...)
|
|
|
|
|
columns = append(columns, t.rhs.Columns()...)
|
|
|
|
|
|
|
|
|
|
return columns
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-31 14:07:58 +02:00
|
|
|
func (t *joinTable) Column(name string) Column {
|
2019-03-30 10:17:32 +01:00
|
|
|
panic("Not implemented")
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-09 09:52:03 +01:00
|
|
|
func (t *joinTable) SerializeSql(out *bytes.Buffer) (err error) {
|
2019-03-02 12:34:08 +01:00
|
|
|
|
|
|
|
|
if t.lhs == nil {
|
|
|
|
|
return errors.Newf("nil lhs. Generated sql: %s", out.String())
|
|
|
|
|
}
|
|
|
|
|
if t.rhs == nil {
|
|
|
|
|
return errors.Newf("nil rhs. Generated sql: %s", out.String())
|
|
|
|
|
}
|
2019-03-16 14:02:45 +01:00
|
|
|
if t.onCondition == nil && t.join_type != CROSS_JOIN {
|
2019-03-02 12:34:08 +01:00
|
|
|
return errors.Newf("nil onCondition. Generated sql: %s", out.String())
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-09 09:52:03 +01:00
|
|
|
if err = t.lhs.SerializeSql(out); err != nil {
|
2019-03-02 12:34:08 +01:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch t.join_type {
|
|
|
|
|
case INNER_JOIN:
|
|
|
|
|
_, _ = out.WriteString(" JOIN ")
|
|
|
|
|
case LEFT_JOIN:
|
|
|
|
|
_, _ = out.WriteString(" LEFT JOIN ")
|
|
|
|
|
case RIGHT_JOIN:
|
|
|
|
|
_, _ = out.WriteString(" RIGHT JOIN ")
|
2019-03-16 14:02:45 +01:00
|
|
|
case FULL_JOIN:
|
|
|
|
|
out.WriteString(" FULL JOIN ")
|
|
|
|
|
case CROSS_JOIN:
|
|
|
|
|
out.WriteString(" CROSS JOIN ")
|
2019-03-02 12:34:08 +01:00
|
|
|
}
|
|
|
|
|
|
2019-03-09 09:52:03 +01:00
|
|
|
if err = t.rhs.SerializeSql(out); err != nil {
|
2019-03-02 12:34:08 +01:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-16 14:02:45 +01:00
|
|
|
if t.onCondition != nil {
|
|
|
|
|
_, _ = out.WriteString(" ON ")
|
|
|
|
|
if err = t.onCondition.SerializeSql(out); err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
2019-03-02 12:34:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-03 11:03:07 +02:00
|
|
|
func (t *joinTable) SELECT(projections ...Projection) SelectStatement {
|
2019-03-02 12:34:08 +01:00
|
|
|
return newSelectStatement(t, projections)
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-03 11:03:07 +02:00
|
|
|
func (t *joinTable) INNER_JOIN(
|
2019-03-02 12:34:08 +01:00
|
|
|
table ReadableTable,
|
|
|
|
|
onCondition BoolExpression) ReadableTable {
|
|
|
|
|
|
|
|
|
|
return InnerJoinOn(t, table, onCondition)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *joinTable) LeftJoinOn(
|
|
|
|
|
table ReadableTable,
|
|
|
|
|
onCondition BoolExpression) ReadableTable {
|
|
|
|
|
|
|
|
|
|
return LeftJoinOn(t, table, onCondition)
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-03 11:03:07 +02:00
|
|
|
func (t *joinTable) FULL_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable {
|
2019-03-31 14:07:58 +02:00
|
|
|
return FullJoin(t, table, onCondition)
|
2019-03-16 14:02:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *joinTable) CrossJoin(table ReadableTable) ReadableTable {
|
|
|
|
|
return CrossJoin(t, table)
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-02 12:34:08 +01:00
|
|
|
func (t *joinTable) RightJoinOn(
|
|
|
|
|
table ReadableTable,
|
|
|
|
|
onCondition BoolExpression) ReadableTable {
|
|
|
|
|
|
|
|
|
|
return RightJoinOn(t, table, onCondition)
|
|
|
|
|
}
|