fork github.com/doug-martin
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
package sqlgen
|
||||
|
||||
import (
|
||||
"git.fsdpf.net/go/db/v2/exp"
|
||||
"git.fsdpf.net/go/db/v2/internal/errors"
|
||||
"git.fsdpf.net/go/db/v2/internal/sb"
|
||||
)
|
||||
|
||||
type (
|
||||
// An adapter interface to be used by a Dataset to generate SQL for a specific dialect.
|
||||
// See DefaultAdapter for a concrete implementation and examples.
|
||||
SelectSQLGenerator interface {
|
||||
Dialect() string
|
||||
Generate(b sb.SQLBuilder, clauses exp.SelectClauses)
|
||||
}
|
||||
// The default adapter. This class should be used when building a new adapter. When creating a new adapter you can
|
||||
// either override methods, or more typically update default values.
|
||||
// See (github.com/doug-martin/goqu/dialect/postgres)
|
||||
selectSQLGenerator struct {
|
||||
CommonSQLGenerator
|
||||
}
|
||||
)
|
||||
|
||||
func ErrNotSupportedJoinType(j exp.JoinExpression) error {
|
||||
return errors.New("dialect does not support %v", j.JoinType())
|
||||
}
|
||||
|
||||
func ErrJoinConditionRequired(j exp.JoinExpression) error {
|
||||
return errors.New("join condition required for conditioned join %v", j.JoinType())
|
||||
}
|
||||
|
||||
func ErrDistinctOnNotSupported(dialect string) error {
|
||||
return errors.New("dialect does not support DISTINCT ON clause [dialect=%s]", dialect)
|
||||
}
|
||||
|
||||
func ErrWindowNotSupported(dialect string) error {
|
||||
return errors.New("dialect does not support WINDOW clause [dialect=%s]", dialect)
|
||||
}
|
||||
|
||||
var ErrNoWindowName = errors.New("window expresion has no valid name")
|
||||
|
||||
func NewSelectSQLGenerator(dialect string, do *SQLDialectOptions) SelectSQLGenerator {
|
||||
return &selectSQLGenerator{NewCommonSQLGenerator(dialect, do)}
|
||||
}
|
||||
|
||||
func (ssg *selectSQLGenerator) Generate(b sb.SQLBuilder, clauses exp.SelectClauses) {
|
||||
for _, f := range ssg.DialectOptions().SelectSQLOrder {
|
||||
if b.Error() != nil {
|
||||
return
|
||||
}
|
||||
switch f {
|
||||
case CommonTableSQLFragment:
|
||||
ssg.ExpressionSQLGenerator().Generate(b, clauses.CommonTables())
|
||||
case SelectSQLFragment:
|
||||
ssg.SelectSQL(b, clauses)
|
||||
case SelectWithLimitSQLFragment:
|
||||
ssg.SelectWithLimitSQL(b, clauses)
|
||||
case FromSQLFragment:
|
||||
ssg.FromSQL(b, clauses.From())
|
||||
case JoinSQLFragment:
|
||||
ssg.JoinSQL(b, clauses.Joins())
|
||||
case WhereSQLFragment:
|
||||
ssg.WhereSQL(b, clauses.Where())
|
||||
case GroupBySQLFragment:
|
||||
ssg.GroupBySQL(b, clauses.GroupBy())
|
||||
case HavingSQLFragment:
|
||||
ssg.HavingSQL(b, clauses.Having())
|
||||
case WindowSQLFragment:
|
||||
ssg.WindowSQL(b, clauses.Windows())
|
||||
case CompoundsSQLFragment:
|
||||
ssg.CompoundsSQL(b, clauses.Compounds())
|
||||
case OrderSQLFragment:
|
||||
ssg.OrderSQL(b, clauses.Order())
|
||||
case OrderWithOffsetFetchSQLFragment:
|
||||
ssg.OrderWithOffsetFetchSQL(b, clauses.Order(), clauses.Offset(), clauses.Limit())
|
||||
case LimitSQLFragment:
|
||||
ssg.LimitSQL(b, clauses.Limit())
|
||||
case OffsetSQLFragment:
|
||||
ssg.OffsetSQL(b, clauses.Offset())
|
||||
case ForSQLFragment:
|
||||
ssg.ForSQL(b, clauses.Lock())
|
||||
default:
|
||||
b.SetError(ErrNotSupportedFragment("SELECT", f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ssg *selectSQLGenerator) selectSQLCommon(b sb.SQLBuilder, clauses exp.SelectClauses) {
|
||||
dc := clauses.Distinct()
|
||||
if dc != nil {
|
||||
b.Write(ssg.DialectOptions().DistinctFragment)
|
||||
if !dc.IsEmpty() {
|
||||
if ssg.DialectOptions().SupportsDistinctOn {
|
||||
b.Write(ssg.DialectOptions().OnFragment).WriteRunes(ssg.DialectOptions().LeftParenRune)
|
||||
ssg.ExpressionSQLGenerator().Generate(b, dc)
|
||||
b.WriteRunes(ssg.DialectOptions().RightParenRune, ssg.DialectOptions().SpaceRune)
|
||||
} else {
|
||||
b.SetError(ErrDistinctOnNotSupported(ssg.Dialect()))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
b.WriteRunes(ssg.DialectOptions().SpaceRune)
|
||||
}
|
||||
}
|
||||
|
||||
if cols := clauses.Select(); clauses.IsDefaultSelect() || len(cols.Columns()) == 0 {
|
||||
b.WriteRunes(ssg.DialectOptions().StarRune)
|
||||
} else {
|
||||
ssg.ExpressionSQLGenerator().Generate(b, cols)
|
||||
}
|
||||
}
|
||||
|
||||
// Adds the SELECT clause and columns to a sql statement
|
||||
func (ssg *selectSQLGenerator) SelectSQL(b sb.SQLBuilder, clauses exp.SelectClauses) {
|
||||
b.Write(ssg.DialectOptions().SelectClause).WriteRunes(ssg.DialectOptions().SpaceRune)
|
||||
ssg.selectSQLCommon(b, clauses)
|
||||
}
|
||||
|
||||
// Adds the SELECT clause along with LIMIT to a SQL statement (e.g. MSSQL dialect: SELECT TOP 10 ...)
|
||||
func (ssg *selectSQLGenerator) SelectWithLimitSQL(b sb.SQLBuilder, clauses exp.SelectClauses) {
|
||||
b.Write(ssg.DialectOptions().SelectClause).WriteRunes(ssg.DialectOptions().SpaceRune)
|
||||
if clauses.Offset() == 0 && clauses.Limit() != nil {
|
||||
ssg.LimitSQL(b, clauses.Limit())
|
||||
b.WriteRunes(ssg.DialectOptions().SpaceRune)
|
||||
}
|
||||
ssg.selectSQLCommon(b, clauses)
|
||||
}
|
||||
|
||||
// Generates the JOIN clauses for an SQL statement
|
||||
func (ssg *selectSQLGenerator) JoinSQL(b sb.SQLBuilder, joins exp.JoinExpressions) {
|
||||
if len(joins) > 0 {
|
||||
for _, j := range joins {
|
||||
joinType, ok := ssg.DialectOptions().JoinTypeLookup[j.JoinType()]
|
||||
if !ok {
|
||||
b.SetError(ErrNotSupportedJoinType(j))
|
||||
return
|
||||
}
|
||||
b.Write(joinType)
|
||||
ssg.ExpressionSQLGenerator().Generate(b, j.Table())
|
||||
if t, ok := j.(exp.ConditionedJoinExpression); ok {
|
||||
if t.IsConditionEmpty() {
|
||||
b.SetError(ErrJoinConditionRequired(j))
|
||||
return
|
||||
}
|
||||
ssg.joinConditionSQL(b, t.Condition())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generates the GROUP BY clause for an SQL statement
|
||||
func (ssg *selectSQLGenerator) GroupBySQL(b sb.SQLBuilder, groupBy exp.ColumnListExpression) {
|
||||
if groupBy != nil && len(groupBy.Columns()) > 0 {
|
||||
b.Write(ssg.DialectOptions().GroupByFragment)
|
||||
ssg.ExpressionSQLGenerator().Generate(b, groupBy)
|
||||
}
|
||||
}
|
||||
|
||||
// Generates the HAVING clause for an SQL statement
|
||||
func (ssg *selectSQLGenerator) HavingSQL(b sb.SQLBuilder, having exp.ExpressionList) {
|
||||
if having != nil && len(having.Expressions()) > 0 {
|
||||
b.Write(ssg.DialectOptions().HavingFragment)
|
||||
ssg.ExpressionSQLGenerator().Generate(b, having)
|
||||
}
|
||||
}
|
||||
|
||||
// Generates the OFFSET clause for an SQL statement
|
||||
func (ssg *selectSQLGenerator) OffsetSQL(b sb.SQLBuilder, offset uint) {
|
||||
if offset > 0 {
|
||||
b.Write(ssg.DialectOptions().OffsetFragment)
|
||||
ssg.ExpressionSQLGenerator().Generate(b, offset)
|
||||
}
|
||||
}
|
||||
|
||||
// Generates the compound sql clause for an SQL statement (e.g. UNION, INTERSECT)
|
||||
func (ssg *selectSQLGenerator) CompoundsSQL(b sb.SQLBuilder, compounds []exp.CompoundExpression) {
|
||||
for _, compound := range compounds {
|
||||
ssg.ExpressionSQLGenerator().Generate(b, compound)
|
||||
}
|
||||
}
|
||||
|
||||
// Generates the FOR (aka "locking") clause for an SQL statement
|
||||
func (ssg *selectSQLGenerator) ForSQL(b sb.SQLBuilder, lockingClause exp.Lock) {
|
||||
if lockingClause == nil {
|
||||
return
|
||||
}
|
||||
switch lockingClause.Strength() {
|
||||
case exp.ForNolock:
|
||||
return
|
||||
case exp.ForUpdate:
|
||||
b.Write(ssg.DialectOptions().ForUpdateFragment)
|
||||
case exp.ForNoKeyUpdate:
|
||||
b.Write(ssg.DialectOptions().ForNoKeyUpdateFragment)
|
||||
case exp.ForShare:
|
||||
b.Write(ssg.DialectOptions().ForShareFragment)
|
||||
case exp.ForKeyShare:
|
||||
b.Write(ssg.DialectOptions().ForKeyShareFragment)
|
||||
}
|
||||
|
||||
of := lockingClause.Of()
|
||||
if ofLen := len(of); ofLen > 0 {
|
||||
if ofFragment := ssg.DialectOptions().OfFragment; len(ofFragment) > 0 {
|
||||
b.Write(ofFragment)
|
||||
for i, table := range of {
|
||||
ssg.ExpressionSQLGenerator().Generate(b, table)
|
||||
if i < ofLen-1 {
|
||||
b.WriteRunes(ssg.DialectOptions().CommaRune, ssg.DialectOptions().SpaceRune)
|
||||
}
|
||||
}
|
||||
b.WriteRunes(ssg.DialectOptions().SpaceRune)
|
||||
}
|
||||
}
|
||||
|
||||
// the WAIT case is the default in Postgres, and is what you get if you don't specify NOWAIT or
|
||||
// SKIP LOCKED. There's no special syntax for it in PG, so we don't do anything for it here
|
||||
switch lockingClause.WaitOption() {
|
||||
case exp.Wait:
|
||||
return
|
||||
case exp.NoWait:
|
||||
b.Write(ssg.DialectOptions().NowaitFragment)
|
||||
case exp.SkipLocked:
|
||||
b.Write(ssg.DialectOptions().SkipLockedFragment)
|
||||
}
|
||||
}
|
||||
|
||||
func (ssg *selectSQLGenerator) WindowSQL(b sb.SQLBuilder, windows []exp.WindowExpression) {
|
||||
weLen := len(windows)
|
||||
if weLen == 0 {
|
||||
return
|
||||
}
|
||||
if !ssg.DialectOptions().SupportsWindowFunction {
|
||||
b.SetError(ErrWindowNotSupported(ssg.Dialect()))
|
||||
return
|
||||
}
|
||||
b.Write(ssg.DialectOptions().WindowFragment)
|
||||
for i, we := range windows {
|
||||
if !we.HasName() {
|
||||
b.SetError(ErrNoWindowName)
|
||||
}
|
||||
ssg.ExpressionSQLGenerator().Generate(b, we)
|
||||
if i < weLen-1 {
|
||||
b.WriteRunes(ssg.DialectOptions().CommaRune, ssg.DialectOptions().SpaceRune)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ssg *selectSQLGenerator) joinConditionSQL(b sb.SQLBuilder, jc exp.JoinCondition) {
|
||||
switch t := jc.(type) {
|
||||
case exp.JoinOnCondition:
|
||||
ssg.joinOnConditionSQL(b, t)
|
||||
case exp.JoinUsingCondition:
|
||||
ssg.joinUsingConditionSQL(b, t)
|
||||
}
|
||||
}
|
||||
|
||||
func (ssg *selectSQLGenerator) joinUsingConditionSQL(b sb.SQLBuilder, jc exp.JoinUsingCondition) {
|
||||
b.Write(ssg.DialectOptions().UsingFragment).
|
||||
WriteRunes(ssg.DialectOptions().LeftParenRune)
|
||||
ssg.ExpressionSQLGenerator().Generate(b, jc.Using())
|
||||
b.WriteRunes(ssg.DialectOptions().RightParenRune)
|
||||
}
|
||||
|
||||
func (ssg *selectSQLGenerator) joinOnConditionSQL(b sb.SQLBuilder, jc exp.JoinOnCondition) {
|
||||
b.Write(ssg.DialectOptions().OnFragment)
|
||||
ssg.ExpressionSQLGenerator().Generate(b, jc.On())
|
||||
}
|
||||
Reference in New Issue
Block a user