mirror of
https://github.com/neilotoole/sq.git
synced 2024-11-24 03:45:56 +03:00
Implement explicit `where()` clause.
This commit is contained in:
parent
b492788ac0
commit
e93f462fff
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
156
libsq/ast/expr.go
Normal 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())
|
||||
}
|
||||
}
|
@ -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
@ -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
@ -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
|
||||
|
@ -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) {}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
@ -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{}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
56
libsq/ast/where.go
Normal 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)
|
||||
}
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
9
libsq/core/utilz/utilz.go
Normal file
9
libsq/core/utilz/utilz.go
Normal 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
|
||||
}
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
105
libsq/query_where_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user