init
commit
babb5963b5
@ -0,0 +1,318 @@
|
|||||||
|
// Modeling of columns
|
||||||
|
|
||||||
|
package sqlbuilder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/dropbox/godropbox/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// XXX: Maybe add UIntColumn
|
||||||
|
|
||||||
|
// Representation of a table for query generation
|
||||||
|
type Column interface {
|
||||||
|
isProjectionInterface
|
||||||
|
|
||||||
|
Name() string
|
||||||
|
// Serialization for use in column lists
|
||||||
|
SerializeSqlForColumnList(out *bytes.Buffer) error
|
||||||
|
// Serialization for use in an expression (Clause)
|
||||||
|
SerializeSql(out *bytes.Buffer) error
|
||||||
|
|
||||||
|
// Internal function for tracking table that a column belongs to
|
||||||
|
// for the purpose of serialization
|
||||||
|
setTableName(table string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type NullableColumn bool
|
||||||
|
|
||||||
|
const (
|
||||||
|
Nullable NullableColumn = true
|
||||||
|
NotNullable NullableColumn = false
|
||||||
|
)
|
||||||
|
|
||||||
|
// A column that can be refer to outside of the projection list
|
||||||
|
type NonAliasColumn interface {
|
||||||
|
Column
|
||||||
|
isOrderByClauseInterface
|
||||||
|
isExpressionInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
type Collation string
|
||||||
|
|
||||||
|
const (
|
||||||
|
UTF8CaseInsensitive Collation = "utf8_unicode_ci"
|
||||||
|
UTF8CaseSensitive Collation = "utf8_unicode"
|
||||||
|
UTF8Binary Collation = "utf8_bin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Representation of MySQL charsets
|
||||||
|
type Charset string
|
||||||
|
|
||||||
|
const (
|
||||||
|
UTF8 Charset = "utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The base type for real materialized columns.
|
||||||
|
type baseColumn struct {
|
||||||
|
isProjection
|
||||||
|
isExpression
|
||||||
|
name string
|
||||||
|
nullable NullableColumn
|
||||||
|
table string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseColumn) Name() string {
|
||||||
|
return c.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseColumn) setTableName(table string) error {
|
||||||
|
c.table = table
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*func (c *baseColumn) SerializeSqlForColumnList(out *bytes.Buffer) error {
|
||||||
|
if c.table != "" {
|
||||||
|
_ = out.WriteByte('`')
|
||||||
|
_, _ = out.WriteString(c.table)
|
||||||
|
_, _ = out.WriteString("`.")
|
||||||
|
}
|
||||||
|
_, _ = out.WriteString("`")
|
||||||
|
_, _ = out.WriteString(c.name)
|
||||||
|
_ = out.WriteByte('`')
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
func (c *baseColumn) SerializeSqlForColumnList(out *bytes.Buffer) error {
|
||||||
|
// Momo modified. we don't need prefixing table name
|
||||||
|
/*
|
||||||
|
if c.table != "" {
|
||||||
|
_ = out.WriteByte('`')
|
||||||
|
_, _ = out.WriteString(c.table)
|
||||||
|
_, _ = out.WriteString("`.")
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
_, _ = out.WriteString("`")
|
||||||
|
_, _ = out.WriteString(c.name)
|
||||||
|
_ = out.WriteByte('`')
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseColumn) SerializeSql(out *bytes.Buffer) error {
|
||||||
|
return c.SerializeSqlForColumnList(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
type bytesColumn struct {
|
||||||
|
baseColumn
|
||||||
|
isExpression
|
||||||
|
}
|
||||||
|
|
||||||
|
// Representation of VARBINARY/BLOB columns
|
||||||
|
// This function will panic if name is not valid
|
||||||
|
func BytesColumn(name string, nullable NullableColumn) NonAliasColumn {
|
||||||
|
if !validIdentifierName(name) {
|
||||||
|
panic("Invalid column name in bytes column")
|
||||||
|
}
|
||||||
|
bc := &bytesColumn{}
|
||||||
|
bc.name = name
|
||||||
|
bc.nullable = nullable
|
||||||
|
return bc
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringColumn struct {
|
||||||
|
baseColumn
|
||||||
|
isExpression
|
||||||
|
charset Charset
|
||||||
|
collation Collation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Representation of VARCHAR/TEXT columns
|
||||||
|
// This function will panic if name is not valid
|
||||||
|
func StrColumn(
|
||||||
|
name string,
|
||||||
|
charset Charset,
|
||||||
|
collation Collation,
|
||||||
|
nullable NullableColumn) NonAliasColumn {
|
||||||
|
|
||||||
|
if !validIdentifierName(name) {
|
||||||
|
panic("Invalid column name in str column")
|
||||||
|
}
|
||||||
|
sc := &stringColumn{charset: charset, collation: collation}
|
||||||
|
sc.name = name
|
||||||
|
sc.nullable = nullable
|
||||||
|
return sc
|
||||||
|
}
|
||||||
|
|
||||||
|
type dateTimeColumn struct {
|
||||||
|
baseColumn
|
||||||
|
isExpression
|
||||||
|
}
|
||||||
|
|
||||||
|
// Representation of DateTime columns
|
||||||
|
// This function will panic if name is not valid
|
||||||
|
func DateTimeColumn(name string, nullable NullableColumn) NonAliasColumn {
|
||||||
|
if !validIdentifierName(name) {
|
||||||
|
panic("Invalid column name in datetime column")
|
||||||
|
}
|
||||||
|
dc := &dateTimeColumn{}
|
||||||
|
dc.name = name
|
||||||
|
dc.nullable = nullable
|
||||||
|
return dc
|
||||||
|
}
|
||||||
|
|
||||||
|
type integerColumn struct {
|
||||||
|
baseColumn
|
||||||
|
isExpression
|
||||||
|
}
|
||||||
|
|
||||||
|
// Representation of any integer column
|
||||||
|
// This function will panic if name is not valid
|
||||||
|
func IntColumn(name string, nullable NullableColumn) NonAliasColumn {
|
||||||
|
if !validIdentifierName(name) {
|
||||||
|
panic("Invalid column name in int column")
|
||||||
|
}
|
||||||
|
ic := &integerColumn{}
|
||||||
|
ic.name = name
|
||||||
|
ic.nullable = nullable
|
||||||
|
return ic
|
||||||
|
}
|
||||||
|
|
||||||
|
type doubleColumn struct {
|
||||||
|
baseColumn
|
||||||
|
isExpression
|
||||||
|
}
|
||||||
|
|
||||||
|
// Representation of any double column
|
||||||
|
// This function will panic if name is not valid
|
||||||
|
func DoubleColumn(name string, nullable NullableColumn) NonAliasColumn {
|
||||||
|
if !validIdentifierName(name) {
|
||||||
|
panic("Invalid column name in int column")
|
||||||
|
}
|
||||||
|
ic := &doubleColumn{}
|
||||||
|
ic.name = name
|
||||||
|
ic.nullable = nullable
|
||||||
|
return ic
|
||||||
|
}
|
||||||
|
|
||||||
|
type booleanColumn struct {
|
||||||
|
baseColumn
|
||||||
|
isExpression
|
||||||
|
|
||||||
|
// XXX: Maybe allow isBoolExpression (for now, not included because
|
||||||
|
// the deferred lookup equivalent can never be isBoolExpression)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Representation of TINYINT used as a bool
|
||||||
|
// This function will panic if name is not valid
|
||||||
|
func BoolColumn(name string, nullable NullableColumn) NonAliasColumn {
|
||||||
|
if !validIdentifierName(name) {
|
||||||
|
panic("Invalid column name in bool column")
|
||||||
|
}
|
||||||
|
bc := &booleanColumn{}
|
||||||
|
bc.name = name
|
||||||
|
bc.nullable = nullable
|
||||||
|
return bc
|
||||||
|
}
|
||||||
|
|
||||||
|
type aliasColumn struct {
|
||||||
|
baseColumn
|
||||||
|
expression Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *aliasColumn) SerializeSql(out *bytes.Buffer) error {
|
||||||
|
_ = out.WriteByte('`')
|
||||||
|
_, _ = out.WriteString(c.name)
|
||||||
|
_ = out.WriteByte('`')
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *aliasColumn) SerializeSqlForColumnList(out *bytes.Buffer) error {
|
||||||
|
if !validIdentifierName(c.name) {
|
||||||
|
return errors.Newf(
|
||||||
|
"Invalid alias name `%s`. Generated sql: %s",
|
||||||
|
c.name,
|
||||||
|
out.String())
|
||||||
|
}
|
||||||
|
if c.expression == nil {
|
||||||
|
return errors.Newf(
|
||||||
|
"Cannot alias a nil expression. Generated sql: %s",
|
||||||
|
out.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = out.WriteByte('(')
|
||||||
|
if c.expression == nil {
|
||||||
|
return errors.Newf("nil alias clause. Generate sql: %s", out.String())
|
||||||
|
}
|
||||||
|
if err := c.expression.SerializeSql(out); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _ = out.WriteString(") AS `")
|
||||||
|
_, _ = out.WriteString(c.name)
|
||||||
|
_ = out.WriteByte('`')
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *aliasColumn) setTableName(table string) error {
|
||||||
|
return errors.Newf(
|
||||||
|
"Alias column '%s' should never have setTableName called on it",
|
||||||
|
c.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Representation of aliased clauses (expression AS name)
|
||||||
|
func Alias(name string, c Expression) Column {
|
||||||
|
ac := &aliasColumn{}
|
||||||
|
ac.name = name
|
||||||
|
ac.expression = c
|
||||||
|
return ac
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a strict subset of the actual allowed identifiers
|
||||||
|
var validIdentifierRegexp = regexp.MustCompile("^[a-zA-Z_]\\w*$")
|
||||||
|
|
||||||
|
// Returns true if the given string is suitable as an identifier.
|
||||||
|
func validIdentifierName(name string) bool {
|
||||||
|
//return validIdentifierRegexp.MatchString(name)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pseudo Column type returned by table.C(name)
|
||||||
|
type deferredLookupColumn struct {
|
||||||
|
isProjection
|
||||||
|
isExpression
|
||||||
|
table *Table
|
||||||
|
colName string
|
||||||
|
|
||||||
|
cachedColumn NonAliasColumn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *deferredLookupColumn) Name() string {
|
||||||
|
return c.colName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *deferredLookupColumn) SerializeSqlForColumnList(
|
||||||
|
out *bytes.Buffer) error {
|
||||||
|
|
||||||
|
return c.SerializeSql(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *deferredLookupColumn) SerializeSql(out *bytes.Buffer) error {
|
||||||
|
if c.cachedColumn != nil {
|
||||||
|
return c.cachedColumn.SerializeSql(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
col, err := c.table.getColumn(c.colName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.cachedColumn = col
|
||||||
|
return col.SerializeSql(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *deferredLookupColumn) setTableName(table string) error {
|
||||||
|
return errors.Newf(
|
||||||
|
"Lookup column '%s' should never have setTableName called on it",
|
||||||
|
c.colName)
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
// A library for generating sql programmatically.
|
||||||
|
//
|
||||||
|
// SQL COMPATIBILITY NOTE: sqlbuilder is designed to generate valid MySQL sql
|
||||||
|
// statements. The generated statements may not work for other sql variants.
|
||||||
|
// For instances, the generated statements does not currently work for
|
||||||
|
// PostgreSQL since column identifiers are escaped with backquotes.
|
||||||
|
// Patches to support other sql flavors are welcome! (see
|
||||||
|
// https://godropbox/issues/33 for additional details).
|
||||||
|
//
|
||||||
|
// Known limitations for SELECT queries:
|
||||||
|
// - does not support subqueries (since mysql is bad at it)
|
||||||
|
// - does not currently support join table alias (and hence self join)
|
||||||
|
// - does not support NATURAL joins and join USING
|
||||||
|
//
|
||||||
|
// Known limitation for INSERT statements:
|
||||||
|
// - does not support "INSERT INTO SELECT"
|
||||||
|
//
|
||||||
|
// Known limitation for UPDATE statements:
|
||||||
|
// - does not support update without a WHERE clause (since it is dangerous)
|
||||||
|
// - does not support multi-table update
|
||||||
|
//
|
||||||
|
// Known limitation for DELETE statements:
|
||||||
|
// - does not support delete without a WHERE clause (since it is dangerous)
|
||||||
|
// - does not support multi-table delete
|
||||||
|
package sqlbuilder
|
@ -0,0 +1,733 @@
|
|||||||
|
// Query building functions for expression components
|
||||||
|
package sqlbuilder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"b612.me/mysql/sqltypes"
|
||||||
|
"github.com/dropbox/godropbox/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type orderByClause struct {
|
||||||
|
isOrderByClause
|
||||||
|
expression Expression
|
||||||
|
ascent bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *orderByClause) SerializeSql(out *bytes.Buffer) error {
|
||||||
|
if o.expression == nil {
|
||||||
|
return errors.Newf(
|
||||||
|
"nil order by clause. Generated sql: %s",
|
||||||
|
out.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := o.expression.SerializeSql(out); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.ascent {
|
||||||
|
_, _ = out.WriteString(" ASC")
|
||||||
|
} else {
|
||||||
|
_, _ = out.WriteString(" DESC")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Asc(expression Expression) OrderByClause {
|
||||||
|
return &orderByClause{expression: expression, ascent: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Desc(expression Expression) OrderByClause {
|
||||||
|
return &orderByClause{expression: expression, ascent: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Representation of an escaped literal
|
||||||
|
type literalExpression struct {
|
||||||
|
isExpression
|
||||||
|
value sqltypes.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c literalExpression) SerializeSql(out *bytes.Buffer) error {
|
||||||
|
sqltypes.Value(c.value).EncodeSql(out)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func serializeClauses(
|
||||||
|
clauses []Clause,
|
||||||
|
separator []byte,
|
||||||
|
out *bytes.Buffer) (err error) {
|
||||||
|
|
||||||
|
if clauses == nil || len(clauses) == 0 {
|
||||||
|
return errors.Newf("Empty clauses. Generated sql: %s", out.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if clauses[0] == nil {
|
||||||
|
return errors.Newf("nil clause. Generated sql: %s", out.String())
|
||||||
|
}
|
||||||
|
if err = clauses[0].SerializeSql(out); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range clauses[1:] {
|
||||||
|
_, _ = out.Write(separator)
|
||||||
|
|
||||||
|
if c == nil {
|
||||||
|
return errors.Newf("nil clause. Generated sql: %s", out.String())
|
||||||
|
}
|
||||||
|
if err = c.SerializeSql(out); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Representation of n-ary conjunctions (AND/OR)
|
||||||
|
type conjunctExpression struct {
|
||||||
|
isExpression
|
||||||
|
isBoolExpression
|
||||||
|
expressions []BoolExpression
|
||||||
|
conjunction []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conj *conjunctExpression) SerializeSql(out *bytes.Buffer) (err error) {
|
||||||
|
if len(conj.expressions) == 0 {
|
||||||
|
return errors.Newf(
|
||||||
|
"Empty conjunction. Generated sql: %s",
|
||||||
|
out.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
clauses := make([]Clause, len(conj.expressions), len(conj.expressions))
|
||||||
|
for i, expr := range conj.expressions {
|
||||||
|
clauses[i] = expr
|
||||||
|
}
|
||||||
|
|
||||||
|
useParentheses := len(clauses) > 1
|
||||||
|
if useParentheses {
|
||||||
|
_ = out.WriteByte('(')
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = serializeClauses(clauses, conj.conjunction, out); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if useParentheses {
|
||||||
|
_ = out.WriteByte(')')
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Representation of n-ary arithmetic (+ - * /)
|
||||||
|
type arithmeticExpression struct {
|
||||||
|
isExpression
|
||||||
|
expressions []Expression
|
||||||
|
operator []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (arith *arithmeticExpression) SerializeSql(out *bytes.Buffer) (err error) {
|
||||||
|
if len(arith.expressions) == 0 {
|
||||||
|
return errors.Newf(
|
||||||
|
"Empty arithmetic expression. Generated sql: %s",
|
||||||
|
out.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
clauses := make([]Clause, len(arith.expressions), len(arith.expressions))
|
||||||
|
for i, expr := range arith.expressions {
|
||||||
|
clauses[i] = expr
|
||||||
|
}
|
||||||
|
|
||||||
|
useParentheses := len(clauses) > 1
|
||||||
|
if useParentheses {
|
||||||
|
_ = out.WriteByte('(')
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = serializeClauses(clauses, arith.operator, out); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if useParentheses {
|
||||||
|
_ = out.WriteByte(')')
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type tupleExpression struct {
|
||||||
|
isExpression
|
||||||
|
elements listClause
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tuple *tupleExpression) SerializeSql(out *bytes.Buffer) error {
|
||||||
|
if len(tuple.elements.clauses) < 1 {
|
||||||
|
return errors.Newf("Tuples must include at least one element")
|
||||||
|
}
|
||||||
|
return tuple.elements.SerializeSql(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Tuple(exprs ...Expression) Expression {
|
||||||
|
clauses := make([]Clause, 0, len(exprs))
|
||||||
|
for _, expr := range exprs {
|
||||||
|
clauses = append(clauses, expr)
|
||||||
|
}
|
||||||
|
return &tupleExpression{
|
||||||
|
elements: listClause{
|
||||||
|
clauses: clauses,
|
||||||
|
includeParentheses: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Representation of a tuple enclosed, comma separated list of clauses
|
||||||
|
type listClause struct {
|
||||||
|
clauses []Clause
|
||||||
|
includeParentheses bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list *listClause) SerializeSql(out *bytes.Buffer) error {
|
||||||
|
if list.includeParentheses {
|
||||||
|
_ = out.WriteByte('(')
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := serializeClauses(list.clauses, []byte(","), out); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if list.includeParentheses {
|
||||||
|
_ = out.WriteByte(')')
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A not expression which negates a expression value
|
||||||
|
type negateExpression struct {
|
||||||
|
isExpression
|
||||||
|
isBoolExpression
|
||||||
|
|
||||||
|
nested BoolExpression
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *negateExpression) SerializeSql(out *bytes.Buffer) (err error) {
|
||||||
|
_, _ = out.WriteString("NOT (")
|
||||||
|
|
||||||
|
if c.nested == nil {
|
||||||
|
return errors.Newf("nil nested. Generated sql: %s", out.String())
|
||||||
|
}
|
||||||
|
if err = c.nested.SerializeSql(out); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = out.WriteByte(')')
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "not expr"
|
||||||
|
func Not(expr BoolExpression) BoolExpression {
|
||||||
|
return &negateExpression{
|
||||||
|
nested: expr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Representation of binary operations (e.g. comparisons, arithmetic)
|
||||||
|
type binaryExpression struct {
|
||||||
|
isExpression
|
||||||
|
lhs, rhs Expression
|
||||||
|
operator []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *binaryExpression) SerializeSql(out *bytes.Buffer) (err error) {
|
||||||
|
if c.lhs == nil {
|
||||||
|
return errors.Newf("nil lhs. Generated sql: %s", out.String())
|
||||||
|
}
|
||||||
|
if err = c.lhs.SerializeSql(out); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = out.Write(c.operator)
|
||||||
|
|
||||||
|
if c.rhs == nil {
|
||||||
|
return errors.Newf("nil rhs. Generated sql: %s", out.String())
|
||||||
|
}
|
||||||
|
if err = c.rhs.SerializeSql(out); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A binary expression that evaluates to a boolean value.
|
||||||
|
type boolExpression struct {
|
||||||
|
isBoolExpression
|
||||||
|
binaryExpression
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBoolExpression(lhs, rhs Expression, operator []byte) *boolExpression {
|
||||||
|
// go does not allow {} syntax for initializing promoted fields ...
|
||||||
|
expr := new(boolExpression)
|
||||||
|
expr.lhs = lhs
|
||||||
|
expr.rhs = rhs
|
||||||
|
expr.operator = operator
|
||||||
|
return expr
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcExpression struct {
|
||||||
|
isExpression
|
||||||
|
funcName string
|
||||||
|
args *listClause
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *funcExpression) SerializeSql(out *bytes.Buffer) (err error) {
|
||||||
|
if !validIdentifierName(c.funcName) {
|
||||||
|
return errors.Newf(
|
||||||
|
"Invalid function name: %s. Generated sql: %s",
|
||||||
|
c.funcName,
|
||||||
|
out.String())
|
||||||
|
}
|
||||||
|
_, _ = out.WriteString(c.funcName)
|
||||||
|
if c.args == nil {
|
||||||
|
_, _ = out.WriteString("()")
|
||||||
|
} else {
|
||||||
|
return c.args.SerializeSql(out)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of sql function call "func_call(c[0], ..., c[n-1])
|
||||||
|
func SqlFunc(funcName string, expressions ...Expression) Expression {
|
||||||
|
f := &funcExpression{
|
||||||
|
funcName: funcName,
|
||||||
|
}
|
||||||
|
if len(expressions) > 0 {
|
||||||
|
args := make([]Clause, len(expressions), len(expressions))
|
||||||
|
for i, expr := range expressions {
|
||||||
|
args[i] = expr
|
||||||
|
}
|
||||||
|
|
||||||
|
f.args = &listClause{
|
||||||
|
clauses: args,
|
||||||
|
includeParentheses: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
type intervalExpression struct {
|
||||||
|
isExpression
|
||||||
|
duration time.Duration
|
||||||
|
negative bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var intervalSep = ":"
|
||||||
|
|
||||||
|
func (c *intervalExpression) SerializeSql(out *bytes.Buffer) (err error) {
|
||||||
|
hours := c.duration / time.Hour
|
||||||
|
minutes := (c.duration % time.Hour) / time.Minute
|
||||||
|
sec := (c.duration % time.Minute) / time.Second
|
||||||
|
msec := (c.duration % time.Second) / time.Microsecond
|
||||||
|
_, _ = out.WriteString("INTERVAL '")
|
||||||
|
if c.negative {
|
||||||
|
_, _ = out.WriteString("-")
|
||||||
|
}
|
||||||
|
_, _ = out.WriteString(strconv.FormatInt(int64(hours), 10))
|
||||||
|
_, _ = out.WriteString(intervalSep)
|
||||||
|
_, _ = out.WriteString(strconv.FormatInt(int64(minutes), 10))
|
||||||
|
_, _ = out.WriteString(intervalSep)
|
||||||
|
_, _ = out.WriteString(strconv.FormatInt(int64(sec), 10))
|
||||||
|
_, _ = out.WriteString(intervalSep)
|
||||||
|
_, _ = out.WriteString(strconv.FormatInt(int64(msec), 10))
|
||||||
|
_, _ = out.WriteString("' HOUR_MICROSECOND")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interval returns a representation of duration
|
||||||
|
// in a form "INTERVAL `hour:min:sec:microsec` HOUR_MICROSECOND"
|
||||||
|
func Interval(duration time.Duration) Expression {
|
||||||
|
negative := false
|
||||||
|
if duration < 0 {
|
||||||
|
negative = true
|
||||||
|
duration = -duration
|
||||||
|
}
|
||||||
|
return &intervalExpression{
|
||||||
|
duration: duration,
|
||||||
|
negative: negative,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var likeEscaper = strings.NewReplacer("_", "\\_", "%", "\\%")
|
||||||
|
|
||||||
|
func EscapeForLike(s string) string {
|
||||||
|
return likeEscaper.Replace(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an escaped literal string
|
||||||
|
func Literal(v interface{}) Expression {
|
||||||
|
value, err := sqltypes.BuildValue(v)
|
||||||
|
if err != nil {
|
||||||
|
panic(errors.Wrap(err, "Invalid literal value"))
|
||||||
|
}
|
||||||
|
return &literalExpression{value: value}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "c[0] AND ... AND c[n-1]" for c in clauses
|
||||||
|
func And(expressions ...BoolExpression) BoolExpression {
|
||||||
|
return &conjunctExpression{
|
||||||
|
expressions: expressions,
|
||||||
|
conjunction: []byte(" AND "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "c[0] OR ... OR c[n-1]" for c in clauses
|
||||||
|
func Or(expressions ...BoolExpression) BoolExpression {
|
||||||
|
return &conjunctExpression{
|
||||||
|
expressions: expressions,
|
||||||
|
conjunction: []byte(" OR "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Like(lhs, rhs Expression) BoolExpression {
|
||||||
|
return newBoolExpression(lhs, rhs, []byte(" LIKE "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func LikeL(lhs Expression, val string) BoolExpression {
|
||||||
|
return Like(lhs, Literal(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Regexp(lhs, rhs Expression) BoolExpression {
|
||||||
|
return newBoolExpression(lhs, rhs, []byte(" REGEXP "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegexpL(lhs Expression, val string) BoolExpression {
|
||||||
|
return Regexp(lhs, Literal(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "c[0] + ... + c[n-1]" for c in clauses
|
||||||
|
func Add(expressions ...Expression) Expression {
|
||||||
|
return &arithmeticExpression{
|
||||||
|
expressions: expressions,
|
||||||
|
operator: []byte(" + "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "c[0] - ... - c[n-1]" for c in clauses
|
||||||
|
func Sub(expressions ...Expression) Expression {
|
||||||
|
return &arithmeticExpression{
|
||||||
|
expressions: expressions,
|
||||||
|
operator: []byte(" - "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "c[0] * ... * c[n-1]" for c in clauses
|
||||||
|
func Mul(expressions ...Expression) Expression {
|
||||||
|
return &arithmeticExpression{
|
||||||
|
expressions: expressions,
|
||||||
|
operator: []byte(" * "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "c[0] / ... / c[n-1]" for c in clauses
|
||||||
|
func Div(expressions ...Expression) Expression {
|
||||||
|
return &arithmeticExpression{
|
||||||
|
expressions: expressions,
|
||||||
|
operator: []byte(" / "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "a=b"
|
||||||
|
func Eq(lhs, rhs Expression) BoolExpression {
|
||||||
|
lit, ok := rhs.(*literalExpression)
|
||||||
|
if ok && sqltypes.Value(lit.value).IsNull() {
|
||||||
|
return newBoolExpression(lhs, rhs, []byte(" IS "))
|
||||||
|
}
|
||||||
|
return newBoolExpression(lhs, rhs, []byte("="))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "a=b", where b is a literal
|
||||||
|
func EqL(lhs Expression, val interface{}) BoolExpression {
|
||||||
|
return Eq(lhs, Literal(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "a!=b"
|
||||||
|
func Neq(lhs, rhs Expression) BoolExpression {
|
||||||
|
lit, ok := rhs.(*literalExpression)
|
||||||
|
if ok && sqltypes.Value(lit.value).IsNull() {
|
||||||
|
return newBoolExpression(lhs, rhs, []byte(" IS NOT "))
|
||||||
|
}
|
||||||
|
return newBoolExpression(lhs, rhs, []byte("!="))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "a!=b", where b is a literal
|
||||||
|
func NeqL(lhs Expression, val interface{}) BoolExpression {
|
||||||
|
return Neq(lhs, Literal(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "a<b"
|
||||||
|
func Lt(lhs Expression, rhs Expression) BoolExpression {
|
||||||
|
return newBoolExpression(lhs, rhs, []byte("<"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "a<b", where b is a literal
|
||||||
|
func LtL(lhs Expression, val interface{}) BoolExpression {
|
||||||
|
return Lt(lhs, Literal(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "a<=b"
|
||||||
|
func Lte(lhs, rhs Expression) BoolExpression {
|
||||||
|
return newBoolExpression(lhs, rhs, []byte("<="))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "a<=b", where b is a literal
|
||||||
|
func LteL(lhs Expression, val interface{}) BoolExpression {
|
||||||
|
return Lte(lhs, Literal(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "a>b"
|
||||||
|
func Gt(lhs, rhs Expression) BoolExpression {
|
||||||
|
return newBoolExpression(lhs, rhs, []byte(">"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "a>b", where b is a literal
|
||||||
|
func GtL(lhs Expression, val interface{}) BoolExpression {
|
||||||
|
return Gt(lhs, Literal(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "a>=b"
|
||||||
|
func Gte(lhs, rhs Expression) BoolExpression {
|
||||||
|
return newBoolExpression(lhs, rhs, []byte(">="))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "a>=b", where b is a literal
|
||||||
|
func GteL(lhs Expression, val interface{}) BoolExpression {
|
||||||
|
return Gte(lhs, Literal(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BitOr(lhs, rhs Expression) Expression {
|
||||||
|
return &binaryExpression{
|
||||||
|
lhs: lhs,
|
||||||
|
rhs: rhs,
|
||||||
|
operator: []byte(" | "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BitAnd(lhs, rhs Expression) Expression {
|
||||||
|
return &binaryExpression{
|
||||||
|
lhs: lhs,
|
||||||
|
rhs: rhs,
|
||||||
|
operator: []byte(" & "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BitXor(lhs, rhs Expression) Expression {
|
||||||
|
return &binaryExpression{
|
||||||
|
lhs: lhs,
|
||||||
|
rhs: rhs,
|
||||||
|
operator: []byte(" ^ "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Plus(lhs, rhs Expression) Expression {
|
||||||
|
return &binaryExpression{
|
||||||
|
lhs: lhs,
|
||||||
|
rhs: rhs,
|
||||||
|
operator: []byte(" + "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Minus(lhs, rhs Expression) Expression {
|
||||||
|
return &binaryExpression{
|
||||||
|
lhs: lhs,
|
||||||
|
rhs: rhs,
|
||||||
|
operator: []byte(" - "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// in expression representation
|
||||||
|
type inExpression struct {
|
||||||
|
isExpression
|
||||||
|
isBoolExpression
|
||||||
|
|
||||||
|
lhs Expression
|
||||||
|
rhs *listClause
|
||||||
|
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *inExpression) SerializeSql(out *bytes.Buffer) error {
|
||||||
|
if c.err != nil {
|
||||||
|
return errors.Wrap(c.err, "Invalid IN expression")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.lhs == nil {
|
||||||
|
return errors.Newf(
|
||||||
|
"lhs of in expression is nil. Generated sql: %s",
|
||||||
|
out.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll serialize the lhs even if we don't need it to ensure no error
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
|
err := c.lhs.SerializeSql(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.rhs == nil {
|
||||||
|
_, _ = out.WriteString("FALSE")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = out.WriteString(buf.String())
|
||||||
|
_, _ = out.WriteString(" IN ")
|
||||||
|
|
||||||
|
err = c.rhs.SerializeSql(out)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of "a IN (b[0], ..., b[n-1])", where b is a list
|
||||||
|
// of literals valList must be a slice type
|
||||||
|
func In(lhs Expression, valList interface{}) BoolExpression {
|
||||||
|
var clauses []Clause
|
||||||
|
switch val := valList.(type) {
|
||||||
|
// This atrocious body of copy-paste code is due to the fact that if you
|
||||||
|
// try to merge the cases, you can't treat val as a list
|
||||||
|
case []int:
|
||||||
|
clauses = make([]Clause, 0, len(val))
|
||||||
|
for _, v := range val {
|
||||||
|
clauses = append(clauses, Literal(v))
|
||||||
|
}
|
||||||
|
case []int32:
|
||||||
|
clauses = make([]Clause, 0, len(val))
|
||||||
|
for _, v := range val {
|
||||||
|
clauses = append(clauses, Literal(v))
|
||||||
|
}
|
||||||
|
case []int64:
|
||||||
|
clauses = make([]Clause, 0, len(val))
|
||||||
|
for _, v := range val {
|
||||||
|
clauses = append(clauses, Literal(v))
|
||||||
|
}
|
||||||
|
case []uint:
|
||||||
|
clauses = make([]Clause, 0, len(val))
|
||||||
|
for _, v := range val {
|
||||||
|
clauses = append(clauses, Literal(v))
|
||||||
|
}
|
||||||
|
case []uint32:
|
||||||
|
clauses = make([]Clause, 0, len(val))
|
||||||
|
for _, v := range val {
|
||||||
|
clauses = append(clauses, Literal(v))
|
||||||
|
}
|
||||||
|
case []uint64:
|
||||||
|
clauses = make([]Clause, 0, len(val))
|
||||||
|
for _, v := range val {
|
||||||
|
clauses = append(clauses, Literal(v))
|
||||||
|
}
|
||||||
|
case []float64:
|
||||||
|
clauses = make([]Clause, 0, len(val))
|
||||||
|
for _, v := range val {
|
||||||
|
clauses = append(clauses, Literal(v))
|
||||||
|
}
|
||||||
|
case []string:
|
||||||
|
clauses = make([]Clause, 0, len(val))
|
||||||
|
for _, v := range val {
|
||||||
|
clauses = append(clauses, Literal(v))
|
||||||
|
}
|
||||||
|
case [][]byte:
|
||||||
|
clauses = make([]Clause, 0, len(val))
|
||||||
|
for _, v := range val {
|
||||||
|
clauses = append(clauses, Literal(v))
|
||||||
|
}
|
||||||
|
case []time.Time:
|
||||||
|
clauses = make([]Clause, 0, len(val))
|
||||||
|
for _, v := range val {
|
||||||
|
clauses = append(clauses, Literal(v))
|
||||||
|
}
|
||||||
|
case []sqltypes.Numeric:
|
||||||
|
clauses = make([]Clause, 0, len(val))
|
||||||
|
for _, v := range val {
|
||||||
|
clauses = append(clauses, Literal(v))
|
||||||
|
}
|
||||||
|
case []sqltypes.Fractional:
|
||||||
|
clauses = make([]Clause, 0, len(val))
|
||||||
|
for _, v := range val {
|
||||||
|
clauses = append(clauses, Literal(v))
|
||||||
|
}
|
||||||
|
case []sqltypes.String:
|
||||||
|
clauses = make([]Clause, 0, len(val))
|
||||||
|
for _, v := range val {
|
||||||
|
clauses = append(clauses, Literal(v))
|
||||||
|
}
|
||||||
|
case []sqltypes.Value:
|
||||||
|
clauses = make([]Clause, 0, len(val))
|
||||||
|
for _, v := range val {
|
||||||
|
clauses = append(clauses, Literal(v))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return &inExpression{
|
||||||
|
err: errors.Newf(
|
||||||
|
"Unknown value list type in IN clause: %s",
|
||||||
|
reflect.TypeOf(valList)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expr := &inExpression{lhs: lhs}
|
||||||
|
if len(clauses) > 0 {
|
||||||
|
expr.rhs = &listClause{clauses: clauses, includeParentheses: true}
|
||||||
|
}
|
||||||
|
return expr
|
||||||
|
}
|
||||||
|
|
||||||
|
type ifExpression struct {
|
||||||
|
isExpression
|
||||||
|
conditional BoolExpression
|
||||||
|
trueExpression Expression
|
||||||
|
falseExpression Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
func (exp *ifExpression) SerializeSql(out *bytes.Buffer) error {
|
||||||
|
_, _ = out.WriteString("IF(")
|
||||||
|
_ = exp.conditional.SerializeSql(out)
|
||||||
|
_, _ = out.WriteString(",")
|
||||||
|
_ = exp.trueExpression.SerializeSql(out)
|
||||||
|
_, _ = out.WriteString(",")
|
||||||
|
_ = exp.falseExpression.SerializeSql(out)
|
||||||
|
_, _ = out.WriteString(")")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a representation of an if-expression, of the form:
|
||||||
|
//
|
||||||
|
// IF (BOOLEAN TEST, VALUE-IF-TRUE, VALUE-IF-FALSE)
|
||||||
|
func If(conditional BoolExpression,
|
||||||
|
trueExpression Expression,
|
||||||
|
falseExpression Expression) Expression {
|
||||||
|
return &ifExpression{
|
||||||
|
conditional: conditional,
|
||||||
|
trueExpression: trueExpression,
|
||||||
|
falseExpression: falseExpression,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type columnValueExpression struct {
|
||||||
|
isExpression
|
||||||
|
column NonAliasColumn
|
||||||
|
}
|
||||||
|
|
||||||
|
func ColumnValue(col NonAliasColumn) Expression {
|
||||||
|
return &columnValueExpression{
|
||||||
|
column: col,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cv *columnValueExpression) SerializeSql(out *bytes.Buffer) error {
|
||||||
|
_, _ = out.WriteString("VALUES(")
|
||||||
|
_ = cv.column.SerializeSqlForColumnList(out)
|
||||||
|
_ = out.WriteByte(')')
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
module b612.me/mysql/sqlbuilder
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
b612.me/mysql/sqltypes v0.0.0-20230701101652-40406d9a2ff2
|
||||||
|
github.com/dropbox/godropbox v0.0.0-20230623171840-436d2007a9fd
|
||||||
|
)
|
@ -0,0 +1,33 @@
|
|||||||
|
b612.me/mysql/sqltypes v0.0.0-20230701101652-40406d9a2ff2 h1:gWGuBHC7hrmyhp9vX1UOOX5C9WRoFHPDjSvRfmP/nS4=
|
||||||
|
b612.me/mysql/sqltypes v0.0.0-20230701101652-40406d9a2ff2/go.mod h1:Py9XWC9lc2cDhzfSPO7gqk07qZcPpJfk0aQ0iUZC5CQ=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dropbox/godropbox v0.0.0-20230623171840-436d2007a9fd h1:s2vYw+2c+7GR1ccOaDuDcKsmNB/4RIxyu5liBm1VRbs=
|
||||||
|
github.com/dropbox/godropbox v0.0.0-20230623171840-436d2007a9fd/go.mod h1:Vr/Q4p40Kce7JAHDITjDhiy/zk07W4tqD5YVi5FD0PA=
|
||||||
|
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||||
|
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||||
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,321 @@
|
|||||||
|
// Modeling of tables. This is where query preparation starts
|
||||||
|
|
||||||
|
package sqlbuilder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/dropbox/godropbox/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The sql table read interface. NOTE: NATURAL JOINs, and join "USING" clause
|
||||||
|
// are not supported.
|
||||||
|
type ReadableTable interface {
|
||||||
|
// Returns the list of columns that are in the current table expression.
|
||||||
|
Columns() []NonAliasColumn
|
||||||
|
|
||||||
|
// Generates the sql string for the current table expression. Note: the
|
||||||
|
// generated string may not be a valid/executable sql statement.
|
||||||
|
// The database is the name of the database the table is on
|
||||||
|
SerializeSql(database string, out *bytes.Buffer) error
|
||||||
|
|
||||||
|
// Generates a select query on the current table.
|
||||||
|
Select(projections ...Projection) SelectStatement
|
||||||
|
|
||||||
|
// Creates a inner join table expression using onCondition.
|
||||||
|
InnerJoinOn(table ReadableTable, onCondition BoolExpression) ReadableTable
|
||||||
|
|
||||||
|
// Creates a left join table expression using onCondition.
|
||||||
|
LeftJoinOn(table ReadableTable, onCondition BoolExpression) ReadableTable
|
||||||
|
|
||||||
|
// Creates a right join table expression using onCondition.
|
||||||
|
RightJoinOn(table ReadableTable, onCondition BoolExpression) ReadableTable
|
||||||
|
}
|
||||||
|
|
||||||
|
// The sql table write interface.
|
||||||
|
type WritableTable interface {
|
||||||
|
// Returns the list of columns that are in the table.
|
||||||
|
Columns() []NonAliasColumn
|
||||||
|
|
||||||
|
// Generates the sql string for the current table expression. Note: the
|
||||||
|
// generated string may not be a valid/executable sql statement.
|
||||||
|
// The database is the name of the database the table is on
|
||||||
|
SerializeSql(database string, out *bytes.Buffer) error
|
||||||
|
|
||||||
|
Insert(columns ...NonAliasColumn) InsertStatement
|
||||||
|
Update() UpdateStatement
|
||||||
|
Delete() DeleteStatement
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defines a physical table in the database that is both readable and writable.
|
||||||
|
// This function will panic if name is not valid
|
||||||
|
func NewTable(name string, columns ...NonAliasColumn) *Table {
|
||||||
|
if !validIdentifierName(name) {
|
||||||
|
panic("Invalid table name")
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &Table{
|
||||||
|
name: name,
|
||||||
|
columns: columns,
|
||||||
|
columnLookup: make(map[string]NonAliasColumn),
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
name string
|
||||||
|
columns []NonAliasColumn
|
||||||
|
columnLookup map[string]NonAliasColumn
|
||||||
|
// If not empty, the name of the index to force
|
||||||
|
forcedIndex string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the specified column, or errors if it doesn't exist in the table
|
||||||
|
func (t *Table) getColumn(name string) (NonAliasColumn, error) {
|
||||||
|
if c, ok := t.columnLookup[name]; ok {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
return nil, errors.Newf("No such column '%s' in table '%s'", name, t.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a pseudo column representation of the column name. Error checking
|
||||||
|
// is deferred to SerializeSql.
|
||||||
|
func (t *Table) C(name string) NonAliasColumn {
|
||||||
|
return &deferredLookupColumn{
|
||||||
|
table: t,
|
||||||
|
colName: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns all columns for a table as a slice of projections
|
||||||
|
func (t *Table) Projections() []Projection {
|
||||||
|
result := make([]Projection, 0)
|
||||||
|
|
||||||
|
for _, col := range t.columns {
|
||||||
|
result = append(result, col)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the table's name in the database
|
||||||
|
func (t *Table) Name() string {
|
||||||
|
return t.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a list of the table's columns
|
||||||
|
func (t *Table) Columns() []NonAliasColumn {
|
||||||
|
return t.columns
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a copy of this table, but with the specified index forced.
|
||||||
|
func (t *Table) ForceIndex(index string) *Table {
|
||||||
|
newTable := *t
|
||||||
|
newTable.forcedIndex = index
|
||||||
|
return &newTable
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates the sql string for the current table expression. Note: the
|
||||||
|
// generated string may not be a valid/executable sql statement.
|
||||||
|
func (t *Table) SerializeSql(database string, out *bytes.Buffer) error {
|
||||||
|
//Momo modified. if database empty, not write
|
||||||
|
if database != "" {
|
||||||
|
_, _ = out.WriteString("`")
|
||||||
|
_, _ = out.WriteString(database)
|
||||||
|
_, _ = out.WriteString("`.")
|
||||||
|
}
|
||||||
|
_, _ = out.WriteString("`")
|
||||||
|
_, _ = out.WriteString(t.Name())
|
||||||
|
_, _ = out.WriteString("`")
|
||||||
|
|
||||||
|
if t.forcedIndex != "" {
|
||||||
|
if !validIdentifierName(t.forcedIndex) {
|
||||||
|
return errors.Newf("'%s' is not a valid identifier for an index", t.forcedIndex)
|
||||||
|
}
|
||||||
|
_, _ = out.WriteString(" FORCE INDEX (`")
|
||||||
|
_, _ = out.WriteString(t.forcedIndex)
|
||||||
|
_, _ = out.WriteString("`)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a select query on the current table.
|
||||||
|
func (t *Table) Select(projections ...Projection) SelectStatement {
|
||||||
|
return newSelectStatement(t, projections)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a inner join table expression using onCondition.
|
||||||
|
func (t *Table) InnerJoinOn(
|
||||||
|
table ReadableTable,
|
||||||
|
onCondition BoolExpression) ReadableTable {
|
||||||
|
|
||||||
|
return InnerJoinOn(t, table, onCondition)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a left join table expression using onCondition.
|
||||||
|
func (t *Table) LeftJoinOn(
|
||||||
|
table ReadableTable,
|
||||||
|
onCondition BoolExpression) ReadableTable {
|
||||||
|
|
||||||
|
return LeftJoinOn(t, table, onCondition)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a right join table expression using onCondition.
|
||||||
|
func (t *Table) RightJoinOn(
|
||||||
|
table ReadableTable,
|
||||||
|
onCondition BoolExpression) ReadableTable {
|
||||||
|
|
||||||
|
return RightJoinOn(t, table, onCondition)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Table) Insert(columns ...NonAliasColumn) InsertStatement {
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *joinTable) Columns() []NonAliasColumn {
|
||||||
|
columns := make([]NonAliasColumn, 0)
|
||||||
|
columns = append(columns, t.lhs.Columns()...)
|
||||||
|
columns = append(columns, t.rhs.Columns()...)
|
||||||
|
|
||||||
|
return columns
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *joinTable) SerializeSql(
|
||||||
|
database string,
|
||||||
|
out *bytes.Buffer) (err error) {
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
if t.onCondition == nil {
|
||||||
|
return errors.Newf("nil onCondition. Generated sql: %s", out.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = t.lhs.SerializeSql(database, out); err != nil {
|
||||||
|
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 ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = t.rhs.SerializeSql(database, out); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = out.WriteString(" ON ")
|
||||||
|
if err = t.onCondition.SerializeSql(out); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *joinTable) Select(projections ...Projection) SelectStatement {
|
||||||
|
return newSelectStatement(t, projections)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *joinTable) InnerJoinOn(
|
||||||
|
table ReadableTable,
|
||||||
|
onCondition BoolExpression) ReadableTable {
|
||||||
|
|
||||||
|
return InnerJoinOn(t, table, onCondition)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *joinTable) LeftJoinOn(
|
||||||
|
table ReadableTable,
|
||||||
|
onCondition BoolExpression) ReadableTable {
|
||||||
|
|
||||||
|
return LeftJoinOn(t, table, onCondition)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *joinTable) RightJoinOn(
|
||||||
|
table ReadableTable,
|
||||||
|
onCondition BoolExpression) ReadableTable {
|
||||||
|
|
||||||
|
return RightJoinOn(t, table, onCondition)
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package sqlbuilder
|
||||||
|
|
||||||
|
var table1Col1 = IntColumn("col1", Nullable)
|
||||||
|
var table1Col2 = IntColumn("col2", Nullable)
|
||||||
|
var table1Col3 = IntColumn("col3", Nullable)
|
||||||
|
var table1Col4 = DateTimeColumn("col4", Nullable)
|
||||||
|
var table1 = NewTable(
|
||||||
|
"table1",
|
||||||
|
table1Col1,
|
||||||
|
table1Col2,
|
||||||
|
table1Col3,
|
||||||
|
table1Col4)
|
||||||
|
|
||||||
|
var table2Col3 = IntColumn("col3", Nullable)
|
||||||
|
var table2Col4 = IntColumn("col4", Nullable)
|
||||||
|
var table2 = NewTable(
|
||||||
|
"table2",
|
||||||
|
table2Col3,
|
||||||
|
table2Col4)
|
||||||
|
|
||||||
|
var table3Col1 = IntColumn("col1", Nullable)
|
||||||
|
var table3Col2 = IntColumn("col2", Nullable)
|
||||||
|
var table3 = NewTable(
|
||||||
|
"table3",
|
||||||
|
table3Col1,
|
||||||
|
table3Col2)
|
@ -0,0 +1,79 @@
|
|||||||
|
package sqlbuilder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Clause interface {
|
||||||
|
SerializeSql(out *bytes.Buffer) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// A clause that can be used in order by
|
||||||
|
type OrderByClause interface {
|
||||||
|
Clause
|
||||||
|
isOrderByClauseInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
// An expression
|
||||||
|
type Expression interface {
|
||||||
|
Clause
|
||||||
|
isExpressionInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
type BoolExpression interface {
|
||||||
|
Clause
|
||||||
|
isBoolExpressionInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
// A clause that is selectable.
|
||||||
|
type Projection interface {
|
||||||
|
Clause
|
||||||
|
isProjectionInterface
|
||||||
|
SerializeSqlForColumnList(out *bytes.Buffer) error
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Boiler plates ...
|
||||||
|
//
|
||||||
|
|
||||||
|
type isOrderByClauseInterface interface {
|
||||||
|
isOrderByClauseType()
|
||||||
|
}
|
||||||
|
|
||||||
|
type isOrderByClause struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *isOrderByClause) isOrderByClauseType() {
|
||||||
|
}
|
||||||
|
|
||||||
|
type isExpressionInterface interface {
|
||||||
|
isExpressionType()
|
||||||
|
}
|
||||||
|
|
||||||
|
type isExpression struct {
|
||||||
|
isOrderByClause // can always use expression in order by.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *isExpression) isExpressionType() {
|
||||||
|
}
|
||||||
|
|
||||||
|
type isBoolExpressionInterface interface {
|
||||||
|
isExpressionInterface
|
||||||
|
isBoolExpressionType()
|
||||||
|
}
|
||||||
|
|
||||||
|
type isBoolExpression struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *isBoolExpression) isBoolExpressionType() {
|
||||||
|
}
|
||||||
|
|
||||||
|
type isProjectionInterface interface {
|
||||||
|
isProjectionType()
|
||||||
|
}
|
||||||
|
|
||||||
|
type isProjection struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *isProjection) isProjectionType() {
|
||||||
|
}
|
Loading…
Reference in New Issue