sq/libsq/ast/selector.go

327 lines
7.8 KiB
Go
Raw Normal View History

2016-10-17 07:14:01 +03:00
package ast
import (
"fmt"
"strings"
2016-10-17 07:14:01 +03:00
"github.com/antlr/antlr4/runtime/Go/antlr/v4"
2020-08-06 20:58:47 +03:00
"github.com/neilotoole/sq/libsq/ast/internal/slq"
2016-10-17 07:14:01 +03:00
)
const (
msgNodeNoAddChild = "%T cannot add children: failed to add %T"
msgNodeNoAddChildren = "%T cannot add children: failed to add %d children"
)
2020-08-06 20:58:47 +03:00
func newSelectorNode(parent Node, ctx slq.ISelectorContext) (*SelectorNode, error) {
selNode := &SelectorNode{}
selNode.parent = parent
selNode.ctx = ctx
selNode.text = ctx.GetText()
var err error
names := ctx.AllNAME()
switch len(names) {
default:
return nil, errorf("expected 1 or 2 name parts in selector (e.g. '.table.column') but got %d parts: %s",
len(names), ctx.GetText())
case 1:
if selNode.name0, err = extractSelVal(names[0]); err != nil {
return nil, err
}
case 2:
if selNode.name0, err = extractSelVal(names[0]); err != nil {
return nil, err
}
if selNode.name1, err = extractSelVal(names[1]); err != nil {
return nil, err
}
}
return selNode, nil
}
var (
_ Node = (*SelectorNode)(nil)
_ Selector = (*SelectorNode)(nil)
)
2016-10-17 07:14:01 +03:00
// SelectorNode is a selector such as ".my_table" or ".my_col". The
2020-08-06 20:58:47 +03:00
// generic selector will typically be replaced with a more specific
// selector node such as TblSelectorNode or ColSelectorNode.
type SelectorNode struct {
2020-08-06 20:58:47 +03:00
baseNode
// alias is the (optional) alias part. For example, given ".first_name:given_name",
// the alias value is "given_name". May be empty.
alias string
// name0 is the first name part.
// - .actor -> name0 = .actor
// - .first_name -> name0 = .first_name
// - .actor.first_name -> name0 = .actor
name0 string
// name1 is the optional second name part.
// - .actor --> name1 = EMPTY
// - .actor.first_name -> name1 = first_name
name1 string
2016-10-17 07:14:01 +03:00
}
// selector implements the ast.selector marker interface.
func (s *SelectorNode) selector() {
}
// Strings returns a log/debug-friendly representation.
func (s *SelectorNode) String() string {
2016-10-17 07:14:01 +03:00
return nodeString(s)
}
// SelValue returns the selector value.
// See extractSelValue.
func (s *SelectorNode) SelValue() (string, error) {
return extractSelVal(s.ctx)
2016-10-17 07:14:01 +03:00
}
var (
_ Node = (*TblSelectorNode)(nil)
_ Tabler = (*TblSelectorNode)(nil)
)
2020-08-06 20:58:47 +03:00
// TblSelectorNode is a selector for a table, such as ".my_table"
2020-08-06 20:58:47 +03:00
// or "@my_src.my_table".
type TblSelectorNode struct {
SelectorNode
handle string
tblName string
2016-10-17 07:14:01 +03:00
}
// newTblSelector creates a new TblSelectorNode from ctx.
func newTblSelector(selNode *SelectorNode) (*TblSelectorNode, error) { //nolint:unparam
n := &TblSelectorNode{
SelectorNode: *selNode,
tblName: selNode.name0,
}
return n, nil
}
// TblName returns the table name. This is the raw value without punctuation.
func (s *TblSelectorNode) TblName() string {
return s.tblName
}
// Handle returns the handle, which may be empty.
func (s *TblSelectorNode) Handle() string {
return s.handle
2016-10-17 07:14:01 +03:00
}
// Tabler implements the Tabler marker interface.
func (s *TblSelectorNode) tabler() {
2016-10-17 07:14:01 +03:00
// no-op
}
// SelValue returns the table name.
// TODO: Can we get rid of this method SelValue?
func (s *TblSelectorNode) SelValue() (string, error) {
return s.TblName(), nil
2016-10-17 07:14:01 +03:00
}
// String returns a log/debug-friendly representation.
func (s *TblSelectorNode) String() string {
2016-10-17 07:14:01 +03:00
text := nodeString(s)
selVal, err := s.SelValue()
if err != nil {
selVal = "error: " + err.Error()
}
text += fmt.Sprintf(" | table: {%s} | datasource: {%s}", selVal, s.Handle())
2016-10-17 07:14:01 +03:00
return text
}
var (
_ Node = (*TblColSelectorNode)(nil)
_ ResultColumn = (*TblColSelectorNode)(nil)
_ Selector = (*TblColSelectorNode)(nil)
)
2020-08-06 20:58:47 +03:00
// TblColSelectorNode models the TABLE.COLUMN selector, e.g. actor.first_name.
type TblColSelectorNode struct {
*SelectorNode
tblName string
colName string
2016-10-17 07:14:01 +03:00
}
// IsColumn implements ResultColumn.
func (n *TblColSelectorNode) IsColumn() bool {
return true
2016-10-17 07:14:01 +03:00
}
// Text implements ResultColumn.
func (n *TblColSelectorNode) Text() string {
return n.ctx.GetText()
2016-10-17 07:14:01 +03:00
}
func newTblColSelectorNode(selNode *SelectorNode) (*TblColSelectorNode, error) {
n := &TblColSelectorNode{
SelectorNode: selNode,
tblName: selNode.name0,
colName: selNode.name1,
}
if n.tblName == "" {
return nil, errorf("cannot create %T: table name is empty: %s", n, n.Text())
}
if n.colName == "" {
return nil, errorf("cannot create %T: column name is empty: %s", n, n.Text())
}
return n, nil
}
// TblName returns the table name, e.g. actor.
func (n *TblColSelectorNode) TblName() string {
return n.tblName
}
// ColName returns the column name, e.g. first_name.
func (n *TblColSelectorNode) ColName() string {
return n.colName
}
// String returns a log/debug-friendly representation.
func (n *TblColSelectorNode) String() string {
return fmt.Sprintf("%T: %s", n, n.ctx.GetText())
}
// Alias returns the column alias, which may be empty.
// For example, given the selector ".first_name:given_name",
// the alias is "given_name".
func (n *TblColSelectorNode) Alias() string {
return n.alias
}
var (
_ Node = (*ColSelectorNode)(nil)
_ ResultColumn = (*ColSelectorNode)(nil)
_ Selector = (*ColSelectorNode)(nil)
)
// ColSelectorNode models a column selector such as ".first_name".
type ColSelectorNode struct {
*SelectorNode
colName string
}
// newColSelectorNode returns a ColSelectorNode constructed from ctx.
func newColSelectorNode(selNode *SelectorNode) (*ColSelectorNode, error) { //nolint:unparam
n := &ColSelectorNode{SelectorNode: selNode}
n.colName = selNode.name0
return n, nil
}
// Text implements ResultColumn.
func (n *ColSelectorNode) Text() string {
return n.ctx.GetText()
}
// IsColumn always returns true.
func (n *ColSelectorNode) IsColumn() bool {
2020-08-06 20:58:47 +03:00
return true
}
// ColName returns the column name. Note the name is not escaped/quoted,
// thus it could contain whitespace, etc.
func (n *ColSelectorNode) ColName() string {
return n.colName
}
// Alias returns the column alias, which may be empty.
// For example, given the selector ".first_name:given_name",
// the alias is "given_name".
func (n *ColSelectorNode) Alias() string {
return n.alias
}
// String returns a log/debug-friendly representation.
func (n *ColSelectorNode) String() string {
str := nodeString(n)
if n.alias != "" {
str += ":" + n.alias
}
return str
2016-10-17 07:14:01 +03:00
}
2020-08-06 20:58:47 +03:00
var _ Node = (*Cmpr)(nil)
// Cmpr models a comparison, such as ".age == 42".
2016-10-17 07:14:01 +03:00
type Cmpr struct {
2020-08-06 20:58:47 +03:00
baseNode
2016-10-17 07:14:01 +03:00
}
func (c *Cmpr) String() string {
return nodeString(c)
}
func newCmpr(parent Node, ctx slq.ICmprContext) *Cmpr {
leaf, _ := ctx.GetChild(0).(*antlr.TerminalNodeImpl) // FIXME: return an error
2016-10-17 07:14:01 +03:00
cmpr := &Cmpr{}
cmpr.ctx = leaf
cmpr.parent = parent
return cmpr
}
// HandleNode models a source handle such as "@sakila_sl3".
type HandleNode struct {
2020-08-06 20:58:47 +03:00
baseNode
2016-10-17 07:14:01 +03:00
}
func (d *HandleNode) String() string {
2016-10-17 07:14:01 +03:00
return nodeString(d)
}
// extractSelVal extracts the value of the selector. The function takes
// a selector node type as input, e.g. ast.SelectorNode.
// Example inputs:
//
// - .actor --> actor
// - .first_name --> first_name
// - ."first name" --> first name
//
// The function will remove the leading period, and quotes around the name.
func extractSelVal(ctx antlr.ParseTree) (string, error) {
if ctx == nil {
return "", errorf("invalid selector: is nil")
}
original := ctx.GetText()
if len(original) < 2 {
return "", errorf("invalid selector expression: too short: %s", original)
}
if original[0] != '.' {
return "", errorf("illegal selector expression: must start with period: %s", original)
}
// Trim the leading period, e.g. ".first_name" -> "first_name".
wip := original[1:]
// Remove quotes if applicable.
if wip[0] == '"' {
if wip[len(wip)-1] != '"' {
return "", errorf("illegal selector expression: unmatched quotes on string: %s", original)
}
wip = strings.TrimPrefix(wip, `"`)
wip = strings.TrimSuffix(wip, `"`)
if len(wip) == 0 {
return "", errorf("invalid selector expression: too short: %s", original)
}
}
return wip, nil
}