mirror of
https://github.com/neilotoole/sq.git
synced 2024-12-29 19:24:26 +03:00
7c56377b40
* Field alignment
144 lines
3.3 KiB
Go
144 lines
3.3 KiB
Go
package libsq
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/neilotoole/sq/libsq/ast"
|
|
"github.com/neilotoole/sq/libsq/core/errz"
|
|
"github.com/neilotoole/sq/libsq/core/loz"
|
|
)
|
|
|
|
// queryModel is a model of an SLQ query built from the AST.
|
|
type queryModel struct {
|
|
AST *ast.AST
|
|
Table *ast.TblSelectorNode
|
|
Range *ast.RowRangeNode
|
|
Where *ast.WhereNode
|
|
OrderBy *ast.OrderByNode
|
|
GroupBy *ast.GroupByNode
|
|
Having *ast.HavingNode
|
|
Distinct *ast.UniqueNode
|
|
Joins []*ast.JoinNode
|
|
Cols []ast.ResultColumn
|
|
}
|
|
|
|
func (qm *queryModel) String() string {
|
|
return fmt.Sprintf("%v | %v | %v", qm.Table, qm.Cols, qm.Range)
|
|
}
|
|
|
|
// buildQueryModel creates a queryModel instance from the AST.
|
|
func buildQueryModel(qc *QueryContext, a *ast.AST) (*queryModel, error) {
|
|
if len(a.Segments()) == 0 {
|
|
return nil, errz.Errorf("invalid query: no segments")
|
|
}
|
|
|
|
var (
|
|
ok bool
|
|
err error
|
|
insp = ast.NewInspector(a)
|
|
qm = &queryModel{AST: a}
|
|
)
|
|
|
|
qm.Table = insp.FindFirstTableSelector()
|
|
if err = specifyTableFully(qc, qm.Table); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if qm.Joins, err = insp.FindJoins(); err != nil {
|
|
return nil, err
|
|
}
|
|
for _, join := range qm.Joins {
|
|
if err = specifyTableFully(qc, join.Table()); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if len(qm.Joins) > 0 && qm.Table == nil {
|
|
return nil, errz.Errorf("invalid query: join doesn't have a preceding table selector")
|
|
}
|
|
|
|
if qm.Range, err = insp.FindRowRangeNode(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
seg, err := insp.FindColExprSegment()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if seg != nil {
|
|
var colExprs []ast.ResultColumn
|
|
if colExprs, ok = loz.ToSliceType[ast.Node, ast.ResultColumn](seg.Children()...); !ok {
|
|
return nil, errz.Errorf("segment children contained elements that were not of type %T: %s",
|
|
ast.ResultColumn(nil), seg.Text())
|
|
}
|
|
|
|
qm.Cols = colExprs
|
|
}
|
|
|
|
whereClauses, err := insp.FindWhereClauses()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(whereClauses) > 1 {
|
|
return nil, errz.Errorf("only one WHERE clause is supported, but found %d", len(whereClauses))
|
|
} else if len(whereClauses) == 1 {
|
|
qm.Where = whereClauses[0]
|
|
}
|
|
|
|
if qm.GroupBy, err = insp.FindGroupByNode(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if qm.Having, err = insp.FindHavingNode(); err != nil {
|
|
return nil, err
|
|
}
|
|
if qm.Having != nil && qm.GroupBy == nil {
|
|
return nil, errz.Errorf("having() clause requires preceding group_by() clause")
|
|
}
|
|
|
|
if qm.Distinct, err = insp.FindUniqueNode(); err != nil {
|
|
return nil, err
|
|
}
|
|
if qm.OrderBy, err = insp.FindOrderByNode(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return qm, nil
|
|
}
|
|
|
|
// specifyTableFully sets the handle, catalog and schema for tbl, if
|
|
// possible. If tbl is nil, this is a no-op.
|
|
func specifyTableFully(qc *QueryContext, tbl *ast.TblSelectorNode) error {
|
|
if tbl == nil {
|
|
return nil
|
|
}
|
|
|
|
if tbl.Handle() == "" {
|
|
// It's possible that there's no active source: this
|
|
// is effectively a no-op in that case.
|
|
tbl.SetHandle(qc.Collection.ActiveHandle())
|
|
}
|
|
|
|
if tbl.Handle() != "" {
|
|
// Check if the source has catalog and schema overrides set,
|
|
// and if so, update tbl with those values.
|
|
src, err := qc.Collection.Get(tbl.Handle())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tfq := tbl.Table()
|
|
if src.Catalog != "" {
|
|
tfq.Catalog = src.Catalog
|
|
}
|
|
if src.Schema != "" {
|
|
tfq.Schema = src.Schema
|
|
}
|
|
tbl.SetTable(tfq)
|
|
}
|
|
|
|
return nil
|
|
}
|