Files
db/sqlgen/select_sql_generator.go
T
whatandClaude 304d553b3c docs: Add CLAUDE.md with codebase guidance
Create comprehensive documentation for future Claude Code instances working in this repository, including:
- Development commands for testing, building, and code quality
- Core architecture overview of the SQL query builder system
- Directory structure and component explanations
- Testing patterns and conventions
- Key dependencies and their purposes

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-27 15:47:28 +08:00

267 lines
8.5 KiB
Go

package sqlgen
import (
"git.fsdpf.net/go/db/exp"
"git.fsdpf.net/go/db/internal/errors"
"git.fsdpf.net/go/db/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())
}