#254: explicit where() clause (#255)

Implement explicit `where()` clause.
This commit is contained in:
Neil O'Toole 2023-06-16 22:54:25 -06:00 committed by GitHub
parent b492788ac0
commit e93f462fff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 2018 additions and 1178 deletions

View File

@ -14,7 +14,7 @@ func newRootCmd() *cobra.Command {
Short: "sq",
Long: `sq is a swiss-army knife for wrangling data.
$ sq '@sakila_pg | .actor | .first_name, .last_name | .[0:10]' --json
$ sq '@sakila_pg | .actor | where(.actor_id > 2) | .first_name, .last_name | .[0:10]'
Use sq to query Postgres, SQLite, SQLServer, MySQL, CSV, Excel, etc,
and output in text, JSON, CSV, Excel and so on, or write output to a

View File

@ -130,7 +130,7 @@ func TestCmdSLQ_OutputFlag(t *testing.T) {
}
func TestCmdSLQ_Join(t *testing.T) {
const queryTpl = `%s.customer, %s.address | join(.address_id) | .customer_id == %d | .[0] | .customer_id, .email, .city_id` //nolint:lll
const queryTpl = `%s.customer, %s.address | join(.address_id) | where(.customer_id == %d) | .[0] | .customer_id, .email, .city_id` //nolint:lll
handles := sakila.SQLAll()
// Attempt to join every SQL test source against every SQL test source.

View File

@ -9,7 +9,7 @@ query: segment ('|' segment)*;
segment: (element) (',' element)*;
element
: handleTable
: handleTable
| handle
| selectorElement
| join
@ -18,8 +18,9 @@ element
| rowRange
| uniqueFunc
| countFunc
| where
| funcElement
| expr;
| exprElement;
// cmpr is a comparison operator.
cmpr: LT_EQ | LT | GT_EQ | GT | EQ | NEQ;
@ -33,7 +34,6 @@ funcName
| 'avg'
| 'max'
| 'min'
| 'where'
| PROPRIETARY_FUNC_NAME
;
@ -79,6 +79,31 @@ funcs because of the several forms it can take.
countFunc: 'count' (LPAR (selector)? RPAR)? (alias)?;
/*
where
-----
The "where" mechanism implements SQL's WHERE clause.
.actor | where(.actor_id > 10 && .first_name == "TOM")
From a SQL perspective, "where" is the natural name to use. However,
and inconveniently, jq uses "select" for this purpose.
https://jqlang.github.io/jq/manual/v1.6/#select(boolean_expression)
One of sq's design principles is to adhere to jq syntax whenver possible.
Alas, for SQL users, "select" means "SELECT these columns", not "select
the matching rows".
It's unclear what the best approach is here, so for now, we will allow
both "where" and "select", and plan to deprecate one of these after user
feedback.
*/
WHERE: 'where' | 'select';
where: WHERE LPAR (expr)? RPAR;
/*
group_by
-------
@ -189,20 +214,12 @@ rowRange:
| NN // [10]
)? ']';
//fnName:
// 'sum'
// | 'SUM'
// | 'avg'
// | 'AVG'
// | 'count'
// | 'COUNT'
// | 'where'
// | 'WHERE';
exprElement: expr (alias)?;
expr:
selector
'(' expr ')'
| selector
| literal
| arg
| unaryOperator expr

View File

@ -64,7 +64,6 @@ func buildAST(log *slog.Logger, query slq.IQueryContext) (*AST, error) {
{typeSelectorNode, narrowColSel},
{typeJoinNode, determineJoinTables},
{typeRowRangeNode, visitCheckRowRange},
{typeExprNode, findWhereClause},
}
for _, visitor := range visitors {
@ -99,14 +98,17 @@ type AST struct {
text string
}
// Parent implements ast.Node.
func (a *AST) Parent() Node {
return nil
}
// SetParent implements ast.Node.
func (a *AST) SetParent(parent Node) error {
return errorf("root node (%T) cannot have parent: tried to add parent %T", a, parent)
}
// Children implements ast.Node.
func (a *AST) Children() []Node {
nodes := make([]Node, len(a.segs))
@ -117,10 +119,12 @@ func (a *AST) Children() []Node {
return nodes
}
// Segments returns the AST's segments (its direct children).
func (a *AST) Segments() []*SegmentNode {
return a.segs
}
// AddChild implements ast.Node.
func (a *AST) AddChild(node Node) error {
seg, ok := node.(*SegmentNode)
if !ok {
@ -131,6 +135,7 @@ func (a *AST) AddChild(node Node) error {
return nil
}
// SetChildren implements ast.Node.
func (a *AST) SetChildren(children []Node) error {
segs := make([]*SegmentNode, len(children))
@ -147,10 +152,12 @@ func (a *AST) SetChildren(children []Node) error {
return nil
}
// Context implements ast.Node.
func (a *AST) Context() antlr.ParseTree {
return a.ctx
}
// SetContext implements ast.Node.
func (a *AST) SetContext(ctx antlr.ParseTree) error {
qCtx, ok := ctx.(*slq.QueryContext)
if !ok {
@ -161,10 +168,12 @@ func (a *AST) SetContext(ctx antlr.ParseTree) error {
return nil
}
// String implements ast.Node.
func (a *AST) String() string {
return nodeString(a)
}
// Text implements ast.Node.
func (a *AST) Text() string {
return a.ctx.GetText()
}

156
libsq/ast/expr.go Normal file
View File

@ -0,0 +1,156 @@
package ast
import "github.com/neilotoole/sq/libsq/ast/internal/slq"
var (
_ Node = (*ExprElementNode)(nil)
_ ResultColumn = (*ExprElementNode)(nil)
)
// ExprElementNode is an expression that acts as a ResultColumn.
//
// .actor | (1+2):alias
//
// In the example above, the expression "(1+2)" is rendered with
// an alias, e.g.
//
// SELECT 1+2 AS "alias" FROM "actor"
type ExprElementNode struct {
baseNode
alias string
exprNode *ExprNode
}
// String returns a log/debug-friendly representation.
func (ex *ExprElementNode) String() string {
str := nodeString(ex)
if ex.alias != "" {
str += ":" + ex.alias
}
return str
}
// Text implements ResultColumn.
func (ex *ExprElementNode) Text() string {
return ex.ctx.GetText()
}
// Alias implements ResultColumn.
func (ex *ExprElementNode) Alias() string {
return ex.alias
}
// ExprNode returns the child expression.
func (ex *ExprElementNode) ExprNode() *ExprNode {
return ex.exprNode
}
// SetChildren implements Node.
func (ex *ExprElementNode) SetChildren(children []Node) error {
ex.setChildren(children)
return nil
}
// AddChild implements Node.
func (ex *ExprElementNode) AddChild(child Node) error {
// TODO: add check for valid ExprElementNode child types
ex.addChild(child)
return child.SetParent(ex)
}
// VisitExprElement implements slq.SLQVisitor.
func (v *parseTreeVisitor) VisitExprElement(ctx *slq.ExprElementContext) interface{} {
childCount := ctx.GetChildCount()
if childCount == 0 || childCount > 2 {
return errorf("parser: invalid expression: expected 1 or 2 children, but got %d: %v",
childCount, ctx.GetText())
}
node := &ExprElementNode{}
node.ctx = ctx
node.text = ctx.GetText()
if err := node.SetParent(v.cur); err != nil {
return err
}
if ctx.Expr() == nil {
return errorf("parser: invalid expression: %s", ctx.GetText())
}
if e := v.using(node, func() any {
exprCtx, ok := ctx.Expr().(*slq.ExprContext)
if !ok || exprCtx == nil {
return errorf("parser: invalid expression: %s", ctx.GetText())
}
return v.VisitExpr(exprCtx)
}); e != nil {
return e
}
var ok bool
if node.exprNode, ok = node.children[0].(*ExprNode); !ok {
return errorf("parser: invalid expression: %s", ctx.GetText())
}
if ctx.Alias() != nil {
aliasCtx, ok := ctx.Alias().(*slq.AliasContext)
if !ok {
return errorf("expected second child to be %T but was %T: %v", aliasCtx, ctx.Alias(), ctx.GetText())
}
if e := v.using(node, func() any {
return v.VisitAlias(aliasCtx)
}); e != nil {
return e
}
}
return v.cur.AddChild(node)
}
// VisitExpr implements slq.SLQVisitor.
func (v *parseTreeVisitor) VisitExpr(ctx *slq.ExprContext) any {
// check if the expr is a selector, e.g. ".uid"
if selCtx := ctx.Selector(); selCtx != nil {
selNode, err := newSelectorNode(v.cur, selCtx)
if err != nil {
return err
}
return v.cur.AddChild(selNode)
}
node := &ExprNode{}
node.ctx = ctx
node.text = ctx.GetText()
if err := node.SetParent(v.cur); err != nil {
return err
}
var err error
if node.parens, err = exprHasParens(ctx); err != nil {
return err
}
if e := v.using(node, func() any {
return v.VisitChildren(ctx)
}); e != nil {
return e
}
return v.cur.AddChild(node)
}
func exprHasParens(ctx *slq.ExprContext) (bool, error) {
if ctx == nil {
return false, errorf("expression context is nil")
}
lpar := ctx.LPAR()
rpar := ctx.RPAR()
switch {
case lpar == nil && rpar == nil:
return false, nil
case lpar != nil && rpar != nil:
return true, nil
default:
return false, errorf("unbalanced parenthesis: %s", ctx.GetText())
}
}

View File

@ -56,11 +56,6 @@ func (fn *FuncNode) SetChildren(children []Node) error {
return nil
}
// IsColumn implements ResultColumn.
func (fn *FuncNode) IsColumn() bool {
return false
}
// AddChild implements Node.
func (fn *FuncNode) AddChild(child Node) error {
// TODO: add check for valid FuncNode child types

File diff suppressed because one or more lines are too long

View File

@ -17,8 +17,8 @@ T__15=16
T__16=17
T__17=18
T__18=19
T__19=20
PROPRIETARY_FUNC_NAME=21
PROPRIETARY_FUNC_NAME=20
WHERE=21
GROUP_BY=22
ORDER_ASC=23
ORDER_DESC=24
@ -53,20 +53,19 @@ LINECOMMENT=49
'avg'=4
'max'=5
'min'=6
'where'=7
'join'=8
'unique'=9
'count'=10
'.['=11
'||'=12
'/'=13
'%'=14
'<<'=15
'>>'=16
'&'=17
'&&'=18
'~'=19
'!'=20
'join'=7
'unique'=8
'count'=9
'.['=10
'||'=11
'/'=12
'%'=13
'<<'=14
'>>'=15
'&'=16
'&&'=17
'~'=18
'!'=19
'group_by'=22
'+'=23
'-'=24

File diff suppressed because one or more lines are too long

View File

@ -17,8 +17,8 @@ T__15=16
T__16=17
T__17=18
T__18=19
T__19=20
PROPRIETARY_FUNC_NAME=21
PROPRIETARY_FUNC_NAME=20
WHERE=21
GROUP_BY=22
ORDER_ASC=23
ORDER_DESC=24
@ -53,20 +53,19 @@ LINECOMMENT=49
'avg'=4
'max'=5
'min'=6
'where'=7
'join'=8
'unique'=9
'count'=10
'.['=11
'||'=12
'/'=13
'%'=14
'<<'=15
'>>'=16
'&'=17
'&&'=18
'~'=19
'!'=20
'join'=7
'unique'=8
'count'=9
'.['=10
'||'=11
'/'=12
'%'=13
'<<'=14
'>>'=15
'&'=16
'&&'=17
'~'=18
'!'=19
'group_by'=22
'+'=23
'-'=24

View File

@ -92,6 +92,12 @@ func (s *BaseSLQListener) EnterCountFunc(ctx *CountFuncContext) {}
// ExitCountFunc is called when production countFunc is exited.
func (s *BaseSLQListener) ExitCountFunc(ctx *CountFuncContext) {}
// EnterWhere is called when production where is entered.
func (s *BaseSLQListener) EnterWhere(ctx *WhereContext) {}
// ExitWhere is called when production where is exited.
func (s *BaseSLQListener) ExitWhere(ctx *WhereContext) {}
// EnterGroupByTerm is called when production groupByTerm is entered.
func (s *BaseSLQListener) EnterGroupByTerm(ctx *GroupByTermContext) {}
@ -158,6 +164,12 @@ func (s *BaseSLQListener) EnterRowRange(ctx *RowRangeContext) {}
// ExitRowRange is called when production rowRange is exited.
func (s *BaseSLQListener) ExitRowRange(ctx *RowRangeContext) {}
// EnterExprElement is called when production exprElement is entered.
func (s *BaseSLQListener) EnterExprElement(ctx *ExprElementContext) {}
// ExitExprElement is called when production exprElement is exited.
func (s *BaseSLQListener) ExitExprElement(ctx *ExprElementContext) {}
// EnterExpr is called when production expr is entered.
func (s *BaseSLQListener) EnterExpr(ctx *ExprContext) {}

View File

@ -55,6 +55,10 @@ func (v *BaseSLQVisitor) VisitCountFunc(ctx *CountFuncContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseSLQVisitor) VisitWhere(ctx *WhereContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseSLQVisitor) VisitGroupByTerm(ctx *GroupByTermContext) interface{} {
return v.VisitChildren(ctx)
}
@ -99,6 +103,10 @@ func (v *BaseSLQVisitor) VisitRowRange(ctx *RowRangeContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseSLQVisitor) VisitExprElement(ctx *ExprElementContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseSLQVisitor) VisitExpr(ctx *ExprContext) interface{} {
return v.VisitChildren(ctx)
}

View File

@ -44,23 +44,24 @@ func slqlexerLexerInit() {
"DEFAULT_MODE",
}
staticData.literalNames = []string{
"", "';'", "'*'", "'sum'", "'avg'", "'max'", "'min'", "'where'", "'join'",
"'unique'", "'count'", "'.['", "'||'", "'/'", "'%'", "'<<'", "'>>'",
"'&'", "'&&'", "'~'", "'!'", "", "'group_by'", "'+'", "'-'", "", "",
"", "'null'", "", "", "'('", "')'", "'['", "']'", "','", "'|'", "':'",
"", "", "'<='", "'<'", "'>='", "'>'", "'!='", "'=='",
"", "';'", "'*'", "'sum'", "'avg'", "'max'", "'min'", "'join'", "'unique'",
"'count'", "'.['", "'||'", "'/'", "'%'", "'<<'", "'>>'", "'&'", "'&&'",
"'~'", "'!'", "", "", "'group_by'", "'+'", "'-'", "", "", "", "'null'",
"", "", "'('", "')'", "'['", "']'", "','", "'|'", "':'", "", "", "'<='",
"'<'", "'>='", "'>'", "'!='", "'=='",
}
staticData.symbolicNames = []string{
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "PROPRIETARY_FUNC_NAME", "GROUP_BY", "ORDER_ASC", "ORDER_DESC",
"ORDER_BY", "ALIAS_RESERVED", "ARG", "NULL", "ID", "WS", "LPAR", "RPAR",
"LBRA", "RBRA", "COMMA", "PIPE", "COLON", "NN", "NUMBER", "LT_EQ", "LT",
"GT_EQ", "GT", "NEQ", "EQ", "NAME", "HANDLE", "STRING", "LINECOMMENT",
"", "", "", "PROPRIETARY_FUNC_NAME", "WHERE", "GROUP_BY", "ORDER_ASC",
"ORDER_DESC", "ORDER_BY", "ALIAS_RESERVED", "ARG", "NULL", "ID", "WS",
"LPAR", "RPAR", "LBRA", "RBRA", "COMMA", "PIPE", "COLON", "NN", "NUMBER",
"LT_EQ", "LT", "GT_EQ", "GT", "NEQ", "EQ", "NAME", "HANDLE", "STRING",
"LINECOMMENT",
}
staticData.ruleNames = []string{
"T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", "T__7", "T__8",
"T__9", "T__10", "T__11", "T__12", "T__13", "T__14", "T__15", "T__16",
"T__17", "T__18", "T__19", "PROPRIETARY_FUNC_NAME", "GROUP_BY", "ORDER_ASC",
"T__17", "T__18", "PROPRIETARY_FUNC_NAME", "WHERE", "GROUP_BY", "ORDER_ASC",
"ORDER_DESC", "ORDER_BY", "ALIAS_RESERVED", "ARG", "NULL", "ID", "WS",
"LPAR", "RPAR", "LBRA", "RBRA", "COMMA", "PIPE", "COLON", "NN", "NUMBER",
"INTF", "EXP", "LT_EQ", "LT", "GT_EQ", "GT", "NEQ", "EQ", "NAME", "HANDLE",
@ -70,7 +71,7 @@ func slqlexerLexerInit() {
}
staticData.predictionContextCache = antlr.NewPredictionContextCache()
staticData.serializedATN = []int32{
4, 0, 49, 522, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2,
4, 0, 49, 529, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2,
4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2,
10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15,
7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7,
@ -87,222 +88,225 @@ func slqlexerLexerInit() {
73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78,
7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2,
1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5,
1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7,
1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9,
1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1,
13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17,
1, 17, 1, 17, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 21, 1,
21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 23,
1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1,
24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 3, 24, 264, 8, 24, 1, 25, 1, 25,
1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1,
1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7,
1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 10,
1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 14, 1,
14, 1, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18,
1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1,
20, 1, 20, 1, 20, 1, 20, 3, 20, 241, 8, 20, 1, 21, 1, 21, 1, 21, 1, 21,
1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 23, 1, 23, 1, 24, 1,
24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24,
1, 24, 1, 24, 1, 24, 3, 24, 271, 8, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1,
25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25,
1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1,
25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25,
1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1,
25, 1, 25, 3, 25, 322, 8, 25, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27,
1, 27, 1, 27, 1, 28, 1, 28, 5, 28, 334, 8, 28, 10, 28, 12, 28, 337, 9,
28, 1, 29, 4, 29, 340, 8, 29, 11, 29, 12, 29, 341, 1, 29, 1, 29, 1, 30,
1, 30, 1, 31, 1, 31, 1, 32, 1, 32, 1, 33, 1, 33, 1, 34, 1, 34, 1, 35, 1,
35, 1, 36, 1, 36, 1, 37, 1, 37, 1, 38, 1, 38, 3, 38, 364, 8, 38, 1, 38,
1, 38, 1, 38, 4, 38, 369, 8, 38, 11, 38, 12, 38, 370, 1, 38, 3, 38, 374,
8, 38, 1, 38, 3, 38, 377, 8, 38, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 383,
8, 38, 1, 38, 3, 38, 386, 8, 38, 1, 39, 1, 39, 1, 39, 5, 39, 391, 8, 39,
10, 39, 12, 39, 394, 9, 39, 3, 39, 396, 8, 39, 1, 40, 1, 40, 3, 40, 400,
8, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 43, 1, 43, 1,
43, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47,
1, 47, 1, 47, 3, 47, 424, 8, 47, 1, 48, 1, 48, 1, 48, 1, 48, 5, 48, 430,
8, 48, 10, 48, 12, 48, 433, 9, 48, 1, 49, 1, 49, 1, 49, 5, 49, 438, 8,
49, 10, 49, 12, 49, 441, 9, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 3, 50,
448, 8, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1,
53, 1, 53, 1, 54, 1, 54, 1, 55, 1, 55, 1, 56, 1, 56, 1, 57, 1, 57, 1, 58,
1, 58, 1, 59, 1, 59, 1, 60, 1, 60, 1, 61, 1, 61, 1, 62, 1, 62, 1, 63, 1,
63, 1, 64, 1, 64, 1, 65, 1, 65, 1, 66, 1, 66, 1, 67, 1, 67, 1, 68, 1, 68,
1, 69, 1, 69, 1, 70, 1, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 1,
74, 1, 74, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 79,
1, 79, 1, 80, 1, 80, 5, 80, 514, 8, 80, 10, 80, 12, 80, 517, 9, 80, 1,
80, 1, 80, 1, 80, 1, 80, 1, 515, 0, 81, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11,
6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15,
31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24,
49, 25, 51, 26, 53, 27, 55, 28, 57, 29, 59, 30, 61, 31, 63, 32, 65, 33,
67, 34, 69, 35, 71, 36, 73, 37, 75, 38, 77, 39, 79, 0, 81, 0, 83, 40, 85,
41, 87, 42, 89, 43, 91, 44, 93, 45, 95, 46, 97, 47, 99, 48, 101, 0, 103,
0, 105, 0, 107, 0, 109, 0, 111, 0, 113, 0, 115, 0, 117, 0, 119, 0, 121,
0, 123, 0, 125, 0, 127, 0, 129, 0, 131, 0, 133, 0, 135, 0, 137, 0, 139,
0, 141, 0, 143, 0, 145, 0, 147, 0, 149, 0, 151, 0, 153, 0, 155, 0, 157,
0, 159, 0, 161, 49, 1, 0, 35, 3, 0, 65, 90, 95, 95, 97, 122, 4, 0, 48,
57, 65, 90, 95, 95, 97, 122, 3, 0, 9, 10, 13, 13, 32, 32, 1, 0, 48, 57,
1, 0, 49, 57, 2, 0, 69, 69, 101, 101, 2, 0, 43, 43, 45, 45, 2, 0, 34, 34,
92, 92, 8, 0, 34, 34, 47, 47, 92, 92, 98, 98, 102, 102, 110, 110, 114,
114, 116, 116, 3, 0, 48, 57, 65, 70, 97, 102, 2, 0, 65, 65, 97, 97, 2,
0, 66, 66, 98, 98, 2, 0, 67, 67, 99, 99, 2, 0, 68, 68, 100, 100, 2, 0,
70, 70, 102, 102, 2, 0, 71, 71, 103, 103, 2, 0, 72, 72, 104, 104, 2, 0,
73, 73, 105, 105, 2, 0, 74, 74, 106, 106, 2, 0, 75, 75, 107, 107, 2, 0,
76, 76, 108, 108, 2, 0, 77, 77, 109, 109, 2, 0, 78, 78, 110, 110, 2, 0,
79, 79, 111, 111, 2, 0, 80, 80, 112, 112, 2, 0, 81, 81, 113, 113, 2, 0,
82, 82, 114, 114, 2, 0, 83, 83, 115, 115, 2, 0, 84, 84, 116, 116, 2, 0,
85, 85, 117, 117, 2, 0, 86, 86, 118, 118, 2, 0, 87, 87, 119, 119, 2, 0,
88, 88, 120, 120, 2, 0, 89, 89, 121, 121, 2, 0, 90, 90, 122, 122, 517,
0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0,
0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0,
0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0,
0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1,
0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39,
1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0,
47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0,
0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0,
0, 0, 63, 1, 0, 0, 0, 0, 65, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 69, 1, 0,
0, 0, 0, 71, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 0, 75, 1, 0, 0, 0, 0, 77, 1,
0, 0, 0, 0, 83, 1, 0, 0, 0, 0, 85, 1, 0, 0, 0, 0, 87, 1, 0, 0, 0, 0, 89,
1, 0, 0, 0, 0, 91, 1, 0, 0, 0, 0, 93, 1, 0, 0, 0, 0, 95, 1, 0, 0, 0, 0,
97, 1, 0, 0, 0, 0, 99, 1, 0, 0, 0, 0, 161, 1, 0, 0, 0, 1, 163, 1, 0, 0,
0, 3, 165, 1, 0, 0, 0, 5, 167, 1, 0, 0, 0, 7, 171, 1, 0, 0, 0, 9, 175,
1, 0, 0, 0, 11, 179, 1, 0, 0, 0, 13, 183, 1, 0, 0, 0, 15, 189, 1, 0, 0,
0, 17, 194, 1, 0, 0, 0, 19, 201, 1, 0, 0, 0, 21, 207, 1, 0, 0, 0, 23, 210,
1, 0, 0, 0, 25, 213, 1, 0, 0, 0, 27, 215, 1, 0, 0, 0, 29, 217, 1, 0, 0,
0, 31, 220, 1, 0, 0, 0, 33, 223, 1, 0, 0, 0, 35, 225, 1, 0, 0, 0, 37, 228,
1, 0, 0, 0, 39, 230, 1, 0, 0, 0, 41, 232, 1, 0, 0, 0, 43, 235, 1, 0, 0,
0, 45, 244, 1, 0, 0, 0, 47, 246, 1, 0, 0, 0, 49, 263, 1, 0, 0, 0, 51, 321,
1, 0, 0, 0, 53, 323, 1, 0, 0, 0, 55, 326, 1, 0, 0, 0, 57, 331, 1, 0, 0,
0, 59, 339, 1, 0, 0, 0, 61, 345, 1, 0, 0, 0, 63, 347, 1, 0, 0, 0, 65, 349,
1, 0, 0, 0, 67, 351, 1, 0, 0, 0, 69, 353, 1, 0, 0, 0, 71, 355, 1, 0, 0,
0, 73, 357, 1, 0, 0, 0, 75, 359, 1, 0, 0, 0, 77, 385, 1, 0, 0, 0, 79, 395,
1, 0, 0, 0, 81, 397, 1, 0, 0, 0, 83, 403, 1, 0, 0, 0, 85, 406, 1, 0, 0,
0, 87, 408, 1, 0, 0, 0, 89, 411, 1, 0, 0, 0, 91, 413, 1, 0, 0, 0, 93, 416,
1, 0, 0, 0, 95, 419, 1, 0, 0, 0, 97, 425, 1, 0, 0, 0, 99, 434, 1, 0, 0,
0, 101, 444, 1, 0, 0, 0, 103, 449, 1, 0, 0, 0, 105, 455, 1, 0, 0, 0, 107,
457, 1, 0, 0, 0, 109, 459, 1, 0, 0, 0, 111, 461, 1, 0, 0, 0, 113, 463,
1, 0, 0, 0, 115, 465, 1, 0, 0, 0, 117, 467, 1, 0, 0, 0, 119, 469, 1, 0,
0, 0, 121, 471, 1, 0, 0, 0, 123, 473, 1, 0, 0, 0, 125, 475, 1, 0, 0, 0,
127, 477, 1, 0, 0, 0, 129, 479, 1, 0, 0, 0, 131, 481, 1, 0, 0, 0, 133,
483, 1, 0, 0, 0, 135, 485, 1, 0, 0, 0, 137, 487, 1, 0, 0, 0, 139, 489,
1, 0, 0, 0, 141, 491, 1, 0, 0, 0, 143, 493, 1, 0, 0, 0, 145, 495, 1, 0,
0, 0, 147, 497, 1, 0, 0, 0, 149, 499, 1, 0, 0, 0, 151, 501, 1, 0, 0, 0,
153, 503, 1, 0, 0, 0, 155, 505, 1, 0, 0, 0, 157, 507, 1, 0, 0, 0, 159,
509, 1, 0, 0, 0, 161, 511, 1, 0, 0, 0, 163, 164, 5, 59, 0, 0, 164, 2, 1,
0, 0, 0, 165, 166, 5, 42, 0, 0, 166, 4, 1, 0, 0, 0, 167, 168, 5, 115, 0,
0, 168, 169, 5, 117, 0, 0, 169, 170, 5, 109, 0, 0, 170, 6, 1, 0, 0, 0,
171, 172, 5, 97, 0, 0, 172, 173, 5, 118, 0, 0, 173, 174, 5, 103, 0, 0,
174, 8, 1, 0, 0, 0, 175, 176, 5, 109, 0, 0, 176, 177, 5, 97, 0, 0, 177,
178, 5, 120, 0, 0, 178, 10, 1, 0, 0, 0, 179, 180, 5, 109, 0, 0, 180, 181,
5, 105, 0, 0, 181, 182, 5, 110, 0, 0, 182, 12, 1, 0, 0, 0, 183, 184, 5,
119, 0, 0, 184, 185, 5, 104, 0, 0, 185, 186, 5, 101, 0, 0, 186, 187, 5,
114, 0, 0, 187, 188, 5, 101, 0, 0, 188, 14, 1, 0, 0, 0, 189, 190, 5, 106,
0, 0, 190, 191, 5, 111, 0, 0, 191, 192, 5, 105, 0, 0, 192, 193, 5, 110,
0, 0, 193, 16, 1, 0, 0, 0, 194, 195, 5, 117, 0, 0, 195, 196, 5, 110, 0,
0, 196, 197, 5, 105, 0, 0, 197, 198, 5, 113, 0, 0, 198, 199, 5, 117, 0,
0, 199, 200, 5, 101, 0, 0, 200, 18, 1, 0, 0, 0, 201, 202, 5, 99, 0, 0,
202, 203, 5, 111, 0, 0, 203, 204, 5, 117, 0, 0, 204, 205, 5, 110, 0, 0,
205, 206, 5, 116, 0, 0, 206, 20, 1, 0, 0, 0, 207, 208, 5, 46, 0, 0, 208,
209, 5, 91, 0, 0, 209, 22, 1, 0, 0, 0, 210, 211, 5, 124, 0, 0, 211, 212,
5, 124, 0, 0, 212, 24, 1, 0, 0, 0, 213, 214, 5, 47, 0, 0, 214, 26, 1, 0,
0, 0, 215, 216, 5, 37, 0, 0, 216, 28, 1, 0, 0, 0, 217, 218, 5, 60, 0, 0,
218, 219, 5, 60, 0, 0, 219, 30, 1, 0, 0, 0, 220, 221, 5, 62, 0, 0, 221,
222, 5, 62, 0, 0, 222, 32, 1, 0, 0, 0, 223, 224, 5, 38, 0, 0, 224, 34,
1, 0, 0, 0, 225, 226, 5, 38, 0, 0, 226, 227, 5, 38, 0, 0, 227, 36, 1, 0,
0, 0, 228, 229, 5, 126, 0, 0, 229, 38, 1, 0, 0, 0, 230, 231, 5, 33, 0,
0, 231, 40, 1, 0, 0, 0, 232, 233, 5, 95, 0, 0, 233, 234, 3, 57, 28, 0,
234, 42, 1, 0, 0, 0, 235, 236, 5, 103, 0, 0, 236, 237, 5, 114, 0, 0, 237,
238, 5, 111, 0, 0, 238, 239, 5, 117, 0, 0, 239, 240, 5, 112, 0, 0, 240,
241, 5, 95, 0, 0, 241, 242, 5, 98, 0, 0, 242, 243, 5, 121, 0, 0, 243, 44,
1, 0, 0, 0, 244, 245, 5, 43, 0, 0, 245, 46, 1, 0, 0, 0, 246, 247, 5, 45,
0, 0, 247, 48, 1, 0, 0, 0, 248, 249, 5, 111, 0, 0, 249, 250, 5, 114, 0,
0, 250, 251, 5, 100, 0, 0, 251, 252, 5, 101, 0, 0, 252, 253, 5, 114, 0,
0, 253, 254, 5, 95, 0, 0, 254, 255, 5, 98, 0, 0, 255, 264, 5, 121, 0, 0,
256, 257, 5, 115, 0, 0, 257, 258, 5, 111, 0, 0, 258, 259, 5, 114, 0, 0,
259, 260, 5, 116, 0, 0, 260, 261, 5, 95, 0, 0, 261, 262, 5, 98, 0, 0, 262,
264, 5, 121, 0, 0, 263, 248, 1, 0, 0, 0, 263, 256, 1, 0, 0, 0, 264, 50,
1, 0, 0, 0, 265, 266, 5, 58, 0, 0, 266, 267, 5, 99, 0, 0, 267, 268, 5,
111, 0, 0, 268, 269, 5, 117, 0, 0, 269, 270, 5, 110, 0, 0, 270, 322, 5,
116, 0, 0, 271, 272, 5, 58, 0, 0, 272, 273, 5, 99, 0, 0, 273, 274, 5, 111,
0, 0, 274, 275, 5, 117, 0, 0, 275, 276, 5, 110, 0, 0, 276, 277, 5, 116,
0, 0, 277, 278, 5, 95, 0, 0, 278, 279, 5, 117, 0, 0, 279, 280, 5, 110,
0, 0, 280, 281, 5, 105, 0, 0, 281, 282, 5, 113, 0, 0, 282, 283, 5, 117,
0, 0, 283, 322, 5, 101, 0, 0, 284, 285, 5, 58, 0, 0, 285, 286, 5, 97, 0,
0, 286, 287, 5, 118, 0, 0, 287, 322, 5, 103, 0, 0, 288, 289, 5, 58, 0,
0, 289, 290, 5, 103, 0, 0, 290, 291, 5, 114, 0, 0, 291, 292, 5, 111, 0,
0, 292, 293, 5, 117, 0, 0, 293, 294, 5, 112, 0, 0, 294, 295, 5, 95, 0,
0, 295, 296, 5, 98, 0, 0, 296, 322, 5, 121, 0, 0, 297, 298, 5, 58, 0, 0,
298, 299, 5, 109, 0, 0, 299, 300, 5, 97, 0, 0, 300, 322, 5, 120, 0, 0,
301, 302, 5, 58, 0, 0, 302, 303, 5, 109, 0, 0, 303, 304, 5, 105, 0, 0,
304, 322, 5, 110, 0, 0, 305, 306, 5, 58, 0, 0, 306, 307, 5, 111, 0, 0,
307, 308, 5, 114, 0, 0, 308, 309, 5, 100, 0, 0, 309, 310, 5, 101, 0, 0,
310, 311, 5, 114, 0, 0, 311, 312, 5, 95, 0, 0, 312, 313, 5, 98, 0, 0, 313,
322, 5, 121, 0, 0, 314, 315, 5, 58, 0, 0, 315, 316, 5, 117, 0, 0, 316,
317, 5, 110, 0, 0, 317, 318, 5, 105, 0, 0, 318, 319, 5, 113, 0, 0, 319,
320, 5, 117, 0, 0, 320, 322, 5, 101, 0, 0, 321, 265, 1, 0, 0, 0, 321, 271,
1, 0, 0, 0, 321, 284, 1, 0, 0, 0, 321, 288, 1, 0, 0, 0, 321, 297, 1, 0,
0, 0, 321, 301, 1, 0, 0, 0, 321, 305, 1, 0, 0, 0, 321, 314, 1, 0, 0, 0,
322, 52, 1, 0, 0, 0, 323, 324, 5, 36, 0, 0, 324, 325, 3, 57, 28, 0, 325,
54, 1, 0, 0, 0, 326, 327, 5, 110, 0, 0, 327, 328, 5, 117, 0, 0, 328, 329,
5, 108, 0, 0, 329, 330, 5, 108, 0, 0, 330, 56, 1, 0, 0, 0, 331, 335, 7,
0, 0, 0, 332, 334, 7, 1, 0, 0, 333, 332, 1, 0, 0, 0, 334, 337, 1, 0, 0,
0, 335, 333, 1, 0, 0, 0, 335, 336, 1, 0, 0, 0, 336, 58, 1, 0, 0, 0, 337,
335, 1, 0, 0, 0, 338, 340, 7, 2, 0, 0, 339, 338, 1, 0, 0, 0, 340, 341,
1, 0, 0, 0, 341, 339, 1, 0, 0, 0, 341, 342, 1, 0, 0, 0, 342, 343, 1, 0,
0, 0, 343, 344, 6, 29, 0, 0, 344, 60, 1, 0, 0, 0, 345, 346, 5, 40, 0, 0,
346, 62, 1, 0, 0, 0, 347, 348, 5, 41, 0, 0, 348, 64, 1, 0, 0, 0, 349, 350,
5, 91, 0, 0, 350, 66, 1, 0, 0, 0, 351, 352, 5, 93, 0, 0, 352, 68, 1, 0,
0, 0, 353, 354, 5, 44, 0, 0, 354, 70, 1, 0, 0, 0, 355, 356, 5, 124, 0,
0, 356, 72, 1, 0, 0, 0, 357, 358, 5, 58, 0, 0, 358, 74, 1, 0, 0, 0, 359,
360, 3, 79, 39, 0, 360, 76, 1, 0, 0, 0, 361, 386, 3, 75, 37, 0, 362, 364,
5, 45, 0, 0, 363, 362, 1, 0, 0, 0, 363, 364, 1, 0, 0, 0, 364, 365, 1, 0,
0, 0, 365, 366, 3, 79, 39, 0, 366, 368, 5, 46, 0, 0, 367, 369, 7, 3, 0,
0, 368, 367, 1, 0, 0, 0, 369, 370, 1, 0, 0, 0, 370, 368, 1, 0, 0, 0, 370,
371, 1, 0, 0, 0, 371, 373, 1, 0, 0, 0, 372, 374, 3, 81, 40, 0, 373, 372,
1, 0, 0, 0, 373, 374, 1, 0, 0, 0, 374, 386, 1, 0, 0, 0, 375, 377, 5, 45,
0, 0, 376, 375, 1, 0, 0, 0, 376, 377, 1, 0, 0, 0, 377, 378, 1, 0, 0, 0,
378, 379, 3, 79, 39, 0, 379, 380, 3, 81, 40, 0, 380, 386, 1, 0, 0, 0, 381,
383, 5, 45, 0, 0, 382, 381, 1, 0, 0, 0, 382, 383, 1, 0, 0, 0, 383, 384,
1, 0, 0, 0, 384, 386, 3, 79, 39, 0, 385, 361, 1, 0, 0, 0, 385, 363, 1,
0, 0, 0, 385, 376, 1, 0, 0, 0, 385, 382, 1, 0, 0, 0, 386, 78, 1, 0, 0,
0, 387, 396, 5, 48, 0, 0, 388, 392, 7, 4, 0, 0, 389, 391, 7, 3, 0, 0, 390,
389, 1, 0, 0, 0, 391, 394, 1, 0, 0, 0, 392, 390, 1, 0, 0, 0, 392, 393,
1, 0, 0, 0, 393, 396, 1, 0, 0, 0, 394, 392, 1, 0, 0, 0, 395, 387, 1, 0,
0, 0, 395, 388, 1, 0, 0, 0, 396, 80, 1, 0, 0, 0, 397, 399, 7, 5, 0, 0,
398, 400, 7, 6, 0, 0, 399, 398, 1, 0, 0, 0, 399, 400, 1, 0, 0, 0, 400,
401, 1, 0, 0, 0, 401, 402, 3, 79, 39, 0, 402, 82, 1, 0, 0, 0, 403, 404,
5, 60, 0, 0, 404, 405, 5, 61, 0, 0, 405, 84, 1, 0, 0, 0, 406, 407, 5, 60,
0, 0, 407, 86, 1, 0, 0, 0, 408, 409, 5, 62, 0, 0, 409, 410, 5, 61, 0, 0,
410, 88, 1, 0, 0, 0, 411, 412, 5, 62, 0, 0, 412, 90, 1, 0, 0, 0, 413, 414,
5, 33, 0, 0, 414, 415, 5, 61, 0, 0, 415, 92, 1, 0, 0, 0, 416, 417, 5, 61,
0, 0, 417, 418, 5, 61, 0, 0, 418, 94, 1, 0, 0, 0, 419, 423, 5, 46, 0, 0,
420, 424, 3, 53, 26, 0, 421, 424, 3, 57, 28, 0, 422, 424, 3, 99, 49, 0,
423, 420, 1, 0, 0, 0, 423, 421, 1, 0, 0, 0, 423, 422, 1, 0, 0, 0, 424,
96, 1, 0, 0, 0, 425, 426, 5, 64, 0, 0, 426, 431, 3, 57, 28, 0, 427, 428,
5, 47, 0, 0, 428, 430, 3, 57, 28, 0, 429, 427, 1, 0, 0, 0, 430, 433, 1,
0, 0, 0, 431, 429, 1, 0, 0, 0, 431, 432, 1, 0, 0, 0, 432, 98, 1, 0, 0,
0, 433, 431, 1, 0, 0, 0, 434, 439, 5, 34, 0, 0, 435, 438, 3, 101, 50, 0,
436, 438, 8, 7, 0, 0, 437, 435, 1, 0, 0, 0, 437, 436, 1, 0, 0, 0, 438,
441, 1, 0, 0, 0, 439, 437, 1, 0, 0, 0, 439, 440, 1, 0, 0, 0, 440, 442,
1, 0, 0, 0, 441, 439, 1, 0, 0, 0, 442, 443, 5, 34, 0, 0, 443, 100, 1, 0,
0, 0, 444, 447, 5, 92, 0, 0, 445, 448, 7, 8, 0, 0, 446, 448, 3, 103, 51,
0, 447, 445, 1, 0, 0, 0, 447, 446, 1, 0, 0, 0, 448, 102, 1, 0, 0, 0, 449,
450, 5, 117, 0, 0, 450, 451, 3, 105, 52, 0, 451, 452, 3, 105, 52, 0, 452,
453, 3, 105, 52, 0, 453, 454, 3, 105, 52, 0, 454, 104, 1, 0, 0, 0, 455,
456, 7, 9, 0, 0, 456, 106, 1, 0, 0, 0, 457, 458, 7, 3, 0, 0, 458, 108,
1, 0, 0, 0, 459, 460, 7, 10, 0, 0, 460, 110, 1, 0, 0, 0, 461, 462, 7, 11,
0, 0, 462, 112, 1, 0, 0, 0, 463, 464, 7, 12, 0, 0, 464, 114, 1, 0, 0, 0,
465, 466, 7, 13, 0, 0, 466, 116, 1, 0, 0, 0, 467, 468, 7, 5, 0, 0, 468,
118, 1, 0, 0, 0, 469, 470, 7, 14, 0, 0, 470, 120, 1, 0, 0, 0, 471, 472,
7, 15, 0, 0, 472, 122, 1, 0, 0, 0, 473, 474, 7, 16, 0, 0, 474, 124, 1,
0, 0, 0, 475, 476, 7, 17, 0, 0, 476, 126, 1, 0, 0, 0, 477, 478, 7, 18,
0, 0, 478, 128, 1, 0, 0, 0, 479, 480, 7, 19, 0, 0, 480, 130, 1, 0, 0, 0,
481, 482, 7, 20, 0, 0, 482, 132, 1, 0, 0, 0, 483, 484, 7, 21, 0, 0, 484,
134, 1, 0, 0, 0, 485, 486, 7, 22, 0, 0, 486, 136, 1, 0, 0, 0, 487, 488,
7, 23, 0, 0, 488, 138, 1, 0, 0, 0, 489, 490, 7, 24, 0, 0, 490, 140, 1,
0, 0, 0, 491, 492, 7, 25, 0, 0, 492, 142, 1, 0, 0, 0, 493, 494, 7, 26,
0, 0, 494, 144, 1, 0, 0, 0, 495, 496, 7, 27, 0, 0, 496, 146, 1, 0, 0, 0,
497, 498, 7, 28, 0, 0, 498, 148, 1, 0, 0, 0, 499, 500, 7, 29, 0, 0, 500,
150, 1, 0, 0, 0, 501, 502, 7, 30, 0, 0, 502, 152, 1, 0, 0, 0, 503, 504,
7, 31, 0, 0, 504, 154, 1, 0, 0, 0, 505, 506, 7, 32, 0, 0, 506, 156, 1,
0, 0, 0, 507, 508, 7, 33, 0, 0, 508, 158, 1, 0, 0, 0, 509, 510, 7, 34,
0, 0, 510, 160, 1, 0, 0, 0, 511, 515, 5, 35, 0, 0, 512, 514, 9, 0, 0, 0,
513, 512, 1, 0, 0, 0, 514, 517, 1, 0, 0, 0, 515, 516, 1, 0, 0, 0, 515,
513, 1, 0, 0, 0, 516, 518, 1, 0, 0, 0, 517, 515, 1, 0, 0, 0, 518, 519,
5, 10, 0, 0, 519, 520, 1, 0, 0, 0, 520, 521, 6, 80, 0, 0, 521, 162, 1,
0, 0, 0, 20, 0, 263, 321, 335, 341, 363, 370, 373, 376, 382, 385, 392,
395, 399, 423, 431, 437, 439, 447, 515, 1, 6, 0, 0,
25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 3, 25,
329, 8, 25, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1,
28, 1, 28, 5, 28, 341, 8, 28, 10, 28, 12, 28, 344, 9, 28, 1, 29, 4, 29,
347, 8, 29, 11, 29, 12, 29, 348, 1, 29, 1, 29, 1, 30, 1, 30, 1, 31, 1,
31, 1, 32, 1, 32, 1, 33, 1, 33, 1, 34, 1, 34, 1, 35, 1, 35, 1, 36, 1, 36,
1, 37, 1, 37, 1, 38, 1, 38, 3, 38, 371, 8, 38, 1, 38, 1, 38, 1, 38, 4,
38, 376, 8, 38, 11, 38, 12, 38, 377, 1, 38, 3, 38, 381, 8, 38, 1, 38, 3,
38, 384, 8, 38, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 390, 8, 38, 1, 38, 3,
38, 393, 8, 38, 1, 39, 1, 39, 1, 39, 5, 39, 398, 8, 39, 10, 39, 12, 39,
401, 9, 39, 3, 39, 403, 8, 39, 1, 40, 1, 40, 3, 40, 407, 8, 40, 1, 40,
1, 40, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1,
44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47,
3, 47, 431, 8, 47, 1, 48, 1, 48, 1, 48, 1, 48, 5, 48, 437, 8, 48, 10, 48,
12, 48, 440, 9, 48, 1, 49, 1, 49, 1, 49, 5, 49, 445, 8, 49, 10, 49, 12,
49, 448, 9, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 3, 50, 455, 8, 50, 1,
51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 53, 1, 53, 1, 54,
1, 54, 1, 55, 1, 55, 1, 56, 1, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 59, 1,
59, 1, 60, 1, 60, 1, 61, 1, 61, 1, 62, 1, 62, 1, 63, 1, 63, 1, 64, 1, 64,
1, 65, 1, 65, 1, 66, 1, 66, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1,
70, 1, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 1, 74, 1, 74, 1, 75,
1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1,
80, 5, 80, 521, 8, 80, 10, 80, 12, 80, 524, 9, 80, 1, 80, 1, 80, 1, 80,
1, 80, 1, 522, 0, 81, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8,
17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17,
35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26,
53, 27, 55, 28, 57, 29, 59, 30, 61, 31, 63, 32, 65, 33, 67, 34, 69, 35,
71, 36, 73, 37, 75, 38, 77, 39, 79, 0, 81, 0, 83, 40, 85, 41, 87, 42, 89,
43, 91, 44, 93, 45, 95, 46, 97, 47, 99, 48, 101, 0, 103, 0, 105, 0, 107,
0, 109, 0, 111, 0, 113, 0, 115, 0, 117, 0, 119, 0, 121, 0, 123, 0, 125,
0, 127, 0, 129, 0, 131, 0, 133, 0, 135, 0, 137, 0, 139, 0, 141, 0, 143,
0, 145, 0, 147, 0, 149, 0, 151, 0, 153, 0, 155, 0, 157, 0, 159, 0, 161,
49, 1, 0, 35, 3, 0, 65, 90, 95, 95, 97, 122, 4, 0, 48, 57, 65, 90, 95,
95, 97, 122, 3, 0, 9, 10, 13, 13, 32, 32, 1, 0, 48, 57, 1, 0, 49, 57, 2,
0, 69, 69, 101, 101, 2, 0, 43, 43, 45, 45, 2, 0, 34, 34, 92, 92, 8, 0,
34, 34, 47, 47, 92, 92, 98, 98, 102, 102, 110, 110, 114, 114, 116, 116,
3, 0, 48, 57, 65, 70, 97, 102, 2, 0, 65, 65, 97, 97, 2, 0, 66, 66, 98,
98, 2, 0, 67, 67, 99, 99, 2, 0, 68, 68, 100, 100, 2, 0, 70, 70, 102, 102,
2, 0, 71, 71, 103, 103, 2, 0, 72, 72, 104, 104, 2, 0, 73, 73, 105, 105,
2, 0, 74, 74, 106, 106, 2, 0, 75, 75, 107, 107, 2, 0, 76, 76, 108, 108,
2, 0, 77, 77, 109, 109, 2, 0, 78, 78, 110, 110, 2, 0, 79, 79, 111, 111,
2, 0, 80, 80, 112, 112, 2, 0, 81, 81, 113, 113, 2, 0, 82, 82, 114, 114,
2, 0, 83, 83, 115, 115, 2, 0, 84, 84, 116, 116, 2, 0, 85, 85, 117, 117,
2, 0, 86, 86, 118, 118, 2, 0, 87, 87, 119, 119, 2, 0, 88, 88, 120, 120,
2, 0, 89, 89, 121, 121, 2, 0, 90, 90, 122, 122, 525, 0, 1, 1, 0, 0, 0,
0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0,
0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0,
0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0,
0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1,
0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41,
1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0,
49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0,
0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 0, 63, 1, 0, 0,
0, 0, 65, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 71, 1, 0,
0, 0, 0, 73, 1, 0, 0, 0, 0, 75, 1, 0, 0, 0, 0, 77, 1, 0, 0, 0, 0, 83, 1,
0, 0, 0, 0, 85, 1, 0, 0, 0, 0, 87, 1, 0, 0, 0, 0, 89, 1, 0, 0, 0, 0, 91,
1, 0, 0, 0, 0, 93, 1, 0, 0, 0, 0, 95, 1, 0, 0, 0, 0, 97, 1, 0, 0, 0, 0,
99, 1, 0, 0, 0, 0, 161, 1, 0, 0, 0, 1, 163, 1, 0, 0, 0, 3, 165, 1, 0, 0,
0, 5, 167, 1, 0, 0, 0, 7, 171, 1, 0, 0, 0, 9, 175, 1, 0, 0, 0, 11, 179,
1, 0, 0, 0, 13, 183, 1, 0, 0, 0, 15, 188, 1, 0, 0, 0, 17, 195, 1, 0, 0,
0, 19, 201, 1, 0, 0, 0, 21, 204, 1, 0, 0, 0, 23, 207, 1, 0, 0, 0, 25, 209,
1, 0, 0, 0, 27, 211, 1, 0, 0, 0, 29, 214, 1, 0, 0, 0, 31, 217, 1, 0, 0,
0, 33, 219, 1, 0, 0, 0, 35, 222, 1, 0, 0, 0, 37, 224, 1, 0, 0, 0, 39, 226,
1, 0, 0, 0, 41, 240, 1, 0, 0, 0, 43, 242, 1, 0, 0, 0, 45, 251, 1, 0, 0,
0, 47, 253, 1, 0, 0, 0, 49, 270, 1, 0, 0, 0, 51, 328, 1, 0, 0, 0, 53, 330,
1, 0, 0, 0, 55, 333, 1, 0, 0, 0, 57, 338, 1, 0, 0, 0, 59, 346, 1, 0, 0,
0, 61, 352, 1, 0, 0, 0, 63, 354, 1, 0, 0, 0, 65, 356, 1, 0, 0, 0, 67, 358,
1, 0, 0, 0, 69, 360, 1, 0, 0, 0, 71, 362, 1, 0, 0, 0, 73, 364, 1, 0, 0,
0, 75, 366, 1, 0, 0, 0, 77, 392, 1, 0, 0, 0, 79, 402, 1, 0, 0, 0, 81, 404,
1, 0, 0, 0, 83, 410, 1, 0, 0, 0, 85, 413, 1, 0, 0, 0, 87, 415, 1, 0, 0,
0, 89, 418, 1, 0, 0, 0, 91, 420, 1, 0, 0, 0, 93, 423, 1, 0, 0, 0, 95, 426,
1, 0, 0, 0, 97, 432, 1, 0, 0, 0, 99, 441, 1, 0, 0, 0, 101, 451, 1, 0, 0,
0, 103, 456, 1, 0, 0, 0, 105, 462, 1, 0, 0, 0, 107, 464, 1, 0, 0, 0, 109,
466, 1, 0, 0, 0, 111, 468, 1, 0, 0, 0, 113, 470, 1, 0, 0, 0, 115, 472,
1, 0, 0, 0, 117, 474, 1, 0, 0, 0, 119, 476, 1, 0, 0, 0, 121, 478, 1, 0,
0, 0, 123, 480, 1, 0, 0, 0, 125, 482, 1, 0, 0, 0, 127, 484, 1, 0, 0, 0,
129, 486, 1, 0, 0, 0, 131, 488, 1, 0, 0, 0, 133, 490, 1, 0, 0, 0, 135,
492, 1, 0, 0, 0, 137, 494, 1, 0, 0, 0, 139, 496, 1, 0, 0, 0, 141, 498,
1, 0, 0, 0, 143, 500, 1, 0, 0, 0, 145, 502, 1, 0, 0, 0, 147, 504, 1, 0,
0, 0, 149, 506, 1, 0, 0, 0, 151, 508, 1, 0, 0, 0, 153, 510, 1, 0, 0, 0,
155, 512, 1, 0, 0, 0, 157, 514, 1, 0, 0, 0, 159, 516, 1, 0, 0, 0, 161,
518, 1, 0, 0, 0, 163, 164, 5, 59, 0, 0, 164, 2, 1, 0, 0, 0, 165, 166, 5,
42, 0, 0, 166, 4, 1, 0, 0, 0, 167, 168, 5, 115, 0, 0, 168, 169, 5, 117,
0, 0, 169, 170, 5, 109, 0, 0, 170, 6, 1, 0, 0, 0, 171, 172, 5, 97, 0, 0,
172, 173, 5, 118, 0, 0, 173, 174, 5, 103, 0, 0, 174, 8, 1, 0, 0, 0, 175,
176, 5, 109, 0, 0, 176, 177, 5, 97, 0, 0, 177, 178, 5, 120, 0, 0, 178,
10, 1, 0, 0, 0, 179, 180, 5, 109, 0, 0, 180, 181, 5, 105, 0, 0, 181, 182,
5, 110, 0, 0, 182, 12, 1, 0, 0, 0, 183, 184, 5, 106, 0, 0, 184, 185, 5,
111, 0, 0, 185, 186, 5, 105, 0, 0, 186, 187, 5, 110, 0, 0, 187, 14, 1,
0, 0, 0, 188, 189, 5, 117, 0, 0, 189, 190, 5, 110, 0, 0, 190, 191, 5, 105,
0, 0, 191, 192, 5, 113, 0, 0, 192, 193, 5, 117, 0, 0, 193, 194, 5, 101,
0, 0, 194, 16, 1, 0, 0, 0, 195, 196, 5, 99, 0, 0, 196, 197, 5, 111, 0,
0, 197, 198, 5, 117, 0, 0, 198, 199, 5, 110, 0, 0, 199, 200, 5, 116, 0,
0, 200, 18, 1, 0, 0, 0, 201, 202, 5, 46, 0, 0, 202, 203, 5, 91, 0, 0, 203,
20, 1, 0, 0, 0, 204, 205, 5, 124, 0, 0, 205, 206, 5, 124, 0, 0, 206, 22,
1, 0, 0, 0, 207, 208, 5, 47, 0, 0, 208, 24, 1, 0, 0, 0, 209, 210, 5, 37,
0, 0, 210, 26, 1, 0, 0, 0, 211, 212, 5, 60, 0, 0, 212, 213, 5, 60, 0, 0,
213, 28, 1, 0, 0, 0, 214, 215, 5, 62, 0, 0, 215, 216, 5, 62, 0, 0, 216,
30, 1, 0, 0, 0, 217, 218, 5, 38, 0, 0, 218, 32, 1, 0, 0, 0, 219, 220, 5,
38, 0, 0, 220, 221, 5, 38, 0, 0, 221, 34, 1, 0, 0, 0, 222, 223, 5, 126,
0, 0, 223, 36, 1, 0, 0, 0, 224, 225, 5, 33, 0, 0, 225, 38, 1, 0, 0, 0,
226, 227, 5, 95, 0, 0, 227, 228, 3, 57, 28, 0, 228, 40, 1, 0, 0, 0, 229,
230, 5, 119, 0, 0, 230, 231, 5, 104, 0, 0, 231, 232, 5, 101, 0, 0, 232,
233, 5, 114, 0, 0, 233, 241, 5, 101, 0, 0, 234, 235, 5, 115, 0, 0, 235,
236, 5, 101, 0, 0, 236, 237, 5, 108, 0, 0, 237, 238, 5, 101, 0, 0, 238,
239, 5, 99, 0, 0, 239, 241, 5, 116, 0, 0, 240, 229, 1, 0, 0, 0, 240, 234,
1, 0, 0, 0, 241, 42, 1, 0, 0, 0, 242, 243, 5, 103, 0, 0, 243, 244, 5, 114,
0, 0, 244, 245, 5, 111, 0, 0, 245, 246, 5, 117, 0, 0, 246, 247, 5, 112,
0, 0, 247, 248, 5, 95, 0, 0, 248, 249, 5, 98, 0, 0, 249, 250, 5, 121, 0,
0, 250, 44, 1, 0, 0, 0, 251, 252, 5, 43, 0, 0, 252, 46, 1, 0, 0, 0, 253,
254, 5, 45, 0, 0, 254, 48, 1, 0, 0, 0, 255, 256, 5, 111, 0, 0, 256, 257,
5, 114, 0, 0, 257, 258, 5, 100, 0, 0, 258, 259, 5, 101, 0, 0, 259, 260,
5, 114, 0, 0, 260, 261, 5, 95, 0, 0, 261, 262, 5, 98, 0, 0, 262, 271, 5,
121, 0, 0, 263, 264, 5, 115, 0, 0, 264, 265, 5, 111, 0, 0, 265, 266, 5,
114, 0, 0, 266, 267, 5, 116, 0, 0, 267, 268, 5, 95, 0, 0, 268, 269, 5,
98, 0, 0, 269, 271, 5, 121, 0, 0, 270, 255, 1, 0, 0, 0, 270, 263, 1, 0,
0, 0, 271, 50, 1, 0, 0, 0, 272, 273, 5, 58, 0, 0, 273, 274, 5, 99, 0, 0,
274, 275, 5, 111, 0, 0, 275, 276, 5, 117, 0, 0, 276, 277, 5, 110, 0, 0,
277, 329, 5, 116, 0, 0, 278, 279, 5, 58, 0, 0, 279, 280, 5, 99, 0, 0, 280,
281, 5, 111, 0, 0, 281, 282, 5, 117, 0, 0, 282, 283, 5, 110, 0, 0, 283,
284, 5, 116, 0, 0, 284, 285, 5, 95, 0, 0, 285, 286, 5, 117, 0, 0, 286,
287, 5, 110, 0, 0, 287, 288, 5, 105, 0, 0, 288, 289, 5, 113, 0, 0, 289,
290, 5, 117, 0, 0, 290, 329, 5, 101, 0, 0, 291, 292, 5, 58, 0, 0, 292,
293, 5, 97, 0, 0, 293, 294, 5, 118, 0, 0, 294, 329, 5, 103, 0, 0, 295,
296, 5, 58, 0, 0, 296, 297, 5, 103, 0, 0, 297, 298, 5, 114, 0, 0, 298,
299, 5, 111, 0, 0, 299, 300, 5, 117, 0, 0, 300, 301, 5, 112, 0, 0, 301,
302, 5, 95, 0, 0, 302, 303, 5, 98, 0, 0, 303, 329, 5, 121, 0, 0, 304, 305,
5, 58, 0, 0, 305, 306, 5, 109, 0, 0, 306, 307, 5, 97, 0, 0, 307, 329, 5,
120, 0, 0, 308, 309, 5, 58, 0, 0, 309, 310, 5, 109, 0, 0, 310, 311, 5,
105, 0, 0, 311, 329, 5, 110, 0, 0, 312, 313, 5, 58, 0, 0, 313, 314, 5,
111, 0, 0, 314, 315, 5, 114, 0, 0, 315, 316, 5, 100, 0, 0, 316, 317, 5,
101, 0, 0, 317, 318, 5, 114, 0, 0, 318, 319, 5, 95, 0, 0, 319, 320, 5,
98, 0, 0, 320, 329, 5, 121, 0, 0, 321, 322, 5, 58, 0, 0, 322, 323, 5, 117,
0, 0, 323, 324, 5, 110, 0, 0, 324, 325, 5, 105, 0, 0, 325, 326, 5, 113,
0, 0, 326, 327, 5, 117, 0, 0, 327, 329, 5, 101, 0, 0, 328, 272, 1, 0, 0,
0, 328, 278, 1, 0, 0, 0, 328, 291, 1, 0, 0, 0, 328, 295, 1, 0, 0, 0, 328,
304, 1, 0, 0, 0, 328, 308, 1, 0, 0, 0, 328, 312, 1, 0, 0, 0, 328, 321,
1, 0, 0, 0, 329, 52, 1, 0, 0, 0, 330, 331, 5, 36, 0, 0, 331, 332, 3, 57,
28, 0, 332, 54, 1, 0, 0, 0, 333, 334, 5, 110, 0, 0, 334, 335, 5, 117, 0,
0, 335, 336, 5, 108, 0, 0, 336, 337, 5, 108, 0, 0, 337, 56, 1, 0, 0, 0,
338, 342, 7, 0, 0, 0, 339, 341, 7, 1, 0, 0, 340, 339, 1, 0, 0, 0, 341,
344, 1, 0, 0, 0, 342, 340, 1, 0, 0, 0, 342, 343, 1, 0, 0, 0, 343, 58, 1,
0, 0, 0, 344, 342, 1, 0, 0, 0, 345, 347, 7, 2, 0, 0, 346, 345, 1, 0, 0,
0, 347, 348, 1, 0, 0, 0, 348, 346, 1, 0, 0, 0, 348, 349, 1, 0, 0, 0, 349,
350, 1, 0, 0, 0, 350, 351, 6, 29, 0, 0, 351, 60, 1, 0, 0, 0, 352, 353,
5, 40, 0, 0, 353, 62, 1, 0, 0, 0, 354, 355, 5, 41, 0, 0, 355, 64, 1, 0,
0, 0, 356, 357, 5, 91, 0, 0, 357, 66, 1, 0, 0, 0, 358, 359, 5, 93, 0, 0,
359, 68, 1, 0, 0, 0, 360, 361, 5, 44, 0, 0, 361, 70, 1, 0, 0, 0, 362, 363,
5, 124, 0, 0, 363, 72, 1, 0, 0, 0, 364, 365, 5, 58, 0, 0, 365, 74, 1, 0,
0, 0, 366, 367, 3, 79, 39, 0, 367, 76, 1, 0, 0, 0, 368, 393, 3, 75, 37,
0, 369, 371, 5, 45, 0, 0, 370, 369, 1, 0, 0, 0, 370, 371, 1, 0, 0, 0, 371,
372, 1, 0, 0, 0, 372, 373, 3, 79, 39, 0, 373, 375, 5, 46, 0, 0, 374, 376,
7, 3, 0, 0, 375, 374, 1, 0, 0, 0, 376, 377, 1, 0, 0, 0, 377, 375, 1, 0,
0, 0, 377, 378, 1, 0, 0, 0, 378, 380, 1, 0, 0, 0, 379, 381, 3, 81, 40,
0, 380, 379, 1, 0, 0, 0, 380, 381, 1, 0, 0, 0, 381, 393, 1, 0, 0, 0, 382,
384, 5, 45, 0, 0, 383, 382, 1, 0, 0, 0, 383, 384, 1, 0, 0, 0, 384, 385,
1, 0, 0, 0, 385, 386, 3, 79, 39, 0, 386, 387, 3, 81, 40, 0, 387, 393, 1,
0, 0, 0, 388, 390, 5, 45, 0, 0, 389, 388, 1, 0, 0, 0, 389, 390, 1, 0, 0,
0, 390, 391, 1, 0, 0, 0, 391, 393, 3, 79, 39, 0, 392, 368, 1, 0, 0, 0,
392, 370, 1, 0, 0, 0, 392, 383, 1, 0, 0, 0, 392, 389, 1, 0, 0, 0, 393,
78, 1, 0, 0, 0, 394, 403, 5, 48, 0, 0, 395, 399, 7, 4, 0, 0, 396, 398,
7, 3, 0, 0, 397, 396, 1, 0, 0, 0, 398, 401, 1, 0, 0, 0, 399, 397, 1, 0,
0, 0, 399, 400, 1, 0, 0, 0, 400, 403, 1, 0, 0, 0, 401, 399, 1, 0, 0, 0,
402, 394, 1, 0, 0, 0, 402, 395, 1, 0, 0, 0, 403, 80, 1, 0, 0, 0, 404, 406,
7, 5, 0, 0, 405, 407, 7, 6, 0, 0, 406, 405, 1, 0, 0, 0, 406, 407, 1, 0,
0, 0, 407, 408, 1, 0, 0, 0, 408, 409, 3, 79, 39, 0, 409, 82, 1, 0, 0, 0,
410, 411, 5, 60, 0, 0, 411, 412, 5, 61, 0, 0, 412, 84, 1, 0, 0, 0, 413,
414, 5, 60, 0, 0, 414, 86, 1, 0, 0, 0, 415, 416, 5, 62, 0, 0, 416, 417,
5, 61, 0, 0, 417, 88, 1, 0, 0, 0, 418, 419, 5, 62, 0, 0, 419, 90, 1, 0,
0, 0, 420, 421, 5, 33, 0, 0, 421, 422, 5, 61, 0, 0, 422, 92, 1, 0, 0, 0,
423, 424, 5, 61, 0, 0, 424, 425, 5, 61, 0, 0, 425, 94, 1, 0, 0, 0, 426,
430, 5, 46, 0, 0, 427, 431, 3, 53, 26, 0, 428, 431, 3, 57, 28, 0, 429,
431, 3, 99, 49, 0, 430, 427, 1, 0, 0, 0, 430, 428, 1, 0, 0, 0, 430, 429,
1, 0, 0, 0, 431, 96, 1, 0, 0, 0, 432, 433, 5, 64, 0, 0, 433, 438, 3, 57,
28, 0, 434, 435, 5, 47, 0, 0, 435, 437, 3, 57, 28, 0, 436, 434, 1, 0, 0,
0, 437, 440, 1, 0, 0, 0, 438, 436, 1, 0, 0, 0, 438, 439, 1, 0, 0, 0, 439,
98, 1, 0, 0, 0, 440, 438, 1, 0, 0, 0, 441, 446, 5, 34, 0, 0, 442, 445,
3, 101, 50, 0, 443, 445, 8, 7, 0, 0, 444, 442, 1, 0, 0, 0, 444, 443, 1,
0, 0, 0, 445, 448, 1, 0, 0, 0, 446, 444, 1, 0, 0, 0, 446, 447, 1, 0, 0,
0, 447, 449, 1, 0, 0, 0, 448, 446, 1, 0, 0, 0, 449, 450, 5, 34, 0, 0, 450,
100, 1, 0, 0, 0, 451, 454, 5, 92, 0, 0, 452, 455, 7, 8, 0, 0, 453, 455,
3, 103, 51, 0, 454, 452, 1, 0, 0, 0, 454, 453, 1, 0, 0, 0, 455, 102, 1,
0, 0, 0, 456, 457, 5, 117, 0, 0, 457, 458, 3, 105, 52, 0, 458, 459, 3,
105, 52, 0, 459, 460, 3, 105, 52, 0, 460, 461, 3, 105, 52, 0, 461, 104,
1, 0, 0, 0, 462, 463, 7, 9, 0, 0, 463, 106, 1, 0, 0, 0, 464, 465, 7, 3,
0, 0, 465, 108, 1, 0, 0, 0, 466, 467, 7, 10, 0, 0, 467, 110, 1, 0, 0, 0,
468, 469, 7, 11, 0, 0, 469, 112, 1, 0, 0, 0, 470, 471, 7, 12, 0, 0, 471,
114, 1, 0, 0, 0, 472, 473, 7, 13, 0, 0, 473, 116, 1, 0, 0, 0, 474, 475,
7, 5, 0, 0, 475, 118, 1, 0, 0, 0, 476, 477, 7, 14, 0, 0, 477, 120, 1, 0,
0, 0, 478, 479, 7, 15, 0, 0, 479, 122, 1, 0, 0, 0, 480, 481, 7, 16, 0,
0, 481, 124, 1, 0, 0, 0, 482, 483, 7, 17, 0, 0, 483, 126, 1, 0, 0, 0, 484,
485, 7, 18, 0, 0, 485, 128, 1, 0, 0, 0, 486, 487, 7, 19, 0, 0, 487, 130,
1, 0, 0, 0, 488, 489, 7, 20, 0, 0, 489, 132, 1, 0, 0, 0, 490, 491, 7, 21,
0, 0, 491, 134, 1, 0, 0, 0, 492, 493, 7, 22, 0, 0, 493, 136, 1, 0, 0, 0,
494, 495, 7, 23, 0, 0, 495, 138, 1, 0, 0, 0, 496, 497, 7, 24, 0, 0, 497,
140, 1, 0, 0, 0, 498, 499, 7, 25, 0, 0, 499, 142, 1, 0, 0, 0, 500, 501,
7, 26, 0, 0, 501, 144, 1, 0, 0, 0, 502, 503, 7, 27, 0, 0, 503, 146, 1,
0, 0, 0, 504, 505, 7, 28, 0, 0, 505, 148, 1, 0, 0, 0, 506, 507, 7, 29,
0, 0, 507, 150, 1, 0, 0, 0, 508, 509, 7, 30, 0, 0, 509, 152, 1, 0, 0, 0,
510, 511, 7, 31, 0, 0, 511, 154, 1, 0, 0, 0, 512, 513, 7, 32, 0, 0, 513,
156, 1, 0, 0, 0, 514, 515, 7, 33, 0, 0, 515, 158, 1, 0, 0, 0, 516, 517,
7, 34, 0, 0, 517, 160, 1, 0, 0, 0, 518, 522, 5, 35, 0, 0, 519, 521, 9,
0, 0, 0, 520, 519, 1, 0, 0, 0, 521, 524, 1, 0, 0, 0, 522, 523, 1, 0, 0,
0, 522, 520, 1, 0, 0, 0, 523, 525, 1, 0, 0, 0, 524, 522, 1, 0, 0, 0, 525,
526, 5, 10, 0, 0, 526, 527, 1, 0, 0, 0, 527, 528, 6, 80, 0, 0, 528, 162,
1, 0, 0, 0, 21, 0, 240, 270, 328, 342, 348, 370, 377, 380, 383, 389, 392,
399, 402, 406, 430, 438, 444, 446, 454, 522, 1, 6, 0, 0,
}
deserializer := antlr.NewATNDeserializer(nil)
staticData.atn = deserializer.Deserialize(staticData.serializedATN)
@ -362,8 +366,8 @@ const (
SLQLexerT__16 = 17
SLQLexerT__17 = 18
SLQLexerT__18 = 19
SLQLexerT__19 = 20
SLQLexerPROPRIETARY_FUNC_NAME = 21
SLQLexerPROPRIETARY_FUNC_NAME = 20
SLQLexerWHERE = 21
SLQLexerGROUP_BY = 22
SLQLexerORDER_ASC = 23
SLQLexerORDER_DESC = 24

View File

@ -43,6 +43,9 @@ type SLQListener interface {
// EnterCountFunc is called when entering the countFunc production.
EnterCountFunc(c *CountFuncContext)
// EnterWhere is called when entering the where production.
EnterWhere(c *WhereContext)
// EnterGroupByTerm is called when entering the groupByTerm production.
EnterGroupByTerm(c *GroupByTermContext)
@ -76,6 +79,9 @@ type SLQListener interface {
// EnterRowRange is called when entering the rowRange production.
EnterRowRange(c *RowRangeContext)
// EnterExprElement is called when entering the exprElement production.
EnterExprElement(c *ExprElementContext)
// EnterExpr is called when entering the expr production.
EnterExpr(c *ExprContext)
@ -121,6 +127,9 @@ type SLQListener interface {
// ExitCountFunc is called when exiting the countFunc production.
ExitCountFunc(c *CountFuncContext)
// ExitWhere is called when exiting the where production.
ExitWhere(c *WhereContext)
// ExitGroupByTerm is called when exiting the groupByTerm production.
ExitGroupByTerm(c *GroupByTermContext)
@ -154,6 +163,9 @@ type SLQListener interface {
// ExitRowRange is called when exiting the rowRange production.
ExitRowRange(c *RowRangeContext)
// ExitExprElement is called when exiting the exprElement production.
ExitExprElement(c *ExprElementContext)
// ExitExpr is called when exiting the expr production.
ExitExpr(c *ExprContext)

File diff suppressed because it is too large Load Diff

View File

@ -43,6 +43,9 @@ type SLQVisitor interface {
// Visit a parse tree produced by SLQParser#countFunc.
VisitCountFunc(ctx *CountFuncContext) interface{}
// Visit a parse tree produced by SLQParser#where.
VisitWhere(ctx *WhereContext) interface{}
// Visit a parse tree produced by SLQParser#groupByTerm.
VisitGroupByTerm(ctx *GroupByTermContext) interface{}
@ -76,6 +79,9 @@ type SLQVisitor interface {
// Visit a parse tree produced by SLQParser#rowRange.
VisitRowRange(ctx *RowRangeContext) interface{}
// Visit a parse tree produced by SLQParser#exprElement.
VisitExprElement(ctx *ExprElementContext) interface{}
// Visit a parse tree produced by SLQParser#expr.
VisitExpr(ctx *ExprContext) interface{}

View File

@ -8,6 +8,110 @@ import (
"github.com/antlr/antlr4/runtime/Go/antlr/v4"
)
// VisitJoin implements slq.SLQVisitor.
func (v *parseTreeVisitor) VisitJoin(ctx *slq.JoinContext) any {
// parent node must be a segment
seg, ok := v.cur.(*SegmentNode)
if !ok {
return errorf("parent of JOIN() must be SegmentNode, but got: %T", v.cur)
}
join := &JoinNode{seg: seg, ctx: ctx}
err := seg.AddChild(join)
if err != nil {
return err
}
expr := ctx.JoinConstraint()
if expr == nil {
return nil
}
// the join contains a constraint, let's hit it
v.cur = join
err2 := v.VisitJoinConstraint(expr.(*slq.JoinConstraintContext))
if err2 != nil {
return err2
}
// set cur back to previous
v.cur = seg
return nil
}
// VisitJoinConstraint implements slq.SLQVisitor.
func (v *parseTreeVisitor) VisitJoinConstraint(ctx *slq.JoinConstraintContext) any {
joinNode, ok := v.cur.(*JoinNode)
if !ok {
return errorf("JOIN constraint must have JOIN parent, but got %T", v.cur)
}
// the constraint could be empty
children := ctx.GetChildren()
if len(children) == 0 {
return nil
}
// the constraint could be a single SEL (in which case, there's no comparison operator)
if ctx.Cmpr() == nil {
// there should be exactly one SEL
sels := ctx.AllSelector()
if len(sels) != 1 {
return errorf("JOIN constraint without a comparison operator must have exactly one selector")
}
joinExprNode := &JoinConstraint{join: joinNode, ctx: ctx}
colSelNode, err := newSelectorNode(joinExprNode, sels[0])
if err != nil {
return err
}
if err := joinExprNode.AddChild(colSelNode); err != nil {
return err
}
return joinNode.AddChild(joinExprNode)
}
// We've got a comparison operator
sels := ctx.AllSelector()
if len(sels) != 2 {
// REVISIT: probably unnecessary, should be caught by the parser
return errorf("JOIN constraint must have 2 operands (left & right), but got %d", len(sels))
}
join, ok := v.cur.(*JoinNode)
if !ok {
return errorf("JoinConstraint must have JoinNode parent, but got %T", v.cur)
}
joinCondition := &JoinConstraint{join: join, ctx: ctx}
leftSel, err := newSelectorNode(joinCondition, sels[0])
if err != nil {
return err
}
if err = joinCondition.AddChild(leftSel); err != nil {
return err
}
cmpr := newCmpr(joinCondition, ctx.Cmpr())
if err = joinCondition.AddChild(cmpr); err != nil {
return err
}
rightSel, err := newSelectorNode(joinCondition, sels[1])
if err != nil {
return err
}
if err = joinCondition.AddChild(rightSel); err != nil {
return err
}
return join.AddChild(joinCondition)
}
var _ Node = (*JoinNode)(nil)
// JoinNode models a SQL JOIN node. It has a child of type JoinConstraint.

View File

@ -60,12 +60,6 @@ type Selector interface {
type ResultColumn interface {
Node
// IsColumn returns true if the expression represents
// a column, e.g. ".first_name" or "actor.first_name".
// This method returns false for functions, e.g. "COUNT(*)".
// REVISIT: We can probably get rid of this?
IsColumn() bool
// String returns a log/debug-friendly representation.
String() string
@ -306,9 +300,43 @@ func nodesWithType(nodes []Node, typ reflect.Type) []Node {
return s
}
// NodeUnwrap "unwraps" node returning the lowest single contained node.
// False is returned if node (or any of its descendants), has more than one
// child. If node has no children, it is returned directly. This function
// is useful for "unwrapping" a node that is contained in an outer node. For
// example, an ExprNode may often contain just a single LiteralNode.
func NodeUnwrap[T Node](node Node) (T, bool) {
var result T
var ok bool
if node == nil {
return result, false
}
var children []Node
for {
children = node.Children()
switch len(children) {
case 0:
result, ok = node.(T)
return result, ok
case 1:
node = children[0]
continue
default:
return result, false
}
}
}
// ExprNode models a SLQ expression such as ".uid > 4".
type ExprNode struct {
baseNode
parens bool
}
// HasParens returns true if the expression is enclosed in parentheses.
func (n *ExprNode) HasParens() bool {
return n.parens
}
// AddChild implements Node.
@ -339,40 +367,6 @@ func (n *OperatorNode) String() string {
return nodeString(n)
}
// WhereNode represents a SQL WHERE clause, i.e. a filter on the SELECT.
type WhereNode struct {
baseNode
}
// String returns a log/debug-friendly representation.
func (n *WhereNode) String() string {
return nodeString(n)
}
// Expr returns the expression that constitutes the SetWhere clause, or nil if no expression.
func (n *WhereNode) Expr() *ExprNode {
if len(n.children) == 0 {
return nil
}
return n.children[0].(*ExprNode)
}
// AddChild implements Node.
func (n *WhereNode) AddChild(node Node) error {
expr, ok := node.(*ExprNode)
if !ok {
return errorf("WHERE child must be %T, but got: %T", expr, node)
}
if len(n.children) > 0 {
return errorf("WHERE has max 1 child: failed to add: %T", node)
}
n.addChild(expr)
return nil
}
// isOperator returns true if the supplied string is a recognized operator, e.g. "!=" or ">".
func isOperator(text string) bool {
switch text {
@ -387,7 +381,7 @@ func isOperator(text string) bool {
var (
typeAST = reflect.TypeOf((*AST)(nil))
typeColSelectorNode = reflect.TypeOf((*ColSelectorNode)(nil))
typeExprNode = reflect.TypeOf((*ExprNode)(nil))
_ = reflect.TypeOf((*ExprNode)(nil))
typeFuncNode = reflect.TypeOf((*FuncNode)(nil))
typeGroupByNode = reflect.TypeOf((*GroupByNode)(nil))
typeHandleNode = reflect.TypeOf((*HandleNode)(nil))

View File

@ -58,3 +58,34 @@ func TestNodePrevNextSibling(t *testing.T) {
require.Equal(t, "2", gotNext.Text())
require.Nil(t, NodeNextSibling(gotNext))
}
func TestNodeUnwrap(t *testing.T) {
var ok bool
exprA := &ExprNode{}
exprB := &ExprNode{}
var gotExpr *ExprNode
gotExpr, ok = NodeUnwrap[*ExprNode](exprA)
require.True(t, ok)
require.True(t, exprA == gotExpr)
require.NoError(t, exprA.AddChild(exprB))
gotExpr, ok = NodeUnwrap[*ExprNode](exprA)
require.True(t, ok)
require.True(t, exprB == gotExpr)
litA := &LiteralNode{}
var gotLit *LiteralNode
require.NoError(t, exprB.AddChild(litA))
gotLit, ok = NodeUnwrap[*LiteralNode](exprA)
require.True(t, ok)
require.True(t, litA == gotLit)
litB := &LiteralNode{}
require.NoError(t, exprB.AddChild(litB))
gotLit, ok = NodeUnwrap[*LiteralNode](exprA)
require.False(t, ok, "should fail because exprB has multiple children")
require.Nil(t, gotLit)
}

View File

@ -180,6 +180,8 @@ func (v *parseTreeVisitor) Visit(ctx antlr.ParseTree) any {
return v.VisitCmpr(ctx)
case *slq.RowRangeContext:
return v.VisitRowRange(ctx)
case *slq.ExprElementContext:
return v.VisitExprElement(ctx)
case *slq.ExprContext:
return v.VisitExpr(ctx)
case *slq.GroupByContext:
@ -200,6 +202,8 @@ func (v *parseTreeVisitor) Visit(ctx antlr.ParseTree) any {
return v.VisitUniqueFunc(ctx)
case *slq.CountFuncContext:
return v.VisitCountFunc(ctx)
case *slq.WhereContext:
return v.VisitWhere(ctx)
case *slq.ArgContext:
return v.VisitArg(ctx)
}
@ -311,7 +315,7 @@ func (v *parseTreeVisitor) VisitElement(ctx *slq.ElementContext) any {
// VisitAlias implements slq.SLQVisitor.
func (v *parseTreeVisitor) VisitAlias(ctx *slq.AliasContext) any {
if ctx.ID() == nil && ctx.GetText() == "" {
if ctx == nil || ctx.ID() == nil && ctx.GetText() == "" {
return nil
}
@ -323,6 +327,8 @@ func (v *parseTreeVisitor) VisitAlias(ctx *slq.AliasContext) any {
switch node := v.cur.(type) {
case *SelectorNode:
node.alias = alias
case *ExprElementNode:
node.alias = alias
case *FuncNode:
if alias != "" {
node.alias = alias
@ -348,7 +354,6 @@ func (v *parseTreeVisitor) VisitAlias(ctx *slq.AliasContext) any {
// instead of just ID, and look for the alias after the colon.
text := ctx.GetText()
// text = strings.TrimPrefix(text, node.fnName)
node.alias = strings.TrimPrefix(text, ":")
default:
@ -358,40 +363,6 @@ func (v *parseTreeVisitor) VisitAlias(ctx *slq.AliasContext) any {
return nil
}
// VisitExpr implements slq.SLQVisitor.
func (v *parseTreeVisitor) VisitExpr(ctx *slq.ExprContext) any {
// check if the expr is a selector, e.g. ".uid"
if selCtx := ctx.Selector(); selCtx != nil {
selNode, err := newSelectorNode(v.cur, selCtx)
if err != nil {
return err
}
return v.cur.AddChild(selNode)
}
if ctx.Literal() != nil {
return v.VisitLiteral(ctx.Literal().(*slq.LiteralContext))
}
ex := &ExprNode{}
ex.ctx = ctx
err := ex.SetParent(v.cur)
if err != nil {
return err
}
prev := v.cur
v.cur = ex
err2 := v.VisitChildren(ctx)
v.cur = prev
if err2 != nil {
return err2.(error)
}
return v.cur.AddChild(ex)
}
// VisitCmpr implements slq.SLQVisitor.
func (v *parseTreeVisitor) VisitCmpr(ctx *slq.CmprContext) any {
return v.VisitChildren(ctx)
@ -407,110 +378,6 @@ func (v *parseTreeVisitor) VisitUnaryOperator(_ *slq.UnaryOperatorContext) any {
return nil
}
// VisitJoin implements slq.SLQVisitor.
func (v *parseTreeVisitor) VisitJoin(ctx *slq.JoinContext) any {
// parent node must be a segment
seg, ok := v.cur.(*SegmentNode)
if !ok {
return errorf("parent of JOIN() must be SegmentNode, but got: %T", v.cur)
}
join := &JoinNode{seg: seg, ctx: ctx}
err := seg.AddChild(join)
if err != nil {
return err
}
expr := ctx.JoinConstraint()
if expr == nil {
return nil
}
// the join contains a constraint, let's hit it
v.cur = join
err2 := v.VisitJoinConstraint(expr.(*slq.JoinConstraintContext))
if err2 != nil {
return err2
}
// set cur back to previous
v.cur = seg
return nil
}
// VisitJoinConstraint implements slq.SLQVisitor.
func (v *parseTreeVisitor) VisitJoinConstraint(ctx *slq.JoinConstraintContext) any {
joinNode, ok := v.cur.(*JoinNode)
if !ok {
return errorf("JOIN constraint must have JOIN parent, but got %T", v.cur)
}
// the constraint could be empty
children := ctx.GetChildren()
if len(children) == 0 {
return nil
}
// the constraint could be a single SEL (in which case, there's no comparison operator)
if ctx.Cmpr() == nil {
// there should be exactly one SEL
sels := ctx.AllSelector()
if len(sels) != 1 {
return errorf("JOIN constraint without a comparison operator must have exactly one selector")
}
joinExprNode := &JoinConstraint{join: joinNode, ctx: ctx}
colSelNode, err := newSelectorNode(joinExprNode, sels[0])
if err != nil {
return err
}
if err := joinExprNode.AddChild(colSelNode); err != nil {
return err
}
return joinNode.AddChild(joinExprNode)
}
// We've got a comparison operator
sels := ctx.AllSelector()
if len(sels) != 2 {
// REVISIT: probably unnecessary, should be caught by the parser
return errorf("JOIN constraint must have 2 operands (left & right), but got %d", len(sels))
}
join, ok := v.cur.(*JoinNode)
if !ok {
return errorf("JoinConstraint must have JoinNode parent, but got %T", v.cur)
}
joinCondition := &JoinConstraint{join: join, ctx: ctx}
leftSel, err := newSelectorNode(joinCondition, sels[0])
if err != nil {
return err
}
if err = joinCondition.AddChild(leftSel); err != nil {
return err
}
cmpr := newCmpr(joinCondition, ctx.Cmpr())
if err = joinCondition.AddChild(cmpr); err != nil {
return err
}
rightSel, err := newSelectorNode(joinCondition, sels[1])
if err != nil {
return err
}
if err = joinCondition.AddChild(rightSel); err != nil {
return err
}
return join.AddChild(joinCondition)
}
// VisitTerminal implements slq.SLQVisitor.
func (v *parseTreeVisitor) VisitTerminal(ctx antlr.TerminalNode) any {
val := ctx.GetText()
@ -518,6 +385,7 @@ func (v *parseTreeVisitor) VisitTerminal(ctx antlr.TerminalNode) any {
if isOperator(val) {
op := &OperatorNode{}
op.ctx = ctx
op.text = ctx.GetText()
err := op.SetParent(v.cur)
if err != nil {

View File

@ -105,8 +105,8 @@ func TestParseBuild(t *testing.T) {
func TestInspector_FindWhereClauses(t *testing.T) {
log := slogt.New(t)
// Verify that ".uid > 4" becomes a WHERE clause.
const input = "@my1 | .actor | .uid > 4 | .uid, .username"
// Verify that "where(.uid > 4)" becomes a WHERE clause.
const input = "@my1 | .actor | where(.uid > 4) | .uid, .username"
ptree, err := parseSLQ(log, input)
require.Nil(t, err)

View File

@ -8,7 +8,6 @@ import (
"github.com/neilotoole/sq/libsq/core/stringz"
)
// Expr implements FragmentBuilder.
func doExpr(rc *Context, expr *ast.ExprNode) (string, error) {
if expr == nil {
return "", nil
@ -16,6 +15,9 @@ func doExpr(rc *Context, expr *ast.ExprNode) (string, error) {
r := rc.Renderer
var sb strings.Builder
if expr.HasParens() {
sb.WriteRune('(')
}
for i, child := range expr.Children() {
if i > 0 {
sb.WriteRune(sp)
@ -59,9 +61,14 @@ func doExpr(rc *Context, expr *ast.ExprNode) (string, error) {
}
sb.WriteString(val)
default:
// Shouldn't happen? Need to investigate.
sb.WriteString(child.Text())
}
}
if expr.HasParens() {
sb.WriteRune(')')
}
return sb.String(), nil
}

View File

@ -66,6 +66,12 @@ func doFunction(rc *Context, fn *ast.FuncNode) (string, error) {
} else {
sb.WriteString(val)
}
case *ast.ExprNode:
s, err := rc.Renderer.Expr(rc, node)
if err != nil {
return "", err
}
sb.WriteString(s)
default:
return "", errz.Errorf("unknown AST child node %T: %s", node, node)
}

View File

@ -2,7 +2,6 @@ package render
import (
"github.com/neilotoole/sq/libsq/ast"
"github.com/neilotoole/sq/libsq/core/errz"
)
func doOperator(rc *Context, op *ast.OperatorNode) (string, error) {
@ -10,22 +9,23 @@ func doOperator(rc *Context, op *ast.OperatorNode) (string, error) {
return "", nil
}
val, ok := rc.Dialect.Ops[op.Text()]
text := op.Text()
// Check if the dialect overrides the operator.
val, ok := rc.Dialect.Ops[text]
if !ok {
return "", errz.Errorf("invalid operator: %s", op.Text())
val = text
}
rhs := ast.NodeNextSibling(op)
if lit, ok := rhs.(*ast.LiteralNode); ok && lit.Text() == "null" {
if lit, ok := ast.NodeUnwrap[*ast.LiteralNode](rhs); ok && lit.Text() == "null" {
switch op.Text() {
case "==":
val = "IS"
case "!=":
val = "IS NOT"
default:
return "", errz.Errorf("invalid operator for null")
}
}
// By default, just return the operator unchanged.
return val, nil
}

View File

@ -36,9 +36,13 @@ func doSelectCols(rc *Context, cols []ast.ResultColumn) (string, error) {
if vals[i], err = rc.Renderer.Function(rc, col); err != nil {
return "", err
}
case *ast.ExprElementNode:
if vals[i], err = rc.Renderer.Expr(rc, col.ExprNode()); err != nil {
return "", err
}
default:
// FIXME: We should be exhaustively checking the cases.
// Here, it's probably an ExprNode?
// Actually, this should probably be an error?
vals[i] = col.Text() // for now, we just return the raw text
}

View File

@ -216,50 +216,6 @@ func narrowColSel(w *Walker, node Node) error {
return nil
}
// findWhereClause locates any expressions that represent the WHERE clause
// of the SQL SELECT stmt, and inserts a WhereNode
// into the AST for that expression.
//
// In practice, a WHERE clause is an *ExprNode that
// is the only child of a segment. For example:
//
// @sakila | .actor | .actor_id > 4 | .first_name, .last_name
//
// In this case, ".actor_id > 4" is the WHERE clause.
func findWhereClause(_ *Walker, node Node) error {
// node is guaranteed to be *ExprNode
expr, ok := node.(*ExprNode)
if !ok {
return errorf("expected %T but got %T", expr, node)
}
seg, ok := expr.Parent().(*SegmentNode)
if !ok {
// The expr is not the direct child of a segment, so we're not interested in it.
return nil
}
if len(seg.Children()) != 1 {
return errorf("%T with expression - representing a WHERE clause - must only have one child", seg)
}
// The expr is the direct and only child of a segment.
// We insert a WhereNode between the segment and the expr.
where := &WhereNode{}
where.ctx = expr.ctx
err := where.AddChild(expr)
if err != nil {
return err
}
err = expr.SetParent(where)
if err != nil {
return err
}
seg.bn.children[0] = where
return nil
}
// determineJoinTables attempts to determine the tables that a JOIN refers to.
func determineJoinTables(_ *Walker, node Node) error {
// node is guaranteed to be FnJoin

56
libsq/ast/where.go Normal file
View File

@ -0,0 +1,56 @@
package ast
import "github.com/neilotoole/sq/libsq/ast/internal/slq"
// WhereNode represents a SQL WHERE clause, i.e. a filter on the SELECT.
type WhereNode struct {
baseNode
}
// String returns a log/debug-friendly representation.
func (n *WhereNode) String() string {
return nodeString(n)
}
// Expr returns the expression that constitutes the SetWhere clause, or nil if no expression.
func (n *WhereNode) Expr() *ExprNode {
if len(n.children) == 0 {
return nil
}
return n.children[0].(*ExprNode)
}
// AddChild implements Node.
func (n *WhereNode) AddChild(node Node) error {
expr, ok := node.(*ExprNode)
if !ok {
return errorf("WHERE child must be %T, but got: %T", expr, node)
}
if len(n.children) > 0 {
return errorf("WHERE has max 1 child: failed to add: %T", node)
}
n.addChild(expr)
return nil
}
// VisitWhere implements slq.SLQVisitor.
func (v *parseTreeVisitor) VisitWhere(ctx *slq.WhereContext) interface{} {
node := &WhereNode{}
node.ctx = ctx
node.text = ctx.GetText()
if e := v.using(node, func() any {
return v.VisitChildren(ctx)
}); e != nil {
return e
}
if len(node.Children()) == 0 {
return errorf("{%s} requires at least one argument", ctx.WHERE().GetText())
}
return v.cur.AddChild(node)
}

View File

@ -36,33 +36,6 @@ func TestQueryParamKeys(t *testing.T) {
}
}
func TestRenameQueryParamKey(t *testing.T) {
testCases := []struct {
q string
oldKey string
newKey string
want string
}{
{"", "a", "b", ""},
{"a=1", "a", "b", "b=1"},
{"a", "a", "b", "b"},
{"aa", "a", "b", "aa"},
{"a=", "a", "b", "b="},
{"a=1&a=2", "a", "b", "b=1&b=2"},
{"a=1&c=2", "a", "b", "b=1&c=2"},
{"a=1&c=2&a=3&a=4", "a", "b", "b=1&c=2&b=3&b=4"},
{"a=a&c=2&a=b&a=c", "a", "b", "b=a&c=2&b=b&b=c"},
}
for i, tc := range testCases {
tc := tc
t.Run(tutil.Name(i, tc.q, tc.oldKey, tc.newKey), func(t *testing.T) {
got := urlz.RenameQueryParamKey(tc.q, tc.oldKey, tc.newKey)
require.Equal(t, tc.want, got)
})
}
}
func TestStripQuery(t *testing.T) {
testCases := []struct {
in string
@ -150,3 +123,54 @@ func TestStripSchemeAndUser(t *testing.T) {
})
}
}
func TestRenameQueryParamKey(t *testing.T) {
testCases := []struct {
q string
oldKey string
newKey string
want string
}{
{"", "a", "b", ""},
{"a=1", "a", "b", "b=1"},
{"a", "a", "b", "b"},
{"aa", "a", "b", "aa"},
{"a=", "a", "b", "b="},
{"a=1&a=2", "a", "b", "b=1&b=2"},
{"a=1&c=2", "a", "b", "b=1&c=2"},
{"a=1&c=2&a=3&a=4", "a", "b", "b=1&c=2&b=3&b=4"},
{"a=a&c=2&a=b&a=c", "a", "b", "b=a&c=2&b=b&b=c"},
}
for i, tc := range testCases {
tc := tc
t.Run(tutil.Name(i, tc.q, tc.oldKey, tc.newKey), func(t *testing.T) {
got := urlz.RenameQueryParamKey(tc.q, tc.oldKey, tc.newKey)
require.Equal(t, tc.want, got)
})
}
}
func TestURLStripQuery(t *testing.T) {
testCases := []struct {
in string
want string
}{
{"https://sq.io", "https://sq.io"},
{"https://sq.io/path", "https://sq.io/path"},
{"https://sq.io/path#frag", "https://sq.io/path#frag"},
{"https://sq.io?a=b", "https://sq.io"},
{"https://sq.io/path?a=b", "https://sq.io/path"},
{"https://sq.io/path?a=b&c=d#frag", "https://sq.io/path#frag"},
}
for i, tc := range testCases {
tc := tc
t.Run(tutil.Name(i, tc), func(t *testing.T) {
u, err := url.Parse(tc.in)
require.NoError(t, err)
got := urlz.StripQuery(*u)
require.Equal(t, tc.want, got)
})
}
}

View File

@ -0,0 +1,9 @@
// Package utilz contains utility functions that don't have a home.
package utilz
// All returns a new slice containing elems.
func All[T any](elems ...T) []T {
a := make([]T, len(elems))
copy(a, elems)
return a
}

View File

@ -14,29 +14,29 @@ import (
func TestQuery_args(t *testing.T) {
testCases := []queryTestCase{
{
name: "arg_value_string",
in: `@sakila | .actor | .first_name == $first`,
args: map[string]string{"first": "TOM"},
wantSQL: `SELECT * FROM "actor" WHERE "first_name" = 'TOM'`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `first_name` = 'TOM'"},
wantRecs: 2,
name: "arg_value_string",
in: `@sakila | .actor | where(.first_name == $first)`,
args: map[string]string{"first": "TOM"},
wantSQL: `SELECT * FROM "actor" WHERE "first_name" = 'TOM'`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `first_name` = 'TOM'"},
wantRecCount: 2,
},
{
name: "arg_value_string_2",
in: `@sakila | .actor | .first_name == $first && .last_name == $last`,
args: map[string]string{"first": "TOM", "last": "MIRANDA"},
wantSQL: `SELECT * FROM "actor" WHERE "first_name" = 'TOM' AND "last_name" = 'MIRANDA'`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `first_name` = 'TOM' AND `last_name` = 'MIRANDA'"},
wantRecs: 1,
name: "arg_value_string_2",
in: `@sakila | .actor | where(.first_name == $first && .last_name == $last)`,
args: map[string]string{"first": "TOM", "last": "MIRANDA"},
wantSQL: `SELECT * FROM "actor" WHERE "first_name" = 'TOM' AND "last_name" = 'MIRANDA'`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `first_name` = 'TOM' AND `last_name` = 'MIRANDA'"},
wantRecCount: 1,
},
{
name: "arg_value_int",
in: `@sakila | .actor | .actor_id == int($id)`,
args: map[string]string{"id": "1"},
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" = 1`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` = 1"},
skip: true, // Skip until we implement casting, e.g. .actor_id == int($id)
wantRecs: 1,
name: "arg_value_int",
in: `@sakila | .actor | where(.actor_id == int($id))`,
args: map[string]string{"id": "1"},
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" = 1`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` = 1"},
skip: true, // Skip until we implement casting, e.g. .actor_id == int($id)
wantRecCount: 1,
},
}

View File

@ -16,27 +16,27 @@ import (
func TestQuery_cols(t *testing.T) {
testCases := []queryTestCase{
{
name: "cols",
in: `@sakila | .actor | .first_name, .last_name`,
wantSQL: `SELECT "first_name", "last_name" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT `first_name`, `last_name` FROM `actor`"},
wantRecs: sakila.TblActorCount,
name: "cols",
in: `@sakila | .actor | .first_name, .last_name`,
wantSQL: `SELECT "first_name", "last_name" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT `first_name`, `last_name` FROM `actor`"},
wantRecCount: sakila.TblActorCount,
},
{
name: "cols-whitespace-single-col",
in: `@sakila | .actor | ."first name"`,
wantSQL: `SELECT "first name" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT `first name` FROM `actor`"},
wantRecs: sakila.TblActorCount,
skipExec: true,
name: "cols-whitespace-single-col",
in: `@sakila | .actor | ."first name"`,
wantSQL: `SELECT "first name" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT `first name` FROM `actor`"},
wantRecCount: sakila.TblActorCount,
skipExec: true,
},
{
name: "cols-whitespace-multiple-cols",
in: `@sakila | .actor | .actor_id, ."first name", ."last name"`,
wantSQL: `SELECT "actor_id", "first name", "last name" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT `actor_id`, `first name`, `last name` FROM `actor`"},
wantRecs: sakila.TblActorCount,
skipExec: true,
name: "cols-whitespace-multiple-cols",
in: `@sakila | .actor | .actor_id, ."first name", ."last name"`,
wantSQL: `SELECT "actor_id", "first name", "last name" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT `actor_id`, `first name`, `last name` FROM `actor`"},
wantRecCount: sakila.TblActorCount,
skipExec: true,
},
{
@ -47,19 +47,32 @@ func TestQuery_cols(t *testing.T) {
skipExec: true,
},
{
name: "cols-aliases",
in: `@sakila | .actor | .first_name:given_name, .last_name:family_name`,
wantSQL: `SELECT "first_name" AS "given_name", "last_name" AS "family_name" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT `first_name` AS `given_name`, `last_name` AS `family_name` FROM `actor`"},
wantRecs: sakila.TblActorCount,
name: "cols-aliases",
in: `@sakila | .actor | .first_name:given_name, .last_name:family_name`,
wantSQL: `SELECT "first_name" AS "given_name", "last_name" AS "family_name" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT `first_name` AS `given_name`, `last_name` AS `family_name` FROM `actor`"},
wantRecCount: sakila.TblActorCount,
},
{
name: "handle-table/cols",
in: `@sakila.actor | .first_name, .last_name`,
wantSQL: `SELECT "first_name", "last_name" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT `first_name`, `last_name` FROM `actor`"},
wantRecs: sakila.TblActorCount,
name: "handle-table/cols",
in: `@sakila.actor | .first_name, .last_name`,
wantSQL: `SELECT "first_name", "last_name" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT `first_name`, `last_name` FROM `actor`"},
wantRecCount: sakila.TblActorCount,
},
{
name: "cols-select-literal-value",
in: `@sakila.actor | .first_name, "xxx", .last_name`,
wantSQL: `SELECT "first_name", 'xxx', "last_name" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT `first_name`, 'xxx', `last_name` FROM `actor`"},
wantRecCount: sakila.TblActorCount,
},
{
name: "select/literal",
in: `@sakila.actor | .first_name, 5`,
wantSQL: `SELECT "first_name", 5 FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT `first_name`, 5 FROM `actor`"},
wantRecCount: sakila.TblActorCount,
},
}

View File

@ -14,18 +14,18 @@ import (
func TestQuery_count(t *testing.T) {
testCases := []queryTestCase{
{
name: "alias",
in: `@sakila | .actor | count:quantity`,
wantSQL: `SELECT count(*) AS "quantity" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `quantity` FROM `actor`"},
wantRecs: 1,
name: "alias",
in: `@sakila | .actor | count:quantity`,
wantSQL: `SELECT count(*) AS "quantity" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `quantity` FROM `actor`"},
wantRecCount: 1,
},
{
name: "count-same-alias",
in: `@sakila | .actor | count:count`,
wantSQL: `SELECT count(*) AS "count" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
wantRecs: 1,
name: "count-same-alias",
in: `@sakila | .actor | count:count`,
wantSQL: `SELECT count(*) AS "count" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
wantRecCount: 1,
},
{
name: "whitespace-col",
@ -35,11 +35,11 @@ func TestQuery_count(t *testing.T) {
skipExec: true,
},
{
name: "select-handle-table",
in: `@sakila.actor | count`,
wantSQL: `SELECT count(*) AS "count" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
wantRecs: 1,
name: "select-handle-table",
in: `@sakila.actor | count`,
wantSQL: `SELECT count(*) AS "count" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
wantRecCount: 1,
},
{
name: "select-handle-table-ws-selector",
@ -52,24 +52,24 @@ func TestQuery_count(t *testing.T) {
name: "no-parens-no-args-with-alias-unique",
// Test that the count:ALIAS form can handle the alias
// being a reserved word (unique).
in: `@sakila | .actor | count:unique`,
wantSQL: `SELECT count(*) AS "unique" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `unique` FROM `actor`"},
wantRecs: 1,
in: `@sakila | .actor | count:unique`,
wantSQL: `SELECT count(*) AS "unique" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `unique` FROM `actor`"},
wantRecCount: 1,
},
{
name: "no-parens-no-args-with-alias-arbitrary",
in: `@sakila | .actor | count:something_123`,
wantSQL: `SELECT count(*) AS "something_123" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `something_123` FROM `actor`"},
wantRecs: 1,
name: "no-parens-no-args-with-alias-arbitrary",
in: `@sakila | .actor | count:something_123`,
wantSQL: `SELECT count(*) AS "something_123" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `something_123` FROM `actor`"},
wantRecCount: 1,
},
{
name: "parens-no-args",
in: `@sakila | .actor | count()`,
wantSQL: `SELECT count(*) AS "count" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
wantRecs: 1,
name: "parens-no-args",
in: `@sakila | .actor | count()`,
wantSQL: `SELECT count(*) AS "count" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
wantRecCount: 1,
},
{
name: "error-star",
@ -77,11 +77,11 @@ func TestQuery_count(t *testing.T) {
wantErr: true, // Star version is not supported
},
{
name: "single-selector",
in: `@sakila | .actor | count(.first_name)`,
wantSQL: `SELECT count("first_name") FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT count(`first_name`) FROM `actor`"},
wantRecs: 1,
name: "single-selector",
in: `@sakila | .actor | count(.first_name)`,
wantSQL: `SELECT count("first_name") FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT count(`first_name`) FROM `actor`"},
wantRecCount: 1,
},
{
name: "error-multiple-selector",
@ -89,20 +89,20 @@ func TestQuery_count(t *testing.T) {
wantErr: true, // Only a single selector is permitted
},
{
name: "count/no-parens-no-args",
in: `@sakila | .actor | count`,
wantSQL: `SELECT count(*) AS "count" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
wantRecs: 1,
name: "count/no-parens-no-args",
in: `@sakila | .actor | count`,
wantSQL: `SELECT count(*) AS "count" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
wantRecCount: 1,
},
{
name: "count/no-parens-no-args-with-alias-count",
// Test that the count:ALIAS form can handle the alias
// being a reserved word (count).
in: `@sakila | .actor | count:count`,
wantSQL: `SELECT count(*) AS "count" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
wantRecs: 1,
in: `@sakila | .actor | count:count`,
wantSQL: `SELECT count(*) AS "count" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT count(*) AS `count` FROM `actor`"},
wantRecCount: 1,
},
}

View File

@ -19,32 +19,32 @@ import (
func TestQuery_datetime(t *testing.T) {
testCases := []queryTestCase{
{
name: "datetime/strftime/sqlite",
in: `@sakila | .payment | _strftime("%m", .payment_date)`,
wantSQL: `SELECT strftime('%m', "payment_date") AS "strftime(""%m"",.payment_date)" FROM "payment"`,
onlyFor: []source.DriverType{sqlite3.Type},
wantRecs: sakila.TblPaymentCount,
name: "datetime/strftime/sqlite",
in: `@sakila | .payment | _strftime("%m", .payment_date)`,
wantSQL: `SELECT strftime('%m', "payment_date") AS "strftime(""%m"",.payment_date)" FROM "payment"`,
onlyFor: []source.DriverType{sqlite3.Type},
wantRecCount: sakila.TblPaymentCount,
},
{
name: "datetime/date_trunc/postgres",
in: `@sakila | .payment | _date_trunc("month", .payment_date)`,
wantSQL: `SELECT date_trunc('month', "payment_date") AS "date_trunc(""month"",.payment_date)" FROM "payment"`,
onlyFor: []source.DriverType{postgres.Type},
wantRecs: sakila.TblPaymentCount,
name: "datetime/date_trunc/postgres",
in: `@sakila | .payment | _date_trunc("month", .payment_date)`,
wantSQL: `SELECT date_trunc('month', "payment_date") AS "date_trunc(""month"",.payment_date)" FROM "payment"`,
onlyFor: []source.DriverType{postgres.Type},
wantRecCount: sakila.TblPaymentCount,
},
{
name: "datetime/month/sqlserver",
in: `@sakila | .payment | _month(.payment_date)`,
wantSQL: `SELECT month("payment_date") AS "month(.payment_date)" FROM "payment"`,
onlyFor: []source.DriverType{sqlserver.Type},
wantRecs: sakila.TblPaymentCount,
name: "datetime/month/sqlserver",
in: `@sakila | .payment | _month(.payment_date)`,
wantSQL: `SELECT month("payment_date") AS "month(.payment_date)" FROM "payment"`,
onlyFor: []source.DriverType{sqlserver.Type},
wantRecCount: sakila.TblPaymentCount,
},
{
name: "datetime/date_format/mysql",
in: `@sakila | .payment | _date_format(.payment_date, "%m")`,
wantSQL: "SELECT date_format(`payment_date`, '%m') AS `date_format(.payment_date,\"%m\")` FROM `payment`",
onlyFor: []source.DriverType{mysql.Type},
wantRecs: sakila.TblPaymentCount,
name: "datetime/date_format/mysql",
in: `@sakila | .payment | _date_format(.payment_date, "%m")`,
wantSQL: "SELECT date_format(`payment_date`, '%m') AS `date_format(.payment_date,\"%m\")` FROM `payment`",
onlyFor: []source.DriverType{mysql.Type},
wantRecCount: sakila.TblPaymentCount,
},
}

