2023-06-18 04:28:11 +03:00
|
|
|
package libsq
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/neilotoole/sq/libsq/core/loz"
|
|
|
|
|
|
|
|
"github.com/neilotoole/sq/libsq/ast"
|
|
|
|
"github.com/neilotoole/sq/libsq/core/errz"
|
|
|
|
)
|
|
|
|
|
|
|
|
// queryModel is a model of an SLQ query built from the AST.
|
|
|
|
type queryModel struct {
|
|
|
|
AST *ast.AST
|
2023-07-03 18:34:19 +03:00
|
|
|
Table *ast.TblSelectorNode
|
|
|
|
Joins []*ast.JoinNode
|
2023-06-18 04:28:11 +03:00
|
|
|
Cols []ast.ResultColumn
|
|
|
|
Range *ast.RowRangeNode
|
|
|
|
Where *ast.WhereNode
|
|
|
|
OrderBy *ast.OrderByNode
|
|
|
|
GroupBy *ast.GroupByNode
|
|
|
|
Distinct *ast.UniqueNode
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
2023-07-03 18:34:19 +03:00
|
|
|
func buildQueryModel(qc *QueryContext, a *ast.AST) (*queryModel, error) {
|
2023-06-18 04:28:11 +03:00
|
|
|
if len(a.Segments()) == 0 {
|
2023-07-03 18:34:19 +03:00
|
|
|
return nil, errz.Errorf("invalid query: no segments")
|
2023-06-18 04:28:11 +03:00
|
|
|
}
|
|
|
|
|
2023-07-03 18:34:19 +03:00
|
|
|
var (
|
|
|
|
ok bool
|
|
|
|
err error
|
|
|
|
insp = ast.NewInspector(a)
|
|
|
|
qm = &queryModel{AST: a}
|
|
|
|
)
|
|
|
|
|
|
|
|
qm.Table = insp.FindFirstTableSelector()
|
|
|
|
if qm.Table != nil {
|
|
|
|
// If the table selector doesn't specify a handle, set the
|
|
|
|
// table's handle to the active handle.
|
|
|
|
if qm.Table.Handle() == "" {
|
|
|
|
// It's possible that there's no active source: this
|
|
|
|
// is effectively a no-op in that case.
|
|
|
|
qm.Table.SetHandle(qc.Collection.ActiveHandle())
|
2023-06-18 04:28:11 +03:00
|
|
|
}
|
2023-07-03 18:34:19 +03:00
|
|
|
}
|
2023-06-18 04:28:11 +03:00
|
|
|
|
2023-07-03 18:34:19 +03:00
|
|
|
if qm.Joins, err = insp.FindJoins(); err != nil {
|
|
|
|
return nil, err
|
2023-06-18 04:28:11 +03:00
|
|
|
}
|
|
|
|
|
2023-07-03 18:34:19 +03:00
|
|
|
if len(qm.Joins) > 0 && qm.Table == nil {
|
|
|
|
return nil, errz.Errorf("invalid query: join doesn't have a preceding table selector")
|
|
|
|
}
|
2023-06-18 04:28:11 +03:00
|
|
|
|
|
|
|
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.OrderBy, err = insp.FindOrderByNode(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if qm.GroupBy, err = insp.FindGroupByNode(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if qm.Distinct, err = insp.FindUniqueNode(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return qm, nil
|
|
|
|
}
|