View File

@ -3,6 +3,8 @@ package libsq_test
import (
"testing"
"github.com/neilotoole/sq/testh/sakila"
"github.com/neilotoole/sq/drivers/mysql"
"github.com/neilotoole/sq/libsq/source"
@ -11,45 +13,45 @@ import (
)
//nolint:exhaustive,lll
func TestQuery_expr(t *testing.T) {
func TestQuery_expr_where(t *testing.T) {
testCases := []queryTestCase{
{
name: "literal/string",
in: `@sakila | .actor | .first_name == "TOM"`,
wantSQL: `SELECT * FROM "actor" WHERE "first_name" = 'TOM'`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `first_name` = 'TOM'"},
wantRecs: 2,
name: "literal/string",
in: `@sakila | .actor | where(.first_name == "TOM")`,
wantSQL: `SELECT * FROM "actor" WHERE "first_name" = 'TOM'`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `first_name` = 'TOM'"},
wantRecCount: 2,
},
{
name: "literal/two-strings",
in: `@sakila | .actor | .first_name == "TOM" && .last_name == "MIRANDA"`,
wantSQL: `SELECT * FROM "actor" WHERE "first_name" = 'TOM' AND "last_name" = 'MIRANDA'`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `first_name` = 'TOM' AND `last_name` = 'MIRANDA'"},
wantRecs: 1,
name: "literal/two-strings",
in: `@sakila | .actor | where(.first_name == "TOM" && .last_name == "MIRANDA")`,
wantSQL: `SELECT * FROM "actor" WHERE "first_name" = 'TOM' AND "last_name" = 'MIRANDA'`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `first_name` = 'TOM' AND `last_name` = 'MIRANDA'"},
wantRecCount: 1,
},
{
name: "literal/integer",
in: `@sakila | .actor | .actor_id == 1`,
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" = 1`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` = 1"},
wantRecs: 1,
name: "literal/integer",
in: `@sakila | .actor | where(.actor_id == 1)`,
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" = 1`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` = 1"},
wantRecCount: 1,
},
{
name: "is_null",
in: `@sakila | .address | .postal_code == null`,
wantSQL: `SELECT * FROM "address" WHERE "postal_code" IS NULL`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `address` WHERE `postal_code` IS NULL"},
wantRecs: 4,
name: "is_null",
in: `@sakila | .address | where(.postal_code == null)`,
wantSQL: `SELECT * FROM "address" WHERE "postal_code" IS NULL`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `address` WHERE `postal_code` IS NULL"},
wantRecCount: 4,
// skipExec because mysql sakila db doesn't have the same null values.
// This is a bug in the dataset.
skipExec: true,
},
{
name: "is_not_null",
in: `@sakila | .address | .postal_code != null`,
wantSQL: `SELECT * FROM "address" WHERE "postal_code" IS NOT NULL`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `address` WHERE `postal_code` IS NOT NULL"},
wantRecs: 599,
name: "is_not_null",
in: `@sakila | .address | where(.postal_code != null)`,
wantSQL: `SELECT * FROM "address" WHERE "postal_code" IS NOT NULL`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `address` WHERE `postal_code` IS NOT NULL"},
wantRecCount: 599,
// skipExec because mysql sakila db doesn't have the same null values.
// This is a bug in the dataset.
skipExec: true,
@ -63,3 +65,86 @@ func TestQuery_expr(t *testing.T) {
})
}
}
//nolint:exhaustive
func TestQuery_expr_literal(t *testing.T) {
testCases := []queryTestCase{
{
name: "col_and_literal",
in: `@sakila | .actor | .first_name, 1`,
wantSQL: `SELECT "first_name", 1 FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT `first_name`, 1 FROM `actor`"},
wantRecCount: sakila.TblActorCount,
sinkFns: []SinkTestFunc{assertSinkColValue(1, int64(1))},
},
{
name: "literal",
in: `@sakila | .actor | 1`,
wantSQL: `SELECT 1 FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT 1 FROM `actor`"},
wantRecCount: sakila.TblActorCount,
sinkFns: []SinkTestFunc{assertSinkColValue(0, int64(1))},
},
{
name: "literal_parens",
in: `@sakila | .actor | (1)`,
wantSQL: `SELECT (1) FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT (1) FROM `actor`"},
wantRecCount: sakila.TblActorCount,
sinkFns: []SinkTestFunc{assertSinkColValue(0, int64(1))},
},
{
name: "addition",
in: `@sakila | .actor | 1+2`,
wantSQL: `SELECT 1 + 2 FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT 1 + 2 FROM `actor`"},
wantRecCount: sakila.TblActorCount,
sinkFns: []SinkTestFunc{assertSinkColValue(0, int64(3))},
},
{
name: "addition_whitespace",
in: `@sakila | .actor | 1+ 2 + 3`,
wantSQL: `SELECT 1 + 2 + 3 FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT 1 + 2 + 3 FROM `actor`"},
wantRecCount: sakila.TblActorCount,
sinkFns: []SinkTestFunc{assertSinkColValue(0, int64(6))},
},
{
name: "math_parens",
in: `@sakila | .actor | (1+ 2) * 3`,
wantSQL: `SELECT (1 + 2) * 3 FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT (1 + 2) * 3 FROM `actor`"},
wantRecCount: sakila.TblActorCount,
sinkFns: []SinkTestFunc{assertSinkColValue(0, int64(9))},
},
{
name: "literal_alias",
in: `@sakila | .actor | 1:total`,
wantSQL: `SELECT 1 AS "total" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT 1 AS `total` FROM `actor`"},
wantRecCount: sakila.TblActorCount,
sinkFns: []SinkTestFunc{
assertSinkColName(0, "total"),
assertSinkColValue(0, int64(1)),
},
},
{
name: "addition_alias",
in: `@sakila | .actor | (1+2):total`,
wantSQL: `SELECT (1 + 2) AS "total" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT (1 + 2) AS `total` FROM `actor`"},
sinkFns: []SinkTestFunc{
assertSinkColValue(0, int64(3)),
assertSinkColName(0, "total"),
},
wantRecCount: sakila.TblActorCount,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
execQueryTestCase(t, tc)
})
}
}

View File

@ -1,31 +0,0 @@
package libsq_test
import (
"testing"
"github.com/neilotoole/sq/drivers/mysql"
"github.com/neilotoole/sq/libsq/source"
_ "github.com/mattn/go-sqlite3"
)
//nolint:exhaustive
func TestQuery_filter(t *testing.T) {
testCases := []queryTestCase{
{
name: "filter/equal",
in: `@sakila | .actor | .actor_id == 1`,
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" = 1`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` = 1"},
wantRecs: 1,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
execQueryTestCase(t, tc)
})
}
}

View File

@ -16,25 +16,25 @@ import (
func TestQuery_groupby(t *testing.T) {
testCases := []queryTestCase{
{
name: "group_by/single-term",
in: `@sakila | .payment | .customer_id, sum(.amount) | group_by(.customer_id)`,
wantSQL: `SELECT "customer_id", sum("amount") AS "sum(.amount)" FROM "payment" GROUP BY "customer_id"`,
override: map[source.DriverType]string{mysql.Type: "SELECT `customer_id`, sum(`amount`) AS `sum(.amount)` FROM `payment` GROUP BY `customer_id`"},
wantRecs: 599,
name: "group_by/single-term",
in: `@sakila | .payment | .customer_id, sum(.amount) | group_by(.customer_id)`,
wantSQL: `SELECT "customer_id", sum("amount") AS "sum(.amount)" FROM "payment" GROUP BY "customer_id"`,
override: map[source.DriverType]string{mysql.Type: "SELECT `customer_id`, sum(`amount`) AS `sum(.amount)` FROM `payment` GROUP BY `customer_id`"},
wantRecCount: 599,
},
{
name: "group_by/multiple_terms",
in: `@sakila | .payment | .customer_id, .staff_id, sum(.amount) | group_by(.customer_id, .staff_id)`,
wantSQL: `SELECT "customer_id", "staff_id", sum("amount") AS "sum(.amount)" FROM "payment" GROUP BY "customer_id", "staff_id"`,
override: map[source.DriverType]string{mysql.Type: "SELECT `customer_id`, `staff_id`, sum(`amount`) AS `sum(.amount)` FROM `payment` GROUP BY `customer_id`, `staff_id`"},
wantRecs: 1198,
name: "group_by/multiple_terms",
in: `@sakila | .payment | .customer_id, .staff_id, sum(.amount) | group_by(.customer_id, .staff_id)`,
wantSQL: `SELECT "customer_id", "staff_id", sum("amount") AS "sum(.amount)" FROM "payment" GROUP BY "customer_id", "staff_id"`,
override: map[source.DriverType]string{mysql.Type: "SELECT `customer_id`, `staff_id`, sum(`amount`) AS `sum(.amount)` FROM `payment` GROUP BY `customer_id`, `staff_id`"},
wantRecCount: 1198,
},
{
name: "group_by/with_func/sqlite",
in: `@sakila | .payment | _date("month", .payment_date):month, count(.payment_id):count | group_by(_date("month", .payment_date))`,
wantSQL: `SELECT date('month', "payment_date") AS "month", count("payment_id") AS "count" FROM "payment" GROUP BY date('month', "payment_date")`,
onlyFor: []source.DriverType{sqlite3.Type},
wantRecs: 1,
name: "group_by/with_func/sqlite",
in: `@sakila | .payment | _date("month", .payment_date):month, count(.payment_id):count | group_by(_date("month", .payment_date))`,
wantSQL: `SELECT date('month', "payment_date") AS "month", count("payment_id") AS "count" FROM "payment" GROUP BY date('month', "payment_date")`,
onlyFor: []source.DriverType{sqlite3.Type},
wantRecCount: 1,
},
}

View File

@ -16,18 +16,18 @@ import (
func TestQuery_join(t *testing.T) {
testCases := []queryTestCase{
{
name: "join/single-selector",
in: `@sakila | .actor, .film_actor | join(.actor_id)`,
wantSQL: `SELECT * FROM "actor" INNER JOIN "film_actor" ON "actor"."actor_id" = "film_actor"."actor_id"`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` INNER JOIN `film_actor` ON `actor`.`actor_id` = `film_actor`.`actor_id`"},
wantRecs: sakila.TblFilmActorCount,
name: "join/single-selector",
in: `@sakila | .actor, .film_actor | join(.actor_id)`,
wantSQL: `SELECT * FROM "actor" INNER JOIN "film_actor" ON "actor"."actor_id" = "film_actor"."actor_id"`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` INNER JOIN `film_actor` ON `actor`.`actor_id` = `film_actor`.`actor_id`"},
wantRecCount: sakila.TblFilmActorCount,
},
{
name: "join/fq-table-cols-equal",
in: `@sakila | .actor, .film_actor | join(.film_actor.actor_id == .actor.actor_id)`,
wantSQL: `SELECT * FROM "actor" INNER JOIN "film_actor" ON "film_actor"."actor_id" = "actor"."actor_id"`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` INNER JOIN `film_actor` ON `film_actor`.`actor_id` = `actor`.`actor_id`"},
wantRecs: sakila.TblFilmActorCount,
name: "join/fq-table-cols-equal",
in: `@sakila | .actor, .film_actor | join(.film_actor.actor_id == .actor.actor_id)`,
wantSQL: `SELECT * FROM "actor" INNER JOIN "film_actor" ON "film_actor"."actor_id" = "actor"."actor_id"`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` INNER JOIN `film_actor` ON `film_actor`.`actor_id` = `actor`.`actor_id`"},
wantRecCount: sakila.TblFilmActorCount,
},
{
name: "join/fq-table-cols-equal-whitespace",

View File

@ -16,46 +16,46 @@ import (
func TestQuery_orderby(t *testing.T) {
testCases := []queryTestCase{
{
name: "order_by/single-element",
in: `@sakila | .actor | order_by(.first_name)`,
wantSQL: `SELECT * FROM "actor" ORDER BY "first_name"`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name`"},
wantRecs: sakila.TblActorCount,
name: "order_by/single-element",
in: `@sakila | .actor | order_by(.first_name)`,
wantSQL: `SELECT * FROM "actor" ORDER BY "first_name"`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name`"},
wantRecCount: sakila.TblActorCount,
},
{
name: "order_by/single-element-table-selector",
in: `@sakila | .actor | order_by(.actor.first_name)`,
wantSQL: `SELECT * FROM "actor" ORDER BY "actor"."first_name"`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` ORDER BY `actor`.`first_name`"},
wantRecs: sakila.TblActorCount,
name: "order_by/single-element-table-selector",
in: `@sakila | .actor | order_by(.actor.first_name)`,
wantSQL: `SELECT * FROM "actor" ORDER BY "actor"."first_name"`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` ORDER BY `actor`.`first_name`"},
wantRecCount: sakila.TblActorCount,
},
{
name: "order_by/single-element-asc",
in: `@sakila | .actor | order_by(.first_name+)`,
wantSQL: `SELECT * FROM "actor" ORDER BY "first_name" ASC`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name` ASC"},
wantRecs: sakila.TblActorCount,
name: "order_by/single-element-asc",
in: `@sakila | .actor | order_by(.first_name+)`,
wantSQL: `SELECT * FROM "actor" ORDER BY "first_name" ASC`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name` ASC"},
wantRecCount: sakila.TblActorCount,
},
{
name: "order_by/single-element-desc",
in: `@sakila | .actor | order_by(.first_name-)`,
wantSQL: `SELECT * FROM "actor" ORDER BY "first_name" DESC`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name` DESC"},
wantRecs: sakila.TblActorCount,
name: "order_by/single-element-desc",
in: `@sakila | .actor | order_by(.first_name-)`,
wantSQL: `SELECT * FROM "actor" ORDER BY "first_name" DESC`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name` DESC"},
wantRecCount: sakila.TblActorCount,
},
{
name: "order_by/multiple-elements",
in: `@sakila | .actor | order_by(.first_name+, .last_name-)`,
wantSQL: `SELECT * FROM "actor" ORDER BY "first_name" ASC, "last_name" DESC`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name` ASC, `last_name` DESC"},
wantRecs: sakila.TblActorCount,
name: "order_by/multiple-elements",
in: `@sakila | .actor | order_by(.first_name+, .last_name-)`,
wantSQL: `SELECT * FROM "actor" ORDER BY "first_name" ASC, "last_name" DESC`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name` ASC, `last_name` DESC"},
wantRecCount: sakila.TblActorCount,
},
{
name: "order_by/synonym-sort-by",
in: `@sakila | .actor | sort_by(.first_name)`,
wantSQL: `SELECT * FROM "actor" ORDER BY "first_name"`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name`"},
wantRecs: sakila.TblActorCount,
name: "order_by/synonym-sort-by",
in: `@sakila | .actor | sort_by(.first_name)`,
wantSQL: `SELECT * FROM "actor" ORDER BY "first_name"`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` ORDER BY `first_name`"},
wantRecCount: sakila.TblActorCount,
},
{
name: "order_by/error-no-selector",

View File

@ -4,6 +4,8 @@ import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/neilotoole/sq/libsq"
"golang.org/x/exp/slices"
@ -55,25 +57,29 @@ type queryTestCase struct {
// data in the Sakila datasets.
skipExec bool
// wantRecs is the number of expected records from actually executing
// wantRecCount is the number of expected records from actually executing
// the query. This is N/A if skipExec is true.
wantRecs int
wantRecCount int
// sinkTest, if non-nil, is executed against the sink returned
// from the query execution.
sinkFns []SinkTestFunc
}
// SinkTestFunc is a function that tests a sink.
type SinkTestFunc func(t testing.TB, sink *testh.RecordSink)
func execQueryTestCase(t *testing.T, tc queryTestCase) {
if tc.skip {
t.Skip()
}
t.Helper()
coll := testh.New(t).NewCollection(sakila.SQLLatest()...)
_ = coll
src, err := coll.Get(sakila.Pg)
require.NoError(t, err)
// srcs :=
for _, src := range []*source.Source{src} {
// for _, src := range coll.Sources() {
coll := testh.New(t).NewCollection(sakila.SQLLatest()...) // FIXME: Revert to using all sakila.SQLLatest
// coll := testh.New(t).NewCollection(sakila.Pg)
for _, src := range coll.Sources() {
src := src
t.Run(string(src.Type), func(t *testing.T) {
@ -121,7 +127,29 @@ func execQueryTestCase(t *testing.T, tc queryTestCase) {
sink, err := th.QuerySLQ(in, tc.args)
require.NoError(t, err)
require.Equal(t, tc.wantRecs, len(sink.Recs))
require.Equal(t, tc.wantRecCount, len(sink.Recs))
for i := range tc.sinkFns {
tc.sinkFns[i](t, sink)
}
})
}
}
// assertSinkColValue returns a SinkTestFunc that asserts that
// the column colIndex of each record matches val.
func assertSinkColValue(colIndex int, val any) SinkTestFunc {
return func(t testing.TB, sink *testh.RecordSink) {
for rowi, rec := range sink.Recs {
assert.Equal(t, val, rec[colIndex], "record[%d:%d] (%s)", rowi, colIndex, sink.RecMeta[colIndex].Name())
}
}
}
// assertSinkColValue returns a SinkTestFunc that asserts that
// the name of column colIndex matches name.
func assertSinkColName(colIndex int, name string) SinkTestFunc {
return func(t testing.TB, sink *testh.RecordSink) {
assert.Equal(t, name, sink.RecMeta[colIndex].Name(), "column %d", colIndex)
}
}

View File

@ -16,25 +16,25 @@ import (
func TestQuery_unique(t *testing.T) {
testCases := []queryTestCase{
{
name: "unique/single-col",
in: `@sakila | .actor | .first_name | unique`,
wantSQL: `SELECT DISTINCT "first_name" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT DISTINCT `first_name` FROM `actor`"},
wantRecs: 128,
name: "unique/single-col",
in: `@sakila | .actor | .first_name | unique`,
wantSQL: `SELECT DISTINCT "first_name" FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT DISTINCT `first_name` FROM `actor`"},
wantRecCount: 128,
},
{
name: "unique/no-col",
in: `@sakila | .actor | unique`,
wantSQL: `SELECT DISTINCT * FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT DISTINCT * FROM `actor`"},
wantRecs: sakila.TblActorCount,
name: "unique/no-col",
in: `@sakila | .actor | unique`,
wantSQL: `SELECT DISTINCT * FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT DISTINCT * FROM `actor`"},
wantRecCount: sakila.TblActorCount,
},
{
name: "unique/no-col",
in: `@sakila | .actor | unique`,
wantSQL: `SELECT DISTINCT * FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT DISTINCT * FROM `actor`"},
wantRecs: sakila.TblActorCount,
name: "unique/no-col",
in: `@sakila | .actor | unique`,
wantSQL: `SELECT DISTINCT * FROM "actor"`,
override: map[source.DriverType]string{mysql.Type: "SELECT DISTINCT * FROM `actor`"},
wantRecCount: sakila.TblActorCount,
},
}

105
libsq/query_where_test.go Normal file
View File

@ -0,0 +1,105 @@
package libsq_test
import (
"testing"
"github.com/neilotoole/sq/drivers/mysql"
"github.com/neilotoole/sq/libsq/source"
_ "github.com/mattn/go-sqlite3"
)
//nolint:exhaustive,lll
func TestQuery_where(t *testing.T) {
testCases := []queryTestCase{
{
name: "operator/eq",
in: `@sakila | .actor | where(.actor_id == 100)`,
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" = 100`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` = 100"},
wantRecCount: 1,
},
{
name: "operator/ne",
in: `@sakila | .actor | where(.actor_id != 100)`,
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" != 100`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` != 100"},
wantRecCount: 199,
},
{
name: "operator/lt",
in: `@sakila | .actor | where(.actor_id < 100)`,
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" < 100`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` < 100"},
wantRecCount: 99,
},
{
name: "operator/lte",
in: `@sakila | .actor | where(.actor_id <= 100)`,
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" <= 100`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` <= 100"},
wantRecCount: 100,
},
{
name: "operator/gt",
in: `@sakila | .actor | where(.actor_id > 100)`,
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" > 100`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` > 100"},
wantRecCount: 100,
},
{
name: "operator/gte",
in: `@sakila | .actor | where(.actor_id >= 100)`,
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" >= 100`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` >= 100"},
wantRecCount: 101,
},
{
name: "error/no-args",
in: `@sakila | .actor | where()`,
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" = 1`,
wantErr: true,
},
{
name: "select_alias",
in: `@sakila | .actor | select(.actor_id == 1)`,
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" = 1`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` = 1"},
wantRecCount: 1,
},
{
name: "where_compound_1",
in: `@sakila | .actor | where(.actor_id >= 100 && .actor_id < 150)`,
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" >= 100 AND "actor_id" < 150`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` >= 100 AND `actor_id` < 150"},
wantRecCount: 50,
},
{
name: "where_compound_2",
in: `@sakila | .actor | where(.actor_id >= 100 || (.actor_id < 150 && .first_name == "TOM"))`,
wantSQL: `SELECT * FROM "actor" WHERE "actor_id" >= 100 OR ("actor_id" < 150 AND "first_name" = 'TOM')`,
override: map[source.DriverType]string{mysql.Type: "SELECT * FROM `actor` WHERE `actor_id` >= 100 OR (`actor_id` < 150 AND `first_name` = 'TOM')"},
wantRecCount: 103,
},
{
name: "where_using_col_alias",
in: `@sakila | .actor | .first_name:given_name | where(.given_name == "TOM")`,
wantSQL: `SELECT "first_name" AS "given_name" FROM "actor" WHERE "given_name" = 'TOM'`,
override: map[source.DriverType]string{mysql.Type: "SELECT `first_name` AS `given_name` FROM `actor` WHERE `given_name` = 'TOM'"},
wantRecCount: 2,
// Skip because this only works on SQLite, not the other SQL databases.
// I'm not sure if this will ever be implemented. Perhaps sq could look at
// the ".given_name" element in the where clause, and substitute in the
// unaliased value? Could be messy. Either which way, not a high priority.
skip: true,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
execQueryTestCase(t, tc)
})
}
}