SQL: Add PIVOT support (#46489)

Add initial PIVOT support for transforming a regular table into a
statistics table around an arbitrary pivoting column:

SELECT * FROM
 (SELECT languages, country, salary, FROM mp)
 PIVOT (AVG(salary) FOR countries IN ('NL', 'DE', 'ES', 'RO', 'US'))

In the current implementation PIVOT allows only one aggregation however
this restriction is likely to be lifted in the future.
Also not all aggregations are working, in particular MatrixStats are not yet supported.

(cherry picked from commit d91263746a222915c570d4a662ec48c1d6b4f583)
This commit is contained in:
Costin Leau 2019-09-23 18:59:46 +03:00 committed by Costin Leau
parent 199fff8a55
commit a610503783
53 changed files with 3966 additions and 2113 deletions

View File

@ -149,4 +149,59 @@ public class FetchSizeTestCase extends JdbcIntegrationTestCase {
assertTrue("No more entries left after row " + rs.getRow(), (i+j == 23 || rs.next()));
}
}
}
/**
* Explicit pagination test for PIVOT.
* Checks that the paging properly consumes the necessary amount of aggregations and the
* page size affects the result not the intermediate query.
*/
public void testPivotPaging() throws Exception {
Request request = new Request("PUT", "/test_pivot/_bulk");
request.addParameter("refresh", "true");
StringBuilder bulk = new StringBuilder();
String[] continent = new String[] { "AF", "AS", "EU", "NA", "SA", "AQ", "AU" };
for (int i = 0; i <= 100; i++) {
bulk.append("{\"index\":{}}\n");
bulk.append("{\"item\":").append(i % 10)
.append(", \"entry\":").append(i)
.append(", \"amount\" : ").append(randomInt(999))
.append(", \"location\" : \"").append(continent[i % (continent.length)]).append("\"")
.append("}\n");
}
request.setJsonEntity(bulk.toString());
assertEquals(200, client().performRequest(request).getStatusLine().getStatusCode());
try (Connection c = esJdbc();
Statement s = c.createStatement()) {
String query = "SELECT * FROM "
+ "(SELECT item, amount, location FROM test_pivot)"
+ " PIVOT (AVG(amount) FOR location IN ( 'AF', 'AS', 'EU', 'NA', 'SA', 'AQ', 'AU') )";
// set size smaller than an agg page
s.setFetchSize(3);
try (ResultSet rs = s.executeQuery(query)) {
assertEquals(8, rs.getMetaData().getColumnCount());
for (int i = 0; i < 10; i++) {
assertTrue(rs.next());
// the page was set to a pivot row (since the initial 3 is lower as a pivot page takes number of pivot entries + 1)
assertEquals(1, rs.getFetchSize());
assertEquals(Long.valueOf(i), rs.getObject("item"));
}
assertFalse(rs.next());
}
// now try with a larger fetch size (8 * 2 + something) - should be 2
s.setFetchSize(20);
try (ResultSet rs = s.executeQuery(query)) {
for (int i = 0; i < 10; i++) {
assertTrue(rs.next());
//
assertEquals(2, rs.getFetchSize());
assertEquals(Long.valueOf(i), rs.getObject("item"));
}
assertFalse(rs.next());
}
}
assertNoSearchContexts();
}
}

View File

@ -0,0 +1,206 @@
averageWithOneValue
schema::languages:bt|'F':d
SELECT * FROM (SELECT languages, gender, salary FROM test_emp) PIVOT (AVG(salary) FOR gender IN ('F'));
languages | 'F'
---------------+------------------
null |62140.666666666664
1 |47073.25
2 |50684.4
3 |53660.0
4 |49291.5
5 |46705.555555555555
;
averageWithAliasAndOneValue
schema::languages:bt|'F':d
SELECT * FROM (SELECT languages, gender, salary FROM test_emp) PIVOT (AVG(salary) AS "AVG" FOR gender IN ('F'));
languages | 'F'
---------------+------------------
null |62140.666666666664
1 |47073.25
2 |50684.4
3 |53660.0
4 |49291.5
5 |46705.555555555555
;
averageWithAliasedValue
schema::languages:bt|XX:d
SELECT * FROM (SELECT languages, gender, salary FROM test_emp) PIVOT (AVG(salary) FOR gender IN ('F' AS "XX"));
languages | XX
---------------+------------------
null |62140.666666666664
1 |47073.25
2 |50684.4
3 |53660.0
4 |49291.5
5 |46705.555555555555
;
averageWithTwoValues
schema::languages:bt|'M':d|'F':d
SELECT * FROM (SELECT languages, gender, salary FROM test_emp) PIVOT (AVG(salary) FOR gender IN ('M', 'F'));
languages | 'M' | 'F'
---------------+-----------------+------------------
null |48396.28571428572|62140.666666666664
1 |49767.22222222222|47073.25
2 |44103.90909090909|50684.4
3 |51741.90909090909|53660.0
4 |47058.90909090909|49291.5
5 |39052.875 |46705.555555555555
;
averageWithTwoValuesAndAlias
schema::languages:bt|XY:d|XX:d
SELECT * FROM (SELECT languages, gender, salary FROM test_emp) PIVOT (AVG(salary) FOR gender IN ('M' AS "XY", 'F' "XX"));
languages | XY | XX
---------------+-----------------+------------------
null |48396.28571428572|62140.666666666664
1 |49767.22222222222|47073.25
2 |44103.90909090909|50684.4
3 |51741.90909090909|53660.0
4 |47058.90909090909|49291.5
5 |39052.875 |46705.555555555555
;
averageWithThreeValuesIncludingNull
schema::languages:bt|'M':d|'F':d
SELECT * FROM (SELECT languages, gender, salary FROM test_emp) PIVOT (AVG(salary) FOR gender IN ('M', 'F'));
languages | 'M' | 'F'
---------------+-----------------+------------------
null |48396.28571428572|62140.666666666664
1 |49767.22222222222|47073.25
2 |44103.90909090909|50684.4
3 |51741.90909090909|53660.0
4 |47058.90909090909|49291.5
5 |39052.875 |46705.555555555555
;
averageWithOneValueAndLimit
schema::languages:bt|'F':d
SELECT * FROM (SELECT languages, gender, salary FROM test_emp) PIVOT (AVG(salary) FOR gender IN ('F')) LIMIT 3;
languages | 'F'
---------------+------------------
null |62140.666666666664
1 |47073.25
2 |50684.4
;
averageWithTwoValuesAndLimit
schema::languages:bt|'M':d|'F':d
SELECT * FROM (SELECT languages, gender, salary FROM test_emp) PIVOT (AVG(salary) FOR gender IN ('M', 'F')) LIMIT 3;
languages | 'M' | 'F'
---------------+-----------------+------------------
null |48396.28571428572|62140.666666666664
1 |49767.22222222222|47073.25
2 |44103.90909090909|50684.4
;
averageWithTwoValuesAndTinyLimit
schema::languages:bt|'M':d|'F':d
SELECT * FROM (SELECT languages, gender, salary FROM test_emp) PIVOT (AVG(salary) FOR gender IN ('M', 'F')) LIMIT 1;
languages | 'M' | 'F'
---------------+-----------------+------------------
null |48396.28571428572|62140.666666666664
;
averageWithTwoValuesAndSmallLimit
schema::languages:bt|'M':d|'F':d
SELECT * FROM (SELECT languages, gender, salary FROM test_emp) PIVOT (AVG(salary) FOR gender IN ('M', 'F')) LIMIT 2;
languages | 'M' | 'F'
---------------+-----------------+------------------
null |48396.28571428572|62140.666666666664
1 |49767.22222222222|47073.25
;
averageWithOneValueAndOrder
schema::languages:bt|'F':d
SELECT * FROM (SELECT languages, gender, salary FROM test_emp) PIVOT (AVG(salary) FOR gender IN ('F')) ORDER BY languages DESC LIMIT 4;
languages | 'F'
---------------+------------------
5 |46705.555555555555
4 |49291.5
3 |53660.0
2 |50684.4
;
averageWithTwoValuesAndOrderDesc
schema::languages:bt|'M':d|'F':d
SELECT * FROM (SELECT languages, gender, salary FROM test_emp) PIVOT (AVG(salary) FOR gender IN ('M', 'F')) ORDER BY languages DESC;
languages | 'M' | 'F'
---------------+-----------------+------------------
5 |39052.875 |46705.555555555555
4 |47058.90909090909|49291.5
3 |51741.90909090909|53660.0
2 |44103.90909090909|50684.4
1 |49767.22222222222|47073.25
null |48396.28571428572|62140.666666666664
;
averageWithTwoValuesAndOrderDescAndLimit
schema::languages:bt|'M':d|'F':d
SELECT * FROM (SELECT languages, gender, salary FROM test_emp) PIVOT (AVG(salary) FOR gender IN ('M', 'F')) ORDER BY languages DESC LIMIT 2;
languages | 'M' | 'F'
---------------+-----------------+------------------
5 |39052.875 |46705.555555555555
4 |47058.90909090909|49291.5
;
averageWithTwoValuesAndOrderAsc
schema::languages:bt|'M':d|'F':d
SELECT * FROM (SELECT languages, gender, salary FROM test_emp) PIVOT (AVG(salary) FOR gender IN ('M', 'F')) ORDER BY languages ASC;
languages | 'M' | 'F'
---------------+-----------------+------------------
null |48396.28571428572|62140.666666666664
1 |49767.22222222222|47073.25
2 |44103.90909090909|50684.4
3 |51741.90909090909|53660.0
4 |47058.90909090909|49291.5
5 |39052.875 |46705.555555555555
;
sumWithoutSubquery
schema::birth_date:ts|emp_no:i|first_name:s|gender:s|hire_date:ts|last_name:s|1:i|2:i
SELECT * FROM test_emp PIVOT (SUM(salary) FOR languages IN (1, 2)) LIMIT 5;
birth_date | emp_no | first_name | gender | hire_date | last_name | 1 | 2
---------------------+---------------+---------------+---------------+---------------------+---------------+---------------+---------------
null |10041 |Uri |F |1989-11-12 00:00:00.0|Lenart |56415 |null
null |10043 |Yishay |M |1990-10-20 00:00:00.0|Tzvieli |34341 |null
null |10044 |Mingsen |F |1994-05-21 00:00:00.0|Casley |39728 |null
1952-04-19 00:00:00.0|10009 |Sumant |F |1985-02-18 00:00:00.0|Peac |66174 |null
1953-01-07 00:00:00.0|10067 |Claudi |M |1987-03-04 00:00:00.0|Stavenow |null |52044
1953-01-23 00:00:00.0|10019 |Lillian |null |1999-04-30 00:00:00.0|Haddadi |73717 |null
;
averageWithOneValueAndMath
schema::languages:bt|'F':d
SELECT * FROM (SELECT languages, gender, salary FROM test_emp) PIVOT (ROUND(AVG(salary) / 2) FOR gender IN ('F'));
languages | 'F'
---------------+---------------
null |31070.0
1 |23537.0
2 |25342.0
3 |26830.0
4 |24646.0
5 |23353.0
;

View File

@ -90,7 +90,7 @@ orderBy
;
querySpecification
: SELECT setQuantifier? selectItem (',' selectItem)*
: SELECT setQuantifier? selectItems
fromClause?
(WHERE where=booleanExpression)?
(GROUP BY groupBy)?
@ -98,7 +98,7 @@ querySpecification
;
fromClause
: FROM relation (',' relation)*
: FROM relation (',' relation)* pivotClause?
;
groupBy
@ -123,6 +123,10 @@ setQuantifier
| ALL
;
selectItems
: selectItem (',' selectItem)*
;
selectItem
: expression (AS? identifier)? #selectExpression
;
@ -154,6 +158,18 @@ relationPrimary
| '(' relation ')' (AS? qualifiedName)? #aliasedRelation
;
pivotClause
: PIVOT '(' aggs=pivotArgs FOR column=qualifiedName IN '(' vals=pivotArgs ')' ')'
;
pivotArgs
: namedValueExpression (',' namedValueExpression)*
;
namedValueExpression
: valueExpression (AS? identifier)?
;
expression
: booleanExpression
;
@ -343,6 +359,7 @@ whenClause
;
// http://developer.mimer.se/validator/sql-reserved-words.tml
// https://developer.mimer.com/wp-content/uploads/standard-sql-reserved-words-summary.pdf
nonReserved
: ANALYZE | ANALYZED
| CATALOGS | COLUMNS | CURRENT_DATE | CURRENT_TIME | CURRENT_TIMESTAMP
@ -355,7 +372,7 @@ nonReserved
| LAST | LIMIT
| MAPPED | MINUTE | MONTH
| OPTIMIZED
| PARSED | PHYSICAL | PLAN
| PARSED | PHYSICAL | PIVOT | PLAN
| QUERY
| RLIKE
| SCHEMAS | SECOND | SHOW | SYS
@ -397,6 +414,7 @@ EXPLAIN: 'EXPLAIN';
EXTRACT: 'EXTRACT';
FALSE: 'FALSE';
FIRST: 'FIRST';
FOR: 'FOR';
FORMAT: 'FORMAT';
FROM: 'FROM';
FROZEN: 'FROZEN';
@ -434,6 +452,7 @@ ORDER: 'ORDER';
OUTER: 'OUTER';
PARSED: 'PARSED';
PHYSICAL: 'PHYSICAL';
PIVOT: 'PIVOT';
PLAN: 'PLAN';
RIGHT: 'RIGHT';
RLIKE: 'RLIKE';

View File

@ -35,105 +35,107 @@ EXPLAIN=34
EXTRACT=35
FALSE=36
FIRST=37
FORMAT=38
FROM=39
FROZEN=40
FULL=41
FUNCTIONS=42
GRAPHVIZ=43
GROUP=44
HAVING=45
HOUR=46
HOURS=47
IN=48
INCLUDE=49
INNER=50
INTERVAL=51
IS=52
JOIN=53
LAST=54
LEFT=55
LIKE=56
LIMIT=57
MAPPED=58
MATCH=59
MINUTE=60
MINUTES=61
MONTH=62
MONTHS=63
NATURAL=64
NOT=65
NULL=66
NULLS=67
ON=68
OPTIMIZED=69
OR=70
ORDER=71
OUTER=72
PARSED=73
PHYSICAL=74
PLAN=75
RIGHT=76
RLIKE=77
QUERY=78
SCHEMAS=79
SECOND=80
SECONDS=81
SELECT=82
SHOW=83
SYS=84
TABLE=85
TABLES=86
TEXT=87
THEN=88
TRUE=89
TO=90
TYPE=91
TYPES=92
USING=93
VERIFY=94
WHEN=95
WHERE=96
WITH=97
YEAR=98
YEARS=99
ESCAPE_ESC=100
FUNCTION_ESC=101
LIMIT_ESC=102
DATE_ESC=103
TIME_ESC=104
TIMESTAMP_ESC=105
GUID_ESC=106
ESC_END=107
EQ=108
NULLEQ=109
NEQ=110
LT=111
LTE=112
GT=113
GTE=114
PLUS=115
MINUS=116
ASTERISK=117
SLASH=118
PERCENT=119
CAST_OP=120
CONCAT=121
DOT=122
PARAM=123
STRING=124
INTEGER_VALUE=125
DECIMAL_VALUE=126
IDENTIFIER=127
DIGIT_IDENTIFIER=128
TABLE_IDENTIFIER=129
QUOTED_IDENTIFIER=130
BACKQUOTED_IDENTIFIER=131
SIMPLE_COMMENT=132
BRACKETED_COMMENT=133
WS=134
UNRECOGNIZED=135
DELIMITER=136
FOR=38
FORMAT=39
FROM=40
FROZEN=41
FULL=42
FUNCTIONS=43
GRAPHVIZ=44
GROUP=45
HAVING=46
HOUR=47
HOURS=48
IN=49
INCLUDE=50
INNER=51
INTERVAL=52
IS=53
JOIN=54
LAST=55
LEFT=56
LIKE=57
LIMIT=58
MAPPED=59
MATCH=60
MINUTE=61
MINUTES=62
MONTH=63
MONTHS=64
NATURAL=65
NOT=66
NULL=67
NULLS=68
ON=69
OPTIMIZED=70
OR=71
ORDER=72
OUTER=73
PARSED=74
PHYSICAL=75
PIVOT=76
PLAN=77
RIGHT=78
RLIKE=79
QUERY=80
SCHEMAS=81
SECOND=82
SECONDS=83
SELECT=84
SHOW=85
SYS=86
TABLE=87
TABLES=88
TEXT=89
THEN=90
TRUE=91
TO=92
TYPE=93
TYPES=94
USING=95
VERIFY=96
WHEN=97
WHERE=98
WITH=99
YEAR=100
YEARS=101
ESCAPE_ESC=102
FUNCTION_ESC=103
LIMIT_ESC=104
DATE_ESC=105
TIME_ESC=106
TIMESTAMP_ESC=107
GUID_ESC=108
ESC_END=109
EQ=110
NULLEQ=111
NEQ=112
LT=113
LTE=114
GT=115
GTE=116
PLUS=117
MINUS=118
ASTERISK=119
SLASH=120
PERCENT=121
CAST_OP=122
CONCAT=123
DOT=124
PARAM=125
STRING=126
INTEGER_VALUE=127
DECIMAL_VALUE=128
IDENTIFIER=129
DIGIT_IDENTIFIER=130
TABLE_IDENTIFIER=131
QUOTED_IDENTIFIER=132
BACKQUOTED_IDENTIFIER=133
SIMPLE_COMMENT=134
BRACKETED_COMMENT=135
WS=136
UNRECOGNIZED=137
DELIMITER=138
'('=1
')'=2
','=3
@ -171,88 +173,90 @@ DELIMITER=136
'EXTRACT'=35
'FALSE'=36
'FIRST'=37
'FORMAT'=38
'FROM'=39
'FROZEN'=40
'FULL'=41
'FUNCTIONS'=42
'GRAPHVIZ'=43
'GROUP'=44
'HAVING'=45
'HOUR'=46
'HOURS'=47
'IN'=48
'INCLUDE'=49
'INNER'=50
'INTERVAL'=51
'IS'=52
'JOIN'=53
'LAST'=54
'LEFT'=55
'LIKE'=56
'LIMIT'=57
'MAPPED'=58
'MATCH'=59
'MINUTE'=60
'MINUTES'=61
'MONTH'=62
'MONTHS'=63
'NATURAL'=64
'NOT'=65
'NULL'=66
'NULLS'=67
'ON'=68
'OPTIMIZED'=69
'OR'=70
'ORDER'=71
'OUTER'=72
'PARSED'=73
'PHYSICAL'=74
'PLAN'=75
'RIGHT'=76
'RLIKE'=77
'QUERY'=78
'SCHEMAS'=79
'SECOND'=80
'SECONDS'=81
'SELECT'=82
'SHOW'=83
'SYS'=84
'TABLE'=85
'TABLES'=86
'TEXT'=87
'THEN'=88
'TRUE'=89
'TO'=90
'TYPE'=91
'TYPES'=92
'USING'=93
'VERIFY'=94
'WHEN'=95
'WHERE'=96
'WITH'=97
'YEAR'=98
'YEARS'=99
'{ESCAPE'=100
'{FN'=101
'{LIMIT'=102
'{D'=103
'{T'=104
'{TS'=105
'{GUID'=106
'}'=107
'='=108
'<=>'=109
'<'=111
'<='=112
'>'=113
'>='=114
'+'=115
'-'=116
'*'=117
'/'=118
'%'=119
'::'=120
'||'=121
'.'=122
'?'=123
'FOR'=38
'FORMAT'=39
'FROM'=40
'FROZEN'=41
'FULL'=42
'FUNCTIONS'=43
'GRAPHVIZ'=44
'GROUP'=45
'HAVING'=46
'HOUR'=47
'HOURS'=48
'IN'=49
'INCLUDE'=50
'INNER'=51
'INTERVAL'=52
'IS'=53
'JOIN'=54
'LAST'=55
'LEFT'=56
'LIKE'=57
'LIMIT'=58
'MAPPED'=59
'MATCH'=60
'MINUTE'=61
'MINUTES'=62
'MONTH'=63
'MONTHS'=64
'NATURAL'=65
'NOT'=66
'NULL'=67
'NULLS'=68
'ON'=69
'OPTIMIZED'=70
'OR'=71
'ORDER'=72
'OUTER'=73
'PARSED'=74
'PHYSICAL'=75
'PIVOT'=76
'PLAN'=77
'RIGHT'=78
'RLIKE'=79
'QUERY'=80
'SCHEMAS'=81
'SECOND'=82
'SECONDS'=83
'SELECT'=84
'SHOW'=85
'SYS'=86
'TABLE'=87
'TABLES'=88
'TEXT'=89
'THEN'=90
'TRUE'=91
'TO'=92
'TYPE'=93
'TYPES'=94
'USING'=95
'VERIFY'=96
'WHEN'=97
'WHERE'=98
'WITH'=99
'YEAR'=100
'YEARS'=101
'{ESCAPE'=102
'{FN'=103
'{LIMIT'=104
'{D'=105
'{T'=106
'{TS'=107
'{GUID'=108
'}'=109
'='=110
'<=>'=111
'<'=113
'<='=114
'>'=115
'>='=116
'+'=117
'-'=118
'*'=119
'/'=120
'%'=121
'::'=122
'||'=123
'.'=124
'?'=125

View File

@ -35,104 +35,106 @@ EXPLAIN=34
EXTRACT=35
FALSE=36
FIRST=37
FORMAT=38
FROM=39
FROZEN=40
FULL=41
FUNCTIONS=42
GRAPHVIZ=43
GROUP=44
HAVING=45
HOUR=46
HOURS=47
IN=48
INCLUDE=49
INNER=50
INTERVAL=51
IS=52
JOIN=53
LAST=54
LEFT=55
LIKE=56
LIMIT=57
MAPPED=58
MATCH=59
MINUTE=60
MINUTES=61
MONTH=62
MONTHS=63
NATURAL=64
NOT=65
NULL=66
NULLS=67
ON=68
OPTIMIZED=69
OR=70
ORDER=71
OUTER=72
PARSED=73
PHYSICAL=74
PLAN=75
RIGHT=76
RLIKE=77
QUERY=78
SCHEMAS=79
SECOND=80
SECONDS=81
SELECT=82
SHOW=83
SYS=84
TABLE=85
TABLES=86
TEXT=87
THEN=88
TRUE=89
TO=90
TYPE=91
TYPES=92
USING=93
VERIFY=94
WHEN=95
WHERE=96
WITH=97
YEAR=98
YEARS=99
ESCAPE_ESC=100
FUNCTION_ESC=101
LIMIT_ESC=102
DATE_ESC=103
TIME_ESC=104
TIMESTAMP_ESC=105
GUID_ESC=106
ESC_END=107
EQ=108
NULLEQ=109
NEQ=110
LT=111
LTE=112
GT=113
GTE=114
PLUS=115
MINUS=116
ASTERISK=117
SLASH=118
PERCENT=119
CAST_OP=120
CONCAT=121
DOT=122
PARAM=123
STRING=124
INTEGER_VALUE=125
DECIMAL_VALUE=126
IDENTIFIER=127
DIGIT_IDENTIFIER=128
TABLE_IDENTIFIER=129
QUOTED_IDENTIFIER=130
BACKQUOTED_IDENTIFIER=131
SIMPLE_COMMENT=132
BRACKETED_COMMENT=133
WS=134
UNRECOGNIZED=135
FOR=38
FORMAT=39
FROM=40
FROZEN=41
FULL=42
FUNCTIONS=43
GRAPHVIZ=44
GROUP=45
HAVING=46
HOUR=47
HOURS=48
IN=49
INCLUDE=50
INNER=51
INTERVAL=52
IS=53
JOIN=54
LAST=55
LEFT=56
LIKE=57
LIMIT=58
MAPPED=59
MATCH=60
MINUTE=61
MINUTES=62
MONTH=63
MONTHS=64
NATURAL=65
NOT=66
NULL=67
NULLS=68
ON=69
OPTIMIZED=70
OR=71
ORDER=72
OUTER=73
PARSED=74
PHYSICAL=75
PIVOT=76
PLAN=77
RIGHT=78
RLIKE=79
QUERY=80
SCHEMAS=81
SECOND=82
SECONDS=83
SELECT=84
SHOW=85
SYS=86
TABLE=87
TABLES=88
TEXT=89
THEN=90
TRUE=91
TO=92
TYPE=93
TYPES=94
USING=95
VERIFY=96
WHEN=97
WHERE=98
WITH=99
YEAR=100
YEARS=101
ESCAPE_ESC=102
FUNCTION_ESC=103
LIMIT_ESC=104
DATE_ESC=105
TIME_ESC=106
TIMESTAMP_ESC=107
GUID_ESC=108
ESC_END=109
EQ=110
NULLEQ=111
NEQ=112
LT=113
LTE=114
GT=115
GTE=116
PLUS=117
MINUS=118
ASTERISK=119
SLASH=120
PERCENT=121
CAST_OP=122
CONCAT=123
DOT=124
PARAM=125
STRING=126
INTEGER_VALUE=127
DECIMAL_VALUE=128
IDENTIFIER=129
DIGIT_IDENTIFIER=130
TABLE_IDENTIFIER=131
QUOTED_IDENTIFIER=132
BACKQUOTED_IDENTIFIER=133
SIMPLE_COMMENT=134
BRACKETED_COMMENT=135
WS=136
UNRECOGNIZED=137
'('=1
')'=2
','=3
@ -170,88 +172,90 @@ UNRECOGNIZED=135
'EXTRACT'=35
'FALSE'=36
'FIRST'=37
'FORMAT'=38
'FROM'=39
'FROZEN'=40
'FULL'=41
'FUNCTIONS'=42
'GRAPHVIZ'=43
'GROUP'=44
'HAVING'=45
'HOUR'=46
'HOURS'=47
'IN'=48
'INCLUDE'=49
'INNER'=50
'INTERVAL'=51
'IS'=52
'JOIN'=53
'LAST'=54
'LEFT'=55
'LIKE'=56
'LIMIT'=57
'MAPPED'=58
'MATCH'=59
'MINUTE'=60
'MINUTES'=61
'MONTH'=62
'MONTHS'=63
'NATURAL'=64
'NOT'=65
'NULL'=66
'NULLS'=67
'ON'=68
'OPTIMIZED'=69
'OR'=70
'ORDER'=71
'OUTER'=72
'PARSED'=73
'PHYSICAL'=74
'PLAN'=75
'RIGHT'=76
'RLIKE'=77
'QUERY'=78
'SCHEMAS'=79
'SECOND'=80
'SECONDS'=81
'SELECT'=82
'SHOW'=83
'SYS'=84
'TABLE'=85
'TABLES'=86
'TEXT'=87
'THEN'=88
'TRUE'=89
'TO'=90
'TYPE'=91
'TYPES'=92
'USING'=93
'VERIFY'=94
'WHEN'=95
'WHERE'=96
'WITH'=97
'YEAR'=98
'YEARS'=99
'{ESCAPE'=100
'{FN'=101
'{LIMIT'=102
'{D'=103
'{T'=104
'{TS'=105
'{GUID'=106
'}'=107
'='=108
'<=>'=109
'<'=111
'<='=112
'>'=113
'>='=114
'+'=115
'-'=116
'*'=117
'/'=118
'%'=119
'::'=120
'||'=121
'.'=122
'?'=123
'FOR'=38
'FORMAT'=39
'FROM'=40
'FROZEN'=41
'FULL'=42
'FUNCTIONS'=43
'GRAPHVIZ'=44
'GROUP'=45
'HAVING'=46
'HOUR'=47
'HOURS'=48
'IN'=49
'INCLUDE'=50
'INNER'=51
'INTERVAL'=52
'IS'=53
'JOIN'=54
'LAST'=55
'LEFT'=56
'LIKE'=57
'LIMIT'=58
'MAPPED'=59
'MATCH'=60
'MINUTE'=61
'MINUTES'=62
'MONTH'=63
'MONTHS'=64
'NATURAL'=65
'NOT'=66
'NULL'=67
'NULLS'=68
'ON'=69
'OPTIMIZED'=70
'OR'=71
'ORDER'=72
'OUTER'=73
'PARSED'=74
'PHYSICAL'=75
'PIVOT'=76
'PLAN'=77
'RIGHT'=78
'RLIKE'=79
'QUERY'=80
'SCHEMAS'=81
'SECOND'=82
'SECONDS'=83
'SELECT'=84
'SHOW'=85
'SYS'=86
'TABLE'=87
'TABLES'=88
'TEXT'=89
'THEN'=90
'TRUE'=91
'TO'=92
'TYPE'=93
'TYPES'=94
'USING'=95
'VERIFY'=96
'WHEN'=97
'WHERE'=98
'WITH'=99
'YEAR'=100
'YEARS'=101
'{ESCAPE'=102
'{FN'=103
'{LIMIT'=104
'{D'=105
'{T'=106
'{TS'=107
'{GUID'=108
'}'=109
'='=110
'<=>'=111
'<'=113
'<='=114
'>'=115
'>='=116
'+'=117
'-'=118
'*'=119
'/'=120
'%'=121
'::'=122
'||'=123
'.'=124
'?'=125

View File

@ -40,6 +40,7 @@ import org.elasticsearch.xpack.sql.plan.logical.Join;
import org.elasticsearch.xpack.sql.plan.logical.LocalRelation;
import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
import org.elasticsearch.xpack.sql.plan.logical.OrderBy;
import org.elasticsearch.xpack.sql.plan.logical.Pivot;
import org.elasticsearch.xpack.sql.plan.logical.Project;
import org.elasticsearch.xpack.sql.plan.logical.SubQueryAlias;
import org.elasticsearch.xpack.sql.plan.logical.UnaryPlan;
@ -419,7 +420,7 @@ public class Analyzer extends RuleExecutor<LogicalPlan> {
return result;
}
private List<NamedExpression> expandStar(UnresolvedStar us, List<Attribute> output) {
static List<NamedExpression> expandStar(UnresolvedStar us, List<Attribute> output) {
List<NamedExpression> expanded = new ArrayList<>();
// a qualifier is specified - since this is a star, it should be a CompoundDataType
@ -460,24 +461,7 @@ public class Analyzer extends RuleExecutor<LogicalPlan> {
}
}
} else {
// add only primitives
// but filter out multi fields (allow only the top-level value)
Set<Attribute> seenMultiFields = new LinkedHashSet<>();
for (Attribute a : output) {
if (!DataTypes.isUnsupported(a.dataType()) && a.dataType().isPrimitive()) {
if (a instanceof FieldAttribute) {
FieldAttribute fa = (FieldAttribute) a;
// skip nested fields and seen multi-fields
if (!fa.isNested() && !seenMultiFields.contains(fa.parent())) {
expanded.add(a);
seenMultiFields.add(a);
}
} else {
expanded.add(a);
}
}
}
expanded.addAll(Expressions.onlyPrimitiveFieldAttributes(output));
}
return expanded;
@ -954,12 +938,24 @@ public class Analyzer extends RuleExecutor<LogicalPlan> {
}
return a;
}
if (plan instanceof Pivot) {
Pivot p = (Pivot) plan;
if (p.childrenResolved()) {
if (hasUnresolvedAliases(p.values())) {
p = new Pivot(p.source(), p.child(), p.column(), assignAliases(p.values()), p.aggregates());
}
if (hasUnresolvedAliases(p.aggregates())) {
p = new Pivot(p.source(), p.child(), p.column(), p.values(), assignAliases(p.aggregates()));
}
}
return p;
}
return plan;
}
private boolean hasUnresolvedAliases(List<? extends NamedExpression> expressions) {
return expressions != null && expressions.stream().anyMatch(e -> e instanceof UnresolvedAlias);
return expressions != null && Expressions.anyMatch(expressions, e -> e instanceof UnresolvedAlias);
}
private List<NamedExpression> assignAliases(List<? extends NamedExpression> exprs) {
@ -1277,13 +1273,20 @@ public class Analyzer extends RuleExecutor<LogicalPlan> {
protected LogicalPlan rule(LogicalPlan plan) {
if (plan instanceof Project) {
Project p = (Project) plan;
return new Project(p.source(), p.child(), cleanSecondaryAliases(p.projections()));
return new Project(p.source(), p.child(), cleanChildrenAliases(p.projections()));
}
if (plan instanceof Aggregate) {
Aggregate a = (Aggregate) plan;
// clean group expressions
return new Aggregate(a.source(), a.child(), cleanAllAliases(a.groupings()), cleanSecondaryAliases(a.aggregates()));
// aliases inside GROUP BY are irellevant so remove all of them
// however aggregations are important (ultimately a projection)
return new Aggregate(a.source(), a.child(), cleanAllAliases(a.groupings()), cleanChildrenAliases(a.aggregates()));
}
if (plan instanceof Pivot) {
Pivot p = (Pivot) plan;
return new Pivot(p.source(), p.child(), trimAliases(p.column()), cleanChildrenAliases(p.values()),
cleanChildrenAliases(p.aggregates()));
}
return plan.transformExpressionsOnly(e -> {
@ -1294,7 +1297,7 @@ public class Analyzer extends RuleExecutor<LogicalPlan> {
});
}
private List<NamedExpression> cleanSecondaryAliases(List<? extends NamedExpression> args) {
private List<NamedExpression> cleanChildrenAliases(List<? extends NamedExpression> args) {
List<NamedExpression> cleaned = new ArrayList<>(args.size());
for (NamedExpression ne : args) {
cleaned.add((NamedExpression) trimNonTopLevelAliases(ne));

View File

@ -13,6 +13,8 @@ import org.elasticsearch.xpack.sql.expression.Exists;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Expressions;
import org.elasticsearch.xpack.sql.expression.FieldAttribute;
import org.elasticsearch.xpack.sql.expression.Literal;
import org.elasticsearch.xpack.sql.expression.NamedExpression;
import org.elasticsearch.xpack.sql.expression.UnresolvedAttribute;
import org.elasticsearch.xpack.sql.expression.function.Function;
import org.elasticsearch.xpack.sql.expression.function.FunctionAttribute;
@ -33,13 +35,16 @@ import org.elasticsearch.xpack.sql.plan.logical.Limit;
import org.elasticsearch.xpack.sql.plan.logical.LocalRelation;
import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
import org.elasticsearch.xpack.sql.plan.logical.OrderBy;
import org.elasticsearch.xpack.sql.plan.logical.Pivot;
import org.elasticsearch.xpack.sql.plan.logical.Project;
import org.elasticsearch.xpack.sql.plan.logical.command.Command;
import org.elasticsearch.xpack.sql.stats.FeatureMetric;
import org.elasticsearch.xpack.sql.stats.Metrics;
import org.elasticsearch.xpack.sql.tree.Node;
import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.type.DataTypes;
import org.elasticsearch.xpack.sql.type.EsField;
import org.elasticsearch.xpack.sql.util.Holder;
import org.elasticsearch.xpack.sql.util.StringUtils;
import java.util.ArrayList;
@ -64,6 +69,7 @@ import static org.elasticsearch.xpack.sql.stats.FeatureMetric.LOCAL;
import static org.elasticsearch.xpack.sql.stats.FeatureMetric.ORDERBY;
import static org.elasticsearch.xpack.sql.stats.FeatureMetric.WHERE;
import static org.elasticsearch.xpack.sql.type.DataType.GEO_SHAPE;
import static org.elasticsearch.xpack.sql.util.CollectionUtils.combine;
/**
* The verifier has the role of checking the analyzed tree for failures and build a list of failures following this check.
@ -237,6 +243,7 @@ public final class Verifier {
checkForScoreInsideFunctions(p, localFailures);
checkNestedUsedInGroupByOrHaving(p, localFailures);
checkForGeoFunctionsOnDocValues(p, localFailures);
checkPivot(p, localFailures);
// everything checks out
// mark the plan as analyzed
@ -464,20 +471,39 @@ public final class Verifier {
private static boolean checkGroupByInexactField(LogicalPlan p, Set<Failure> localFailures) {
if (p instanceof Aggregate) {
Aggregate a = (Aggregate) p;
// The grouping can not be an aggregate function or an inexact field (e.g. text without a keyword)
a.groupings().forEach(e -> e.forEachUp(c -> {
EsField.Exact exact = c.getExactInfo();
if (exact.hasExact() == false) {
localFailures.add(fail(c, "Field [" + c.sourceText() + "] of data type [" + c.dataType().typeName + "] " +
"cannot be used for grouping; " + exact.errorMsg()));
}
}, FieldAttribute.class));
return onlyExactFields(((Aggregate) p).groupings(), localFailures);
}
return true;
}
// The grouping can not be an aggregate function or an inexact field (e.g. text without a keyword)
private static boolean onlyExactFields(List<Expression> expressions, Set<Failure> localFailures) {
Holder<Boolean> onlyExact = new Holder<>(Boolean.TRUE);
expressions.forEach(e -> e.forEachUp(c -> {
EsField.Exact exact = c.getExactInfo();
if (exact.hasExact() == false) {
localFailures.add(fail(c, "Field [{}] of data type [{}] cannot be used for grouping; {}", c.sourceText(),
c.dataType().typeName, exact.errorMsg()));
onlyExact.set(Boolean.FALSE);
}
}, FieldAttribute.class));
return onlyExact.get();
}
private static boolean onlyRawFields(Iterable<? extends Expression> expressions, Set<Failure> localFailures) {
Holder<Boolean> onlyExact = new Holder<>(Boolean.TRUE);
expressions.forEach(e -> e.forEachDown(c -> {
if (c instanceof Function || c instanceof FunctionAttribute) {
localFailures.add(fail(c, "No functions allowed (yet); encountered [{}]", c.sourceText()));
onlyExact.set(Boolean.FALSE);
}
}));
return onlyExact.get();
}
private static boolean checkGroupByTime(LogicalPlan p, Set<Failure> localFailures) {
if (p instanceof Aggregate) {
Aggregate a = (Aggregate) p;
@ -625,8 +651,9 @@ public final class Verifier {
Project proj = (Project) p;
proj.projections().forEach(e -> e.forEachDown(f ->
localFailures.add(fail(f, "[{}] needs to be part of the grouping", Expressions.name(f))), GroupingFunction.class));
} else if (p instanceof Aggregate) {
// if it does have a GROUP BY, check if the groupings contain the grouping functions (Histograms)
}
// if it does have a GROUP BY, check if the groupings contain the grouping functions (Histograms)
else if (p instanceof Aggregate) {
Aggregate a = (Aggregate) p;
a.aggregates().forEach(agg -> agg.forEachDown(e -> {
if (a.groupings().size() == 0
@ -749,4 +776,62 @@ public final class Verifier {
}
}, FieldAttribute.class)), OrderBy.class);
}
}
private static void checkPivot(LogicalPlan p, Set<Failure> localFailures) {
p.forEachDown(pv -> {
// check only exact fields are used inside PIVOTing
if (onlyExactFields(combine(pv.groupingSet(), pv.column()), localFailures) == false
|| onlyRawFields(pv.groupingSet(), localFailures) == false) {
// if that is not the case, no need to do further validation since the declaration is fundamentally wrong
return;
}
// check values
DataType colType = pv.column().dataType();
for (NamedExpression v : pv.values()) {
// check all values are foldable
Expression ex = v instanceof Alias ? ((Alias) v).child() : v;
if (ex instanceof Literal == false) {
localFailures.add(fail(v, "Non-literal [{}] found inside PIVOT values", v.name()));
}
else if (ex.foldable() && ex.fold() == null) {
localFailures.add(fail(v, "Null not allowed as a PIVOT value", v.name()));
}
// and that their type is compatible with that of the column
else if (DataTypes.areTypesCompatible(colType, v.dataType()) == false) {
localFailures.add(fail(v, "Literal [{}] of type [{}] does not match type [{}] of PIVOT column [{}]", v.name(),
v.dataType().typeName, colType.typeName, pv.column().sourceText()));
}
}
// check aggregate function, in particular formulas that might hide literals or scalars
pv.aggregates().forEach(a -> {
Holder<Boolean> hasAggs = new Holder<>(Boolean.FALSE);
List<Expression> aggs = a.collectFirstChildren(c -> {
// skip aggregate functions
if (Functions.isAggregate(c)) {
hasAggs.set(Boolean.TRUE);
return true;
}
if (c.children().isEmpty()) {
return true;
}
return false;
});
if (Boolean.FALSE.equals(hasAggs.get())) {
localFailures.add(fail(a, "No aggregate function found in PIVOT at [{}]", a.sourceText()));
}
// check mixture of Agg and column (wrapped in scalar)
else {
for (Expression agg : aggs) {
if (agg instanceof FieldAttribute) {
localFailures.add(fail(a, "Non-aggregate function found in PIVOT at [{}]", a.sourceText()));
}
}
}
});
}, Pivot.class);
}
}

View File

@ -37,14 +37,14 @@ import java.util.BitSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.BiFunction;
import java.util.function.Supplier;
/**
* Cursor for composite aggregation (GROUP BY).
* Stores the query that gets updated/slides across requests.
*/
public class CompositeAggregationCursor implements Cursor {
public class CompositeAggCursor implements Cursor {
private final Logger log = LogManager.getLogger(getClass());
@ -57,7 +57,7 @@ public class CompositeAggregationCursor implements Cursor {
private final int limit;
private final boolean includeFrozen;
CompositeAggregationCursor(byte[] next, List<BucketExtractor> exts, BitSet mask, int remainingLimit, boolean includeFrozen,
CompositeAggCursor(byte[] next, List<BucketExtractor> exts, BitSet mask, int remainingLimit, boolean includeFrozen,
String... indices) {
this.indices = indices;
this.nextQuery = next;
@ -67,7 +67,7 @@ public class CompositeAggregationCursor implements Cursor {
this.includeFrozen = includeFrozen;
}
public CompositeAggregationCursor(StreamInput in) throws IOException {
public CompositeAggCursor(StreamInput in) throws IOException {
indices = in.readStringArray();
nextQuery = in.readByteArray();
limit = in.readVInt();
@ -86,7 +86,6 @@ public class CompositeAggregationCursor implements Cursor {
out.writeNamedWriteableList(extractors);
out.writeByteArray(mask.toByteArray());
out.writeBoolean(includeFrozen);
}
@Override
@ -133,16 +132,17 @@ public class CompositeAggregationCursor implements Cursor {
log.trace("About to execute composite query {} on {}", StringUtils.toString(query), indices);
}
SearchRequest search = Querier.prepareRequest(client, query, cfg.pageTimeout(), includeFrozen, indices);
SearchRequest request = Querier.prepareRequest(client, query, cfg.pageTimeout(), includeFrozen, indices);
client.search(search, new ActionListener<SearchResponse>() {
client.search(request, new ActionListener<SearchResponse>() {
@Override
public void onResponse(SearchResponse r) {
handle(r, search.source(), ba -> new CompositeAggsRowSet(extractors, mask, r, limit, ba),
() -> client.search(search, this),
p -> listener.onResponse(p),
e -> listener.onFailure(e),
Schema.EMPTY, includeFrozen, indices);
public void onResponse(SearchResponse response) {
handle(response, request.source(),
makeRowSet(response),
makeCursor(),
() -> client.search(request, this),
listener,
Schema.EMPTY);
}
@Override
@ -152,40 +152,55 @@ public class CompositeAggregationCursor implements Cursor {
});
}
static void handle(SearchResponse response, SearchSourceBuilder source, Function<byte[], CompositeAggsRowSet> makeRowSet,
Runnable retry, Consumer<Page> onPage, Consumer<Exception> onFailure,
Schema schema, boolean includeFrozen, String[] indices) {
protected Supplier<CompositeAggRowSet> makeRowSet(SearchResponse response) {
return () -> new CompositeAggRowSet(extractors, mask, response, limit);
}
protected BiFunction<byte[], CompositeAggRowSet, CompositeAggCursor> makeCursor() {
return (q, r) -> new CompositeAggCursor(q, r.extractors(), r.mask(), r.remainingData(), includeFrozen, indices);
}
static void handle(SearchResponse response, SearchSourceBuilder source,
Supplier<CompositeAggRowSet> makeRowSet,
BiFunction<byte[], CompositeAggRowSet, CompositeAggCursor> makeCursor,
Runnable retry,
ActionListener<Page> listener,
Schema schema) {
// there are some results
if (response.getAggregations().asList().isEmpty() == false) {
// retry
if (CompositeAggregationCursor.shouldRetryDueToEmptyPage(response)) {
CompositeAggregationCursor.updateCompositeAfterKey(response, source);
if (shouldRetryDueToEmptyPage(response)) {
updateCompositeAfterKey(response, source);
retry.run();
return;
}
try {
boolean hasAfterKey = updateCompositeAfterKey(response, source);
byte[] queryAsBytes = hasAfterKey ? serializeQuery(source) : null;
CompositeAggsRowSet rowSet = makeRowSet.apply(queryAsBytes);
CompositeAggRowSet rowSet = makeRowSet.get();
Map<String, Object> afterKey = rowSet.afterKey();
byte[] queryAsBytes = null;
if (afterKey != null) {
updateSourceAfterKey(afterKey, source);
queryAsBytes = serializeQuery(source);
}
Cursor next = rowSet.remainingData() == 0
? Cursor.EMPTY
: new CompositeAggregationCursor(queryAsBytes, rowSet.extractors(), rowSet.mask(),
rowSet.remainingData(), includeFrozen, indices);
onPage.accept(new Page(rowSet, next));
: makeCursor.apply(queryAsBytes, rowSet);
listener.onResponse(new Page(rowSet, next));
} catch (Exception ex) {
onFailure.accept(ex);
listener.onFailure(ex);
}
}
// no results
else {
onPage.accept(Page.last(Rows.empty(schema)));
listener.onResponse(Page.last(Rows.empty(schema)));
}
}
static boolean shouldRetryDueToEmptyPage(SearchResponse response) {
private static boolean shouldRetryDueToEmptyPage(SearchResponse response) {
CompositeAggregation composite = getComposite(response);
// if there are no buckets but a next page, go fetch it instead of sending an empty response to the client
return composite != null && composite.getBuckets().isEmpty() && composite.afterKey() != null && !composite.afterKey().isEmpty();
@ -204,25 +219,22 @@ public class CompositeAggregationCursor implements Cursor {
throw new SqlIllegalArgumentException("Unrecognized root group found; {}", agg.getClass());
}
static boolean updateCompositeAfterKey(SearchResponse r, SearchSourceBuilder next) {
private static void updateCompositeAfterKey(SearchResponse r, SearchSourceBuilder search) {
CompositeAggregation composite = getComposite(r);
if (composite == null) {
throw new SqlIllegalArgumentException("Invalid server response; no group-by detected");
}
Map<String, Object> afterKey = composite.afterKey();
// a null after-key means done
if (afterKey == null) {
return false;
updateSourceAfterKey(composite.afterKey(), search);
}
AggregationBuilder aggBuilder = next.aggregations().getAggregatorFactories().iterator().next();
private static void updateSourceAfterKey(Map<String, Object> afterKey, SearchSourceBuilder search) {
AggregationBuilder aggBuilder = search.aggregations().getAggregatorFactories().iterator().next();
// update after-key with the new value
if (aggBuilder instanceof CompositeAggregationBuilder) {
CompositeAggregationBuilder comp = (CompositeAggregationBuilder) aggBuilder;
comp.aggregateAfter(afterKey);
return true;
} else {
throw new SqlIllegalArgumentException("Invalid client request; expected a group-by but instead got {}", aggBuilder);
}
@ -240,7 +252,7 @@ public class CompositeAggregationCursor implements Cursor {
/**
* Serializes the search source to a byte array.
*/
static byte[] serializeQuery(SearchSourceBuilder source) throws IOException {
private static byte[] serializeQuery(SearchSourceBuilder source) throws IOException {
if (source == null) {
return new byte[0];
}
@ -259,7 +271,7 @@ public class CompositeAggregationCursor implements Cursor {
@Override
public int hashCode() {
return Objects.hash(Arrays.hashCode(indices), Arrays.hashCode(nextQuery), extractors, limit);
return Objects.hash(Arrays.hashCode(indices), Arrays.hashCode(nextQuery), extractors, limit, mask, includeFrozen);
}
@Override
@ -267,15 +279,16 @@ public class CompositeAggregationCursor implements Cursor {
if (obj == null || obj.getClass() != getClass()) {
return false;
}
CompositeAggregationCursor other = (CompositeAggregationCursor) obj;
CompositeAggCursor other = (CompositeAggCursor) obj;
return Arrays.equals(indices, other.indices)
&& Arrays.equals(nextQuery, other.nextQuery)
&& Objects.equals(extractors, other.extractors)
&& Objects.equals(limit, other.limit);
&& Objects.equals(limit, other.limit)
&& Objects.equals(includeFrozen, other.includeFrozen);
}
@Override
public String toString() {
return "cursor for composite on index [" + Arrays.toString(indices) + "]";
}
}
}

View File

@ -12,50 +12,50 @@ import org.elasticsearch.xpack.sql.session.RowSet;
import java.util.BitSet;
import java.util.List;
import java.util.Map;
import static java.util.Collections.emptyList;
/**
* {@link RowSet} specific to (GROUP BY) aggregation.
*/
class CompositeAggsRowSet extends ResultRowSet<BucketExtractor> {
class CompositeAggRowSet extends ResultRowSet<BucketExtractor> {
private final List<? extends CompositeAggregation.Bucket> buckets;
private final int remainingData;
private final int size;
private int row = 0;
final List<? extends CompositeAggregation.Bucket> buckets;
CompositeAggsRowSet(List<BucketExtractor> exts, BitSet mask, SearchResponse response, int limit, byte[] next) {
Map<String, Object> afterKey;
int remainingData;
int size;
int row = 0;
CompositeAggRowSet(List<BucketExtractor> exts, BitSet mask, SearchResponse response, int limit) {
super(exts, mask);
CompositeAggregation composite = CompositeAggregationCursor.getComposite(response);
CompositeAggregation composite = CompositeAggCursor.getComposite(response);
if (composite != null) {
buckets = composite.getBuckets();
afterKey = composite.afterKey();
} else {
buckets = emptyList();
afterKey = null;
}
// page size
size = limit == -1 ? buckets.size() : Math.min(buckets.size(), limit);
remainingData = remainingData(afterKey != null, size, limit);
}
if (next == null) {
remainingData = 0;
static int remainingData(boolean hasNextPage, int size, int limit) {
if (hasNextPage == false) {
return 0;
} else {
// Compute remaining limit
// If the limit is -1 then we have a local sorting (sort on aggregate function) that requires all the buckets
// to be processed so we stop only when all data is exhausted.
int remainingLimit = (limit == -1) ? limit : ((limit - size) >= 0 ? (limit - size) : 0);
// if the computed limit is zero, or the size is zero it means either there's nothing left or the limit has been reached
// note that a composite agg might be valid but return zero groups (since these can be filtered with HAVING/bucket selector)
// however the Querier takes care of that and keeps making requests until either the query is invalid or at least one response
// is returned.
if (size == 0 || remainingLimit == 0) {
remainingData = 0;
} else {
remainingData = remainingLimit;
}
return size == 0 ? size : remainingLimit;
}
}
@ -91,4 +91,8 @@ class CompositeAggsRowSet extends ResultRowSet<BucketExtractor> {
int remainingData() {
return remainingData;
}
Map<String, Object> afterKey() {
return afterKey;
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.execution.search;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.sql.execution.search.extractor.BucketExtractor;
import org.elasticsearch.xpack.sql.type.Schema;
import java.io.IOException;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Supplier;
public class PivotCursor extends CompositeAggCursor {
public static final String NAME = "p";
private final Map<String, Object> previousKey;
PivotCursor(Map<String, Object> previousKey, byte[] next, List<BucketExtractor> exts, BitSet mask, int remainingLimit,
boolean includeFrozen,
String... indices) {
super(next, exts, mask, remainingLimit, includeFrozen, indices);
this.previousKey = previousKey;
}
public PivotCursor(StreamInput in) throws IOException {
super(in);
previousKey = in.readBoolean() == true ? in.readMap() : null;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
if (previousKey != null) {
out.writeBoolean(true);
out.writeMap(previousKey);
} else {
out.writeBoolean(false);
}
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
protected Supplier<CompositeAggRowSet> makeRowSet(SearchResponse response) {
return () -> new PivotRowSet(Schema.EMPTY, extractors(), mask(), response, limit(), previousKey);
}
@Override
protected BiFunction<byte[], CompositeAggRowSet, CompositeAggCursor> makeCursor() {
return (q, r) -> {
Map<String, Object> lastAfterKey = r instanceof PivotRowSet ? ((PivotRowSet) r).lastAfterKey() : null;
return new PivotCursor(lastAfterKey, q, r.extractors(), r.mask(), r.remainingData(), includeFrozen(), indices());
};
}
@Override
public String toString() {
return "pivot for index [" + Arrays.toString(indices()) + "]";
}
}

View File

@ -0,0 +1,139 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.execution.search;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.aggregations.bucket.composite.CompositeAggregation;
import org.elasticsearch.xpack.sql.execution.search.extractor.BucketExtractor;
import org.elasticsearch.xpack.sql.type.Schema;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import static java.util.Collections.emptyList;
class PivotRowSet extends SchemaCompositeAggRowSet {
private final List<Object[]> data;
private final Map<String, Object> lastAfterKey;
PivotRowSet(Schema schema, List<BucketExtractor> exts, BitSet mask, SearchResponse response, int limit,
Map<String, Object> previousLastKey) {
super(schema, exts, mask, response, limit);
data = buckets.isEmpty() ? emptyList() : new ArrayList<>();
// the last page contains no data, handle that to avoid NPEs and such
if (buckets.isEmpty()) {
lastAfterKey = null;
return;
}
// consume buckets until all pivot columns are initialized or the next grouping starts
// to determine a group, find all group-by extractors (CompositeKeyExtractor)
// extract their values and keep iterating through the buckets as long as the result is the same
Map<String, Object> currentRowGroupKey = null;
Map<String, Object> lastCompletedGroupKey = null;
Object[] currentRow = new Object[columnCount()];
for (int bucketIndex = 0; bucketIndex < buckets.size(); bucketIndex++) {
CompositeAggregation.Bucket bucket = buckets.get(bucketIndex);
Map<String, Object> key = bucket.getKey();
// does the bucket below to the same group?
if (currentRowGroupKey == null || sameCompositeKey(currentRowGroupKey, key)) {
currentRowGroupKey = key;
}
// done computing row
else {
// be sure to remember the last consumed group before changing to the new one
lastCompletedGroupKey = currentRowGroupKey;
currentRowGroupKey = key;
// save the data
data.add(currentRow);
// create a new row
currentRow = new Object[columnCount()];
}
for (int columnIndex = 0; columnIndex < currentRow.length; columnIndex++) {
BucketExtractor extractor = userExtractor(columnIndex);
Object value = extractor.extract(bucket);
// rerun the bucket through all the extractors but update only the non-null components
// since the pivot extractors will react only when encountering the matching group
if (currentRow[columnIndex] == null && value != null) {
currentRow[columnIndex] = value;
}
}
}
// add the last group if any of the following matches:
// a. the last key has been sent before (it's the last page)
if ((previousLastKey != null && sameCompositeKey(previousLastKey, currentRowGroupKey))) {
data.add(currentRow);
afterKey = null;
}
// b. all the values are initialized (there might be another page but no need to ask for the group again)
// c. or no data was added (typically because there's a null value such as the group)
else if (hasNull(currentRow) == false || data.isEmpty()) {
data.add(currentRow);
afterKey = currentRowGroupKey;
}
//otherwise we can't tell whether it's complete or not
// so discard the last group and ask for it on the next page
else {
afterKey = lastCompletedGroupKey;
}
// lastly initialize the size and remainingData
size = data.size();
remainingData = remainingData(afterKey != null, size, limit);
lastAfterKey = currentRowGroupKey;
}
private boolean hasNull(Object[] currentRow) {
for (Object object : currentRow) {
if (object == null) {
return true;
}
}
return false;
}
// compare the equality of two composite key WITHOUT the last group
// this method relies on the internal map implementation which preserves the key position
// hence why the comparison happens against the current key (not the previous one which might
// have a different order due to serialization)
static boolean sameCompositeKey(Map<String, Object> previous, Map<String, Object> current) {
int keys = current.size() - 1;
int keyIndex = 0;
for (Entry<String, Object> entry : current.entrySet()) {
if (keyIndex++ >= keys) {
return true;
}
if (Objects.equals(entry.getValue(), previous.get(entry.getKey())) == false) {
return false;
}
}
// there's no other key, it's the same group
return true;
}
@Override
protected Object getColumn(int column) {
return data.get(row)[column];
}
Map<String, Object> lastAfterKey() {
return lastAfterKey;
}
}

View File

@ -36,6 +36,7 @@ import org.elasticsearch.xpack.sql.execution.search.extractor.ConstantExtractor;
import org.elasticsearch.xpack.sql.execution.search.extractor.FieldHitExtractor;
import org.elasticsearch.xpack.sql.execution.search.extractor.HitExtractor;
import org.elasticsearch.xpack.sql.execution.search.extractor.MetricAggExtractor;
import org.elasticsearch.xpack.sql.execution.search.extractor.PivotExtractor;
import org.elasticsearch.xpack.sql.execution.search.extractor.TopHitsAggExtractor;
import org.elasticsearch.xpack.sql.expression.Attribute;
import org.elasticsearch.xpack.sql.expression.ExpressionId;
@ -50,6 +51,7 @@ import org.elasticsearch.xpack.sql.querydsl.container.ComputedRef;
import org.elasticsearch.xpack.sql.querydsl.container.GlobalCountRef;
import org.elasticsearch.xpack.sql.querydsl.container.GroupByRef;
import org.elasticsearch.xpack.sql.querydsl.container.MetricAggRef;
import org.elasticsearch.xpack.sql.querydsl.container.PivotColumnRef;
import org.elasticsearch.xpack.sql.querydsl.container.QueryContainer;
import org.elasticsearch.xpack.sql.querydsl.container.ScriptFieldRef;
import org.elasticsearch.xpack.sql.querydsl.container.SearchHitFieldRef;
@ -71,9 +73,12 @@ import java.util.BitSet;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import static java.util.Collections.singletonList;
import static org.elasticsearch.action.ActionListener.wrap;
@ -320,21 +325,39 @@ public class Querier {
*/
static class CompositeActionListener extends BaseAggActionListener {
private final boolean isPivot;
CompositeActionListener(ActionListener<Page> listener, Client client, Configuration cfg,
List<Attribute> output, QueryContainer query, SearchRequest request) {
super(listener, client, cfg, output, query, request);
isPivot = query.fields().stream().anyMatch(t -> t.v1() instanceof PivotColumnRef);
}
@Override
protected void handleResponse(SearchResponse response, ActionListener<Page> listener) {
CompositeAggregationCursor.handle(response, request.source(),
ba -> new SchemaCompositeAggsRowSet(schema, initBucketExtractors(response), mask, response,
query.sortingColumns().isEmpty() ? query.limit() : -1, ba),
Supplier<CompositeAggRowSet> makeRowSet = isPivot ?
() -> new PivotRowSet(schema, initBucketExtractors(response), mask, response,
query.sortingColumns().isEmpty() ? query.limit() : -1, null) :
() -> new SchemaCompositeAggRowSet(schema, initBucketExtractors(response), mask, response,
query.sortingColumns().isEmpty() ? query.limit() : -1);
BiFunction<byte[], CompositeAggRowSet, CompositeAggCursor> makeCursor = isPivot ?
(q, r) -> {
Map<String, Object> lastAfterKey = r instanceof PivotRowSet ? ((PivotRowSet) r).lastAfterKey() : null;
return new PivotCursor(lastAfterKey, q, r.extractors(), r.mask(), r.remainingData(), query.shouldIncludeFrozen(),
request.indices());
} :
(q, r) -> new CompositeAggCursor(q, r.extractors(), r.mask(), r.remainingData, query.shouldIncludeFrozen(),
request.indices());
CompositeAggCursor.handle(response, request.source(),
makeRowSet,
makeCursor,
() -> client.search(request, this),
p -> listener.onResponse(p),
e -> listener.onFailure(e),
schema, query.shouldIncludeFrozen(), request.indices());
listener,
schema);
}
}
@ -380,6 +403,11 @@ public class Querier {
return new TopHitsAggExtractor(r.name(), r.fieldDataType(), cfg.zoneId());
}
if (ref instanceof PivotColumnRef) {
PivotColumnRef r = (PivotColumnRef) ref;
return new PivotExtractor(createExtractor(r.pivot(), totalCount), createExtractor(r.agg(), totalCount), r.value());
}
if (ref == GlobalCountRef.INSTANCE) {
return totalCount;
}

View File

@ -18,12 +18,12 @@ import java.util.List;
* Extension of the {@link RowSet} over a composite agg, extending it to provide its schema.
* Used for the initial response.
*/
class SchemaCompositeAggsRowSet extends CompositeAggsRowSet implements SchemaRowSet {
class SchemaCompositeAggRowSet extends CompositeAggRowSet implements SchemaRowSet {
private final Schema schema;
SchemaCompositeAggsRowSet(Schema schema, List<BucketExtractor> exts, BitSet mask, SearchResponse r, int limitAggs, byte[] next) {
super(exts, mask, r, limitAggs, next);
SchemaCompositeAggRowSet(Schema schema, List<BucketExtractor> exts, BitSet mask, SearchResponse r, int limitAggs) {
super(exts, mask, r, limitAggs);
this.schema = schema;
}

View File

@ -0,0 +1,52 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.execution.search;
import org.elasticsearch.xpack.sql.session.RowSet;
import org.elasticsearch.xpack.sql.session.SchemaRowSet;
import org.elasticsearch.xpack.sql.type.Schema;
class SchemaDelegatingRowSet implements SchemaRowSet {
private final Schema schema;
private final RowSet delegate;
SchemaDelegatingRowSet(Schema schema, RowSet delegate) {
this.schema = schema;
this.delegate = delegate;
}
@Override
public Schema schema() {
return schema;
}
@Override
public boolean hasCurrentRow() {
return delegate.hasCurrentRow();
}
@Override
public boolean advanceRow() {
return delegate.advanceRow();
}
@Override
public int size() {
return delegate.size();
}
@Override
public void reset() {
delegate.reset();
}
@Override
public Object column(int index) {
return delegate.column(index);
}
}

View File

@ -76,6 +76,10 @@ public abstract class SourceGenerator {
// set page size
if (size != null) {
int sz = container.limit() > 0 ? Math.min(container.limit(), size) : size;
// now take into account the the minimum page (if set)
// that is, return the multiple of the minimum page size closer to the set size
int minSize = container.minPageSize();
sz = minSize > 0 ? (Math.max(sz / minSize, 1) * minSize) : sz;
if (source.size() == -1) {
source.size(sz);

View File

@ -26,6 +26,7 @@ public final class BucketExtractors {
entries.add(new Entry(BucketExtractor.class, MetricAggExtractor.NAME, MetricAggExtractor::new));
entries.add(new Entry(BucketExtractor.class, TopHitsAggExtractor.NAME, TopHitsAggExtractor::new));
entries.add(new Entry(BucketExtractor.class, ConstantExtractor.NAME, ConstantExtractor::new));
entries.add(new Entry(BucketExtractor.class, PivotExtractor.NAME, PivotExtractor::new));
return entries;
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.execution.search.extractor;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation.Bucket;
import java.io.IOException;
import java.util.Objects;
public class PivotExtractor implements BucketExtractor {
static final String NAME = "pv";
private final BucketExtractor groupExtractor;
private final BucketExtractor metricExtractor;
private final Object value;
public PivotExtractor(BucketExtractor groupExtractor, BucketExtractor metricExtractor, Object value) {
this.groupExtractor = groupExtractor;
this.metricExtractor = metricExtractor;
this.value = value;
}
PivotExtractor(StreamInput in) throws IOException {
groupExtractor = in.readNamedWriteable(BucketExtractor.class);
metricExtractor = in.readNamedWriteable(BucketExtractor.class);
value = in.readGenericValue();
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeNamedWriteable(groupExtractor);
out.writeNamedWriteable(metricExtractor);
out.writeGenericValue(value);
}
@Override
public Object extract(Bucket bucket) {
if (Objects.equals(value, groupExtractor.extract(bucket))) {
return metricExtractor.extract(bucket);
}
return null;
}
@Override
public int hashCode() {
return Objects.hash(groupExtractor, metricExtractor, value);
}
@Override
public boolean equals(Object obj) {
if (obj == null || obj.getClass() != getClass()) {
return false;
}
PivotExtractor other = (PivotExtractor) obj;
return Objects.equals(groupExtractor, other.groupExtractor)
&& Objects.equals(metricExtractor, other.metricExtractor)
&& Objects.equals(value, other.value);
}
}

View File

@ -108,7 +108,7 @@ public class Alias extends NamedExpression {
Attribute attr = Expressions.attribute(c);
if (attr != null) {
return attr.clone(source(), name(), qualifier, child.nullable(), id(), synthetic());
return attr.clone(source(), name(), child.dataType(), qualifier, child.nullable(), id(), synthetic());
}
else {
// TODO: WE need to fix this fake Field

View File

@ -9,6 +9,7 @@ import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.type.DataType;
import java.util.List;
import java.util.Objects;
@ -87,19 +88,33 @@ public abstract class Attribute extends NamedExpression {
}
public Attribute withLocation(Source source) {
return Objects.equals(source(), source) ? this : clone(source, name(), qualifier(), nullable(), id(), synthetic());
return Objects.equals(source(), source) ? this : clone(source, name(), dataType(), qualifier(), nullable(), id(), synthetic());
}
public Attribute withQualifier(String qualifier) {
return Objects.equals(qualifier(), qualifier) ? this : clone(source(), name(), qualifier, nullable(), id(), synthetic());
return Objects.equals(qualifier(), qualifier) ? this : clone(source(), name(), dataType(), qualifier, nullable(), id(),
synthetic());
}
public Attribute withName(String name) {
return Objects.equals(name(), name) ? this : clone(source(), name, dataType(), qualifier(), nullable(), id(), synthetic());
}
public Attribute withNullability(Nullability nullability) {
return Objects.equals(nullable(), nullability) ? this : clone(source(), name(), qualifier(), nullability, id(), synthetic());
return Objects.equals(nullable(), nullability) ? this : clone(source(), name(), dataType(), qualifier(), nullability, id(),
synthetic());
}
protected abstract Attribute clone(Source source, String name, String qualifier, Nullability nullability, ExpressionId id,
boolean synthetic);
public Attribute withDataType(DataType type) {
return Objects.equals(dataType(), type) ? this : clone(source(), name(), type, qualifier(), nullable(), id(), synthetic());
}
public Attribute withId(ExpressionId id) {
return clone(source(), name(), dataType(), qualifier(), nullable(), id, synthetic());
}
protected abstract Attribute clone(Source source, String name, DataType type, String qualifier, Nullability nullability,
ExpressionId id, boolean synthetic);
@Override
public Attribute toAttribute() {

View File

@ -25,6 +25,10 @@ public class ExpressionId {
this.id = COUNTER.incrementAndGet();
}
public ExpressionId(long id) {
this.id = id;
}
@Override
public int hashCode() {
return Objects.hash(id);

View File

@ -8,10 +8,13 @@ package org.elasticsearch.xpack.sql.expression;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.type.DataTypes;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import static java.util.Collections.emptyList;
@ -134,6 +137,30 @@ public final class Expressions {
return true;
}
public static List<Attribute> onlyPrimitiveFieldAttributes(Collection<Attribute> attributes) {
List<Attribute> filtered = new ArrayList<>();
// add only primitives
// but filter out multi fields (allow only the top-level value)
Set<Attribute> seenMultiFields = new LinkedHashSet<>();
for (Attribute a : attributes) {
if (!DataTypes.isUnsupported(a.dataType()) && a.dataType().isPrimitive()) {
if (a instanceof FieldAttribute) {
FieldAttribute fa = (FieldAttribute) a;
// skip nested fields and seen multi-fields
if (!fa.isNested() && !seenMultiFields.contains(fa.parent())) {
filtered.add(a);
seenMultiFields.add(a);
}
} else {
filtered.add(a);
}
}
}
return filtered;
}
public static Pipe pipe(Expression e) {
if (e instanceof NamedExpression) {
return ((NamedExpression) e).asPipe();

View File

@ -36,10 +36,15 @@ public class FieldAttribute extends TypedAttribute {
public FieldAttribute(Source source, FieldAttribute parent, String name, EsField field) {
this(source, parent, name, field, null, Nullability.TRUE, null, false);
}
public FieldAttribute(Source source, FieldAttribute parent, String name, EsField field, String qualifier, Nullability nullability,
ExpressionId id, boolean synthetic) {
this(source, parent, name, field.getDataType(), field, qualifier, nullability, id, synthetic);
}
public FieldAttribute(Source source, FieldAttribute parent, String name, EsField field, String qualifier,
public FieldAttribute(Source source, FieldAttribute parent, String name, DataType type, EsField field, String qualifier,
Nullability nullability, ExpressionId id, boolean synthetic) {
super(source, name, field.getDataType(), qualifier, nullability, id, synthetic);
super(source, name, type, qualifier, nullability, id, synthetic);
this.path = parent != null ? parent.name() : StringUtils.EMPTY;
this.parent = parent;
this.field = field;
@ -57,7 +62,7 @@ public class FieldAttribute extends TypedAttribute {
@Override
protected NodeInfo<FieldAttribute> info() {
return NodeInfo.create(this, FieldAttribute::new, parent, name(), field, qualifier(), nullable(), id(), synthetic());
return NodeInfo.create(this, FieldAttribute::new, parent, name(), dataType(), field, qualifier(), nullable(), id(), synthetic());
}
public FieldAttribute parent() {
@ -103,8 +108,8 @@ public class FieldAttribute extends TypedAttribute {
}
@Override
protected Attribute clone(Source source, String name, String qualifier, Nullability nullability,
ExpressionId id, boolean synthetic) {
protected Attribute clone(Source source, String name, DataType type, String qualifier,
Nullability nullability, ExpressionId id, boolean synthetic) {
FieldAttribute qualifiedParent = parent != null ? (FieldAttribute) parent.withQualifier(qualifier) : null;
return new FieldAttribute(source, qualifiedParent, name, field, qualifier, nullability, id, synthetic);
}

View File

@ -77,7 +77,7 @@ public class Literal extends NamedExpression {
@Override
public Attribute toAttribute() {
return new LiteralAttribute(source(), name(), null, nullable(), id(), false, dataType, this);
return new LiteralAttribute(source(), name(), dataType, null, nullable(), id(), false, this);
}
@Override

View File

@ -14,8 +14,8 @@ public class LiteralAttribute extends TypedAttribute {
private final Literal literal;
public LiteralAttribute(Source source, String name, String qualifier, Nullability nullability, ExpressionId id, boolean synthetic,
DataType dataType, Literal literal) {
public LiteralAttribute(Source source, String name, DataType dataType, String qualifier, Nullability nullability, ExpressionId id,
boolean synthetic, Literal literal) {
super(source, name, dataType, qualifier, nullability, id, synthetic);
this.literal = literal;
}
@ -23,13 +23,13 @@ public class LiteralAttribute extends TypedAttribute {
@Override
protected NodeInfo<LiteralAttribute> info() {
return NodeInfo.create(this, LiteralAttribute::new,
name(), qualifier(), nullable(), id(), synthetic(), dataType(), literal);
name(), dataType(), qualifier(), nullable(), id(), synthetic(), literal);
}
@Override
protected LiteralAttribute clone(Source source, String name, String qualifier, Nullability nullability,
protected LiteralAttribute clone(Source source, String name, DataType dataType, String qualifier, Nullability nullability,
ExpressionId id, boolean synthetic) {
return new LiteralAttribute(source, name, qualifier, nullability, id, synthetic, dataType(), literal);
return new LiteralAttribute(source, name, dataType, qualifier, nullability, id, synthetic, literal);
}
@Override

View File

@ -65,7 +65,7 @@ public class UnresolvedAttribute extends Attribute implements Unresolvable {
}
@Override
protected Attribute clone(Source source, String name, String qualifier, Nullability nullability,
protected Attribute clone(Source source, String name, DataType dataType, String qualifier, Nullability nullability,
ExpressionId id, boolean synthetic) {
return this;
}

View File

@ -41,9 +41,9 @@ public class ScoreAttribute extends FunctionAttribute {
}
@Override
protected Attribute clone(Source source, String name, String qualifier, Nullability nullability,
protected Attribute clone(Source source, String name, DataType dataType, String qualifier, Nullability nullability,
ExpressionId id, boolean synthetic) {
return new ScoreAttribute(source, name, dataType(), qualifier, nullability, id, synthetic);
return new ScoreAttribute(source, name, dataType, qualifier, nullability, id, synthetic);
}
@Override

View File

@ -28,7 +28,7 @@ import static java.util.Collections.singletonList;
public abstract class AggregateFunction extends Function {
private final Expression field;
private final List<Expression> parameters;
private final List<? extends Expression> parameters;
private AggregateFunctionAttribute lazyAttribute;
@ -36,7 +36,7 @@ public abstract class AggregateFunction extends Function {
this(source, field, emptyList());
}
protected AggregateFunction(Source source, Expression field, List<Expression> parameters) {
protected AggregateFunction(Source source, Expression field, List<? extends Expression> parameters) {
super(source, CollectionUtils.combine(singletonList(field), parameters));
this.field = field;
this.parameters = parameters;
@ -46,7 +46,7 @@ public abstract class AggregateFunction extends Function {
return field;
}
public List<Expression> parameters() {
public List<? extends Expression> parameters() {
return parameters;
}

View File

@ -60,10 +60,11 @@ public class AggregateFunctionAttribute extends FunctionAttribute {
}
@Override
protected Attribute clone(Source source, String name, String qualifier, Nullability nullability, ExpressionId id, boolean synthetic) {
protected Attribute clone(Source source, String name, DataType dataType, String qualifier, Nullability nullability, ExpressionId id,
boolean synthetic) {
// this is highly correlated with QueryFolder$FoldAggregate#addFunction (regarding the function name within the querydsl)
// that is the functionId is actually derived from the expression id to easily track it across contexts
return new AggregateFunctionAttribute(source, name, dataType(), qualifier, nullability, id, synthetic, functionId(), innerId,
return new AggregateFunctionAttribute(source, name, dataType, qualifier, nullability, id, synthetic, functionId(), innerId,
propertyPath);
}

View File

@ -37,11 +37,11 @@ public class GroupingFunctionAttribute extends FunctionAttribute {
}
@Override
protected Attribute clone(Source source, String name, String qualifier, Nullability nullability,
protected Attribute clone(Source source, String name, DataType dataType, String qualifier, Nullability nullability,
ExpressionId id, boolean synthetic) {
// this is highly correlated with QueryFolder$FoldAggregate#addFunction (regarding the function name within the querydsl)
// that is the functionId is actually derived from the expression id to easily track it across contexts
return new GroupingFunctionAttribute(source, name, dataType(), qualifier, nullability, id, synthetic, functionId());
return new GroupingFunctionAttribute(source, name, dataType, qualifier, nullability, id, synthetic, functionId());
}
public GroupingFunctionAttribute withFunctionId(String functionId, String propertyPath) {

View File

@ -66,9 +66,9 @@ public class ScalarFunctionAttribute extends FunctionAttribute {
}
@Override
protected Attribute clone(Source source, String name, String qualifier, Nullability nullability,
protected Attribute clone(Source source, String name, DataType dataType, String qualifier, Nullability nullability,
ExpressionId id, boolean synthetic) {
return new ScalarFunctionAttribute(source, name, dataType(), qualifier, nullability,
return new ScalarFunctionAttribute(source, name, dataType, qualifier, nullability,
id, synthetic, functionId(), script, orderBy, pipe);
}

View File

@ -19,6 +19,7 @@ import org.elasticsearch.xpack.sql.expression.Literal;
import org.elasticsearch.xpack.sql.expression.NamedExpression;
import org.elasticsearch.xpack.sql.expression.Nullability;
import org.elasticsearch.xpack.sql.expression.Order;
import org.elasticsearch.xpack.sql.expression.UnresolvedAttribute;
import org.elasticsearch.xpack.sql.expression.function.Function;
import org.elasticsearch.xpack.sql.expression.function.FunctionAttribute;
import org.elasticsearch.xpack.sql.expression.function.Functions;
@ -72,6 +73,7 @@ import org.elasticsearch.xpack.sql.plan.logical.Limit;
import org.elasticsearch.xpack.sql.plan.logical.LocalRelation;
import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
import org.elasticsearch.xpack.sql.plan.logical.OrderBy;
import org.elasticsearch.xpack.sql.plan.logical.Pivot;
import org.elasticsearch.xpack.sql.plan.logical.Project;
import org.elasticsearch.xpack.sql.plan.logical.SubQueryAlias;
import org.elasticsearch.xpack.sql.plan.logical.UnaryPlan;
@ -96,6 +98,7 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Consumer;
import static java.util.Collections.singletonList;
import static org.elasticsearch.xpack.sql.expression.Expressions.equalsAsAttribute;
import static org.elasticsearch.xpack.sql.expression.Literal.FALSE;
import static org.elasticsearch.xpack.sql.expression.Literal.TRUE;
@ -120,6 +123,9 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
@Override
protected Iterable<RuleExecutor<LogicalPlan>.Batch> batches() {
Batch pivot = new Batch("Pivot Rewrite", Limiter.ONCE,
new RewritePivot());
Batch operators = new Batch("Operator Optimization",
new PruneDuplicatesInGroupBy(),
// combining
@ -170,9 +176,40 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
CleanAliases.INSTANCE,
new SetAsOptimized());
return Arrays.asList(operators, aggregate, local, label);
return Arrays.asList(pivot, operators, aggregate, local, label);
}
static class RewritePivot extends OptimizerRule<Pivot> {
@Override
protected LogicalPlan rule(Pivot plan) {
// 1. add the IN filter
List<Expression> rawValues = new ArrayList<>(plan.values().size());
for (NamedExpression namedExpression : plan.values()) {
// everything should have resolved to an alias
if (namedExpression instanceof Alias) {
rawValues.add(((Alias) namedExpression).child());
}
// TODO: this should be removed when refactoring NamedExpression
else if (namedExpression instanceof Literal) {
rawValues.add(namedExpression);
}
// TODO: NamedExpression refactoring should remove this
else if (namedExpression.foldable()) {
rawValues.add(Literal.of(namedExpression.name(), namedExpression));
}
// TODO: same as above
else {
UnresolvedAttribute attr = new UnresolvedAttribute(namedExpression.source(), namedExpression.name(), null,
"Unexpected alias");
return new Pivot(plan.source(), plan.child(), plan.column(), singletonList(attr), plan.aggregates());
}
}
Filter filter = new Filter(plan.source(), plan.child(), new In(plan.source(), plan.column(), rawValues));
// 2. preserve the PIVOT
return new Pivot(plan.source(), filter, plan.column(), plan.values(), plan.aggregates());
}
}
static class PruneDuplicatesInGroupBy extends OptimizerRule<Aggregate> {
@ -1038,7 +1075,14 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
Aggregate a = (Aggregate) child;
return new Aggregate(a.source(), a.child(), a.groupings(), combineProjections(project.projections(), a.aggregates()));
}
// if the pivot custom columns are not used, convert the project + pivot into a GROUP BY/Aggregate
if (child instanceof Pivot) {
Pivot p = (Pivot) child;
if (project.outputSet().subsetOf(p.groupingSet())) {
return new Aggregate(p.source(), p.child(), new ArrayList<>(project.projections()), project.projections());
}
}
// TODO: add rule for combining Agg/Pivot with underlying project
return project;
}
@ -1172,7 +1216,7 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
return Literal.of(in, null);
}
} else if (e instanceof Alias == false
} else if (e instanceof Alias == false
&& e.nullable() == Nullability.TRUE
&& Expressions.anyMatch(e.children(), Expressions::isNull)) {
return Literal.of(e, null);
@ -1976,7 +2020,8 @@ public class Optimizer extends RuleExecutor<LogicalPlan> {
}
} else if (n.foldable()) {
values.add(n.fold());
} else {
}
else {
// not everything is foldable, bail-out early
return values;
}

View File

@ -8,11 +8,13 @@ package org.elasticsearch.xpack.sql.parser;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.elasticsearch.xpack.sql.expression.Alias;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Literal;
import org.elasticsearch.xpack.sql.expression.NamedExpression;
import org.elasticsearch.xpack.sql.expression.Order;
import org.elasticsearch.xpack.sql.expression.UnresolvedAlias;
import org.elasticsearch.xpack.sql.expression.UnresolvedAttribute;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.AliasedQueryContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.AliasedRelationContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.FromClauseContext;
@ -22,7 +24,10 @@ import org.elasticsearch.xpack.sql.parser.SqlBaseParser.JoinCriteriaContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.JoinRelationContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.LimitClauseContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.NamedQueryContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.NamedValueExpressionContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.OrderByContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.PivotArgsContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.PivotClauseContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.QueryContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.QueryNoWithContext;
import org.elasticsearch.xpack.sql.parser.SqlBaseParser.QuerySpecificationContext;
@ -39,20 +44,22 @@ import org.elasticsearch.xpack.sql.plan.logical.Limit;
import org.elasticsearch.xpack.sql.plan.logical.LocalRelation;
import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
import org.elasticsearch.xpack.sql.plan.logical.OrderBy;
import org.elasticsearch.xpack.sql.plan.logical.Pivot;
import org.elasticsearch.xpack.sql.plan.logical.Project;
import org.elasticsearch.xpack.sql.plan.logical.SubQueryAlias;
import org.elasticsearch.xpack.sql.plan.logical.UnresolvedRelation;
import org.elasticsearch.xpack.sql.plan.logical.With;
import org.elasticsearch.xpack.sql.proto.SqlTypedParamValue;
import org.elasticsearch.xpack.sql.session.SingletonExecutable;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.type.DataType;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;
abstract class LogicalPlanBuilder extends ExpressionBuilder {
@ -119,14 +126,8 @@ abstract class LogicalPlanBuilder extends ExpressionBuilder {
query = new Filter(source(ctx), query, expression(ctx.where));
}
List<NamedExpression> selectTarget = emptyList();
// SELECT a, b, c ...
if (!ctx.selectItem().isEmpty()) {
selectTarget = expressions(ctx.selectItem()).stream()
.map(e -> (e instanceof NamedExpression) ? (NamedExpression) e : new UnresolvedAlias(e.source(), e))
.collect(toList());
}
List<NamedExpression> selectTarget = ctx.selectItems().isEmpty() ? emptyList() : visitList(ctx.selectItems().selectItem(),
NamedExpression.class);
// GROUP BY
GroupByContext groupByCtx = ctx.groupBy();
@ -142,7 +143,7 @@ abstract class LogicalPlanBuilder extends ExpressionBuilder {
query = new Aggregate(source(ctx.GROUP(), endSource), query, groupBy, selectTarget);
}
else if (!selectTarget.isEmpty()) {
query = new Project(source(ctx.selectItem(0)), query, selectTarget);
query = new Project(source(ctx.selectItems()), query, selectTarget);
}
// HAVING
@ -160,9 +161,37 @@ abstract class LogicalPlanBuilder extends ExpressionBuilder {
public LogicalPlan visitFromClause(FromClauseContext ctx) {
// if there are multiple FROM clauses, convert each pair in a inner join
List<LogicalPlan> plans = plans(ctx.relation());
return plans.stream()
LogicalPlan plan = plans.stream()
.reduce((left, right) -> new Join(source(ctx), left, right, Join.JoinType.IMPLICIT, null))
.get();
// PIVOT
if (ctx.pivotClause() != null) {
PivotClauseContext pivotClause = ctx.pivotClause();
UnresolvedAttribute column = new UnresolvedAttribute(source(pivotClause.column), visitQualifiedName(pivotClause.column));
List<NamedExpression> values = namedValues(pivotClause.aggs);
if (values.size() > 1) {
throw new ParsingException(source(pivotClause.aggs), "PIVOT currently supports only one aggregation, found [{}]",
values.size());
}
plan = new Pivot(source(pivotClause), plan, column, namedValues(pivotClause.vals), namedValues(pivotClause.aggs));
}
return plan;
}
private List<NamedExpression> namedValues(PivotArgsContext args) {
if (args == null || args.isEmpty()) {
return emptyList();
}
List<NamedExpression> values = new ArrayList<>();
for (NamedValueExpressionContext value : args.namedValueExpression()) {
Expression exp = expression(value.valueExpression());
String alias = visitIdentifier(value.identifier());
Source source = source(value);
values.add(alias != null ? new Alias(source, alias, exp) : new UnresolvedAlias(source, exp));
}
return values;
}
@Override

View File

@ -311,6 +311,18 @@ class SqlBaseBaseListener implements SqlBaseListener {
* <p>The default implementation does nothing.</p>
*/
@Override public void exitSetQuantifier(SqlBaseParser.SetQuantifierContext ctx) { }
/**
* {@inheritDoc}
*
* <p>The default implementation does nothing.</p>
*/
@Override public void enterSelectItems(SqlBaseParser.SelectItemsContext ctx) { }
/**
* {@inheritDoc}
*
* <p>The default implementation does nothing.</p>
*/
@Override public void exitSelectItems(SqlBaseParser.SelectItemsContext ctx) { }
/**
* {@inheritDoc}
*
@ -407,6 +419,42 @@ class SqlBaseBaseListener implements SqlBaseListener {
* <p>The default implementation does nothing.</p>
*/
@Override public void exitAliasedRelation(SqlBaseParser.AliasedRelationContext ctx) { }
/**
* {@inheritDoc}
*
* <p>The default implementation does nothing.</p>
*/
@Override public void enterPivotClause(SqlBaseParser.PivotClauseContext ctx) { }
/**
* {@inheritDoc}
*
* <p>The default implementation does nothing.</p>
*/
@Override public void exitPivotClause(SqlBaseParser.PivotClauseContext ctx) { }
/**
* {@inheritDoc}
*
* <p>The default implementation does nothing.</p>
*/
@Override public void enterPivotArgs(SqlBaseParser.PivotArgsContext ctx) { }
/**
* {@inheritDoc}
*
* <p>The default implementation does nothing.</p>
*/
@Override public void exitPivotArgs(SqlBaseParser.PivotArgsContext ctx) { }
/**
* {@inheritDoc}
*
* <p>The default implementation does nothing.</p>
*/
@Override public void enterNamedValueExpression(SqlBaseParser.NamedValueExpressionContext ctx) { }
/**
* {@inheritDoc}
*
* <p>The default implementation does nothing.</p>
*/
@Override public void exitNamedValueExpression(SqlBaseParser.NamedValueExpressionContext ctx) { }
/**
* {@inheritDoc}
*

View File

@ -186,6 +186,13 @@ class SqlBaseBaseVisitor<T> extends AbstractParseTreeVisitor<T> implements SqlBa
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitSetQuantifier(SqlBaseParser.SetQuantifierContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitSelectItems(SqlBaseParser.SelectItemsContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
@ -242,6 +249,27 @@ class SqlBaseBaseVisitor<T> extends AbstractParseTreeVisitor<T> implements SqlBa
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitAliasedRelation(SqlBaseParser.AliasedRelationContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitPivotClause(SqlBaseParser.PivotClauseContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitPivotArgs(SqlBaseParser.PivotArgsContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitNamedValueExpression(SqlBaseParser.NamedValueExpressionContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*

View File

@ -22,21 +22,22 @@ class SqlBaseLexer extends Lexer {
COLUMNS=18, CONVERT=19, CURRENT_DATE=20, CURRENT_TIME=21, CURRENT_TIMESTAMP=22,
DAY=23, DAYS=24, DEBUG=25, DESC=26, DESCRIBE=27, DISTINCT=28, ELSE=29,
END=30, ESCAPE=31, EXECUTABLE=32, EXISTS=33, EXPLAIN=34, EXTRACT=35, FALSE=36,
FIRST=37, FORMAT=38, FROM=39, FROZEN=40, FULL=41, FUNCTIONS=42, GRAPHVIZ=43,
GROUP=44, HAVING=45, HOUR=46, HOURS=47, IN=48, INCLUDE=49, INNER=50, INTERVAL=51,
IS=52, JOIN=53, LAST=54, LEFT=55, LIKE=56, LIMIT=57, MAPPED=58, MATCH=59,
MINUTE=60, MINUTES=61, MONTH=62, MONTHS=63, NATURAL=64, NOT=65, NULL=66,
NULLS=67, ON=68, OPTIMIZED=69, OR=70, ORDER=71, OUTER=72, PARSED=73, PHYSICAL=74,
PLAN=75, RIGHT=76, RLIKE=77, QUERY=78, SCHEMAS=79, SECOND=80, SECONDS=81,
SELECT=82, SHOW=83, SYS=84, TABLE=85, TABLES=86, TEXT=87, THEN=88, TRUE=89,
TO=90, TYPE=91, TYPES=92, USING=93, VERIFY=94, WHEN=95, WHERE=96, WITH=97,
YEAR=98, YEARS=99, ESCAPE_ESC=100, FUNCTION_ESC=101, LIMIT_ESC=102, DATE_ESC=103,
TIME_ESC=104, TIMESTAMP_ESC=105, GUID_ESC=106, ESC_END=107, EQ=108, NULLEQ=109,
NEQ=110, LT=111, LTE=112, GT=113, GTE=114, PLUS=115, MINUS=116, ASTERISK=117,
SLASH=118, PERCENT=119, CAST_OP=120, CONCAT=121, DOT=122, PARAM=123, STRING=124,
INTEGER_VALUE=125, DECIMAL_VALUE=126, IDENTIFIER=127, DIGIT_IDENTIFIER=128,
TABLE_IDENTIFIER=129, QUOTED_IDENTIFIER=130, BACKQUOTED_IDENTIFIER=131,
SIMPLE_COMMENT=132, BRACKETED_COMMENT=133, WS=134, UNRECOGNIZED=135;
FIRST=37, FOR=38, FORMAT=39, FROM=40, FROZEN=41, FULL=42, FUNCTIONS=43,
GRAPHVIZ=44, GROUP=45, HAVING=46, HOUR=47, HOURS=48, IN=49, INCLUDE=50,
INNER=51, INTERVAL=52, IS=53, JOIN=54, LAST=55, LEFT=56, LIKE=57, LIMIT=58,
MAPPED=59, MATCH=60, MINUTE=61, MINUTES=62, MONTH=63, MONTHS=64, NATURAL=65,
NOT=66, NULL=67, NULLS=68, ON=69, OPTIMIZED=70, OR=71, ORDER=72, OUTER=73,
PARSED=74, PHYSICAL=75, PIVOT=76, PLAN=77, RIGHT=78, RLIKE=79, QUERY=80,
SCHEMAS=81, SECOND=82, SECONDS=83, SELECT=84, SHOW=85, SYS=86, TABLE=87,
TABLES=88, TEXT=89, THEN=90, TRUE=91, TO=92, TYPE=93, TYPES=94, USING=95,
VERIFY=96, WHEN=97, WHERE=98, WITH=99, YEAR=100, YEARS=101, ESCAPE_ESC=102,
FUNCTION_ESC=103, LIMIT_ESC=104, DATE_ESC=105, TIME_ESC=106, TIMESTAMP_ESC=107,
GUID_ESC=108, ESC_END=109, EQ=110, NULLEQ=111, NEQ=112, LT=113, LTE=114,
GT=115, GTE=116, PLUS=117, MINUS=118, ASTERISK=119, SLASH=120, PERCENT=121,
CAST_OP=122, CONCAT=123, DOT=124, PARAM=125, STRING=126, INTEGER_VALUE=127,
DECIMAL_VALUE=128, IDENTIFIER=129, DIGIT_IDENTIFIER=130, TABLE_IDENTIFIER=131,
QUOTED_IDENTIFIER=132, BACKQUOTED_IDENTIFIER=133, SIMPLE_COMMENT=134,
BRACKETED_COMMENT=135, WS=136, UNRECOGNIZED=137;
public static String[] modeNames = {
"DEFAULT_MODE"
};
@ -46,21 +47,22 @@ class SqlBaseLexer extends Lexer {
"AS", "ASC", "BETWEEN", "BY", "CASE", "CAST", "CATALOG", "CATALOGS", "COLUMNS",
"CONVERT", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "DAY",
"DAYS", "DEBUG", "DESC", "DESCRIBE", "DISTINCT", "ELSE", "END", "ESCAPE",
"EXECUTABLE", "EXISTS", "EXPLAIN", "EXTRACT", "FALSE", "FIRST", "FORMAT",
"FROM", "FROZEN", "FULL", "FUNCTIONS", "GRAPHVIZ", "GROUP", "HAVING",
"HOUR", "HOURS", "IN", "INCLUDE", "INNER", "INTERVAL", "IS", "JOIN", "LAST",
"LEFT", "LIKE", "LIMIT", "MAPPED", "MATCH", "MINUTE", "MINUTES", "MONTH",
"MONTHS", "NATURAL", "NOT", "NULL", "NULLS", "ON", "OPTIMIZED", "OR",
"ORDER", "OUTER", "PARSED", "PHYSICAL", "PLAN", "RIGHT", "RLIKE", "QUERY",
"SCHEMAS", "SECOND", "SECONDS", "SELECT", "SHOW", "SYS", "TABLE", "TABLES",
"TEXT", "THEN", "TRUE", "TO", "TYPE", "TYPES", "USING", "VERIFY", "WHEN",
"WHERE", "WITH", "YEAR", "YEARS", "ESCAPE_ESC", "FUNCTION_ESC", "LIMIT_ESC",
"DATE_ESC", "TIME_ESC", "TIMESTAMP_ESC", "GUID_ESC", "ESC_END", "EQ",
"NULLEQ", "NEQ", "LT", "LTE", "GT", "GTE", "PLUS", "MINUS", "ASTERISK",
"SLASH", "PERCENT", "CAST_OP", "CONCAT", "DOT", "PARAM", "STRING", "INTEGER_VALUE",
"DECIMAL_VALUE", "IDENTIFIER", "DIGIT_IDENTIFIER", "TABLE_IDENTIFIER",
"QUOTED_IDENTIFIER", "BACKQUOTED_IDENTIFIER", "EXPONENT", "DIGIT", "LETTER",
"SIMPLE_COMMENT", "BRACKETED_COMMENT", "WS", "UNRECOGNIZED"
"EXECUTABLE", "EXISTS", "EXPLAIN", "EXTRACT", "FALSE", "FIRST", "FOR",
"FORMAT", "FROM", "FROZEN", "FULL", "FUNCTIONS", "GRAPHVIZ", "GROUP",
"HAVING", "HOUR", "HOURS", "IN", "INCLUDE", "INNER", "INTERVAL", "IS",
"JOIN", "LAST", "LEFT", "LIKE", "LIMIT", "MAPPED", "MATCH", "MINUTE",
"MINUTES", "MONTH", "MONTHS", "NATURAL", "NOT", "NULL", "NULLS", "ON",
"OPTIMIZED", "OR", "ORDER", "OUTER", "PARSED", "PHYSICAL", "PIVOT", "PLAN",
"RIGHT", "RLIKE", "QUERY", "SCHEMAS", "SECOND", "SECONDS", "SELECT", "SHOW",
"SYS", "TABLE", "TABLES", "TEXT", "THEN", "TRUE", "TO", "TYPE", "TYPES",
"USING", "VERIFY", "WHEN", "WHERE", "WITH", "YEAR", "YEARS", "ESCAPE_ESC",
"FUNCTION_ESC", "LIMIT_ESC", "DATE_ESC", "TIME_ESC", "TIMESTAMP_ESC",
"GUID_ESC", "ESC_END", "EQ", "NULLEQ", "NEQ", "LT", "LTE", "GT", "GTE",
"PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", "CAST_OP", "CONCAT",
"DOT", "PARAM", "STRING", "INTEGER_VALUE", "DECIMAL_VALUE", "IDENTIFIER",
"DIGIT_IDENTIFIER", "TABLE_IDENTIFIER", "QUOTED_IDENTIFIER", "BACKQUOTED_IDENTIFIER",
"EXPONENT", "DIGIT", "LETTER", "SIMPLE_COMMENT", "BRACKETED_COMMENT",
"WS", "UNRECOGNIZED"
};
private static final String[] _LITERAL_NAMES = {
@ -69,40 +71,40 @@ class SqlBaseLexer extends Lexer {
"'CATALOG'", "'CATALOGS'", "'COLUMNS'", "'CONVERT'", "'CURRENT_DATE'",
"'CURRENT_TIME'", "'CURRENT_TIMESTAMP'", "'DAY'", "'DAYS'", "'DEBUG'",
"'DESC'", "'DESCRIBE'", "'DISTINCT'", "'ELSE'", "'END'", "'ESCAPE'", "'EXECUTABLE'",
"'EXISTS'", "'EXPLAIN'", "'EXTRACT'", "'FALSE'", "'FIRST'", "'FORMAT'",
"'EXISTS'", "'EXPLAIN'", "'EXTRACT'", "'FALSE'", "'FIRST'", "'FOR'", "'FORMAT'",
"'FROM'", "'FROZEN'", "'FULL'", "'FUNCTIONS'", "'GRAPHVIZ'", "'GROUP'",
"'HAVING'", "'HOUR'", "'HOURS'", "'IN'", "'INCLUDE'", "'INNER'", "'INTERVAL'",
"'IS'", "'JOIN'", "'LAST'", "'LEFT'", "'LIKE'", "'LIMIT'", "'MAPPED'",
"'MATCH'", "'MINUTE'", "'MINUTES'", "'MONTH'", "'MONTHS'", "'NATURAL'",
"'NOT'", "'NULL'", "'NULLS'", "'ON'", "'OPTIMIZED'", "'OR'", "'ORDER'",
"'OUTER'", "'PARSED'", "'PHYSICAL'", "'PLAN'", "'RIGHT'", "'RLIKE'", "'QUERY'",
"'SCHEMAS'", "'SECOND'", "'SECONDS'", "'SELECT'", "'SHOW'", "'SYS'", "'TABLE'",
"'TABLES'", "'TEXT'", "'THEN'", "'TRUE'", "'TO'", "'TYPE'", "'TYPES'",
"'USING'", "'VERIFY'", "'WHEN'", "'WHERE'", "'WITH'", "'YEAR'", "'YEARS'",
"'{ESCAPE'", "'{FN'", "'{LIMIT'", "'{D'", "'{T'", "'{TS'", "'{GUID'",
"'}'", "'='", "'<=>'", null, "'<'", "'<='", "'>'", "'>='", "'+'", "'-'",
"'*'", "'/'", "'%'", "'::'", "'||'", "'.'", "'?'"
"'OUTER'", "'PARSED'", "'PHYSICAL'", "'PIVOT'", "'PLAN'", "'RIGHT'", "'RLIKE'",
"'QUERY'", "'SCHEMAS'", "'SECOND'", "'SECONDS'", "'SELECT'", "'SHOW'",
"'SYS'", "'TABLE'", "'TABLES'", "'TEXT'", "'THEN'", "'TRUE'", "'TO'",
"'TYPE'", "'TYPES'", "'USING'", "'VERIFY'", "'WHEN'", "'WHERE'", "'WITH'",
"'YEAR'", "'YEARS'", "'{ESCAPE'", "'{FN'", "'{LIMIT'", "'{D'", "'{T'",
"'{TS'", "'{GUID'", "'}'", "'='", "'<=>'", null, "'<'", "'<='", "'>'",
"'>='", "'+'", "'-'", "'*'", "'/'", "'%'", "'::'", "'||'", "'.'", "'?'"
};
private static final String[] _SYMBOLIC_NAMES = {
null, null, null, null, null, "ALL", "ANALYZE", "ANALYZED", "AND", "ANY",
"AS", "ASC", "BETWEEN", "BY", "CASE", "CAST", "CATALOG", "CATALOGS", "COLUMNS",
"CONVERT", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "DAY",
"DAYS", "DEBUG", "DESC", "DESCRIBE", "DISTINCT", "ELSE", "END", "ESCAPE",
"EXECUTABLE", "EXISTS", "EXPLAIN", "EXTRACT", "FALSE", "FIRST", "FORMAT",
"FROM", "FROZEN", "FULL", "FUNCTIONS", "GRAPHVIZ", "GROUP", "HAVING",
"HOUR", "HOURS", "IN", "INCLUDE", "INNER", "INTERVAL", "IS", "JOIN", "LAST",
"LEFT", "LIKE", "LIMIT", "MAPPED", "MATCH", "MINUTE", "MINUTES", "MONTH",
"MONTHS", "NATURAL", "NOT", "NULL", "NULLS", "ON", "OPTIMIZED", "OR",
"ORDER", "OUTER", "PARSED", "PHYSICAL", "PLAN", "RIGHT", "RLIKE", "QUERY",
"SCHEMAS", "SECOND", "SECONDS", "SELECT", "SHOW", "SYS", "TABLE", "TABLES",
"TEXT", "THEN", "TRUE", "TO", "TYPE", "TYPES", "USING", "VERIFY", "WHEN",
"WHERE", "WITH", "YEAR", "YEARS", "ESCAPE_ESC", "FUNCTION_ESC", "LIMIT_ESC",
"DATE_ESC", "TIME_ESC", "TIMESTAMP_ESC", "GUID_ESC", "ESC_END", "EQ",
"NULLEQ", "NEQ", "LT", "LTE", "GT", "GTE", "PLUS", "MINUS", "ASTERISK",
"SLASH", "PERCENT", "CAST_OP", "CONCAT", "DOT", "PARAM", "STRING", "INTEGER_VALUE",
"DECIMAL_VALUE", "IDENTIFIER", "DIGIT_IDENTIFIER", "TABLE_IDENTIFIER",
"QUOTED_IDENTIFIER", "BACKQUOTED_IDENTIFIER", "SIMPLE_COMMENT", "BRACKETED_COMMENT",
"WS", "UNRECOGNIZED"
"EXECUTABLE", "EXISTS", "EXPLAIN", "EXTRACT", "FALSE", "FIRST", "FOR",
"FORMAT", "FROM", "FROZEN", "FULL", "FUNCTIONS", "GRAPHVIZ", "GROUP",
"HAVING", "HOUR", "HOURS", "IN", "INCLUDE", "INNER", "INTERVAL", "IS",
"JOIN", "LAST", "LEFT", "LIKE", "LIMIT", "MAPPED", "MATCH", "MINUTE",
"MINUTES", "MONTH", "MONTHS", "NATURAL", "NOT", "NULL", "NULLS", "ON",
"OPTIMIZED", "OR", "ORDER", "OUTER", "PARSED", "PHYSICAL", "PIVOT", "PLAN",
"RIGHT", "RLIKE", "QUERY", "SCHEMAS", "SECOND", "SECONDS", "SELECT", "SHOW",
"SYS", "TABLE", "TABLES", "TEXT", "THEN", "TRUE", "TO", "TYPE", "TYPES",
"USING", "VERIFY", "WHEN", "WHERE", "WITH", "YEAR", "YEARS", "ESCAPE_ESC",
"FUNCTION_ESC", "LIMIT_ESC", "DATE_ESC", "TIME_ESC", "TIMESTAMP_ESC",
"GUID_ESC", "ESC_END", "EQ", "NULLEQ", "NEQ", "LT", "LTE", "GT", "GTE",
"PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", "CAST_OP", "CONCAT",
"DOT", "PARAM", "STRING", "INTEGER_VALUE", "DECIMAL_VALUE", "IDENTIFIER",
"DIGIT_IDENTIFIER", "TABLE_IDENTIFIER", "QUOTED_IDENTIFIER", "BACKQUOTED_IDENTIFIER",
"SIMPLE_COMMENT", "BRACKETED_COMMENT", "WS", "UNRECOGNIZED"
};
public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES);
@ -159,7 +161,7 @@ class SqlBaseLexer extends Lexer {
public ATN getATN() { return _ATN; }
public static final String _serializedATN =
"\3\u0430\ud6d1\u8206\uad2d\u4417\uaef1\u8d80\uaadd\2\u0089\u0471\b\1\4"+
"\3\u0430\ud6d1\u8206\uad2d\u4417\uaef1\u8d80\uaadd\2\u008b\u047f\b\1\4"+
"\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n"+
"\4\13\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22"+
"\t\22\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t\27\4\30\t\30\4\31"+
@ -175,384 +177,391 @@ class SqlBaseLexer extends Lexer {
"\4w\tw\4x\tx\4y\ty\4z\tz\4{\t{\4|\t|\4}\t}\4~\t~\4\177\t\177\4\u0080\t"+
"\u0080\4\u0081\t\u0081\4\u0082\t\u0082\4\u0083\t\u0083\4\u0084\t\u0084"+
"\4\u0085\t\u0085\4\u0086\t\u0086\4\u0087\t\u0087\4\u0088\t\u0088\4\u0089"+
"\t\u0089\4\u008a\t\u008a\4\u008b\t\u008b\3\2\3\2\3\3\3\3\3\4\3\4\3\5\3"+
"\5\3\6\3\6\3\6\3\6\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\b\3\b\3\b\3\b\3\b"+
"\3\b\3\b\3\b\3\b\3\t\3\t\3\t\3\t\3\n\3\n\3\n\3\n\3\13\3\13\3\13\3\f\3"+
"\f\3\f\3\f\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\16\3\16\3\16\3\17\3\17\3"+
"\17\3\17\3\17\3\20\3\20\3\20\3\20\3\20\3\21\3\21\3\21\3\21\3\21\3\21\3"+
"\21\3\21\3\22\3\22\3\22\3\22\3\22\3\22\3\22\3\22\3\22\3\23\3\23\3\23\3"+
"\23\3\23\3\23\3\23\3\23\3\24\3\24\3\24\3\24\3\24\3\24\3\24\3\24\3\25\3"+
"\25\3\25\3\25\3\25\3\25\3\25\3\25\3\25\3\25\3\25\3\25\3\25\3\26\3\26\3"+
"\26\3\26\3\26\3\26\3\26\3\26\3\26\3\26\3\26\3\26\3\26\3\27\3\27\3\27\3"+
"\27\3\27\3\27\3\27\3\27\3\27\3\27\3\27\3\27\3\27\3\27\3\27\3\27\3\27\3"+
"\27\3\30\3\30\3\30\3\30\3\31\3\31\3\31\3\31\3\31\3\32\3\32\3\32\3\32\3"+
"\32\3\32\3\33\3\33\3\33\3\33\3\33\3\34\3\34\3\34\3\34\3\34\3\34\3\34\3"+
"\34\3\34\3\35\3\35\3\35\3\35\3\35\3\35\3\35\3\35\3\35\3\36\3\36\3\36\3"+
"\36\3\36\3\37\3\37\3\37\3\37\3 \3 \3 \3 \3 \3 \3 \3!\3!\3!\3!\3!\3!\3"+
"!\3!\3!\3!\3!\3\"\3\"\3\"\3\"\3\"\3\"\3\"\3#\3#\3#\3#\3#\3#\3#\3#\3$\3"+
"$\3$\3$\3$\3$\3$\3$\3%\3%\3%\3%\3%\3%\3&\3&\3&\3&\3&\3&\3\'\3\'\3\'\3"+
"\'\3\'\3\'\3\'\3(\3(\3(\3(\3(\3)\3)\3)\3)\3)\3)\3)\3*\3*\3*\3*\3*\3+\3"+
"+\3+\3+\3+\3+\3+\3+\3+\3+\3,\3,\3,\3,\3,\3,\3,\3,\3,\3-\3-\3-\3-\3-\3"+
"-\3.\3.\3.\3.\3.\3.\3.\3/\3/\3/\3/\3/\3\60\3\60\3\60\3\60\3\60\3\60\3"+
"\61\3\61\3\61\3\62\3\62\3\62\3\62\3\62\3\62\3\62\3\62\3\63\3\63\3\63\3"+
"\63\3\63\3\63\3\64\3\64\3\64\3\64\3\64\3\64\3\64\3\64\3\64\3\65\3\65\3"+
"\65\3\66\3\66\3\66\3\66\3\66\3\67\3\67\3\67\3\67\3\67\38\38\38\38\38\3"+
"9\39\39\39\39\3:\3:\3:\3:\3:\3:\3;\3;\3;\3;\3;\3;\3;\3<\3<\3<\3<\3<\3"+
"<\3=\3=\3=\3=\3=\3=\3=\3>\3>\3>\3>\3>\3>\3>\3>\3?\3?\3?\3?\3?\3?\3@\3"+
"@\3@\3@\3@\3@\3@\3A\3A\3A\3A\3A\3A\3A\3A\3B\3B\3B\3B\3C\3C\3C\3C\3C\3"+
"D\3D\3D\3D\3D\3D\3E\3E\3E\3F\3F\3F\3F\3F\3F\3F\3F\3F\3F\3G\3G\3G\3H\3"+
"H\3H\3H\3H\3H\3I\3I\3I\3I\3I\3I\3J\3J\3J\3J\3J\3J\3J\3K\3K\3K\3K\3K\3"+
"K\3K\3K\3K\3L\3L\3L\3L\3L\3M\3M\3M\3M\3M\3M\3N\3N\3N\3N\3N\3N\3O\3O\3"+
"O\3O\3O\3O\3P\3P\3P\3P\3P\3P\3P\3P\3Q\3Q\3Q\3Q\3Q\3Q\3Q\3R\3R\3R\3R\3"+
"R\3R\3R\3R\3S\3S\3S\3S\3S\3S\3S\3T\3T\3T\3T\3T\3U\3U\3U\3U\3V\3V\3V\3"+
"V\3V\3V\3W\3W\3W\3W\3W\3W\3W\3X\3X\3X\3X\3X\3Y\3Y\3Y\3Y\3Y\3Z\3Z\3Z\3"+
"Z\3Z\3[\3[\3[\3\\\3\\\3\\\3\\\3\\\3]\3]\3]\3]\3]\3]\3^\3^\3^\3^\3^\3^"+
"\3_\3_\3_\3_\3_\3_\3_\3`\3`\3`\3`\3`\3a\3a\3a\3a\3a\3a\3b\3b\3b\3b\3b"+
"\3c\3c\3c\3c\3c\3d\3d\3d\3d\3d\3d\3e\3e\3e\3e\3e\3e\3e\3e\3f\3f\3f\3f"+
"\3g\3g\3g\3g\3g\3g\3g\3h\3h\3h\3i\3i\3i\3j\3j\3j\3j\3k\3k\3k\3k\3k\3k"+
"\3l\3l\3m\3m\3n\3n\3n\3n\3o\3o\3o\3o\5o\u03af\no\3p\3p\3q\3q\3q\3r\3r"+
"\3s\3s\3s\3t\3t\3u\3u\3v\3v\3w\3w\3x\3x\3y\3y\3y\3z\3z\3z\3{\3{\3|\3|"+
"\3}\3}\3}\3}\7}\u03d3\n}\f}\16}\u03d6\13}\3}\3}\3~\6~\u03db\n~\r~\16~"+
"\u03dc\3\177\6\177\u03e0\n\177\r\177\16\177\u03e1\3\177\3\177\7\177\u03e6"+
"\n\177\f\177\16\177\u03e9\13\177\3\177\3\177\6\177\u03ed\n\177\r\177\16"+
"\177\u03ee\3\177\6\177\u03f2\n\177\r\177\16\177\u03f3\3\177\3\177\7\177"+
"\u03f8\n\177\f\177\16\177\u03fb\13\177\5\177\u03fd\n\177\3\177\3\177\3"+
"\177\3\177\6\177\u0403\n\177\r\177\16\177\u0404\3\177\3\177\5\177\u0409"+
"\n\177\3\u0080\3\u0080\5\u0080\u040d\n\u0080\3\u0080\3\u0080\3\u0080\7"+
"\u0080\u0412\n\u0080\f\u0080\16\u0080\u0415\13\u0080\3\u0081\3\u0081\3"+
"\u0081\3\u0081\6\u0081\u041b\n\u0081\r\u0081\16\u0081\u041c\3\u0082\3"+
"\u0082\3\u0082\6\u0082\u0422\n\u0082\r\u0082\16\u0082\u0423\3\u0083\3"+
"\u0083\3\u0083\3\u0083\7\u0083\u042a\n\u0083\f\u0083\16\u0083\u042d\13"+
"\u0083\3\u0083\3\u0083\3\u0084\3\u0084\3\u0084\3\u0084\7\u0084\u0435\n"+
"\u0084\f\u0084\16\u0084\u0438\13\u0084\3\u0084\3\u0084\3\u0085\3\u0085"+
"\5\u0085\u043e\n\u0085\3\u0085\6\u0085\u0441\n\u0085\r\u0085\16\u0085"+
"\u0442\3\u0086\3\u0086\3\u0087\3\u0087\3\u0088\3\u0088\3\u0088\3\u0088"+
"\7\u0088\u044d\n\u0088\f\u0088\16\u0088\u0450\13\u0088\3\u0088\5\u0088"+
"\u0453\n\u0088\3\u0088\5\u0088\u0456\n\u0088\3\u0088\3\u0088\3\u0089\3"+
"\u0089\3\u0089\3\u0089\3\u0089\7\u0089\u045f\n\u0089\f\u0089\16\u0089"+
"\u0462\13\u0089\3\u0089\3\u0089\3\u0089\3\u0089\3\u0089\3\u008a\6\u008a"+
"\u046a\n\u008a\r\u008a\16\u008a\u046b\3\u008a\3\u008a\3\u008b\3\u008b"+
"\3\u0460\2\u008c\3\3\5\4\7\5\t\6\13\7\r\b\17\t\21\n\23\13\25\f\27\r\31"+
"\16\33\17\35\20\37\21!\22#\23%\24\'\25)\26+\27-\30/\31\61\32\63\33\65"+
"\34\67\359\36;\37= ?!A\"C#E$G%I&K\'M(O)Q*S+U,W-Y.[/]\60_\61a\62c\63e\64"+
"g\65i\66k\67m8o9q:s;u<w=y>{?}@\177A\u0081B\u0083C\u0085D\u0087E\u0089"+
"F\u008bG\u008dH\u008fI\u0091J\u0093K\u0095L\u0097M\u0099N\u009bO\u009d"+
"P\u009fQ\u00a1R\u00a3S\u00a5T\u00a7U\u00a9V\u00abW\u00adX\u00afY\u00b1"+
"Z\u00b3[\u00b5\\\u00b7]\u00b9^\u00bb_\u00bd`\u00bfa\u00c1b\u00c3c\u00c5"+
"d\u00c7e\u00c9f\u00cbg\u00cdh\u00cfi\u00d1j\u00d3k\u00d5l\u00d7m\u00d9"+
"n\u00dbo\u00ddp\u00dfq\u00e1r\u00e3s\u00e5t\u00e7u\u00e9v\u00ebw\u00ed"+
"x\u00efy\u00f1z\u00f3{\u00f5|\u00f7}\u00f9~\u00fb\177\u00fd\u0080\u00ff"+
"\u0081\u0101\u0082\u0103\u0083\u0105\u0084\u0107\u0085\u0109\2\u010b\2"+
"\u010d\2\u010f\u0086\u0111\u0087\u0113\u0088\u0115\u0089\3\2\13\3\2))"+
"\4\2BBaa\3\2$$\3\2bb\4\2--//\3\2\62;\3\2C\\\4\2\f\f\17\17\5\2\13\f\17"+
"\17\"\"\u0491\2\3\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2\2\2\t\3\2\2\2\2\13\3\2"+
"\2\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2\2\2\25\3\2\2\2\2"+
"\27\3\2\2\2\2\31\3\2\2\2\2\33\3\2\2\2\2\35\3\2\2\2\2\37\3\2\2\2\2!\3\2"+
"\2\2\2#\3\2\2\2\2%\3\2\2\2\2\'\3\2\2\2\2)\3\2\2\2\2+\3\2\2\2\2-\3\2\2"+
"\2\2/\3\2\2\2\2\61\3\2\2\2\2\63\3\2\2\2\2\65\3\2\2\2\2\67\3\2\2\2\29\3"+
"\2\2\2\2;\3\2\2\2\2=\3\2\2\2\2?\3\2\2\2\2A\3\2\2\2\2C\3\2\2\2\2E\3\2\2"+
"\2\2G\3\2\2\2\2I\3\2\2\2\2K\3\2\2\2\2M\3\2\2\2\2O\3\2\2\2\2Q\3\2\2\2\2"+
"S\3\2\2\2\2U\3\2\2\2\2W\3\2\2\2\2Y\3\2\2\2\2[\3\2\2\2\2]\3\2\2\2\2_\3"+
"\2\2\2\2a\3\2\2\2\2c\3\2\2\2\2e\3\2\2\2\2g\3\2\2\2\2i\3\2\2\2\2k\3\2\2"+
"\2\2m\3\2\2\2\2o\3\2\2\2\2q\3\2\2\2\2s\3\2\2\2\2u\3\2\2\2\2w\3\2\2\2\2"+
"y\3\2\2\2\2{\3\2\2\2\2}\3\2\2\2\2\177\3\2\2\2\2\u0081\3\2\2\2\2\u0083"+
"\3\2\2\2\2\u0085\3\2\2\2\2\u0087\3\2\2\2\2\u0089\3\2\2\2\2\u008b\3\2\2"+
"\2\2\u008d\3\2\2\2\2\u008f\3\2\2\2\2\u0091\3\2\2\2\2\u0093\3\2\2\2\2\u0095"+
"\3\2\2\2\2\u0097\3\2\2\2\2\u0099\3\2\2\2\2\u009b\3\2\2\2\2\u009d\3\2\2"+
"\2\2\u009f\3\2\2\2\2\u00a1\3\2\2\2\2\u00a3\3\2\2\2\2\u00a5\3\2\2\2\2\u00a7"+
"\3\2\2\2\2\u00a9\3\2\2\2\2\u00ab\3\2\2\2\2\u00ad\3\2\2\2\2\u00af\3\2\2"+
"\2\2\u00b1\3\2\2\2\2\u00b3\3\2\2\2\2\u00b5\3\2\2\2\2\u00b7\3\2\2\2\2\u00b9"+
"\3\2\2\2\2\u00bb\3\2\2\2\2\u00bd\3\2\2\2\2\u00bf\3\2\2\2\2\u00c1\3\2\2"+
"\2\2\u00c3\3\2\2\2\2\u00c5\3\2\2\2\2\u00c7\3\2\2\2\2\u00c9\3\2\2\2\2\u00cb"+
"\3\2\2\2\2\u00cd\3\2\2\2\2\u00cf\3\2\2\2\2\u00d1\3\2\2\2\2\u00d3\3\2\2"+
"\2\2\u00d5\3\2\2\2\2\u00d7\3\2\2\2\2\u00d9\3\2\2\2\2\u00db\3\2\2\2\2\u00dd"+
"\3\2\2\2\2\u00df\3\2\2\2\2\u00e1\3\2\2\2\2\u00e3\3\2\2\2\2\u00e5\3\2\2"+
"\2\2\u00e7\3\2\2\2\2\u00e9\3\2\2\2\2\u00eb\3\2\2\2\2\u00ed\3\2\2\2\2\u00ef"+
"\3\2\2\2\2\u00f1\3\2\2\2\2\u00f3\3\2\2\2\2\u00f5\3\2\2\2\2\u00f7\3\2\2"+
"\2\2\u00f9\3\2\2\2\2\u00fb\3\2\2\2\2\u00fd\3\2\2\2\2\u00ff\3\2\2\2\2\u0101"+
"\3\2\2\2\2\u0103\3\2\2\2\2\u0105\3\2\2\2\2\u0107\3\2\2\2\2\u010f\3\2\2"+
"\2\2\u0111\3\2\2\2\2\u0113\3\2\2\2\2\u0115\3\2\2\2\3\u0117\3\2\2\2\5\u0119"+
"\3\2\2\2\7\u011b\3\2\2\2\t\u011d\3\2\2\2\13\u011f\3\2\2\2\r\u0123\3\2"+
"\2\2\17\u012b\3\2\2\2\21\u0134\3\2\2\2\23\u0138\3\2\2\2\25\u013c\3\2\2"+
"\2\27\u013f\3\2\2\2\31\u0143\3\2\2\2\33\u014b\3\2\2\2\35\u014e\3\2\2\2"+
"\37\u0153\3\2\2\2!\u0158\3\2\2\2#\u0160\3\2\2\2%\u0169\3\2\2\2\'\u0171"+
"\3\2\2\2)\u0179\3\2\2\2+\u0186\3\2\2\2-\u0193\3\2\2\2/\u01a5\3\2\2\2\61"+
"\u01a9\3\2\2\2\63\u01ae\3\2\2\2\65\u01b4\3\2\2\2\67\u01b9\3\2\2\29\u01c2"+
"\3\2\2\2;\u01cb\3\2\2\2=\u01d0\3\2\2\2?\u01d4\3\2\2\2A\u01db\3\2\2\2C"+
"\u01e6\3\2\2\2E\u01ed\3\2\2\2G\u01f5\3\2\2\2I\u01fd\3\2\2\2K\u0203\3\2"+
"\2\2M\u0209\3\2\2\2O\u0210\3\2\2\2Q\u0215\3\2\2\2S\u021c\3\2\2\2U\u0221"+
"\3\2\2\2W\u022b\3\2\2\2Y\u0234\3\2\2\2[\u023a\3\2\2\2]\u0241\3\2\2\2_"+
"\u0246\3\2\2\2a\u024c\3\2\2\2c\u024f\3\2\2\2e\u0257\3\2\2\2g\u025d\3\2"+
"\2\2i\u0266\3\2\2\2k\u0269\3\2\2\2m\u026e\3\2\2\2o\u0273\3\2\2\2q\u0278"+
"\3\2\2\2s\u027d\3\2\2\2u\u0283\3\2\2\2w\u028a\3\2\2\2y\u0290\3\2\2\2{"+
"\u0297\3\2\2\2}\u029f\3\2\2\2\177\u02a5\3\2\2\2\u0081\u02ac\3\2\2\2\u0083"+
"\u02b4\3\2\2\2\u0085\u02b8\3\2\2\2\u0087\u02bd\3\2\2\2\u0089\u02c3\3\2"+
"\2\2\u008b\u02c6\3\2\2\2\u008d\u02d0\3\2\2\2\u008f\u02d3\3\2\2\2\u0091"+
"\u02d9\3\2\2\2\u0093\u02df\3\2\2\2\u0095\u02e6\3\2\2\2\u0097\u02ef\3\2"+
"\2\2\u0099\u02f4\3\2\2\2\u009b\u02fa\3\2\2\2\u009d\u0300\3\2\2\2\u009f"+
"\u0306\3\2\2\2\u00a1\u030e\3\2\2\2\u00a3\u0315\3\2\2\2\u00a5\u031d\3\2"+
"\2\2\u00a7\u0324\3\2\2\2\u00a9\u0329\3\2\2\2\u00ab\u032d\3\2\2\2\u00ad"+
"\u0333\3\2\2\2\u00af\u033a\3\2\2\2\u00b1\u033f\3\2\2\2\u00b3\u0344\3\2"+
"\2\2\u00b5\u0349\3\2\2\2\u00b7\u034c\3\2\2\2\u00b9\u0351\3\2\2\2\u00bb"+
"\u0357\3\2\2\2\u00bd\u035d\3\2\2\2\u00bf\u0364\3\2\2\2\u00c1\u0369\3\2"+
"\2\2\u00c3\u036f\3\2\2\2\u00c5\u0374\3\2\2\2\u00c7\u0379\3\2\2\2\u00c9"+
"\u037f\3\2\2\2\u00cb\u0387\3\2\2\2\u00cd\u038b\3\2\2\2\u00cf\u0392\3\2"+
"\2\2\u00d1\u0395\3\2\2\2\u00d3\u0398\3\2\2\2\u00d5\u039c\3\2\2\2\u00d7"+
"\u03a2\3\2\2\2\u00d9\u03a4\3\2\2\2\u00db\u03a6\3\2\2\2\u00dd\u03ae\3\2"+
"\2\2\u00df\u03b0\3\2\2\2\u00e1\u03b2\3\2\2\2\u00e3\u03b5\3\2\2\2\u00e5"+
"\u03b7\3\2\2\2\u00e7\u03ba\3\2\2\2\u00e9\u03bc\3\2\2\2\u00eb\u03be\3\2"+
"\2\2\u00ed\u03c0\3\2\2\2\u00ef\u03c2\3\2\2\2\u00f1\u03c4\3\2\2\2\u00f3"+
"\u03c7\3\2\2\2\u00f5\u03ca\3\2\2\2\u00f7\u03cc\3\2\2\2\u00f9\u03ce\3\2"+
"\2\2\u00fb\u03da\3\2\2\2\u00fd\u0408\3\2\2\2\u00ff\u040c\3\2\2\2\u0101"+
"\u0416\3\2\2\2\u0103\u0421\3\2\2\2\u0105\u0425\3\2\2\2\u0107\u0430\3\2"+
"\2\2\u0109\u043b\3\2\2\2\u010b\u0444\3\2\2\2\u010d\u0446\3\2\2\2\u010f"+
"\u0448\3\2\2\2\u0111\u0459\3\2\2\2\u0113\u0469\3\2\2\2\u0115\u046f\3\2"+
"\2\2\u0117\u0118\7*\2\2\u0118\4\3\2\2\2\u0119\u011a\7+\2\2\u011a\6\3\2"+
"\2\2\u011b\u011c\7.\2\2\u011c\b\3\2\2\2\u011d\u011e\7<\2\2\u011e\n\3\2"+
"\2\2\u011f\u0120\7C\2\2\u0120\u0121\7N\2\2\u0121\u0122\7N\2\2\u0122\f"+
"\3\2\2\2\u0123\u0124\7C\2\2\u0124\u0125\7P\2\2\u0125\u0126\7C\2\2\u0126"+
"\u0127\7N\2\2\u0127\u0128\7[\2\2\u0128\u0129\7\\\2\2\u0129\u012a\7G\2"+
"\2\u012a\16\3\2\2\2\u012b\u012c\7C\2\2\u012c\u012d\7P\2\2\u012d\u012e"+
"\7C\2\2\u012e\u012f\7N\2\2\u012f\u0130\7[\2\2\u0130\u0131\7\\\2\2\u0131"+
"\u0132\7G\2\2\u0132\u0133\7F\2\2\u0133\20\3\2\2\2\u0134\u0135\7C\2\2\u0135"+
"\u0136\7P\2\2\u0136\u0137\7F\2\2\u0137\22\3\2\2\2\u0138\u0139\7C\2\2\u0139"+
"\u013a\7P\2\2\u013a\u013b\7[\2\2\u013b\24\3\2\2\2\u013c\u013d\7C\2\2\u013d"+
"\u013e\7U\2\2\u013e\26\3\2\2\2\u013f\u0140\7C\2\2\u0140\u0141\7U\2\2\u0141"+
"\u0142\7E\2\2\u0142\30\3\2\2\2\u0143\u0144\7D\2\2\u0144\u0145\7G\2\2\u0145"+
"\u0146\7V\2\2\u0146\u0147\7Y\2\2\u0147\u0148\7G\2\2\u0148\u0149\7G\2\2"+
"\u0149\u014a\7P\2\2\u014a\32\3\2\2\2\u014b\u014c\7D\2\2\u014c\u014d\7"+
"[\2\2\u014d\34\3\2\2\2\u014e\u014f\7E\2\2\u014f\u0150\7C\2\2\u0150\u0151"+
"\7U\2\2\u0151\u0152\7G\2\2\u0152\36\3\2\2\2\u0153\u0154\7E\2\2\u0154\u0155"+
"\7C\2\2\u0155\u0156\7U\2\2\u0156\u0157\7V\2\2\u0157 \3\2\2\2\u0158\u0159"+
"\7E\2\2\u0159\u015a\7C\2\2\u015a\u015b\7V\2\2\u015b\u015c\7C\2\2\u015c"+
"\u015d\7N\2\2\u015d\u015e\7Q\2\2\u015e\u015f\7I\2\2\u015f\"\3\2\2\2\u0160"+
"\u0161\7E\2\2\u0161\u0162\7C\2\2\u0162\u0163\7V\2\2\u0163\u0164\7C\2\2"+
"\u0164\u0165\7N\2\2\u0165\u0166\7Q\2\2\u0166\u0167\7I\2\2\u0167\u0168"+
"\7U\2\2\u0168$\3\2\2\2\u0169\u016a\7E\2\2\u016a\u016b\7Q\2\2\u016b\u016c"+
"\7N\2\2\u016c\u016d\7W\2\2\u016d\u016e\7O\2\2\u016e\u016f\7P\2\2\u016f"+
"\u0170\7U\2\2\u0170&\3\2\2\2\u0171\u0172\7E\2\2\u0172\u0173\7Q\2\2\u0173"+
"\u0174\7P\2\2\u0174\u0175\7X\2\2\u0175\u0176\7G\2\2\u0176\u0177\7T\2\2"+
"\u0177\u0178\7V\2\2\u0178(\3\2\2\2\u0179\u017a\7E\2\2\u017a\u017b\7W\2"+
"\2\u017b\u017c\7T\2\2\u017c\u017d\7T\2\2\u017d\u017e\7G\2\2\u017e\u017f"+
"\7P\2\2\u017f\u0180\7V\2\2\u0180\u0181\7a\2\2\u0181\u0182\7F\2\2\u0182"+
"\u0183\7C\2\2\u0183\u0184\7V\2\2\u0184\u0185\7G\2\2\u0185*\3\2\2\2\u0186"+
"\u0187\7E\2\2\u0187\u0188\7W\2\2\u0188\u0189\7T\2\2\u0189\u018a\7T\2\2"+
"\u018a\u018b\7G\2\2\u018b\u018c\7P\2\2\u018c\u018d\7V\2\2\u018d\u018e"+
"\7a\2\2\u018e\u018f\7V\2\2\u018f\u0190\7K\2\2\u0190\u0191\7O\2\2\u0191"+
"\u0192\7G\2\2\u0192,\3\2\2\2\u0193\u0194\7E\2\2\u0194\u0195\7W\2\2\u0195"+
"\u0196\7T\2\2\u0196\u0197\7T\2\2\u0197\u0198\7G\2\2\u0198\u0199\7P\2\2"+
"\u0199\u019a\7V\2\2\u019a\u019b\7a\2\2\u019b\u019c\7V\2\2\u019c\u019d"+
"\7K\2\2\u019d\u019e\7O\2\2\u019e\u019f\7G\2\2\u019f\u01a0\7U\2\2\u01a0"+
"\u01a1\7V\2\2\u01a1\u01a2\7C\2\2\u01a2\u01a3\7O\2\2\u01a3\u01a4\7R\2\2"+
"\u01a4.\3\2\2\2\u01a5\u01a6\7F\2\2\u01a6\u01a7\7C\2\2\u01a7\u01a8\7[\2"+
"\2\u01a8\60\3\2\2\2\u01a9\u01aa\7F\2\2\u01aa\u01ab\7C\2\2\u01ab\u01ac"+
"\7[\2\2\u01ac\u01ad\7U\2\2\u01ad\62\3\2\2\2\u01ae\u01af\7F\2\2\u01af\u01b0"+
"\7G\2\2\u01b0\u01b1\7D\2\2\u01b1\u01b2\7W\2\2\u01b2\u01b3\7I\2\2\u01b3"+
"\64\3\2\2\2\u01b4\u01b5\7F\2\2\u01b5\u01b6\7G\2\2\u01b6\u01b7\7U\2\2\u01b7"+
"\u01b8\7E\2\2\u01b8\66\3\2\2\2\u01b9\u01ba\7F\2\2\u01ba\u01bb\7G\2\2\u01bb"+
"\u01bc\7U\2\2\u01bc\u01bd\7E\2\2\u01bd\u01be\7T\2\2\u01be\u01bf\7K\2\2"+
"\u01bf\u01c0\7D\2\2\u01c0\u01c1\7G\2\2\u01c18\3\2\2\2\u01c2\u01c3\7F\2"+
"\2\u01c3\u01c4\7K\2\2\u01c4\u01c5\7U\2\2\u01c5\u01c6\7V\2\2\u01c6\u01c7"+
"\7K\2\2\u01c7\u01c8\7P\2\2\u01c8\u01c9\7E\2\2\u01c9\u01ca\7V\2\2\u01ca"+
":\3\2\2\2\u01cb\u01cc\7G\2\2\u01cc\u01cd\7N\2\2\u01cd\u01ce\7U\2\2\u01ce"+
"\u01cf\7G\2\2\u01cf<\3\2\2\2\u01d0\u01d1\7G\2\2\u01d1\u01d2\7P\2\2\u01d2"+
"\u01d3\7F\2\2\u01d3>\3\2\2\2\u01d4\u01d5\7G\2\2\u01d5\u01d6\7U\2\2\u01d6"+
"\u01d7\7E\2\2\u01d7\u01d8\7C\2\2\u01d8\u01d9\7R\2\2\u01d9\u01da\7G\2\2"+
"\u01da@\3\2\2\2\u01db\u01dc\7G\2\2\u01dc\u01dd\7Z\2\2\u01dd\u01de\7G\2"+
"\2\u01de\u01df\7E\2\2\u01df\u01e0\7W\2\2\u01e0\u01e1\7V\2\2\u01e1\u01e2"+
"\7C\2\2\u01e2\u01e3\7D\2\2\u01e3\u01e4\7N\2\2\u01e4\u01e5\7G\2\2\u01e5"+
"B\3\2\2\2\u01e6\u01e7\7G\2\2\u01e7\u01e8\7Z\2\2\u01e8\u01e9\7K\2\2\u01e9"+
"\u01ea\7U\2\2\u01ea\u01eb\7V\2\2\u01eb\u01ec\7U\2\2\u01ecD\3\2\2\2\u01ed"+
"\u01ee\7G\2\2\u01ee\u01ef\7Z\2\2\u01ef\u01f0\7R\2\2\u01f0\u01f1\7N\2\2"+
"\u01f1\u01f2\7C\2\2\u01f2\u01f3\7K\2\2\u01f3\u01f4\7P\2\2\u01f4F\3\2\2"+
"\2\u01f5\u01f6\7G\2\2\u01f6\u01f7\7Z\2\2\u01f7\u01f8\7V\2\2\u01f8\u01f9"+
"\7T\2\2\u01f9\u01fa\7C\2\2\u01fa\u01fb\7E\2\2\u01fb\u01fc\7V\2\2\u01fc"+
"H\3\2\2\2\u01fd\u01fe\7H\2\2\u01fe\u01ff\7C\2\2\u01ff\u0200\7N\2\2\u0200"+
"\u0201\7U\2\2\u0201\u0202\7G\2\2\u0202J\3\2\2\2\u0203\u0204\7H\2\2\u0204"+
"\u0205\7K\2\2\u0205\u0206\7T\2\2\u0206\u0207\7U\2\2\u0207\u0208\7V\2\2"+
"\u0208L\3\2\2\2\u0209\u020a\7H\2\2\u020a\u020b\7Q\2\2\u020b\u020c\7T\2"+
"\2\u020c\u020d\7O\2\2\u020d\u020e\7C\2\2\u020e\u020f\7V\2\2\u020fN\3\2"+
"\2\2\u0210\u0211\7H\2\2\u0211\u0212\7T\2\2\u0212\u0213\7Q\2\2\u0213\u0214"+
"\7O\2\2\u0214P\3\2\2\2\u0215\u0216\7H\2\2\u0216\u0217\7T\2\2\u0217\u0218"+
"\7Q\2\2\u0218\u0219\7\\\2\2\u0219\u021a\7G\2\2\u021a\u021b\7P\2\2\u021b"+
"R\3\2\2\2\u021c\u021d\7H\2\2\u021d\u021e\7W\2\2\u021e\u021f\7N\2\2\u021f"+
"\u0220\7N\2\2\u0220T\3\2\2\2\u0221\u0222\7H\2\2\u0222\u0223\7W\2\2\u0223"+
"\u0224\7P\2\2\u0224\u0225\7E\2\2\u0225\u0226\7V\2\2\u0226\u0227\7K\2\2"+
"\u0227\u0228\7Q\2\2\u0228\u0229\7P\2\2\u0229\u022a\7U\2\2\u022aV\3\2\2"+
"\2\u022b\u022c\7I\2\2\u022c\u022d\7T\2\2\u022d\u022e\7C\2\2\u022e\u022f"+
"\7R\2\2\u022f\u0230\7J\2\2\u0230\u0231\7X\2\2\u0231\u0232\7K\2\2\u0232"+
"\u0233\7\\\2\2\u0233X\3\2\2\2\u0234\u0235\7I\2\2\u0235\u0236\7T\2\2\u0236"+
"\u0237\7Q\2\2\u0237\u0238\7W\2\2\u0238\u0239\7R\2\2\u0239Z\3\2\2\2\u023a"+
"\u023b\7J\2\2\u023b\u023c\7C\2\2\u023c\u023d\7X\2\2\u023d\u023e\7K\2\2"+
"\u023e\u023f\7P\2\2\u023f\u0240\7I\2\2\u0240\\\3\2\2\2\u0241\u0242\7J"+
"\2\2\u0242\u0243\7Q\2\2\u0243\u0244\7W\2\2\u0244\u0245\7T\2\2\u0245^\3"+
"\2\2\2\u0246\u0247\7J\2\2\u0247\u0248\7Q\2\2\u0248\u0249\7W\2\2\u0249"+
"\u024a\7T\2\2\u024a\u024b\7U\2\2\u024b`\3\2\2\2\u024c\u024d\7K\2\2\u024d"+
"\u024e\7P\2\2\u024eb\3\2\2\2\u024f\u0250\7K\2\2\u0250\u0251\7P\2\2\u0251"+
"\u0252\7E\2\2\u0252\u0253\7N\2\2\u0253\u0254\7W\2\2\u0254\u0255\7F\2\2"+
"\u0255\u0256\7G\2\2\u0256d\3\2\2\2\u0257\u0258\7K\2\2\u0258\u0259\7P\2"+
"\2\u0259\u025a\7P\2\2\u025a\u025b\7G\2\2\u025b\u025c\7T\2\2\u025cf\3\2"+
"\2\2\u025d\u025e\7K\2\2\u025e\u025f\7P\2\2\u025f\u0260\7V\2\2\u0260\u0261"+
"\7G\2\2\u0261\u0262\7T\2\2\u0262\u0263\7X\2\2\u0263\u0264\7C\2\2\u0264"+
"\u0265\7N\2\2\u0265h\3\2\2\2\u0266\u0267\7K\2\2\u0267\u0268\7U\2\2\u0268"+
"j\3\2\2\2\u0269\u026a\7L\2\2\u026a\u026b\7Q\2\2\u026b\u026c\7K\2\2\u026c"+
"\u026d\7P\2\2\u026dl\3\2\2\2\u026e\u026f\7N\2\2\u026f\u0270\7C\2\2\u0270"+
"\u0271\7U\2\2\u0271\u0272\7V\2\2\u0272n\3\2\2\2\u0273\u0274\7N\2\2\u0274"+
"\u0275\7G\2\2\u0275\u0276\7H\2\2\u0276\u0277\7V\2\2\u0277p\3\2\2\2\u0278"+
"\u0279\7N\2\2\u0279\u027a\7K\2\2\u027a\u027b\7M\2\2\u027b\u027c\7G\2\2"+
"\u027cr\3\2\2\2\u027d\u027e\7N\2\2\u027e\u027f\7K\2\2\u027f\u0280\7O\2"+
"\2\u0280\u0281\7K\2\2\u0281\u0282\7V\2\2\u0282t\3\2\2\2\u0283\u0284\7"+
"O\2\2\u0284\u0285\7C\2\2\u0285\u0286\7R\2\2\u0286\u0287\7R\2\2\u0287\u0288"+
"\7G\2\2\u0288\u0289\7F\2\2\u0289v\3\2\2\2\u028a\u028b\7O\2\2\u028b\u028c"+
"\7C\2\2\u028c\u028d\7V\2\2\u028d\u028e\7E\2\2\u028e\u028f\7J\2\2\u028f"+
"x\3\2\2\2\u0290\u0291\7O\2\2\u0291\u0292\7K\2\2\u0292\u0293\7P\2\2\u0293"+
"\u0294\7W\2\2\u0294\u0295\7V\2\2\u0295\u0296\7G\2\2\u0296z\3\2\2\2\u0297"+
"\u0298\7O\2\2\u0298\u0299\7K\2\2\u0299\u029a\7P\2\2\u029a\u029b\7W\2\2"+
"\u029b\u029c\7V\2\2\u029c\u029d\7G\2\2\u029d\u029e\7U\2\2\u029e|\3\2\2"+
"\2\u029f\u02a0\7O\2\2\u02a0\u02a1\7Q\2\2\u02a1\u02a2\7P\2\2\u02a2\u02a3"+
"\7V\2\2\u02a3\u02a4\7J\2\2\u02a4~\3\2\2\2\u02a5\u02a6\7O\2\2\u02a6\u02a7"+
"\7Q\2\2\u02a7\u02a8\7P\2\2\u02a8\u02a9\7V\2\2\u02a9\u02aa\7J\2\2\u02aa"+
"\u02ab\7U\2\2\u02ab\u0080\3\2\2\2\u02ac\u02ad\7P\2\2\u02ad\u02ae\7C\2"+
"\2\u02ae\u02af\7V\2\2\u02af\u02b0\7W\2\2\u02b0\u02b1\7T\2\2\u02b1\u02b2"+
"\7C\2\2\u02b2\u02b3\7N\2\2\u02b3\u0082\3\2\2\2\u02b4\u02b5\7P\2\2\u02b5"+
"\u02b6\7Q\2\2\u02b6\u02b7\7V\2\2\u02b7\u0084\3\2\2\2\u02b8\u02b9\7P\2"+
"\2\u02b9\u02ba\7W\2\2\u02ba\u02bb\7N\2\2\u02bb\u02bc\7N\2\2\u02bc\u0086"+
"\3\2\2\2\u02bd\u02be\7P\2\2\u02be\u02bf\7W\2\2\u02bf\u02c0\7N\2\2\u02c0"+
"\u02c1\7N\2\2\u02c1\u02c2\7U\2\2\u02c2\u0088\3\2\2\2\u02c3\u02c4\7Q\2"+
"\2\u02c4\u02c5\7P\2\2\u02c5\u008a\3\2\2\2\u02c6\u02c7\7Q\2\2\u02c7\u02c8"+
"\7R\2\2\u02c8\u02c9\7V\2\2\u02c9\u02ca\7K\2\2\u02ca\u02cb\7O\2\2\u02cb"+
"\u02cc\7K\2\2\u02cc\u02cd\7\\\2\2\u02cd\u02ce\7G\2\2\u02ce\u02cf\7F\2"+
"\2\u02cf\u008c\3\2\2\2\u02d0\u02d1\7Q\2\2\u02d1\u02d2\7T\2\2\u02d2\u008e"+
"\3\2\2\2\u02d3\u02d4\7Q\2\2\u02d4\u02d5\7T\2\2\u02d5\u02d6\7F\2\2\u02d6"+
"\u02d7\7G\2\2\u02d7\u02d8\7T\2\2\u02d8\u0090\3\2\2\2\u02d9\u02da\7Q\2"+
"\2\u02da\u02db\7W\2\2\u02db\u02dc\7V\2\2\u02dc\u02dd\7G\2\2\u02dd\u02de"+
"\7T\2\2\u02de\u0092\3\2\2\2\u02df\u02e0\7R\2\2\u02e0\u02e1\7C\2\2\u02e1"+
"\u02e2\7T\2\2\u02e2\u02e3\7U\2\2\u02e3\u02e4\7G\2\2\u02e4\u02e5\7F\2\2"+
"\u02e5\u0094\3\2\2\2\u02e6\u02e7\7R\2\2\u02e7\u02e8\7J\2\2\u02e8\u02e9"+
"\7[\2\2\u02e9\u02ea\7U\2\2\u02ea\u02eb\7K\2\2\u02eb\u02ec\7E\2\2\u02ec"+
"\u02ed\7C\2\2\u02ed\u02ee\7N\2\2\u02ee\u0096\3\2\2\2\u02ef\u02f0\7R\2"+
"\2\u02f0\u02f1\7N\2\2\u02f1\u02f2\7C\2\2\u02f2\u02f3\7P\2\2\u02f3\u0098"+
"\3\2\2\2\u02f4\u02f5\7T\2\2\u02f5\u02f6\7K\2\2\u02f6\u02f7\7I\2\2\u02f7"+
"\u02f8\7J\2\2\u02f8\u02f9\7V\2\2\u02f9\u009a\3\2\2\2\u02fa\u02fb\7T\2"+
"\2\u02fb\u02fc\7N\2\2\u02fc\u02fd\7K\2\2\u02fd\u02fe\7M\2\2\u02fe\u02ff"+
"\7G\2\2\u02ff\u009c\3\2\2\2\u0300\u0301\7S\2\2\u0301\u0302\7W\2\2\u0302"+
"\u0303\7G\2\2\u0303\u0304\7T\2\2\u0304\u0305\7[\2\2\u0305\u009e\3\2\2"+
"\2\u0306\u0307\7U\2\2\u0307\u0308\7E\2\2\u0308\u0309\7J\2\2\u0309\u030a"+
"\7G\2\2\u030a\u030b\7O\2\2\u030b\u030c\7C\2\2\u030c\u030d\7U\2\2\u030d"+
"\u00a0\3\2\2\2\u030e\u030f\7U\2\2\u030f\u0310\7G\2\2\u0310\u0311\7E\2"+
"\2\u0311\u0312\7Q\2\2\u0312\u0313\7P\2\2\u0313\u0314\7F\2\2\u0314\u00a2"+
"\3\2\2\2\u0315\u0316\7U\2\2\u0316\u0317\7G\2\2\u0317\u0318\7E\2\2\u0318"+
"\u0319\7Q\2\2\u0319\u031a\7P\2\2\u031a\u031b\7F\2\2\u031b\u031c\7U\2\2"+
"\u031c\u00a4\3\2\2\2\u031d\u031e\7U\2\2\u031e\u031f\7G\2\2\u031f\u0320"+
"\7N\2\2\u0320\u0321\7G\2\2\u0321\u0322\7E\2\2\u0322\u0323\7V\2\2\u0323"+
"\u00a6\3\2\2\2\u0324\u0325\7U\2\2\u0325\u0326\7J\2\2\u0326\u0327\7Q\2"+
"\2\u0327\u0328\7Y\2\2\u0328\u00a8\3\2\2\2\u0329\u032a\7U\2\2\u032a\u032b"+
"\7[\2\2\u032b\u032c\7U\2\2\u032c\u00aa\3\2\2\2\u032d\u032e\7V\2\2\u032e"+
"\u032f\7C\2\2\u032f\u0330\7D\2\2\u0330\u0331\7N\2\2\u0331\u0332\7G\2\2"+
"\u0332\u00ac\3\2\2\2\u0333\u0334\7V\2\2\u0334\u0335\7C\2\2\u0335\u0336"+
"\7D\2\2\u0336\u0337\7N\2\2\u0337\u0338\7G\2\2\u0338\u0339\7U\2\2\u0339"+
"\u00ae\3\2\2\2\u033a\u033b\7V\2\2\u033b\u033c\7G\2\2\u033c\u033d\7Z\2"+
"\2\u033d\u033e\7V\2\2\u033e\u00b0\3\2\2\2\u033f\u0340\7V\2\2\u0340\u0341"+
"\7J\2\2\u0341\u0342\7G\2\2\u0342\u0343\7P\2\2\u0343\u00b2\3\2\2\2\u0344"+
"\u0345\7V\2\2\u0345\u0346\7T\2\2\u0346\u0347\7W\2\2\u0347\u0348\7G\2\2"+
"\u0348\u00b4\3\2\2\2\u0349\u034a\7V\2\2\u034a\u034b\7Q\2\2\u034b\u00b6"+
"\3\2\2\2\u034c\u034d\7V\2\2\u034d\u034e\7[\2\2\u034e\u034f\7R\2\2\u034f"+
"\u0350\7G\2\2\u0350\u00b8\3\2\2\2\u0351\u0352\7V\2\2\u0352\u0353\7[\2"+
"\2\u0353\u0354\7R\2\2\u0354\u0355\7G\2\2\u0355\u0356\7U\2\2\u0356\u00ba"+
"\3\2\2\2\u0357\u0358\7W\2\2\u0358\u0359\7U\2\2\u0359\u035a\7K\2\2\u035a"+
"\u035b\7P\2\2\u035b\u035c\7I\2\2\u035c\u00bc\3\2\2\2\u035d\u035e\7X\2"+
"\2\u035e\u035f\7G\2\2\u035f\u0360\7T\2\2\u0360\u0361\7K\2\2\u0361\u0362"+
"\7H\2\2\u0362\u0363\7[\2\2\u0363\u00be\3\2\2\2\u0364\u0365\7Y\2\2\u0365"+
"\u0366\7J\2\2\u0366\u0367\7G\2\2\u0367\u0368\7P\2\2\u0368\u00c0\3\2\2"+
"\2\u0369\u036a\7Y\2\2\u036a\u036b\7J\2\2\u036b\u036c\7G\2\2\u036c\u036d"+
"\7T\2\2\u036d\u036e\7G\2\2\u036e\u00c2\3\2\2\2\u036f\u0370\7Y\2\2\u0370"+
"\u0371\7K\2\2\u0371\u0372\7V\2\2\u0372\u0373\7J\2\2\u0373\u00c4\3\2\2"+
"\2\u0374\u0375\7[\2\2\u0375\u0376\7G\2\2\u0376\u0377\7C\2\2\u0377\u0378"+
"\7T\2\2\u0378\u00c6\3\2\2\2\u0379\u037a\7[\2\2\u037a\u037b\7G\2\2\u037b"+
"\u037c\7C\2\2\u037c\u037d\7T\2\2\u037d\u037e\7U\2\2\u037e\u00c8\3\2\2"+
"\2\u037f\u0380\7}\2\2\u0380\u0381\7G\2\2\u0381\u0382\7U\2\2\u0382\u0383"+
"\7E\2\2\u0383\u0384\7C\2\2\u0384\u0385\7R\2\2\u0385\u0386\7G\2\2\u0386"+
"\u00ca\3\2\2\2\u0387\u0388\7}\2\2\u0388\u0389\7H\2\2\u0389\u038a\7P\2"+
"\2\u038a\u00cc\3\2\2\2\u038b\u038c\7}\2\2\u038c\u038d\7N\2\2\u038d\u038e"+
"\7K\2\2\u038e\u038f\7O\2\2\u038f\u0390\7K\2\2\u0390\u0391\7V\2\2\u0391"+
"\u00ce\3\2\2\2\u0392\u0393\7}\2\2\u0393\u0394\7F\2\2\u0394\u00d0\3\2\2"+
"\2\u0395\u0396\7}\2\2\u0396\u0397\7V\2\2\u0397\u00d2\3\2\2\2\u0398\u0399"+
"\7}\2\2\u0399\u039a\7V\2\2\u039a\u039b\7U\2\2\u039b\u00d4\3\2\2\2\u039c"+
"\u039d\7}\2\2\u039d\u039e\7I\2\2\u039e\u039f\7W\2\2\u039f\u03a0\7K\2\2"+
"\u03a0\u03a1\7F\2\2\u03a1\u00d6\3\2\2\2\u03a2\u03a3\7\177\2\2\u03a3\u00d8"+
"\3\2\2\2\u03a4\u03a5\7?\2\2\u03a5\u00da\3\2\2\2\u03a6\u03a7\7>\2\2\u03a7"+
"\u03a8\7?\2\2\u03a8\u03a9\7@\2\2\u03a9\u00dc\3\2\2\2\u03aa\u03ab\7>\2"+
"\2\u03ab\u03af\7@\2\2\u03ac\u03ad\7#\2\2\u03ad\u03af\7?\2\2\u03ae\u03aa"+
"\3\2\2\2\u03ae\u03ac\3\2\2\2\u03af\u00de\3\2\2\2\u03b0\u03b1\7>\2\2\u03b1"+
"\u00e0\3\2\2\2\u03b2\u03b3\7>\2\2\u03b3\u03b4\7?\2\2\u03b4\u00e2\3\2\2"+
"\2\u03b5\u03b6\7@\2\2\u03b6\u00e4\3\2\2\2\u03b7\u03b8\7@\2\2\u03b8\u03b9"+
"\7?\2\2\u03b9\u00e6\3\2\2\2\u03ba\u03bb\7-\2\2\u03bb\u00e8\3\2\2\2\u03bc"+
"\u03bd\7/\2\2\u03bd\u00ea\3\2\2\2\u03be\u03bf\7,\2\2\u03bf\u00ec\3\2\2"+
"\2\u03c0\u03c1\7\61\2\2\u03c1\u00ee\3\2\2\2\u03c2\u03c3\7\'\2\2\u03c3"+
"\u00f0\3\2\2\2\u03c4\u03c5\7<\2\2\u03c5\u03c6\7<\2\2\u03c6\u00f2\3\2\2"+
"\2\u03c7\u03c8\7~\2\2\u03c8\u03c9\7~\2\2\u03c9\u00f4\3\2\2\2\u03ca\u03cb"+
"\7\60\2\2\u03cb\u00f6\3\2\2\2\u03cc\u03cd\7A\2\2\u03cd\u00f8\3\2\2\2\u03ce"+
"\u03d4\7)\2\2\u03cf\u03d3\n\2\2\2\u03d0\u03d1\7)\2\2\u03d1\u03d3\7)\2"+
"\2\u03d2\u03cf\3\2\2\2\u03d2\u03d0\3\2\2\2\u03d3\u03d6\3\2\2\2\u03d4\u03d2"+
"\3\2\2\2\u03d4\u03d5\3\2\2\2\u03d5\u03d7\3\2\2\2\u03d6\u03d4\3\2\2\2\u03d7"+
"\u03d8\7)\2\2\u03d8\u00fa\3\2\2\2\u03d9\u03db\5\u010b\u0086\2\u03da\u03d9"+
"\3\2\2\2\u03db\u03dc\3\2\2\2\u03dc\u03da\3\2\2\2\u03dc\u03dd\3\2\2\2\u03dd"+
"\u00fc\3\2\2\2\u03de\u03e0\5\u010b\u0086\2\u03df\u03de\3\2\2\2\u03e0\u03e1"+
"\3\2\2\2\u03e1\u03df\3\2\2\2\u03e1\u03e2\3\2\2\2\u03e2\u03e3\3\2\2\2\u03e3"+
"\u03e7\5\u00f5{\2\u03e4\u03e6\5\u010b\u0086\2\u03e5\u03e4\3\2\2\2\u03e6"+
"\u03e9\3\2\2\2\u03e7\u03e5\3\2\2\2\u03e7\u03e8\3\2\2\2\u03e8\u0409\3\2"+
"\2\2\u03e9\u03e7\3\2\2\2\u03ea\u03ec\5\u00f5{\2\u03eb\u03ed\5\u010b\u0086"+
"\2\u03ec\u03eb\3\2\2\2\u03ed\u03ee\3\2\2\2\u03ee\u03ec\3\2\2\2\u03ee\u03ef"+
"\3\2\2\2\u03ef\u0409\3\2\2\2\u03f0\u03f2\5\u010b\u0086\2\u03f1\u03f0\3"+
"\2\2\2\u03f2\u03f3\3\2\2\2\u03f3\u03f1\3\2\2\2\u03f3\u03f4\3\2\2\2\u03f4"+
"\u03fc\3\2\2\2\u03f5\u03f9\5\u00f5{\2\u03f6\u03f8\5\u010b\u0086\2\u03f7"+
"\u03f6\3\2\2\2\u03f8\u03fb\3\2\2\2\u03f9\u03f7\3\2\2\2\u03f9\u03fa\3\2"+
"\2\2\u03fa\u03fd\3\2\2\2\u03fb\u03f9\3\2\2\2\u03fc\u03f5\3\2\2\2\u03fc"+
"\u03fd\3\2\2\2\u03fd\u03fe\3\2\2\2\u03fe\u03ff\5\u0109\u0085\2\u03ff\u0409"+
"\3\2\2\2\u0400\u0402\5\u00f5{\2\u0401\u0403\5\u010b\u0086\2\u0402\u0401"+
"\3\2\2\2\u0403\u0404\3\2\2\2\u0404\u0402\3\2\2\2\u0404\u0405\3\2\2\2\u0405"+
"\u0406\3\2\2\2\u0406\u0407\5\u0109\u0085\2\u0407\u0409\3\2\2\2\u0408\u03df"+
"\3\2\2\2\u0408\u03ea\3\2\2\2\u0408\u03f1\3\2\2\2\u0408\u0400\3\2\2\2\u0409"+
"\u00fe\3\2\2\2\u040a\u040d\5\u010d\u0087\2\u040b\u040d\7a\2\2\u040c\u040a"+
"\3\2\2\2\u040c\u040b\3\2\2\2\u040d\u0413\3\2\2\2\u040e\u0412\5\u010d\u0087"+
"\2\u040f\u0412\5\u010b\u0086\2\u0410\u0412\t\3\2\2\u0411\u040e\3\2\2\2"+
"\u0411\u040f\3\2\2\2\u0411\u0410\3\2\2\2\u0412\u0415\3\2\2\2\u0413\u0411"+
"\3\2\2\2\u0413\u0414\3\2\2\2\u0414\u0100\3\2\2\2\u0415\u0413\3\2\2\2\u0416"+
"\u041a\5\u010b\u0086\2\u0417\u041b\5\u010d\u0087\2\u0418\u041b\5\u010b"+
"\u0086\2\u0419\u041b\t\3\2\2\u041a\u0417\3\2\2\2\u041a\u0418\3\2\2\2\u041a"+
"\u0419\3\2\2\2\u041b\u041c\3\2\2\2\u041c\u041a\3\2\2\2\u041c\u041d\3\2"+
"\2\2\u041d\u0102\3\2\2\2\u041e\u0422\5\u010d\u0087\2\u041f\u0422\5\u010b"+
"\u0086\2\u0420\u0422\7a\2\2\u0421\u041e\3\2\2\2\u0421\u041f\3\2\2\2\u0421"+
"\u0420\3\2\2\2\u0422\u0423\3\2\2\2\u0423\u0421\3\2\2\2\u0423\u0424\3\2"+
"\2\2\u0424\u0104\3\2\2\2\u0425\u042b\7$\2\2\u0426\u042a\n\4\2\2\u0427"+
"\u0428\7$\2\2\u0428\u042a\7$\2\2\u0429\u0426\3\2\2\2\u0429\u0427\3\2\2"+
"\2\u042a\u042d\3\2\2\2\u042b\u0429\3\2\2\2\u042b\u042c\3\2\2\2\u042c\u042e"+
"\3\2\2\2\u042d\u042b\3\2\2\2\u042e\u042f\7$\2\2\u042f\u0106\3\2\2\2\u0430"+
"\u0436\7b\2\2\u0431\u0435\n\5\2\2\u0432\u0433\7b\2\2\u0433\u0435\7b\2"+
"\2\u0434\u0431\3\2\2\2\u0434\u0432\3\2\2\2\u0435\u0438\3\2\2\2\u0436\u0434"+
"\3\2\2\2\u0436\u0437\3\2\2\2\u0437\u0439\3\2\2\2\u0438\u0436\3\2\2\2\u0439"+
"\u043a\7b\2\2\u043a\u0108\3\2\2\2\u043b\u043d\7G\2\2\u043c\u043e\t\6\2"+
"\2\u043d\u043c\3\2\2\2\u043d\u043e\3\2\2\2\u043e\u0440\3\2\2\2\u043f\u0441"+
"\5\u010b\u0086\2\u0440\u043f\3\2\2\2\u0441\u0442\3\2\2\2\u0442\u0440\3"+
"\2\2\2\u0442\u0443\3\2\2\2\u0443\u010a\3\2\2\2\u0444\u0445\t\7\2\2\u0445"+
"\u010c\3\2\2\2\u0446\u0447\t\b\2\2\u0447\u010e\3\2\2\2\u0448\u0449\7/"+
"\2\2\u0449\u044a\7/\2\2\u044a\u044e\3\2\2\2\u044b\u044d\n\t\2\2\u044c"+
"\u044b\3\2\2\2\u044d\u0450\3\2\2\2\u044e\u044c\3\2\2\2\u044e\u044f\3\2"+
"\2\2\u044f\u0452\3\2\2\2\u0450\u044e\3\2\2\2\u0451\u0453\7\17\2\2\u0452"+
"\u0451\3\2\2\2\u0452\u0453\3\2\2\2\u0453\u0455\3\2\2\2\u0454\u0456\7\f"+
"\2\2\u0455\u0454\3\2\2\2\u0455\u0456\3\2\2\2\u0456\u0457\3\2\2\2\u0457"+
"\u0458\b\u0088\2\2\u0458\u0110\3\2\2\2\u0459\u045a\7\61\2\2\u045a\u045b"+
"\7,\2\2\u045b\u0460\3\2\2\2\u045c\u045f\5\u0111\u0089\2\u045d\u045f\13"+
"\2\2\2\u045e\u045c\3\2\2\2\u045e\u045d\3\2\2\2\u045f\u0462\3\2\2\2\u0460"+
"\u0461\3\2\2\2\u0460\u045e\3\2\2\2\u0461\u0463\3\2\2\2\u0462\u0460\3\2"+
"\2\2\u0463\u0464\7,\2\2\u0464\u0465\7\61\2\2\u0465\u0466\3\2\2\2\u0466"+
"\u0467\b\u0089\2\2\u0467\u0112\3\2\2\2\u0468\u046a\t\n\2\2\u0469\u0468"+
"\3\2\2\2\u046a\u046b\3\2\2\2\u046b\u0469\3\2\2\2\u046b\u046c\3\2\2\2\u046c"+
"\u046d\3\2\2\2\u046d\u046e\b\u008a\2\2\u046e\u0114\3\2\2\2\u046f\u0470"+
"\13\2\2\2\u0470\u0116\3\2\2\2\"\2\u03ae\u03d2\u03d4\u03dc\u03e1\u03e7"+
"\u03ee\u03f3\u03f9\u03fc\u0404\u0408\u040c\u0411\u0413\u041a\u041c\u0421"+
"\u0423\u0429\u042b\u0434\u0436\u043d\u0442\u044e\u0452\u0455\u045e\u0460"+
"\u046b\3\2\3\2";
"\t\u0089\4\u008a\t\u008a\4\u008b\t\u008b\4\u008c\t\u008c\4\u008d\t\u008d"+
"\3\2\3\2\3\3\3\3\3\4\3\4\3\5\3\5\3\6\3\6\3\6\3\6\3\7\3\7\3\7\3\7\3\7\3"+
"\7\3\7\3\7\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\t\3\t\3\t\3\t\3\n\3\n"+
"\3\n\3\n\3\13\3\13\3\13\3\f\3\f\3\f\3\f\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3"+
"\r\3\16\3\16\3\16\3\17\3\17\3\17\3\17\3\17\3\20\3\20\3\20\3\20\3\20\3"+
"\21\3\21\3\21\3\21\3\21\3\21\3\21\3\21\3\22\3\22\3\22\3\22\3\22\3\22\3"+
"\22\3\22\3\22\3\23\3\23\3\23\3\23\3\23\3\23\3\23\3\23\3\24\3\24\3\24\3"+
"\24\3\24\3\24\3\24\3\24\3\25\3\25\3\25\3\25\3\25\3\25\3\25\3\25\3\25\3"+
"\25\3\25\3\25\3\25\3\26\3\26\3\26\3\26\3\26\3\26\3\26\3\26\3\26\3\26\3"+
"\26\3\26\3\26\3\27\3\27\3\27\3\27\3\27\3\27\3\27\3\27\3\27\3\27\3\27\3"+
"\27\3\27\3\27\3\27\3\27\3\27\3\27\3\30\3\30\3\30\3\30\3\31\3\31\3\31\3"+
"\31\3\31\3\32\3\32\3\32\3\32\3\32\3\32\3\33\3\33\3\33\3\33\3\33\3\34\3"+
"\34\3\34\3\34\3\34\3\34\3\34\3\34\3\34\3\35\3\35\3\35\3\35\3\35\3\35\3"+
"\35\3\35\3\35\3\36\3\36\3\36\3\36\3\36\3\37\3\37\3\37\3\37\3 \3 \3 \3"+
" \3 \3 \3 \3!\3!\3!\3!\3!\3!\3!\3!\3!\3!\3!\3\"\3\"\3\"\3\"\3\"\3\"\3"+
"\"\3#\3#\3#\3#\3#\3#\3#\3#\3$\3$\3$\3$\3$\3$\3$\3$\3%\3%\3%\3%\3%\3%\3"+
"&\3&\3&\3&\3&\3&\3\'\3\'\3\'\3\'\3(\3(\3(\3(\3(\3(\3(\3)\3)\3)\3)\3)\3"+
"*\3*\3*\3*\3*\3*\3*\3+\3+\3+\3+\3+\3,\3,\3,\3,\3,\3,\3,\3,\3,\3,\3-\3"+
"-\3-\3-\3-\3-\3-\3-\3-\3.\3.\3.\3.\3.\3.\3/\3/\3/\3/\3/\3/\3/\3\60\3\60"+
"\3\60\3\60\3\60\3\61\3\61\3\61\3\61\3\61\3\61\3\62\3\62\3\62\3\63\3\63"+
"\3\63\3\63\3\63\3\63\3\63\3\63\3\64\3\64\3\64\3\64\3\64\3\64\3\65\3\65"+
"\3\65\3\65\3\65\3\65\3\65\3\65\3\65\3\66\3\66\3\66\3\67\3\67\3\67\3\67"+
"\3\67\38\38\38\38\38\39\39\39\39\39\3:\3:\3:\3:\3:\3;\3;\3;\3;\3;\3;\3"+
"<\3<\3<\3<\3<\3<\3<\3=\3=\3=\3=\3=\3=\3>\3>\3>\3>\3>\3>\3>\3?\3?\3?\3"+
"?\3?\3?\3?\3?\3@\3@\3@\3@\3@\3@\3A\3A\3A\3A\3A\3A\3A\3B\3B\3B\3B\3B\3"+
"B\3B\3B\3C\3C\3C\3C\3D\3D\3D\3D\3D\3E\3E\3E\3E\3E\3E\3F\3F\3F\3G\3G\3"+
"G\3G\3G\3G\3G\3G\3G\3G\3H\3H\3H\3I\3I\3I\3I\3I\3I\3J\3J\3J\3J\3J\3J\3"+
"K\3K\3K\3K\3K\3K\3K\3L\3L\3L\3L\3L\3L\3L\3L\3L\3M\3M\3M\3M\3M\3M\3N\3"+
"N\3N\3N\3N\3O\3O\3O\3O\3O\3O\3P\3P\3P\3P\3P\3P\3Q\3Q\3Q\3Q\3Q\3Q\3R\3"+
"R\3R\3R\3R\3R\3R\3R\3S\3S\3S\3S\3S\3S\3S\3T\3T\3T\3T\3T\3T\3T\3T\3U\3"+
"U\3U\3U\3U\3U\3U\3V\3V\3V\3V\3V\3W\3W\3W\3W\3X\3X\3X\3X\3X\3X\3Y\3Y\3"+
"Y\3Y\3Y\3Y\3Y\3Z\3Z\3Z\3Z\3Z\3[\3[\3[\3[\3[\3\\\3\\\3\\\3\\\3\\\3]\3]"+
"\3]\3^\3^\3^\3^\3^\3_\3_\3_\3_\3_\3_\3`\3`\3`\3`\3`\3`\3a\3a\3a\3a\3a"+
"\3a\3a\3b\3b\3b\3b\3b\3c\3c\3c\3c\3c\3c\3d\3d\3d\3d\3d\3e\3e\3e\3e\3e"+
"\3f\3f\3f\3f\3f\3f\3g\3g\3g\3g\3g\3g\3g\3g\3h\3h\3h\3h\3i\3i\3i\3i\3i"+
"\3i\3i\3j\3j\3j\3k\3k\3k\3l\3l\3l\3l\3m\3m\3m\3m\3m\3m\3n\3n\3o\3o\3p"+
"\3p\3p\3p\3q\3q\3q\3q\5q\u03bd\nq\3r\3r\3s\3s\3s\3t\3t\3u\3u\3u\3v\3v"+
"\3w\3w\3x\3x\3y\3y\3z\3z\3{\3{\3{\3|\3|\3|\3}\3}\3~\3~\3\177\3\177\3\177"+
"\3\177\7\177\u03e1\n\177\f\177\16\177\u03e4\13\177\3\177\3\177\3\u0080"+
"\6\u0080\u03e9\n\u0080\r\u0080\16\u0080\u03ea\3\u0081\6\u0081\u03ee\n"+
"\u0081\r\u0081\16\u0081\u03ef\3\u0081\3\u0081\7\u0081\u03f4\n\u0081\f"+
"\u0081\16\u0081\u03f7\13\u0081\3\u0081\3\u0081\6\u0081\u03fb\n\u0081\r"+
"\u0081\16\u0081\u03fc\3\u0081\6\u0081\u0400\n\u0081\r\u0081\16\u0081\u0401"+
"\3\u0081\3\u0081\7\u0081\u0406\n\u0081\f\u0081\16\u0081\u0409\13\u0081"+
"\5\u0081\u040b\n\u0081\3\u0081\3\u0081\3\u0081\3\u0081\6\u0081\u0411\n"+
"\u0081\r\u0081\16\u0081\u0412\3\u0081\3\u0081\5\u0081\u0417\n\u0081\3"+
"\u0082\3\u0082\5\u0082\u041b\n\u0082\3\u0082\3\u0082\3\u0082\7\u0082\u0420"+
"\n\u0082\f\u0082\16\u0082\u0423\13\u0082\3\u0083\3\u0083\3\u0083\3\u0083"+
"\6\u0083\u0429\n\u0083\r\u0083\16\u0083\u042a\3\u0084\3\u0084\3\u0084"+
"\6\u0084\u0430\n\u0084\r\u0084\16\u0084\u0431\3\u0085\3\u0085\3\u0085"+
"\3\u0085\7\u0085\u0438\n\u0085\f\u0085\16\u0085\u043b\13\u0085\3\u0085"+
"\3\u0085\3\u0086\3\u0086\3\u0086\3\u0086\7\u0086\u0443\n\u0086\f\u0086"+
"\16\u0086\u0446\13\u0086\3\u0086\3\u0086\3\u0087\3\u0087\5\u0087\u044c"+
"\n\u0087\3\u0087\6\u0087\u044f\n\u0087\r\u0087\16\u0087\u0450\3\u0088"+
"\3\u0088\3\u0089\3\u0089\3\u008a\3\u008a\3\u008a\3\u008a\7\u008a\u045b"+
"\n\u008a\f\u008a\16\u008a\u045e\13\u008a\3\u008a\5\u008a\u0461\n\u008a"+
"\3\u008a\5\u008a\u0464\n\u008a\3\u008a\3\u008a\3\u008b\3\u008b\3\u008b"+
"\3\u008b\3\u008b\7\u008b\u046d\n\u008b\f\u008b\16\u008b\u0470\13\u008b"+
"\3\u008b\3\u008b\3\u008b\3\u008b\3\u008b\3\u008c\6\u008c\u0478\n\u008c"+
"\r\u008c\16\u008c\u0479\3\u008c\3\u008c\3\u008d\3\u008d\3\u046e\2\u008e"+
"\3\3\5\4\7\5\t\6\13\7\r\b\17\t\21\n\23\13\25\f\27\r\31\16\33\17\35\20"+
"\37\21!\22#\23%\24\'\25)\26+\27-\30/\31\61\32\63\33\65\34\67\359\36;\37"+
"= ?!A\"C#E$G%I&K\'M(O)Q*S+U,W-Y.[/]\60_\61a\62c\63e\64g\65i\66k\67m8o"+
"9q:s;u<w=y>{?}@\177A\u0081B\u0083C\u0085D\u0087E\u0089F\u008bG\u008dH"+
"\u008fI\u0091J\u0093K\u0095L\u0097M\u0099N\u009bO\u009dP\u009fQ\u00a1"+
"R\u00a3S\u00a5T\u00a7U\u00a9V\u00abW\u00adX\u00afY\u00b1Z\u00b3[\u00b5"+
"\\\u00b7]\u00b9^\u00bb_\u00bd`\u00bfa\u00c1b\u00c3c\u00c5d\u00c7e\u00c9"+
"f\u00cbg\u00cdh\u00cfi\u00d1j\u00d3k\u00d5l\u00d7m\u00d9n\u00dbo\u00dd"+
"p\u00dfq\u00e1r\u00e3s\u00e5t\u00e7u\u00e9v\u00ebw\u00edx\u00efy\u00f1"+
"z\u00f3{\u00f5|\u00f7}\u00f9~\u00fb\177\u00fd\u0080\u00ff\u0081\u0101"+
"\u0082\u0103\u0083\u0105\u0084\u0107\u0085\u0109\u0086\u010b\u0087\u010d"+
"\2\u010f\2\u0111\2\u0113\u0088\u0115\u0089\u0117\u008a\u0119\u008b\3\2"+
"\13\3\2))\4\2BBaa\3\2$$\3\2bb\4\2--//\3\2\62;\3\2C\\\4\2\f\f\17\17\5\2"+
"\13\f\17\17\"\"\u049f\2\3\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2\2\2\t\3\2\2\2"+
"\2\13\3\2\2\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2\2\2\25"+
"\3\2\2\2\2\27\3\2\2\2\2\31\3\2\2\2\2\33\3\2\2\2\2\35\3\2\2\2\2\37\3\2"+
"\2\2\2!\3\2\2\2\2#\3\2\2\2\2%\3\2\2\2\2\'\3\2\2\2\2)\3\2\2\2\2+\3\2\2"+
"\2\2-\3\2\2\2\2/\3\2\2\2\2\61\3\2\2\2\2\63\3\2\2\2\2\65\3\2\2\2\2\67\3"+
"\2\2\2\29\3\2\2\2\2;\3\2\2\2\2=\3\2\2\2\2?\3\2\2\2\2A\3\2\2\2\2C\3\2\2"+
"\2\2E\3\2\2\2\2G\3\2\2\2\2I\3\2\2\2\2K\3\2\2\2\2M\3\2\2\2\2O\3\2\2\2\2"+
"Q\3\2\2\2\2S\3\2\2\2\2U\3\2\2\2\2W\3\2\2\2\2Y\3\2\2\2\2[\3\2\2\2\2]\3"+
"\2\2\2\2_\3\2\2\2\2a\3\2\2\2\2c\3\2\2\2\2e\3\2\2\2\2g\3\2\2\2\2i\3\2\2"+
"\2\2k\3\2\2\2\2m\3\2\2\2\2o\3\2\2\2\2q\3\2\2\2\2s\3\2\2\2\2u\3\2\2\2\2"+
"w\3\2\2\2\2y\3\2\2\2\2{\3\2\2\2\2}\3\2\2\2\2\177\3\2\2\2\2\u0081\3\2\2"+
"\2\2\u0083\3\2\2\2\2\u0085\3\2\2\2\2\u0087\3\2\2\2\2\u0089\3\2\2\2\2\u008b"+
"\3\2\2\2\2\u008d\3\2\2\2\2\u008f\3\2\2\2\2\u0091\3\2\2\2\2\u0093\3\2\2"+
"\2\2\u0095\3\2\2\2\2\u0097\3\2\2\2\2\u0099\3\2\2\2\2\u009b\3\2\2\2\2\u009d"+
"\3\2\2\2\2\u009f\3\2\2\2\2\u00a1\3\2\2\2\2\u00a3\3\2\2\2\2\u00a5\3\2\2"+
"\2\2\u00a7\3\2\2\2\2\u00a9\3\2\2\2\2\u00ab\3\2\2\2\2\u00ad\3\2\2\2\2\u00af"+
"\3\2\2\2\2\u00b1\3\2\2\2\2\u00b3\3\2\2\2\2\u00b5\3\2\2\2\2\u00b7\3\2\2"+
"\2\2\u00b9\3\2\2\2\2\u00bb\3\2\2\2\2\u00bd\3\2\2\2\2\u00bf\3\2\2\2\2\u00c1"+
"\3\2\2\2\2\u00c3\3\2\2\2\2\u00c5\3\2\2\2\2\u00c7\3\2\2\2\2\u00c9\3\2\2"+
"\2\2\u00cb\3\2\2\2\2\u00cd\3\2\2\2\2\u00cf\3\2\2\2\2\u00d1\3\2\2\2\2\u00d3"+
"\3\2\2\2\2\u00d5\3\2\2\2\2\u00d7\3\2\2\2\2\u00d9\3\2\2\2\2\u00db\3\2\2"+
"\2\2\u00dd\3\2\2\2\2\u00df\3\2\2\2\2\u00e1\3\2\2\2\2\u00e3\3\2\2\2\2\u00e5"+
"\3\2\2\2\2\u00e7\3\2\2\2\2\u00e9\3\2\2\2\2\u00eb\3\2\2\2\2\u00ed\3\2\2"+
"\2\2\u00ef\3\2\2\2\2\u00f1\3\2\2\2\2\u00f3\3\2\2\2\2\u00f5\3\2\2\2\2\u00f7"+
"\3\2\2\2\2\u00f9\3\2\2\2\2\u00fb\3\2\2\2\2\u00fd\3\2\2\2\2\u00ff\3\2\2"+
"\2\2\u0101\3\2\2\2\2\u0103\3\2\2\2\2\u0105\3\2\2\2\2\u0107\3\2\2\2\2\u0109"+
"\3\2\2\2\2\u010b\3\2\2\2\2\u0113\3\2\2\2\2\u0115\3\2\2\2\2\u0117\3\2\2"+
"\2\2\u0119\3\2\2\2\3\u011b\3\2\2\2\5\u011d\3\2\2\2\7\u011f\3\2\2\2\t\u0121"+
"\3\2\2\2\13\u0123\3\2\2\2\r\u0127\3\2\2\2\17\u012f\3\2\2\2\21\u0138\3"+
"\2\2\2\23\u013c\3\2\2\2\25\u0140\3\2\2\2\27\u0143\3\2\2\2\31\u0147\3\2"+
"\2\2\33\u014f\3\2\2\2\35\u0152\3\2\2\2\37\u0157\3\2\2\2!\u015c\3\2\2\2"+
"#\u0164\3\2\2\2%\u016d\3\2\2\2\'\u0175\3\2\2\2)\u017d\3\2\2\2+\u018a\3"+
"\2\2\2-\u0197\3\2\2\2/\u01a9\3\2\2\2\61\u01ad\3\2\2\2\63\u01b2\3\2\2\2"+
"\65\u01b8\3\2\2\2\67\u01bd\3\2\2\29\u01c6\3\2\2\2;\u01cf\3\2\2\2=\u01d4"+
"\3\2\2\2?\u01d8\3\2\2\2A\u01df\3\2\2\2C\u01ea\3\2\2\2E\u01f1\3\2\2\2G"+
"\u01f9\3\2\2\2I\u0201\3\2\2\2K\u0207\3\2\2\2M\u020d\3\2\2\2O\u0211\3\2"+
"\2\2Q\u0218\3\2\2\2S\u021d\3\2\2\2U\u0224\3\2\2\2W\u0229\3\2\2\2Y\u0233"+
"\3\2\2\2[\u023c\3\2\2\2]\u0242\3\2\2\2_\u0249\3\2\2\2a\u024e\3\2\2\2c"+
"\u0254\3\2\2\2e\u0257\3\2\2\2g\u025f\3\2\2\2i\u0265\3\2\2\2k\u026e\3\2"+
"\2\2m\u0271\3\2\2\2o\u0276\3\2\2\2q\u027b\3\2\2\2s\u0280\3\2\2\2u\u0285"+
"\3\2\2\2w\u028b\3\2\2\2y\u0292\3\2\2\2{\u0298\3\2\2\2}\u029f\3\2\2\2\177"+
"\u02a7\3\2\2\2\u0081\u02ad\3\2\2\2\u0083\u02b4\3\2\2\2\u0085\u02bc\3\2"+
"\2\2\u0087\u02c0\3\2\2\2\u0089\u02c5\3\2\2\2\u008b\u02cb\3\2\2\2\u008d"+
"\u02ce\3\2\2\2\u008f\u02d8\3\2\2\2\u0091\u02db\3\2\2\2\u0093\u02e1\3\2"+
"\2\2\u0095\u02e7\3\2\2\2\u0097\u02ee\3\2\2\2\u0099\u02f7\3\2\2\2\u009b"+
"\u02fd\3\2\2\2\u009d\u0302\3\2\2\2\u009f\u0308\3\2\2\2\u00a1\u030e\3\2"+
"\2\2\u00a3\u0314\3\2\2\2\u00a5\u031c\3\2\2\2\u00a7\u0323\3\2\2\2\u00a9"+
"\u032b\3\2\2\2\u00ab\u0332\3\2\2\2\u00ad\u0337\3\2\2\2\u00af\u033b\3\2"+
"\2\2\u00b1\u0341\3\2\2\2\u00b3\u0348\3\2\2\2\u00b5\u034d\3\2\2\2\u00b7"+
"\u0352\3\2\2\2\u00b9\u0357\3\2\2\2\u00bb\u035a\3\2\2\2\u00bd\u035f\3\2"+
"\2\2\u00bf\u0365\3\2\2\2\u00c1\u036b\3\2\2\2\u00c3\u0372\3\2\2\2\u00c5"+
"\u0377\3\2\2\2\u00c7\u037d\3\2\2\2\u00c9\u0382\3\2\2\2\u00cb\u0387\3\2"+
"\2\2\u00cd\u038d\3\2\2\2\u00cf\u0395\3\2\2\2\u00d1\u0399\3\2\2\2\u00d3"+
"\u03a0\3\2\2\2\u00d5\u03a3\3\2\2\2\u00d7\u03a6\3\2\2\2\u00d9\u03aa\3\2"+
"\2\2\u00db\u03b0\3\2\2\2\u00dd\u03b2\3\2\2\2\u00df\u03b4\3\2\2\2\u00e1"+
"\u03bc\3\2\2\2\u00e3\u03be\3\2\2\2\u00e5\u03c0\3\2\2\2\u00e7\u03c3\3\2"+
"\2\2\u00e9\u03c5\3\2\2\2\u00eb\u03c8\3\2\2\2\u00ed\u03ca\3\2\2\2\u00ef"+
"\u03cc\3\2\2\2\u00f1\u03ce\3\2\2\2\u00f3\u03d0\3\2\2\2\u00f5\u03d2\3\2"+
"\2\2\u00f7\u03d5\3\2\2\2\u00f9\u03d8\3\2\2\2\u00fb\u03da\3\2\2\2\u00fd"+
"\u03dc\3\2\2\2\u00ff\u03e8\3\2\2\2\u0101\u0416\3\2\2\2\u0103\u041a\3\2"+
"\2\2\u0105\u0424\3\2\2\2\u0107\u042f\3\2\2\2\u0109\u0433\3\2\2\2\u010b"+
"\u043e\3\2\2\2\u010d\u0449\3\2\2\2\u010f\u0452\3\2\2\2\u0111\u0454\3\2"+
"\2\2\u0113\u0456\3\2\2\2\u0115\u0467\3\2\2\2\u0117\u0477\3\2\2\2\u0119"+
"\u047d\3\2\2\2\u011b\u011c\7*\2\2\u011c\4\3\2\2\2\u011d\u011e\7+\2\2\u011e"+
"\6\3\2\2\2\u011f\u0120\7.\2\2\u0120\b\3\2\2\2\u0121\u0122\7<\2\2\u0122"+
"\n\3\2\2\2\u0123\u0124\7C\2\2\u0124\u0125\7N\2\2\u0125\u0126\7N\2\2\u0126"+
"\f\3\2\2\2\u0127\u0128\7C\2\2\u0128\u0129\7P\2\2\u0129\u012a\7C\2\2\u012a"+
"\u012b\7N\2\2\u012b\u012c\7[\2\2\u012c\u012d\7\\\2\2\u012d\u012e\7G\2"+
"\2\u012e\16\3\2\2\2\u012f\u0130\7C\2\2\u0130\u0131\7P\2\2\u0131\u0132"+
"\7C\2\2\u0132\u0133\7N\2\2\u0133\u0134\7[\2\2\u0134\u0135\7\\\2\2\u0135"+
"\u0136\7G\2\2\u0136\u0137\7F\2\2\u0137\20\3\2\2\2\u0138\u0139\7C\2\2\u0139"+
"\u013a\7P\2\2\u013a\u013b\7F\2\2\u013b\22\3\2\2\2\u013c\u013d\7C\2\2\u013d"+
"\u013e\7P\2\2\u013e\u013f\7[\2\2\u013f\24\3\2\2\2\u0140\u0141\7C\2\2\u0141"+
"\u0142\7U\2\2\u0142\26\3\2\2\2\u0143\u0144\7C\2\2\u0144\u0145\7U\2\2\u0145"+
"\u0146\7E\2\2\u0146\30\3\2\2\2\u0147\u0148\7D\2\2\u0148\u0149\7G\2\2\u0149"+
"\u014a\7V\2\2\u014a\u014b\7Y\2\2\u014b\u014c\7G\2\2\u014c\u014d\7G\2\2"+
"\u014d\u014e\7P\2\2\u014e\32\3\2\2\2\u014f\u0150\7D\2\2\u0150\u0151\7"+
"[\2\2\u0151\34\3\2\2\2\u0152\u0153\7E\2\2\u0153\u0154\7C\2\2\u0154\u0155"+
"\7U\2\2\u0155\u0156\7G\2\2\u0156\36\3\2\2\2\u0157\u0158\7E\2\2\u0158\u0159"+
"\7C\2\2\u0159\u015a\7U\2\2\u015a\u015b\7V\2\2\u015b \3\2\2\2\u015c\u015d"+
"\7E\2\2\u015d\u015e\7C\2\2\u015e\u015f\7V\2\2\u015f\u0160\7C\2\2\u0160"+
"\u0161\7N\2\2\u0161\u0162\7Q\2\2\u0162\u0163\7I\2\2\u0163\"\3\2\2\2\u0164"+
"\u0165\7E\2\2\u0165\u0166\7C\2\2\u0166\u0167\7V\2\2\u0167\u0168\7C\2\2"+
"\u0168\u0169\7N\2\2\u0169\u016a\7Q\2\2\u016a\u016b\7I\2\2\u016b\u016c"+
"\7U\2\2\u016c$\3\2\2\2\u016d\u016e\7E\2\2\u016e\u016f\7Q\2\2\u016f\u0170"+
"\7N\2\2\u0170\u0171\7W\2\2\u0171\u0172\7O\2\2\u0172\u0173\7P\2\2\u0173"+
"\u0174\7U\2\2\u0174&\3\2\2\2\u0175\u0176\7E\2\2\u0176\u0177\7Q\2\2\u0177"+
"\u0178\7P\2\2\u0178\u0179\7X\2\2\u0179\u017a\7G\2\2\u017a\u017b\7T\2\2"+
"\u017b\u017c\7V\2\2\u017c(\3\2\2\2\u017d\u017e\7E\2\2\u017e\u017f\7W\2"+
"\2\u017f\u0180\7T\2\2\u0180\u0181\7T\2\2\u0181\u0182\7G\2\2\u0182\u0183"+
"\7P\2\2\u0183\u0184\7V\2\2\u0184\u0185\7a\2\2\u0185\u0186\7F\2\2\u0186"+
"\u0187\7C\2\2\u0187\u0188\7V\2\2\u0188\u0189\7G\2\2\u0189*\3\2\2\2\u018a"+
"\u018b\7E\2\2\u018b\u018c\7W\2\2\u018c\u018d\7T\2\2\u018d\u018e\7T\2\2"+
"\u018e\u018f\7G\2\2\u018f\u0190\7P\2\2\u0190\u0191\7V\2\2\u0191\u0192"+
"\7a\2\2\u0192\u0193\7V\2\2\u0193\u0194\7K\2\2\u0194\u0195\7O\2\2\u0195"+
"\u0196\7G\2\2\u0196,\3\2\2\2\u0197\u0198\7E\2\2\u0198\u0199\7W\2\2\u0199"+
"\u019a\7T\2\2\u019a\u019b\7T\2\2\u019b\u019c\7G\2\2\u019c\u019d\7P\2\2"+
"\u019d\u019e\7V\2\2\u019e\u019f\7a\2\2\u019f\u01a0\7V\2\2\u01a0\u01a1"+
"\7K\2\2\u01a1\u01a2\7O\2\2\u01a2\u01a3\7G\2\2\u01a3\u01a4\7U\2\2\u01a4"+
"\u01a5\7V\2\2\u01a5\u01a6\7C\2\2\u01a6\u01a7\7O\2\2\u01a7\u01a8\7R\2\2"+
"\u01a8.\3\2\2\2\u01a9\u01aa\7F\2\2\u01aa\u01ab\7C\2\2\u01ab\u01ac\7[\2"+
"\2\u01ac\60\3\2\2\2\u01ad\u01ae\7F\2\2\u01ae\u01af\7C\2\2\u01af\u01b0"+
"\7[\2\2\u01b0\u01b1\7U\2\2\u01b1\62\3\2\2\2\u01b2\u01b3\7F\2\2\u01b3\u01b4"+
"\7G\2\2\u01b4\u01b5\7D\2\2\u01b5\u01b6\7W\2\2\u01b6\u01b7\7I\2\2\u01b7"+
"\64\3\2\2\2\u01b8\u01b9\7F\2\2\u01b9\u01ba\7G\2\2\u01ba\u01bb\7U\2\2\u01bb"+
"\u01bc\7E\2\2\u01bc\66\3\2\2\2\u01bd\u01be\7F\2\2\u01be\u01bf\7G\2\2\u01bf"+
"\u01c0\7U\2\2\u01c0\u01c1\7E\2\2\u01c1\u01c2\7T\2\2\u01c2\u01c3\7K\2\2"+
"\u01c3\u01c4\7D\2\2\u01c4\u01c5\7G\2\2\u01c58\3\2\2\2\u01c6\u01c7\7F\2"+
"\2\u01c7\u01c8\7K\2\2\u01c8\u01c9\7U\2\2\u01c9\u01ca\7V\2\2\u01ca\u01cb"+
"\7K\2\2\u01cb\u01cc\7P\2\2\u01cc\u01cd\7E\2\2\u01cd\u01ce\7V\2\2\u01ce"+
":\3\2\2\2\u01cf\u01d0\7G\2\2\u01d0\u01d1\7N\2\2\u01d1\u01d2\7U\2\2\u01d2"+
"\u01d3\7G\2\2\u01d3<\3\2\2\2\u01d4\u01d5\7G\2\2\u01d5\u01d6\7P\2\2\u01d6"+
"\u01d7\7F\2\2\u01d7>\3\2\2\2\u01d8\u01d9\7G\2\2\u01d9\u01da\7U\2\2\u01da"+
"\u01db\7E\2\2\u01db\u01dc\7C\2\2\u01dc\u01dd\7R\2\2\u01dd\u01de\7G\2\2"+
"\u01de@\3\2\2\2\u01df\u01e0\7G\2\2\u01e0\u01e1\7Z\2\2\u01e1\u01e2\7G\2"+
"\2\u01e2\u01e3\7E\2\2\u01e3\u01e4\7W\2\2\u01e4\u01e5\7V\2\2\u01e5\u01e6"+
"\7C\2\2\u01e6\u01e7\7D\2\2\u01e7\u01e8\7N\2\2\u01e8\u01e9\7G\2\2\u01e9"+
"B\3\2\2\2\u01ea\u01eb\7G\2\2\u01eb\u01ec\7Z\2\2\u01ec\u01ed\7K\2\2\u01ed"+
"\u01ee\7U\2\2\u01ee\u01ef\7V\2\2\u01ef\u01f0\7U\2\2\u01f0D\3\2\2\2\u01f1"+
"\u01f2\7G\2\2\u01f2\u01f3\7Z\2\2\u01f3\u01f4\7R\2\2\u01f4\u01f5\7N\2\2"+
"\u01f5\u01f6\7C\2\2\u01f6\u01f7\7K\2\2\u01f7\u01f8\7P\2\2\u01f8F\3\2\2"+
"\2\u01f9\u01fa\7G\2\2\u01fa\u01fb\7Z\2\2\u01fb\u01fc\7V\2\2\u01fc\u01fd"+
"\7T\2\2\u01fd\u01fe\7C\2\2\u01fe\u01ff\7E\2\2\u01ff\u0200\7V\2\2\u0200"+
"H\3\2\2\2\u0201\u0202\7H\2\2\u0202\u0203\7C\2\2\u0203\u0204\7N\2\2\u0204"+
"\u0205\7U\2\2\u0205\u0206\7G\2\2\u0206J\3\2\2\2\u0207\u0208\7H\2\2\u0208"+
"\u0209\7K\2\2\u0209\u020a\7T\2\2\u020a\u020b\7U\2\2\u020b\u020c\7V\2\2"+
"\u020cL\3\2\2\2\u020d\u020e\7H\2\2\u020e\u020f\7Q\2\2\u020f\u0210\7T\2"+
"\2\u0210N\3\2\2\2\u0211\u0212\7H\2\2\u0212\u0213\7Q\2\2\u0213\u0214\7"+
"T\2\2\u0214\u0215\7O\2\2\u0215\u0216\7C\2\2\u0216\u0217\7V\2\2\u0217P"+
"\3\2\2\2\u0218\u0219\7H\2\2\u0219\u021a\7T\2\2\u021a\u021b\7Q\2\2\u021b"+
"\u021c\7O\2\2\u021cR\3\2\2\2\u021d\u021e\7H\2\2\u021e\u021f\7T\2\2\u021f"+
"\u0220\7Q\2\2\u0220\u0221\7\\\2\2\u0221\u0222\7G\2\2\u0222\u0223\7P\2"+
"\2\u0223T\3\2\2\2\u0224\u0225\7H\2\2\u0225\u0226\7W\2\2\u0226\u0227\7"+
"N\2\2\u0227\u0228\7N\2\2\u0228V\3\2\2\2\u0229\u022a\7H\2\2\u022a\u022b"+
"\7W\2\2\u022b\u022c\7P\2\2\u022c\u022d\7E\2\2\u022d\u022e\7V\2\2\u022e"+
"\u022f\7K\2\2\u022f\u0230\7Q\2\2\u0230\u0231\7P\2\2\u0231\u0232\7U\2\2"+
"\u0232X\3\2\2\2\u0233\u0234\7I\2\2\u0234\u0235\7T\2\2\u0235\u0236\7C\2"+
"\2\u0236\u0237\7R\2\2\u0237\u0238\7J\2\2\u0238\u0239\7X\2\2\u0239\u023a"+
"\7K\2\2\u023a\u023b\7\\\2\2\u023bZ\3\2\2\2\u023c\u023d\7I\2\2\u023d\u023e"+
"\7T\2\2\u023e\u023f\7Q\2\2\u023f\u0240\7W\2\2\u0240\u0241\7R\2\2\u0241"+
"\\\3\2\2\2\u0242\u0243\7J\2\2\u0243\u0244\7C\2\2\u0244\u0245\7X\2\2\u0245"+
"\u0246\7K\2\2\u0246\u0247\7P\2\2\u0247\u0248\7I\2\2\u0248^\3\2\2\2\u0249"+
"\u024a\7J\2\2\u024a\u024b\7Q\2\2\u024b\u024c\7W\2\2\u024c\u024d\7T\2\2"+
"\u024d`\3\2\2\2\u024e\u024f\7J\2\2\u024f\u0250\7Q\2\2\u0250\u0251\7W\2"+
"\2\u0251\u0252\7T\2\2\u0252\u0253\7U\2\2\u0253b\3\2\2\2\u0254\u0255\7"+
"K\2\2\u0255\u0256\7P\2\2\u0256d\3\2\2\2\u0257\u0258\7K\2\2\u0258\u0259"+
"\7P\2\2\u0259\u025a\7E\2\2\u025a\u025b\7N\2\2\u025b\u025c\7W\2\2\u025c"+
"\u025d\7F\2\2\u025d\u025e\7G\2\2\u025ef\3\2\2\2\u025f\u0260\7K\2\2\u0260"+
"\u0261\7P\2\2\u0261\u0262\7P\2\2\u0262\u0263\7G\2\2\u0263\u0264\7T\2\2"+
"\u0264h\3\2\2\2\u0265\u0266\7K\2\2\u0266\u0267\7P\2\2\u0267\u0268\7V\2"+
"\2\u0268\u0269\7G\2\2\u0269\u026a\7T\2\2\u026a\u026b\7X\2\2\u026b\u026c"+
"\7C\2\2\u026c\u026d\7N\2\2\u026dj\3\2\2\2\u026e\u026f\7K\2\2\u026f\u0270"+
"\7U\2\2\u0270l\3\2\2\2\u0271\u0272\7L\2\2\u0272\u0273\7Q\2\2\u0273\u0274"+
"\7K\2\2\u0274\u0275\7P\2\2\u0275n\3\2\2\2\u0276\u0277\7N\2\2\u0277\u0278"+
"\7C\2\2\u0278\u0279\7U\2\2\u0279\u027a\7V\2\2\u027ap\3\2\2\2\u027b\u027c"+
"\7N\2\2\u027c\u027d\7G\2\2\u027d\u027e\7H\2\2\u027e\u027f\7V\2\2\u027f"+
"r\3\2\2\2\u0280\u0281\7N\2\2\u0281\u0282\7K\2\2\u0282\u0283\7M\2\2\u0283"+
"\u0284\7G\2\2\u0284t\3\2\2\2\u0285\u0286\7N\2\2\u0286\u0287\7K\2\2\u0287"+
"\u0288\7O\2\2\u0288\u0289\7K\2\2\u0289\u028a\7V\2\2\u028av\3\2\2\2\u028b"+
"\u028c\7O\2\2\u028c\u028d\7C\2\2\u028d\u028e\7R\2\2\u028e\u028f\7R\2\2"+
"\u028f\u0290\7G\2\2\u0290\u0291\7F\2\2\u0291x\3\2\2\2\u0292\u0293\7O\2"+
"\2\u0293\u0294\7C\2\2\u0294\u0295\7V\2\2\u0295\u0296\7E\2\2\u0296\u0297"+
"\7J\2\2\u0297z\3\2\2\2\u0298\u0299\7O\2\2\u0299\u029a\7K\2\2\u029a\u029b"+
"\7P\2\2\u029b\u029c\7W\2\2\u029c\u029d\7V\2\2\u029d\u029e\7G\2\2\u029e"+
"|\3\2\2\2\u029f\u02a0\7O\2\2\u02a0\u02a1\7K\2\2\u02a1\u02a2\7P\2\2\u02a2"+
"\u02a3\7W\2\2\u02a3\u02a4\7V\2\2\u02a4\u02a5\7G\2\2\u02a5\u02a6\7U\2\2"+
"\u02a6~\3\2\2\2\u02a7\u02a8\7O\2\2\u02a8\u02a9\7Q\2\2\u02a9\u02aa\7P\2"+
"\2\u02aa\u02ab\7V\2\2\u02ab\u02ac\7J\2\2\u02ac\u0080\3\2\2\2\u02ad\u02ae"+
"\7O\2\2\u02ae\u02af\7Q\2\2\u02af\u02b0\7P\2\2\u02b0\u02b1\7V\2\2\u02b1"+
"\u02b2\7J\2\2\u02b2\u02b3\7U\2\2\u02b3\u0082\3\2\2\2\u02b4\u02b5\7P\2"+
"\2\u02b5\u02b6\7C\2\2\u02b6\u02b7\7V\2\2\u02b7\u02b8\7W\2\2\u02b8\u02b9"+
"\7T\2\2\u02b9\u02ba\7C\2\2\u02ba\u02bb\7N\2\2\u02bb\u0084\3\2\2\2\u02bc"+
"\u02bd\7P\2\2\u02bd\u02be\7Q\2\2\u02be\u02bf\7V\2\2\u02bf\u0086\3\2\2"+
"\2\u02c0\u02c1\7P\2\2\u02c1\u02c2\7W\2\2\u02c2\u02c3\7N\2\2\u02c3\u02c4"+
"\7N\2\2\u02c4\u0088\3\2\2\2\u02c5\u02c6\7P\2\2\u02c6\u02c7\7W\2\2\u02c7"+
"\u02c8\7N\2\2\u02c8\u02c9\7N\2\2\u02c9\u02ca\7U\2\2\u02ca\u008a\3\2\2"+
"\2\u02cb\u02cc\7Q\2\2\u02cc\u02cd\7P\2\2\u02cd\u008c\3\2\2\2\u02ce\u02cf"+
"\7Q\2\2\u02cf\u02d0\7R\2\2\u02d0\u02d1\7V\2\2\u02d1\u02d2\7K\2\2\u02d2"+
"\u02d3\7O\2\2\u02d3\u02d4\7K\2\2\u02d4\u02d5\7\\\2\2\u02d5\u02d6\7G\2"+
"\2\u02d6\u02d7\7F\2\2\u02d7\u008e\3\2\2\2\u02d8\u02d9\7Q\2\2\u02d9\u02da"+
"\7T\2\2\u02da\u0090\3\2\2\2\u02db\u02dc\7Q\2\2\u02dc\u02dd\7T\2\2\u02dd"+
"\u02de\7F\2\2\u02de\u02df\7G\2\2\u02df\u02e0\7T\2\2\u02e0\u0092\3\2\2"+
"\2\u02e1\u02e2\7Q\2\2\u02e2\u02e3\7W\2\2\u02e3\u02e4\7V\2\2\u02e4\u02e5"+
"\7G\2\2\u02e5\u02e6\7T\2\2\u02e6\u0094\3\2\2\2\u02e7\u02e8\7R\2\2\u02e8"+
"\u02e9\7C\2\2\u02e9\u02ea\7T\2\2\u02ea\u02eb\7U\2\2\u02eb\u02ec\7G\2\2"+
"\u02ec\u02ed\7F\2\2\u02ed\u0096\3\2\2\2\u02ee\u02ef\7R\2\2\u02ef\u02f0"+
"\7J\2\2\u02f0\u02f1\7[\2\2\u02f1\u02f2\7U\2\2\u02f2\u02f3\7K\2\2\u02f3"+
"\u02f4\7E\2\2\u02f4\u02f5\7C\2\2\u02f5\u02f6\7N\2\2\u02f6\u0098\3\2\2"+
"\2\u02f7\u02f8\7R\2\2\u02f8\u02f9\7K\2\2\u02f9\u02fa\7X\2\2\u02fa\u02fb"+
"\7Q\2\2\u02fb\u02fc\7V\2\2\u02fc\u009a\3\2\2\2\u02fd\u02fe\7R\2\2\u02fe"+
"\u02ff\7N\2\2\u02ff\u0300\7C\2\2\u0300\u0301\7P\2\2\u0301\u009c\3\2\2"+
"\2\u0302\u0303\7T\2\2\u0303\u0304\7K\2\2\u0304\u0305\7I\2\2\u0305\u0306"+
"\7J\2\2\u0306\u0307\7V\2\2\u0307\u009e\3\2\2\2\u0308\u0309\7T\2\2\u0309"+
"\u030a\7N\2\2\u030a\u030b\7K\2\2\u030b\u030c\7M\2\2\u030c\u030d\7G\2\2"+
"\u030d\u00a0\3\2\2\2\u030e\u030f\7S\2\2\u030f\u0310\7W\2\2\u0310\u0311"+
"\7G\2\2\u0311\u0312\7T\2\2\u0312\u0313\7[\2\2\u0313\u00a2\3\2\2\2\u0314"+
"\u0315\7U\2\2\u0315\u0316\7E\2\2\u0316\u0317\7J\2\2\u0317\u0318\7G\2\2"+
"\u0318\u0319\7O\2\2\u0319\u031a\7C\2\2\u031a\u031b\7U\2\2\u031b\u00a4"+
"\3\2\2\2\u031c\u031d\7U\2\2\u031d\u031e\7G\2\2\u031e\u031f\7E\2\2\u031f"+
"\u0320\7Q\2\2\u0320\u0321\7P\2\2\u0321\u0322\7F\2\2\u0322\u00a6\3\2\2"+
"\2\u0323\u0324\7U\2\2\u0324\u0325\7G\2\2\u0325\u0326\7E\2\2\u0326\u0327"+
"\7Q\2\2\u0327\u0328\7P\2\2\u0328\u0329\7F\2\2\u0329\u032a\7U\2\2\u032a"+
"\u00a8\3\2\2\2\u032b\u032c\7U\2\2\u032c\u032d\7G\2\2\u032d\u032e\7N\2"+
"\2\u032e\u032f\7G\2\2\u032f\u0330\7E\2\2\u0330\u0331\7V\2\2\u0331\u00aa"+
"\3\2\2\2\u0332\u0333\7U\2\2\u0333\u0334\7J\2\2\u0334\u0335\7Q\2\2\u0335"+
"\u0336\7Y\2\2\u0336\u00ac\3\2\2\2\u0337\u0338\7U\2\2\u0338\u0339\7[\2"+
"\2\u0339\u033a\7U\2\2\u033a\u00ae\3\2\2\2\u033b\u033c\7V\2\2\u033c\u033d"+
"\7C\2\2\u033d\u033e\7D\2\2\u033e\u033f\7N\2\2\u033f\u0340\7G\2\2\u0340"+
"\u00b0\3\2\2\2\u0341\u0342\7V\2\2\u0342\u0343\7C\2\2\u0343\u0344\7D\2"+
"\2\u0344\u0345\7N\2\2\u0345\u0346\7G\2\2\u0346\u0347\7U\2\2\u0347\u00b2"+
"\3\2\2\2\u0348\u0349\7V\2\2\u0349\u034a\7G\2\2\u034a\u034b\7Z\2\2\u034b"+
"\u034c\7V\2\2\u034c\u00b4\3\2\2\2\u034d\u034e\7V\2\2\u034e\u034f\7J\2"+
"\2\u034f\u0350\7G\2\2\u0350\u0351\7P\2\2\u0351\u00b6\3\2\2\2\u0352\u0353"+
"\7V\2\2\u0353\u0354\7T\2\2\u0354\u0355\7W\2\2\u0355\u0356\7G\2\2\u0356"+
"\u00b8\3\2\2\2\u0357\u0358\7V\2\2\u0358\u0359\7Q\2\2\u0359\u00ba\3\2\2"+
"\2\u035a\u035b\7V\2\2\u035b\u035c\7[\2\2\u035c\u035d\7R\2\2\u035d\u035e"+
"\7G\2\2\u035e\u00bc\3\2\2\2\u035f\u0360\7V\2\2\u0360\u0361\7[\2\2\u0361"+
"\u0362\7R\2\2\u0362\u0363\7G\2\2\u0363\u0364\7U\2\2\u0364\u00be\3\2\2"+
"\2\u0365\u0366\7W\2\2\u0366\u0367\7U\2\2\u0367\u0368\7K\2\2\u0368\u0369"+
"\7P\2\2\u0369\u036a\7I\2\2\u036a\u00c0\3\2\2\2\u036b\u036c\7X\2\2\u036c"+
"\u036d\7G\2\2\u036d\u036e\7T\2\2\u036e\u036f\7K\2\2\u036f\u0370\7H\2\2"+
"\u0370\u0371\7[\2\2\u0371\u00c2\3\2\2\2\u0372\u0373\7Y\2\2\u0373\u0374"+
"\7J\2\2\u0374\u0375\7G\2\2\u0375\u0376\7P\2\2\u0376\u00c4\3\2\2\2\u0377"+
"\u0378\7Y\2\2\u0378\u0379\7J\2\2\u0379\u037a\7G\2\2\u037a\u037b\7T\2\2"+
"\u037b\u037c\7G\2\2\u037c\u00c6\3\2\2\2\u037d\u037e\7Y\2\2\u037e\u037f"+
"\7K\2\2\u037f\u0380\7V\2\2\u0380\u0381\7J\2\2\u0381\u00c8\3\2\2\2\u0382"+
"\u0383\7[\2\2\u0383\u0384\7G\2\2\u0384\u0385\7C\2\2\u0385\u0386\7T\2\2"+
"\u0386\u00ca\3\2\2\2\u0387\u0388\7[\2\2\u0388\u0389\7G\2\2\u0389\u038a"+
"\7C\2\2\u038a\u038b\7T\2\2\u038b\u038c\7U\2\2\u038c\u00cc\3\2\2\2\u038d"+
"\u038e\7}\2\2\u038e\u038f\7G\2\2\u038f\u0390\7U\2\2\u0390\u0391\7E\2\2"+
"\u0391\u0392\7C\2\2\u0392\u0393\7R\2\2\u0393\u0394\7G\2\2\u0394\u00ce"+
"\3\2\2\2\u0395\u0396\7}\2\2\u0396\u0397\7H\2\2\u0397\u0398\7P\2\2\u0398"+
"\u00d0\3\2\2\2\u0399\u039a\7}\2\2\u039a\u039b\7N\2\2\u039b\u039c\7K\2"+
"\2\u039c\u039d\7O\2\2\u039d\u039e\7K\2\2\u039e\u039f\7V\2\2\u039f\u00d2"+
"\3\2\2\2\u03a0\u03a1\7}\2\2\u03a1\u03a2\7F\2\2\u03a2\u00d4\3\2\2\2\u03a3"+
"\u03a4\7}\2\2\u03a4\u03a5\7V\2\2\u03a5\u00d6\3\2\2\2\u03a6\u03a7\7}\2"+
"\2\u03a7\u03a8\7V\2\2\u03a8\u03a9\7U\2\2\u03a9\u00d8\3\2\2\2\u03aa\u03ab"+
"\7}\2\2\u03ab\u03ac\7I\2\2\u03ac\u03ad\7W\2\2\u03ad\u03ae\7K\2\2\u03ae"+
"\u03af\7F\2\2\u03af\u00da\3\2\2\2\u03b0\u03b1\7\177\2\2\u03b1\u00dc\3"+
"\2\2\2\u03b2\u03b3\7?\2\2\u03b3\u00de\3\2\2\2\u03b4\u03b5\7>\2\2\u03b5"+
"\u03b6\7?\2\2\u03b6\u03b7\7@\2\2\u03b7\u00e0\3\2\2\2\u03b8\u03b9\7>\2"+
"\2\u03b9\u03bd\7@\2\2\u03ba\u03bb\7#\2\2\u03bb\u03bd\7?\2\2\u03bc\u03b8"+
"\3\2\2\2\u03bc\u03ba\3\2\2\2\u03bd\u00e2\3\2\2\2\u03be\u03bf\7>\2\2\u03bf"+
"\u00e4\3\2\2\2\u03c0\u03c1\7>\2\2\u03c1\u03c2\7?\2\2\u03c2\u00e6\3\2\2"+
"\2\u03c3\u03c4\7@\2\2\u03c4\u00e8\3\2\2\2\u03c5\u03c6\7@\2\2\u03c6\u03c7"+
"\7?\2\2\u03c7\u00ea\3\2\2\2\u03c8\u03c9\7-\2\2\u03c9\u00ec\3\2\2\2\u03ca"+
"\u03cb\7/\2\2\u03cb\u00ee\3\2\2\2\u03cc\u03cd\7,\2\2\u03cd\u00f0\3\2\2"+
"\2\u03ce\u03cf\7\61\2\2\u03cf\u00f2\3\2\2\2\u03d0\u03d1\7\'\2\2\u03d1"+
"\u00f4\3\2\2\2\u03d2\u03d3\7<\2\2\u03d3\u03d4\7<\2\2\u03d4\u00f6\3\2\2"+
"\2\u03d5\u03d6\7~\2\2\u03d6\u03d7\7~\2\2\u03d7\u00f8\3\2\2\2\u03d8\u03d9"+
"\7\60\2\2\u03d9\u00fa\3\2\2\2\u03da\u03db\7A\2\2\u03db\u00fc\3\2\2\2\u03dc"+
"\u03e2\7)\2\2\u03dd\u03e1\n\2\2\2\u03de\u03df\7)\2\2\u03df\u03e1\7)\2"+
"\2\u03e0\u03dd\3\2\2\2\u03e0\u03de\3\2\2\2\u03e1\u03e4\3\2\2\2\u03e2\u03e0"+
"\3\2\2\2\u03e2\u03e3\3\2\2\2\u03e3\u03e5\3\2\2\2\u03e4\u03e2\3\2\2\2\u03e5"+
"\u03e6\7)\2\2\u03e6\u00fe\3\2\2\2\u03e7\u03e9\5\u010f\u0088\2\u03e8\u03e7"+
"\3\2\2\2\u03e9\u03ea\3\2\2\2\u03ea\u03e8\3\2\2\2\u03ea\u03eb\3\2\2\2\u03eb"+
"\u0100\3\2\2\2\u03ec\u03ee\5\u010f\u0088\2\u03ed\u03ec\3\2\2\2\u03ee\u03ef"+
"\3\2\2\2\u03ef\u03ed\3\2\2\2\u03ef\u03f0\3\2\2\2\u03f0\u03f1\3\2\2\2\u03f1"+
"\u03f5\5\u00f9}\2\u03f2\u03f4\5\u010f\u0088\2\u03f3\u03f2\3\2\2\2\u03f4"+
"\u03f7\3\2\2\2\u03f5\u03f3\3\2\2\2\u03f5\u03f6\3\2\2\2\u03f6\u0417\3\2"+
"\2\2\u03f7\u03f5\3\2\2\2\u03f8\u03fa\5\u00f9}\2\u03f9\u03fb\5\u010f\u0088"+
"\2\u03fa\u03f9\3\2\2\2\u03fb\u03fc\3\2\2\2\u03fc\u03fa\3\2\2\2\u03fc\u03fd"+
"\3\2\2\2\u03fd\u0417\3\2\2\2\u03fe\u0400\5\u010f\u0088\2\u03ff\u03fe\3"+
"\2\2\2\u0400\u0401\3\2\2\2\u0401\u03ff\3\2\2\2\u0401\u0402\3\2\2\2\u0402"+
"\u040a\3\2\2\2\u0403\u0407\5\u00f9}\2\u0404\u0406\5\u010f\u0088\2\u0405"+
"\u0404\3\2\2\2\u0406\u0409\3\2\2\2\u0407\u0405\3\2\2\2\u0407\u0408\3\2"+
"\2\2\u0408\u040b\3\2\2\2\u0409\u0407\3\2\2\2\u040a\u0403\3\2\2\2\u040a"+
"\u040b\3\2\2\2\u040b\u040c\3\2\2\2\u040c\u040d\5\u010d\u0087\2\u040d\u0417"+
"\3\2\2\2\u040e\u0410\5\u00f9}\2\u040f\u0411\5\u010f\u0088\2\u0410\u040f"+
"\3\2\2\2\u0411\u0412\3\2\2\2\u0412\u0410\3\2\2\2\u0412\u0413\3\2\2\2\u0413"+
"\u0414\3\2\2\2\u0414\u0415\5\u010d\u0087\2\u0415\u0417\3\2\2\2\u0416\u03ed"+
"\3\2\2\2\u0416\u03f8\3\2\2\2\u0416\u03ff\3\2\2\2\u0416\u040e\3\2\2\2\u0417"+
"\u0102\3\2\2\2\u0418\u041b\5\u0111\u0089\2\u0419\u041b\7a\2\2\u041a\u0418"+
"\3\2\2\2\u041a\u0419\3\2\2\2\u041b\u0421\3\2\2\2\u041c\u0420\5\u0111\u0089"+
"\2\u041d\u0420\5\u010f\u0088\2\u041e\u0420\t\3\2\2\u041f\u041c\3\2\2\2"+
"\u041f\u041d\3\2\2\2\u041f\u041e\3\2\2\2\u0420\u0423\3\2\2\2\u0421\u041f"+
"\3\2\2\2\u0421\u0422\3\2\2\2\u0422\u0104\3\2\2\2\u0423\u0421\3\2\2\2\u0424"+
"\u0428\5\u010f\u0088\2\u0425\u0429\5\u0111\u0089\2\u0426\u0429\5\u010f"+
"\u0088\2\u0427\u0429\t\3\2\2\u0428\u0425\3\2\2\2\u0428\u0426\3\2\2\2\u0428"+
"\u0427\3\2\2\2\u0429\u042a\3\2\2\2\u042a\u0428\3\2\2\2\u042a\u042b\3\2"+
"\2\2\u042b\u0106\3\2\2\2\u042c\u0430\5\u0111\u0089\2\u042d\u0430\5\u010f"+
"\u0088\2\u042e\u0430\7a\2\2\u042f\u042c\3\2\2\2\u042f\u042d\3\2\2\2\u042f"+
"\u042e\3\2\2\2\u0430\u0431\3\2\2\2\u0431\u042f\3\2\2\2\u0431\u0432\3\2"+
"\2\2\u0432\u0108\3\2\2\2\u0433\u0439\7$\2\2\u0434\u0438\n\4\2\2\u0435"+
"\u0436\7$\2\2\u0436\u0438\7$\2\2\u0437\u0434\3\2\2\2\u0437\u0435\3\2\2"+
"\2\u0438\u043b\3\2\2\2\u0439\u0437\3\2\2\2\u0439\u043a\3\2\2\2\u043a\u043c"+
"\3\2\2\2\u043b\u0439\3\2\2\2\u043c\u043d\7$\2\2\u043d\u010a\3\2\2\2\u043e"+
"\u0444\7b\2\2\u043f\u0443\n\5\2\2\u0440\u0441\7b\2\2\u0441\u0443\7b\2"+
"\2\u0442\u043f\3\2\2\2\u0442\u0440\3\2\2\2\u0443\u0446\3\2\2\2\u0444\u0442"+
"\3\2\2\2\u0444\u0445\3\2\2\2\u0445\u0447\3\2\2\2\u0446\u0444\3\2\2\2\u0447"+
"\u0448\7b\2\2\u0448\u010c\3\2\2\2\u0449\u044b\7G\2\2\u044a\u044c\t\6\2"+
"\2\u044b\u044a\3\2\2\2\u044b\u044c\3\2\2\2\u044c\u044e\3\2\2\2\u044d\u044f"+
"\5\u010f\u0088\2\u044e\u044d\3\2\2\2\u044f\u0450\3\2\2\2\u0450\u044e\3"+
"\2\2\2\u0450\u0451\3\2\2\2\u0451\u010e\3\2\2\2\u0452\u0453\t\7\2\2\u0453"+
"\u0110\3\2\2\2\u0454\u0455\t\b\2\2\u0455\u0112\3\2\2\2\u0456\u0457\7/"+
"\2\2\u0457\u0458\7/\2\2\u0458\u045c\3\2\2\2\u0459\u045b\n\t\2\2\u045a"+
"\u0459\3\2\2\2\u045b\u045e\3\2\2\2\u045c\u045a\3\2\2\2\u045c\u045d\3\2"+
"\2\2\u045d\u0460\3\2\2\2\u045e\u045c\3\2\2\2\u045f\u0461\7\17\2\2\u0460"+
"\u045f\3\2\2\2\u0460\u0461\3\2\2\2\u0461\u0463\3\2\2\2\u0462\u0464\7\f"+
"\2\2\u0463\u0462\3\2\2\2\u0463\u0464\3\2\2\2\u0464\u0465\3\2\2\2\u0465"+
"\u0466\b\u008a\2\2\u0466\u0114\3\2\2\2\u0467\u0468\7\61\2\2\u0468\u0469"+
"\7,\2\2\u0469\u046e\3\2\2\2\u046a\u046d\5\u0115\u008b\2\u046b\u046d\13"+
"\2\2\2\u046c\u046a\3\2\2\2\u046c\u046b\3\2\2\2\u046d\u0470\3\2\2\2\u046e"+
"\u046f\3\2\2\2\u046e\u046c\3\2\2\2\u046f\u0471\3\2\2\2\u0470\u046e\3\2"+
"\2\2\u0471\u0472\7,\2\2\u0472\u0473\7\61\2\2\u0473\u0474\3\2\2\2\u0474"+
"\u0475\b\u008b\2\2\u0475\u0116\3\2\2\2\u0476\u0478\t\n\2\2\u0477\u0476"+
"\3\2\2\2\u0478\u0479\3\2\2\2\u0479\u0477\3\2\2\2\u0479\u047a\3\2\2\2\u047a"+
"\u047b\3\2\2\2\u047b\u047c\b\u008c\2\2\u047c\u0118\3\2\2\2\u047d\u047e"+
"\13\2\2\2\u047e\u011a\3\2\2\2\"\2\u03bc\u03e0\u03e2\u03ea\u03ef\u03f5"+
"\u03fc\u0401\u0407\u040a\u0412\u0416\u041a\u041f\u0421\u0428\u042a\u042f"+
"\u0431\u0437\u0439\u0442\u0444\u044b\u0450\u045c\u0460\u0463\u046c\u046e"+
"\u0479\3\2\3\2";
public static final ATN _ATN =
new ATNDeserializer().deserialize(_serializedATN.toCharArray());
static {

View File

@ -283,6 +283,16 @@ interface SqlBaseListener extends ParseTreeListener {
* @param ctx the parse tree
*/
void exitSetQuantifier(SqlBaseParser.SetQuantifierContext ctx);
/**
* Enter a parse tree produced by {@link SqlBaseParser#selectItems}.
* @param ctx the parse tree
*/
void enterSelectItems(SqlBaseParser.SelectItemsContext ctx);
/**
* Exit a parse tree produced by {@link SqlBaseParser#selectItems}.
* @param ctx the parse tree
*/
void exitSelectItems(SqlBaseParser.SelectItemsContext ctx);
/**
* Enter a parse tree produced by the {@code selectExpression}
* labeled alternative in {@link SqlBaseParser#selectItem}.
@ -371,6 +381,36 @@ interface SqlBaseListener extends ParseTreeListener {
* @param ctx the parse tree
*/
void exitAliasedRelation(SqlBaseParser.AliasedRelationContext ctx);
/**
* Enter a parse tree produced by {@link SqlBaseParser#pivotClause}.
* @param ctx the parse tree
*/
void enterPivotClause(SqlBaseParser.PivotClauseContext ctx);
/**
* Exit a parse tree produced by {@link SqlBaseParser#pivotClause}.
* @param ctx the parse tree
*/
void exitPivotClause(SqlBaseParser.PivotClauseContext ctx);
/**
* Enter a parse tree produced by {@link SqlBaseParser#pivotArgs}.
* @param ctx the parse tree
*/
void enterPivotArgs(SqlBaseParser.PivotArgsContext ctx);
/**
* Exit a parse tree produced by {@link SqlBaseParser#pivotArgs}.
* @param ctx the parse tree
*/
void exitPivotArgs(SqlBaseParser.PivotArgsContext ctx);
/**
* Enter a parse tree produced by {@link SqlBaseParser#namedValueExpression}.
* @param ctx the parse tree
*/
void enterNamedValueExpression(SqlBaseParser.NamedValueExpressionContext ctx);
/**
* Exit a parse tree produced by {@link SqlBaseParser#namedValueExpression}.
* @param ctx the parse tree
*/
void exitNamedValueExpression(SqlBaseParser.NamedValueExpressionContext ctx);
/**
* Enter a parse tree produced by {@link SqlBaseParser#expression}.
* @param ctx the parse tree

View File

@ -173,6 +173,12 @@ interface SqlBaseVisitor<T> extends ParseTreeVisitor<T> {
* @return the visitor result
*/
T visitSetQuantifier(SqlBaseParser.SetQuantifierContext ctx);
/**
* Visit a parse tree produced by {@link SqlBaseParser#selectItems}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitSelectItems(SqlBaseParser.SelectItemsContext ctx);
/**
* Visit a parse tree produced by the {@code selectExpression}
* labeled alternative in {@link SqlBaseParser#selectItem}.
@ -225,6 +231,24 @@ interface SqlBaseVisitor<T> extends ParseTreeVisitor<T> {
* @return the visitor result
*/
T visitAliasedRelation(SqlBaseParser.AliasedRelationContext ctx);
/**
* Visit a parse tree produced by {@link SqlBaseParser#pivotClause}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitPivotClause(SqlBaseParser.PivotClauseContext ctx);
/**
* Visit a parse tree produced by {@link SqlBaseParser#pivotArgs}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitPivotArgs(SqlBaseParser.PivotArgsContext ctx);
/**
* Visit a parse tree produced by {@link SqlBaseParser#namedValueExpression}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitNamedValueExpression(SqlBaseParser.NamedValueExpressionContext ctx);
/**
* Visit a parse tree produced by {@link SqlBaseParser#expression}.
* @param ctx the parse tree

View File

@ -10,8 +10,8 @@ import org.elasticsearch.xpack.sql.expression.Attribute;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Expressions;
import org.elasticsearch.xpack.sql.expression.NamedExpression;
import org.elasticsearch.xpack.sql.tree.Source;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.tree.Source;
import java.util.List;
import java.util.Objects;

View File

@ -0,0 +1,142 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.plan.logical;
import org.elasticsearch.xpack.sql.capabilities.Resolvables;
import org.elasticsearch.xpack.sql.expression.Attribute;
import org.elasticsearch.xpack.sql.expression.AttributeSet;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.ExpressionId;
import org.elasticsearch.xpack.sql.expression.Expressions;
import org.elasticsearch.xpack.sql.expression.NamedExpression;
import org.elasticsearch.xpack.sql.expression.function.Function;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.tree.Source;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static java.util.Collections.singletonList;
public class Pivot extends UnaryPlan {
private final Expression column;
private final List<NamedExpression> values;
private final List<NamedExpression> aggregates;
// derived properties
private AttributeSet groupingSet;
private AttributeSet valueOutput;
private List<Attribute> output;
public Pivot(Source source, LogicalPlan child, Expression column, List<NamedExpression> values, List<NamedExpression> aggregates) {
super(source, child);
this.column = column;
this.values = values;
this.aggregates = aggregates;
}
@Override
protected NodeInfo<Pivot> info() {
return NodeInfo.create(this, Pivot::new, child(), column, values, aggregates);
}
@Override
protected Pivot replaceChild(LogicalPlan newChild) {
return new Pivot(source(), newChild, column, values, aggregates);
}
public Expression column() {
return column;
}
public List<NamedExpression> values() {
return values;
}
public List<NamedExpression> aggregates() {
return aggregates;
}
public AttributeSet groupingSet() {
if (groupingSet == null) {
AttributeSet columnSet = Expressions.references(singletonList(column));
// grouping can happen only on "primitive" fields, thus exclude multi-fields or nested docs
// the verifier enforces this rule so it does not catch folks by surprise
groupingSet = new AttributeSet(Expressions.onlyPrimitiveFieldAttributes(child().output()))
// make sure to have the column as the last entry (helps with translation)
.subtract(columnSet)
.subtract(Expressions.references(aggregates))
.combine(columnSet);
}
return groupingSet;
}
public AttributeSet valuesOutput() {
// TODO: the generated id is a hack since it can clash with other potentially generated ids
if (valueOutput == null) {
List<Attribute> out = new ArrayList<>(aggregates.size() * values.size());
if (aggregates.size() == 1) {
NamedExpression agg = aggregates.get(0);
for (NamedExpression value : values) {
ExpressionId id = new ExpressionId(agg.id().hashCode() + value.id().hashCode());
out.add(value.toAttribute().withDataType(agg.dataType()).withId(id));
}
}
// for multiple args, concat the function and the value
else {
for (NamedExpression agg : aggregates) {
String name = agg instanceof Function ? ((Function) agg).functionName() : agg.name();
for (NamedExpression value : values) {
ExpressionId id = new ExpressionId(agg.id().hashCode() + value.id().hashCode());
out.add(value.toAttribute().withName(value.name() + "_" + name).withDataType(agg.dataType()).withId(id));
}
}
}
valueOutput = new AttributeSet(out);
}
return valueOutput;
}
@Override
public List<Attribute> output() {
if (output == null) {
output = new ArrayList<>(groupingSet()
.subtract(Expressions.references(singletonList(column)))
.combine(valuesOutput()));
}
return output;
}
@Override
public boolean expressionsResolved() {
return column.resolved() && Resolvables.resolved(values) && Resolvables.resolved(aggregates);
}
@Override
public int hashCode() {
return Objects.hash(column, values, aggregates, child());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Pivot other = (Pivot) obj;
return Objects.equals(column, other.column)
&& Objects.equals(values, other.values)
&& Objects.equals(aggregates, other.aggregates)
&& Objects.equals(child(), other.child());
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.plan.physical;
import org.elasticsearch.xpack.sql.expression.Attribute;
import org.elasticsearch.xpack.sql.plan.logical.Pivot;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.tree.Source;
import java.util.List;
import java.util.Objects;
public class PivotExec extends UnaryExec implements Unexecutable {
private final Pivot pivot;
public PivotExec(Source source, PhysicalPlan child, Pivot pivot) {
super(source, child);
this.pivot = pivot;
}
@Override
protected NodeInfo<PivotExec> info() {
return NodeInfo.create(this, PivotExec::new, child(), pivot);
}
@Override
protected PivotExec replaceChild(PhysicalPlan newChild) {
return new PivotExec(source(), newChild, pivot);
}
@Override
public List<Attribute> output() {
return pivot.output();
}
public Pivot pivot() {
return pivot;
}
@Override
public int hashCode() {
return Objects.hash(pivot, child());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
PivotExec other = (PivotExec) obj;
return Objects.equals(pivot, other.pivot)
&& Objects.equals(child(), other.child());
}
}

View File

@ -14,6 +14,7 @@ import org.elasticsearch.xpack.sql.plan.logical.Limit;
import org.elasticsearch.xpack.sql.plan.logical.LocalRelation;
import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
import org.elasticsearch.xpack.sql.plan.logical.OrderBy;
import org.elasticsearch.xpack.sql.plan.logical.Pivot;
import org.elasticsearch.xpack.sql.plan.logical.Project;
import org.elasticsearch.xpack.sql.plan.logical.With;
import org.elasticsearch.xpack.sql.plan.logical.command.Command;
@ -25,6 +26,7 @@ import org.elasticsearch.xpack.sql.plan.physical.LimitExec;
import org.elasticsearch.xpack.sql.plan.physical.LocalExec;
import org.elasticsearch.xpack.sql.plan.physical.OrderExec;
import org.elasticsearch.xpack.sql.plan.physical.PhysicalPlan;
import org.elasticsearch.xpack.sql.plan.physical.PivotExec;
import org.elasticsearch.xpack.sql.plan.physical.ProjectExec;
import org.elasticsearch.xpack.sql.plan.physical.UnplannedExec;
import org.elasticsearch.xpack.sql.querydsl.container.QueryContainer;
@ -88,6 +90,11 @@ class Mapper extends RuleExecutor<PhysicalPlan> {
return new AggregateExec(p.source(), map(a.child()), a.groupings(), a.aggregates());
}
if (p instanceof Pivot) {
Pivot pv = (Pivot) p;
return new PivotExec(pv.source(), map(pv.child()), pv);
}
if (p instanceof EsRelation) {
EsRelation c = (EsRelation) p;
List<Attribute> output = c.output();

View File

@ -8,10 +8,13 @@ package org.elasticsearch.xpack.sql.planner;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.execution.search.AggRef;
import org.elasticsearch.xpack.sql.execution.search.FieldExtraction;
import org.elasticsearch.xpack.sql.expression.Alias;
import org.elasticsearch.xpack.sql.expression.Attribute;
import org.elasticsearch.xpack.sql.expression.AttributeMap;
import org.elasticsearch.xpack.sql.expression.AttributeSet;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.ExpressionId;
import org.elasticsearch.xpack.sql.expression.Expressions;
import org.elasticsearch.xpack.sql.expression.Foldables;
import org.elasticsearch.xpack.sql.expression.NamedExpression;
@ -32,6 +35,7 @@ import org.elasticsearch.xpack.sql.expression.gen.pipeline.AggPathInput;
import org.elasticsearch.xpack.sql.expression.gen.pipeline.Pipe;
import org.elasticsearch.xpack.sql.expression.gen.pipeline.UnaryPipe;
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
import org.elasticsearch.xpack.sql.plan.logical.Pivot;
import org.elasticsearch.xpack.sql.plan.physical.AggregateExec;
import org.elasticsearch.xpack.sql.plan.physical.EsQueryExec;
import org.elasticsearch.xpack.sql.plan.physical.FilterExec;
@ -39,6 +43,7 @@ import org.elasticsearch.xpack.sql.plan.physical.LimitExec;
import org.elasticsearch.xpack.sql.plan.physical.LocalExec;
import org.elasticsearch.xpack.sql.plan.physical.OrderExec;
import org.elasticsearch.xpack.sql.plan.physical.PhysicalPlan;
import org.elasticsearch.xpack.sql.plan.physical.PivotExec;
import org.elasticsearch.xpack.sql.plan.physical.ProjectExec;
import org.elasticsearch.xpack.sql.planner.QueryTranslator.GroupingContext;
import org.elasticsearch.xpack.sql.planner.QueryTranslator.QueryTranslation;
@ -52,6 +57,7 @@ import org.elasticsearch.xpack.sql.querydsl.container.GlobalCountRef;
import org.elasticsearch.xpack.sql.querydsl.container.GroupByRef;
import org.elasticsearch.xpack.sql.querydsl.container.GroupByRef.Property;
import org.elasticsearch.xpack.sql.querydsl.container.MetricAggRef;
import org.elasticsearch.xpack.sql.querydsl.container.PivotColumnRef;
import org.elasticsearch.xpack.sql.querydsl.container.QueryContainer;
import org.elasticsearch.xpack.sql.querydsl.container.ScoreSort;
import org.elasticsearch.xpack.sql.querydsl.container.ScriptSort;
@ -64,14 +70,17 @@ import org.elasticsearch.xpack.sql.rule.RuleExecutor;
import org.elasticsearch.xpack.sql.session.EmptyExecutable;
import org.elasticsearch.xpack.sql.util.Check;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import static org.elasticsearch.xpack.sql.planner.QueryTranslator.and;
import static org.elasticsearch.xpack.sql.planner.QueryTranslator.toAgg;
import static org.elasticsearch.xpack.sql.planner.QueryTranslator.toQuery;
import static org.elasticsearch.xpack.sql.util.CollectionUtils.combine;
/**
* Folds the PhysicalPlan into a {@link Query}.
@ -85,6 +94,7 @@ class QueryFolder extends RuleExecutor<PhysicalPlan> {
@Override
protected Iterable<RuleExecutor<PhysicalPlan>.Batch> batches() {
Batch rollup = new Batch("Fold queries",
new FoldPivot(),
new FoldAggregate(),
new FoldProject(),
new FoldFilter(),
@ -149,7 +159,8 @@ class QueryFolder extends RuleExecutor<PhysicalPlan> {
queryC.sort(),
queryC.limit(),
queryC.shouldTrackHits(),
queryC.shouldIncludeFrozen());
queryC.shouldIncludeFrozen(),
queryC.minPageSize());
return new EsQueryExec(exec.source(), exec.index(), project.output(), clone);
}
return project;
@ -179,7 +190,8 @@ class QueryFolder extends RuleExecutor<PhysicalPlan> {
qContainer.sort(),
qContainer.limit(),
qContainer.shouldTrackHits(),
qContainer.shouldIncludeFrozen());
qContainer.shouldIncludeFrozen(),
qContainer.minPageSize());
return exec.with(qContainer);
}
@ -204,190 +216,190 @@ class QueryFolder extends RuleExecutor<PhysicalPlan> {
private static class FoldAggregate extends FoldingRule<AggregateExec> {
@Override
protected PhysicalPlan rule(AggregateExec a) {
if (a.child() instanceof EsQueryExec) {
EsQueryExec exec = (EsQueryExec) a.child();
return fold(a, exec);
}
return a;
}
static EsQueryExec fold(AggregateExec a, EsQueryExec exec) {
// build the group aggregation
// and also collect info about it (since the group columns might be used inside the select)
// build the group aggregation
// and also collect info about it (since the group columns might be used inside the select)
GroupingContext groupingContext = QueryTranslator.groupBy(a.groupings());
GroupingContext groupingContext = QueryTranslator.groupBy(a.groupings());
QueryContainer queryC = exec.queryContainer();
if (groupingContext != null) {
queryC = queryC.addGroups(groupingContext.groupMap.values());
}
QueryContainer queryC = exec.queryContainer();
if (groupingContext != null) {
queryC = queryC.addGroups(groupingContext.groupMap.values());
}
Map<Attribute, Attribute> aliases = new LinkedHashMap<>();
// tracker for compound aggs seen in a group
Map<CompoundNumericAggregate, String> compoundAggMap = new LinkedHashMap<>();
Map<Attribute, Attribute> aliases = new LinkedHashMap<>();
// tracker for compound aggs seen in a group
Map<CompoundNumericAggregate, String> compoundAggMap = new LinkedHashMap<>();
// followed by actual aggregates
for (NamedExpression ne : a.aggregates()) {
// followed by actual aggregates
for (NamedExpression ne : a.aggregates()) {
// unwrap alias - it can be
// - an attribute (since we support aliases inside group-by)
// SELECT emp_no ... GROUP BY emp_no
// SELECT YEAR(hire_date) ... GROUP BY YEAR(hire_date)
// unwrap alias - it can be
// - an attribute (since we support aliases inside group-by)
// SELECT emp_no ... GROUP BY emp_no
// SELECT YEAR(hire_date) ... GROUP BY YEAR(hire_date)
// - an agg function (typically)
// SELECT COUNT(*), AVG(salary) ... GROUP BY salary;
// - an agg function (typically)
// SELECT COUNT(*), AVG(salary) ... GROUP BY salary;
// - a scalar function, which can be applied on an attribute or aggregate and can require one or multiple inputs
// - a scalar function, which can be applied on an attribute or aggregate and can require one or multiple inputs
// SELECT SIN(emp_no) ... GROUP BY emp_no
// SELECT CAST(YEAR(hire_date)) ... GROUP BY YEAR(hire_date)
// SELECT CAST(AVG(salary)) ... GROUP BY salary
// SELECT AVG(salary) + SIN(MIN(salary)) ... GROUP BY salary
// SELECT SIN(emp_no) ... GROUP BY emp_no
// SELECT CAST(YEAR(hire_date)) ... GROUP BY YEAR(hire_date)
// SELECT CAST(AVG(salary)) ... GROUP BY salary
// SELECT AVG(salary) + SIN(MIN(salary)) ... GROUP BY salary
if (ne instanceof Alias || ne instanceof Function) {
Alias as = ne instanceof Alias ? (Alias) ne : null;
Expression child = as != null ? as.child() : ne;
if (ne instanceof Alias || ne instanceof Function) {
Alias as = ne instanceof Alias ? (Alias) ne : null;
Expression child = as != null ? as.child() : ne;
// record aliases in case they are later referred in the tree
if (as != null && as.child() instanceof NamedExpression) {
aliases.put(as.toAttribute(), ((NamedExpression) as.child()).toAttribute());
}
// record aliases in case they are later referred in the tree
if (as != null && as.child() instanceof NamedExpression) {
aliases.put(as.toAttribute(), ((NamedExpression) as.child()).toAttribute());
}
//
// look first for scalar functions which might wrap the actual grouped target
// (e.g.
// CAST(field) GROUP BY field or
// ABS(YEAR(field)) GROUP BY YEAR(field) or
// ABS(AVG(salary)) ... GROUP BY salary
// )
if (child instanceof ScalarFunction) {
ScalarFunction f = (ScalarFunction) child;
Pipe proc = f.asPipe();
//
// look first for scalar functions which might wrap the actual grouped target
// (e.g.
// CAST(field) GROUP BY field or
// ABS(YEAR(field)) GROUP BY YEAR(field) or
// ABS(AVG(salary)) ... GROUP BY salary
// )
if (child instanceof ScalarFunction) {
ScalarFunction f = (ScalarFunction) child;
Pipe proc = f.asPipe();
final AtomicReference<QueryContainer> qC = new AtomicReference<>(queryC);
final AtomicReference<QueryContainer> qC = new AtomicReference<>(queryC);
proc = proc.transformUp(p -> {
// bail out if the def is resolved
if (p.resolved()) {
return p;
}
// get the backing expression and check if it belongs to a agg group or whether it's
// an expression in the first place
Expression exp = p.expression();
GroupByKey matchingGroup = null;
if (groupingContext != null) {
// is there a group (aggregation) for this expression ?
matchingGroup = groupingContext.groupFor(exp);
}
else {
// a scalar function can be used only if has already been mentioned for grouping
// (otherwise it is the opposite of grouping)
if (exp instanceof ScalarFunction) {
throw new FoldingException(exp, "Scalar function " +exp.toString()
+ " can be used only if included already in grouping");
}
}
// found match for expression; if it's an attribute or scalar, end the processing chain with
// the reference to the backing agg
if (matchingGroup != null) {
if (exp instanceof Attribute || exp instanceof ScalarFunction || exp instanceof GroupingFunction) {
Processor action = null;
boolean isDateBased = exp.dataType().isDateBased();
/*
* special handling of dates since aggs return the typed Date object which needs
* extraction instead of handling this in the scroller, the folder handles this
* as it already got access to the extraction action
*/
if (exp instanceof DateTimeHistogramFunction) {
action = ((UnaryPipe) p).action();
isDateBased = true;
}
return new AggPathInput(exp.source(), exp,
new GroupByRef(matchingGroup.id(), null, isDateBased), action);
}
}
// or found an aggregate expression (which has to work on an attribute used for grouping)
// (can happen when dealing with a root group)
if (Functions.isAggregate(exp)) {
Tuple<QueryContainer, AggPathInput> withFunction = addAggFunction(matchingGroup,
(AggregateFunction) exp, compoundAggMap, qC.get());
qC.set(withFunction.v1());
return withFunction.v2();
}
// not an aggregate and no matching - go to a higher node (likely a function YEAR(birth_date))
proc = proc.transformUp(p -> {
// bail out if the def is resolved
if (p.resolved()) {
return p;
});
if (!proc.resolved()) {
throw new FoldingException(child, "Cannot find grouping for '{}'", Expressions.name(child));
}
// add the computed column
queryC = qC.get().addColumn(new ComputedRef(proc), f.toAttribute());
// TODO: is this needed?
// redirect the alias to the scalar group id (changing the id altogether doesn't work it is
// already used in the aggpath)
//aliases.put(as.toAttribute(), sf.toAttribute());
}
// apply the same logic above (for function inputs) to non-scalar functions with small variations:
// instead of adding things as input, add them as full blown column
else {
// get the backing expression and check if it belongs to a agg group or whether it's
// an expression in the first place
Expression exp = p.expression();
GroupByKey matchingGroup = null;
if (groupingContext != null) {
// is there a group (aggregation) for this expression ?
matchingGroup = groupingContext.groupFor(child);
matchingGroup = groupingContext.groupFor(exp);
} else {
// a scalar function can be used only if has already been mentioned for grouping
// (otherwise it is the opposite of grouping)
if (exp instanceof ScalarFunction) {
throw new FoldingException(exp,
"Scalar function " + exp.toString() + " can be used only if included already in grouping");
}
}
// attributes can only refer to declared groups
if (child instanceof Attribute) {
Check.notNull(matchingGroup, "Cannot find group [{}]", Expressions.name(child));
queryC = queryC.addColumn(
new GroupByRef(matchingGroup.id(), null, child.dataType().isDateBased()), ((Attribute) child));
// found match for expression; if it's an attribute or scalar, end the processing chain with
// the reference to the backing agg
if (matchingGroup != null) {
if (exp instanceof Attribute || exp instanceof ScalarFunction || exp instanceof GroupingFunction) {
Processor action = null;
boolean isDateBased = exp.dataType().isDateBased();
/*
* special handling of dates since aggs return the typed Date object which needs
* extraction instead of handling this in the scroller, the folder handles this
* as it already got access to the extraction action
*/
if (exp instanceof DateTimeHistogramFunction) {
action = ((UnaryPipe) p).action();
isDateBased = true;
}
return new AggPathInput(exp.source(), exp, new GroupByRef(matchingGroup.id(), null, isDateBased),
action);
}
}
// handle histogram
else if (child instanceof GroupingFunction) {
queryC = queryC.addColumn(new GroupByRef(matchingGroup.id(), null, child.dataType().isDateBased()),
((GroupingFunction) child).toAttribute());
// or found an aggregate expression (which has to work on an attribute used for grouping)
// (can happen when dealing with a root group)
if (Functions.isAggregate(exp)) {
Tuple<QueryContainer, AggPathInput> withFunction = addAggFunction(matchingGroup, (AggregateFunction) exp,
compoundAggMap, qC.get());
qC.set(withFunction.v1());
return withFunction.v2();
}
// not an aggregate and no matching - go to a higher node (likely a function YEAR(birth_date))
return p;
});
if (!proc.resolved()) {
throw new FoldingException(child, "Cannot find grouping for '{}'", Expressions.name(child));
}
// add the computed column
queryC = qC.get().addColumn(new ComputedRef(proc), f.toAttribute());
// TODO: is this needed?
// redirect the alias to the scalar group id (changing the id altogether doesn't work it is
// already used in the aggpath)
//aliases.put(as.toAttribute(), sf.toAttribute());
}
// apply the same logic above (for function inputs) to non-scalar functions with small variations:
// instead of adding things as input, add them as full blown column
else {
GroupByKey matchingGroup = null;
if (groupingContext != null) {
// is there a group (aggregation) for this expression ?
matchingGroup = groupingContext.groupFor(child);
}
// attributes can only refer to declared groups
if (child instanceof Attribute) {
Check.notNull(matchingGroup, "Cannot find group [{}]", Expressions.name(child));
queryC = queryC.addColumn(new GroupByRef(matchingGroup.id(), null, child.dataType().isDateBased()),
((Attribute) child));
}
// handle histogram
else if (child instanceof GroupingFunction) {
queryC = queryC.addColumn(new GroupByRef(matchingGroup.id(), null, child.dataType().isDateBased()),
((GroupingFunction) child).toAttribute());
}
else if (child.foldable()) {
queryC = queryC.addColumn(ne.toAttribute());
}
// fallback to regular agg functions
else {
// the only thing left is agg function
Check.isTrue(Functions.isAggregate(child),
"Expected aggregate function inside alias; got [{}]", child.nodeString());
AggregateFunction af = (AggregateFunction) child;
Tuple<QueryContainer, AggPathInput> withAgg = addAggFunction(matchingGroup, af, compoundAggMap, queryC);
// make sure to add the inner id (to handle compound aggs)
queryC = withAgg.v1().addColumn(withAgg.v2().context(), af.toAttribute());
}
// fallback to regular agg functions
else {
// the only thing left is agg function
Check.isTrue(Functions.isAggregate(child), "Expected aggregate function inside alias; got [{}]",
child.nodeString());
AggregateFunction af = (AggregateFunction) child;
Tuple<QueryContainer, AggPathInput> withAgg = addAggFunction(matchingGroup, af, compoundAggMap, queryC);
// make sure to add the inner id (to handle compound aggs)
queryC = withAgg.v1().addColumn(withAgg.v2().context(), af.toAttribute());
}
}
// not an Alias or Function means it's an Attribute so apply the same logic as above
} else {
GroupByKey matchingGroup = null;
if (groupingContext != null) {
matchingGroup = groupingContext.groupFor(ne);
Check.notNull(matchingGroup, "Cannot find group [{}]", Expressions.name(ne));
} else {
GroupByKey matchingGroup = null;
if (groupingContext != null) {
matchingGroup = groupingContext.groupFor(ne);
Check.notNull(matchingGroup, "Cannot find group [{}]", Expressions.name(ne));
queryC = queryC.addColumn(
new GroupByRef(matchingGroup.id(), null, ne.dataType().isDateBased()), ne.toAttribute());
}
queryC = queryC.addColumn(new GroupByRef(matchingGroup.id(), null, ne.dataType().isDateBased()), ne.toAttribute());
}
else if (ne.foldable()) {
queryC = queryC.addColumn(ne.toAttribute());
}
}
}
if (!aliases.isEmpty()) {
Map<Attribute, Attribute> newAliases = new LinkedHashMap<>(queryC.aliases());
newAliases.putAll(aliases);
queryC = queryC.withAliases(new AttributeMap<>(newAliases));
}
return new EsQueryExec(exec.source(), exec.index(), a.output(), queryC);
if (!aliases.isEmpty()) {
Map<Attribute, Attribute> newAliases = new LinkedHashMap<>(queryC.aliases());
newAliases.putAll(aliases);
queryC = queryC.withAliases(new AttributeMap<>(newAliases));
}
return a;
return new EsQueryExec(exec.source(), exec.index(), a.output(), queryC);
}
private Tuple<QueryContainer, AggPathInput> addAggFunction(GroupByKey groupingAgg, AggregateFunction f,
private static Tuple<QueryContainer, AggPathInput> addAggFunction(GroupByKey groupingAgg, AggregateFunction f,
Map<CompoundNumericAggregate, String> compoundAggMap, QueryContainer queryC) {
String functionId = f.functionId();
// handle count as a special case agg
@ -551,6 +563,52 @@ class QueryFolder extends RuleExecutor<PhysicalPlan> {
}
}
private static class FoldPivot extends FoldingRule<PivotExec> {
@Override
protected PhysicalPlan rule(PivotExec plan) {
if (plan.child() instanceof EsQueryExec) {
EsQueryExec exec = (EsQueryExec) plan.child();
Pivot p = plan.pivot();
EsQueryExec fold = FoldAggregate
.fold(new AggregateExec(plan.source(), exec,
new ArrayList<>(p.groupingSet()), combine(p.groupingSet(), p.aggregates())), exec);
// replace the aggregate extractors with pivot specific extractors
// these require a reference to the pivoting column in order to compare the value
// due to the Pivot structure - the column is the last entry in the grouping set
QueryContainer query = fold.queryContainer();
List<Tuple<FieldExtraction, ExpressionId>> fields = new ArrayList<>(query.fields());
int startingIndex = fields.size() - p.aggregates().size() - 1;
// pivot grouping
Tuple<FieldExtraction, ExpressionId> groupTuple = fields.remove(startingIndex);
AttributeSet valuesOutput = plan.pivot().valuesOutput();
for (int i = startingIndex; i < fields.size(); i++) {
Tuple<FieldExtraction, ExpressionId> tuple = fields.remove(i);
for (Attribute attribute : valuesOutput) {
fields.add(new Tuple<>(new PivotColumnRef(groupTuple.v1(), tuple.v1(), attribute.fold()), attribute.id()));
}
i += valuesOutput.size();
}
return fold.with(new QueryContainer(query.query(), query.aggs(),
fields,
query.aliases(),
query.pseudoFunctions(),
query.scalarFunctions(),
query.sort(),
query.limit(),
query.shouldTrackHits(),
query.shouldIncludeFrozen(),
valuesOutput.size()));
}
return plan;
}
}
//
// local
//

View File

@ -5,7 +5,9 @@
*/
package org.elasticsearch.xpack.sql.planner;
import org.elasticsearch.xpack.sql.expression.function.aggregate.InnerAggregate;
import org.elasticsearch.xpack.sql.plan.physical.PhysicalPlan;
import org.elasticsearch.xpack.sql.plan.physical.PivotExec;
import org.elasticsearch.xpack.sql.plan.physical.Unexecutable;
import org.elasticsearch.xpack.sql.plan.physical.UnplannedExec;
import org.elasticsearch.xpack.sql.tree.Node;
@ -14,6 +16,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
abstract class Verifier {
static class Failure {
@ -53,8 +57,8 @@ abstract class Verifier {
}
}
private static Failure fail(Node<?> source, String message) {
return new Failure(source, message);
private static Failure fail(Node<?> source, String message, Object... args) {
return new Failure(source, format(null, message, args));
}
static List<Failure> verifyMappingPlan(PhysicalPlan plan) {
@ -70,10 +74,22 @@ abstract class Verifier {
}
});
});
// verify Pivot
checkInnerAggsPivot(plan, failures);
return failures;
}
private static void checkInnerAggsPivot(PhysicalPlan plan, List<Failure> failures) {
plan.forEachDown(p -> {
p.pivot().aggregates().forEach(agg -> agg.forEachDown(e -> {
if (e instanceof InnerAggregate) {
failures.add(fail(e, "Aggregation [{}] not supported (yet) by PIVOT", e.sourceText()));
}
}));
}, PivotExec.class);
}
static List<Failure> verifyExecutingPlan(PhysicalPlan plan) {
List<Failure> failures = new ArrayList<>();

View File

@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.querydsl.container;
import org.elasticsearch.xpack.sql.execution.search.AggRef;
import org.elasticsearch.xpack.sql.execution.search.FieldExtraction;
public class PivotColumnRef extends AggRef {
private final FieldExtraction agg;
private final FieldExtraction pivot;
private final Object value;
public PivotColumnRef(FieldExtraction pivot, FieldExtraction agg, Object value) {
this.pivot = pivot;
this.agg = agg;
// due to the way Elasticsearch aggs work
// promote the object to expect types so that the comparison works
this.value = esAggType(value);
}
private static Object esAggType(Object value) {
if (value instanceof Number) {
Number n = (Number) value;
if (value instanceof Double) {
return value;
}
if (value instanceof Float) {
return Double.valueOf(n.doubleValue());
}
return Long.valueOf(n.longValue());
}
return value;
}
public FieldExtraction pivot() {
return pivot;
}
public FieldExtraction agg() {
return agg;
}
public Object value() {
return value;
}
}

View File

@ -83,13 +83,15 @@ public class QueryContainer {
private final int limit;
private final boolean trackHits;
private final boolean includeFrozen;
// used when pivoting for retrieving at least one pivot row
private final int minPageSize;
// computed
private Boolean aggsOnly;
private Boolean customSort;
public QueryContainer() {
this(null, null, null, null, null, null, null, -1, false, false);
this(null, null, null, null, null, null, null, -1, false, false, -1);
}
public QueryContainer(Query query,
@ -102,7 +104,8 @@ public class QueryContainer {
Set<Sort> sort,
int limit,
boolean trackHits,
boolean includeFrozen) {
boolean includeFrozen,
int minPageSize) {
this.query = query;
this.aggs = aggs == null ? Aggs.EMPTY : aggs;
this.fields = fields == null || fields.isEmpty() ? emptyList() : fields;
@ -113,6 +116,7 @@ public class QueryContainer {
this.limit = limit;
this.trackHits = trackHits;
this.includeFrozen = includeFrozen;
this.minPageSize = minPageSize;
}
/**
@ -247,49 +251,62 @@ public class QueryContainer {
return includeFrozen;
}
public int minPageSize() {
return minPageSize;
}
//
// copy methods
//
public QueryContainer with(Query q) {
return new QueryContainer(q, aggs, fields, aliases, pseudoFunctions, scalarFunctions, sort, limit, trackHits, includeFrozen);
return new QueryContainer(q, aggs, fields, aliases, pseudoFunctions, scalarFunctions, sort, limit, trackHits, includeFrozen,
minPageSize);
}
public QueryContainer withFields(List<Tuple<FieldExtraction, ExpressionId>> f) {
return new QueryContainer(query, aggs, f, aliases, pseudoFunctions, scalarFunctions, sort, limit, trackHits, includeFrozen,
minPageSize);
}
public QueryContainer withAliases(AttributeMap<Attribute> a) {
return new QueryContainer(query, aggs, fields, a, pseudoFunctions, scalarFunctions, sort, limit, trackHits, includeFrozen);
return new QueryContainer(query, aggs, fields, a, pseudoFunctions, scalarFunctions, sort, limit, trackHits, includeFrozen,
minPageSize);
}
public QueryContainer withPseudoFunctions(Map<String, GroupByKey> p) {
return new QueryContainer(query, aggs, fields, aliases, p, scalarFunctions, sort, limit, trackHits, includeFrozen);
return new QueryContainer(query, aggs, fields, aliases, p, scalarFunctions, sort, limit, trackHits, includeFrozen, minPageSize);
}
public QueryContainer with(Aggs a) {
return new QueryContainer(query, a, fields, aliases, pseudoFunctions, scalarFunctions, sort, limit, trackHits, includeFrozen);
return new QueryContainer(query, a, fields, aliases, pseudoFunctions, scalarFunctions, sort, limit, trackHits, includeFrozen,
minPageSize);
}
public QueryContainer withLimit(int l) {
return l == limit ? this : new QueryContainer(query, aggs, fields, aliases, pseudoFunctions, scalarFunctions, sort, l, trackHits,
includeFrozen);
includeFrozen, minPageSize);
}
public QueryContainer withTrackHits() {
return trackHits ? this : new QueryContainer(query, aggs, fields, aliases, pseudoFunctions, scalarFunctions, sort, limit, true,
includeFrozen);
includeFrozen, minPageSize);
}
public QueryContainer withFrozen() {
return includeFrozen ? this : new QueryContainer(query, aggs, fields, aliases, pseudoFunctions, scalarFunctions, sort, limit,
trackHits, true);
trackHits, true, minPageSize);
}
public QueryContainer withScalarProcessors(AttributeMap<Pipe> procs) {
return new QueryContainer(query, aggs, fields, aliases, pseudoFunctions, procs, sort, limit, trackHits, includeFrozen);
return new QueryContainer(query, aggs, fields, aliases, pseudoFunctions, procs, sort, limit, trackHits, includeFrozen, minPageSize);
}
public QueryContainer addSort(Sort sortable) {
Set<Sort> sort = new LinkedHashSet<>(this.sort);
sort.add(sortable);
return new QueryContainer(query, aggs, fields, aliases, pseudoFunctions, scalarFunctions, sort, limit, trackHits, includeFrozen);
return new QueryContainer(query, aggs, fields, aliases, pseudoFunctions, scalarFunctions, sort, limit, trackHits, includeFrozen,
minPageSize);
}
private String aliasName(Attribute attr) {
@ -344,7 +361,8 @@ public class QueryContainer {
false, attr.parent().name());
return new Tuple<>(
new QueryContainer(q, aggs, fields, aliases, pseudoFunctions, scalarFunctions, sort, limit, trackHits, includeFrozen),
new QueryContainer(q, aggs, fields, aliases, pseudoFunctions, scalarFunctions, sort, limit, trackHits, includeFrozen,
minPageSize),
nestedFieldRef);
}
@ -447,7 +465,7 @@ public class QueryContainer {
ExpressionId id = attr instanceof AggregateFunctionAttribute ? ((AggregateFunctionAttribute) attr).innerId() : attr.id();
return new QueryContainer(query, aggs, combine(fields, new Tuple<>(ref, id)), aliases, pseudoFunctions,
scalarFunctions,
sort, limit, trackHits, includeFrozen);
sort, limit, trackHits, includeFrozen, minPageSize);
}
public AttributeMap<Pipe> scalarFunctions() {

View File

@ -12,7 +12,8 @@ import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.common.io.SqlStreamInput;
import org.elasticsearch.xpack.sql.common.io.SqlStreamOutput;
import org.elasticsearch.xpack.sql.execution.search.CompositeAggregationCursor;
import org.elasticsearch.xpack.sql.execution.search.CompositeAggCursor;
import org.elasticsearch.xpack.sql.execution.search.PivotCursor;
import org.elasticsearch.xpack.sql.execution.search.ScrollCursor;
import org.elasticsearch.xpack.sql.execution.search.extractor.BucketExtractors;
import org.elasticsearch.xpack.sql.execution.search.extractor.HitExtractors;
@ -45,7 +46,8 @@ public final class Cursors {
// cursors
entries.add(new NamedWriteableRegistry.Entry(Cursor.class, EmptyCursor.NAME, in -> Cursor.EMPTY));
entries.add(new NamedWriteableRegistry.Entry(Cursor.class, ScrollCursor.NAME, ScrollCursor::new));
entries.add(new NamedWriteableRegistry.Entry(Cursor.class, CompositeAggregationCursor.NAME, CompositeAggregationCursor::new));
entries.add(new NamedWriteableRegistry.Entry(Cursor.class, CompositeAggCursor.NAME, CompositeAggCursor::new));
entries.add(new NamedWriteableRegistry.Entry(Cursor.class, PivotCursor.NAME, PivotCursor::new));
entries.add(new NamedWriteableRegistry.Entry(Cursor.class, TextFormatterCursor.NAME, TextFormatterCursor::new));
entries.add(new NamedWriteableRegistry.Entry(Cursor.class, ListCursor.NAME, ListCursor::new));

View File

@ -21,7 +21,7 @@ import static java.util.Collections.emptyList;
public class ListCursor implements Cursor {
public static final String NAME = "p";
public static final String NAME = "l";
private final List<List<?>> data;
private final int columnCount;

View File

@ -844,4 +844,57 @@ public class VerifierErrorMessagesTests extends ESTestCase {
accept("SELECT ST_X(shape) FROM test");
}
}
//
// Pivot verifications
//
public void testPivotNonExactColumn() {
assertEquals("1:72: Field [text] of data type [text] cannot be used for grouping;"
+ " No keyword/multi-field defined exact matches for [text]; define one or use MATCH/QUERY instead",
error("SELECT * FROM (SELECT int, text, keyword FROM test) " + "PIVOT(AVG(int) FOR text IN ('bla'))"));
}
public void testPivotColumnUsedInsteadOfAgg() {
assertEquals("1:59: No aggregate function found in PIVOT at [int]",
error("SELECT * FROM (SELECT int, keyword, bool FROM test) " + "PIVOT(int FOR keyword IN ('bla'))"));
}
public void testPivotScalarUsedInsteadOfAgg() {
assertEquals("1:59: No aggregate function found in PIVOT at [ROUND(int)]",
error("SELECT * FROM (SELECT int, keyword, bool FROM test) " + "PIVOT(ROUND(int) FOR keyword IN ('bla'))"));
}
public void testPivotScalarUsedAlongSideAgg() {
assertEquals("1:59: Non-aggregate function found in PIVOT at [AVG(int) + ROUND(int)]",
error("SELECT * FROM (SELECT int, keyword, bool FROM test) " + "PIVOT(AVG(int) + ROUND(int) FOR keyword IN ('bla'))"));
}
public void testPivotValueNotFoldable() {
assertEquals("1:91: Non-literal [bool] found inside PIVOT values",
error("SELECT * FROM (SELECT int, keyword, bool FROM test) " + "PIVOT(AVG(int) FOR keyword IN ('bla', bool))"));
}
public void testPivotWithFunctionInput() {
assertEquals("1:37: No functions allowed (yet); encountered [YEAR(date)]",
error("SELECT * FROM (SELECT int, keyword, YEAR(date) FROM test) " + "PIVOT(AVG(int) FOR keyword IN ('bla'))"));
}
public void testPivotWithFoldableFunctionInValues() {
assertEquals("1:85: Non-literal [UCASE('bla')] found inside PIVOT values",
error("SELECT * FROM (SELECT int, keyword, bool FROM test) " + "PIVOT(AVG(int) FOR keyword IN ( UCASE('bla') ))"));
}
public void testPivotWithNull() {
assertEquals("1:85: Null not allowed as a PIVOT value",
error("SELECT * FROM (SELECT int, keyword, bool FROM test) " + "PIVOT(AVG(int) FOR keyword IN ( null ))"));
}
public void testPivotValuesHaveDifferentTypeThanColumn() {
assertEquals("1:81: Literal ['bla'] of type [keyword] does not match type [boolean] of PIVOT column [bool]",
error("SELECT * FROM (SELECT int, keyword, bool FROM test) " + "PIVOT(AVG(int) FOR bool IN ('bla'))"));
}
public void testPivotValuesWithMultipleDifferencesThanColumn() {
assertEquals("1:81: Literal ['bla'] of type [keyword] does not match type [boolean] of PIVOT column [bool]",
error("SELECT * FROM (SELECT int, keyword, bool FROM test) " + "PIVOT(AVG(int) FOR bool IN ('bla', true))"));
}
}

View File

@ -19,8 +19,8 @@ import java.util.BitSet;
import java.util.List;
import java.util.function.Supplier;
public class CompositeAggregationCursorTests extends AbstractSqlWireSerializingTestCase<CompositeAggregationCursor> {
public static CompositeAggregationCursor randomCompositeCursor() {
public class CompositeAggregationCursorTests extends AbstractSqlWireSerializingTestCase<CompositeAggCursor> {
public static CompositeAggCursor randomCompositeCursor() {
int extractorsSize = between(1, 20);
ZoneId id = randomSafeZone();
List<BucketExtractor> extractors = new ArrayList<>(extractorsSize);
@ -28,7 +28,7 @@ public class CompositeAggregationCursorTests extends AbstractSqlWireSerializingT
extractors.add(randomBucketExtractor(id));
}
return new CompositeAggregationCursor(new byte[randomInt(256)], extractors, randomBitSet(extractorsSize),
return new CompositeAggCursor(new byte[randomInt(256)], extractors, randomBitSet(extractorsSize),
randomIntBetween(10, 1024), randomBoolean(), randomAlphaOfLength(5));
}
@ -41,8 +41,8 @@ public class CompositeAggregationCursorTests extends AbstractSqlWireSerializingT
}
@Override
protected CompositeAggregationCursor mutateInstance(CompositeAggregationCursor instance) throws IOException {
return new CompositeAggregationCursor(instance.next(), instance.extractors(),
protected CompositeAggCursor mutateInstance(CompositeAggCursor instance) throws IOException {
return new CompositeAggCursor(instance.next(), instance.extractors(),
randomValueOtherThan(instance.mask(), () -> randomBitSet(instance.extractors().size())),
randomValueOtherThan(instance.limit(), () -> randomIntBetween(1, 512)),
!instance.includeFrozen(),
@ -50,17 +50,17 @@ public class CompositeAggregationCursorTests extends AbstractSqlWireSerializingT
}
@Override
protected CompositeAggregationCursor createTestInstance() {
protected CompositeAggCursor createTestInstance() {
return randomCompositeCursor();
}
@Override
protected Reader<CompositeAggregationCursor> instanceReader() {
return CompositeAggregationCursor::new;
protected Reader<CompositeAggCursor> instanceReader() {
return CompositeAggCursor::new;
}
@Override
protected ZoneId instanceZoneId(CompositeAggregationCursor instance) {
protected ZoneId instanceZoneId(CompositeAggCursor instance) {
List<BucketExtractor> extractors = instance.extractors();
for (BucketExtractor bucketExtractor : extractors) {
ZoneId zoneId = MetricAggExtractorTests.extractZoneId(bucketExtractor);

View File

@ -7,6 +7,7 @@ package org.elasticsearch.xpack.sql.optimizer;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.sql.analysis.analyzer.Analyzer.PruneSubqueryAliases;
import org.elasticsearch.xpack.sql.analysis.index.EsIndex;
import org.elasticsearch.xpack.sql.expression.Alias;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Expression.TypeResolution;
@ -20,6 +21,7 @@ import org.elasticsearch.xpack.sql.expression.Order;
import org.elasticsearch.xpack.sql.expression.Order.OrderDirection;
import org.elasticsearch.xpack.sql.expression.function.Function;
import org.elasticsearch.xpack.sql.expression.function.aggregate.AggregateFunction;
import org.elasticsearch.xpack.sql.expression.function.aggregate.Avg;
import org.elasticsearch.xpack.sql.expression.function.aggregate.Count;
import org.elasticsearch.xpack.sql.expression.function.aggregate.First;
import org.elasticsearch.xpack.sql.expression.function.aggregate.Last;
@ -87,14 +89,17 @@ import org.elasticsearch.xpack.sql.optimizer.Optimizer.PropagateEquals;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.PruneDuplicateFunctions;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.ReplaceFoldableAttributes;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.ReplaceMinMaxWithTopHits;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.RewritePivot;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.SimplifyCase;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.SimplifyConditional;
import org.elasticsearch.xpack.sql.optimizer.Optimizer.SortAggregateOnOrderBy;
import org.elasticsearch.xpack.sql.plan.logical.Aggregate;
import org.elasticsearch.xpack.sql.plan.logical.EsRelation;
import org.elasticsearch.xpack.sql.plan.logical.Filter;
import org.elasticsearch.xpack.sql.plan.logical.LocalRelation;
import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
import org.elasticsearch.xpack.sql.plan.logical.OrderBy;
import org.elasticsearch.xpack.sql.plan.logical.Pivot;
import org.elasticsearch.xpack.sql.plan.logical.Project;
import org.elasticsearch.xpack.sql.plan.logical.SubQueryAlias;
import org.elasticsearch.xpack.sql.plan.logical.command.ShowTables;
@ -1498,4 +1503,23 @@ public class OptimizerTests extends ESTestCase {
assertEquals(firstAlias, groupings.get(0));
assertEquals(secondAlias, groupings.get(1));
}
}
public void testPivotRewrite() {
FieldAttribute column = getFieldAttribute("pivot");
FieldAttribute number = getFieldAttribute("number");
List<NamedExpression> values = Arrays.asList(new Alias(EMPTY, "ONE", L(1)), new Alias(EMPTY, "TWO", L(2)));
List<NamedExpression> aggs = Arrays.asList(new Avg(EMPTY, number));
Pivot pivot = new Pivot(EMPTY, new EsRelation(EMPTY, new EsIndex("table", emptyMap()), false), column, values, aggs);
LogicalPlan result = new RewritePivot().apply(pivot);
assertEquals(Pivot.class, result.getClass());
Pivot pv = (Pivot) result;
assertEquals(pv.aggregates(), aggs);
assertEquals(Filter.class, pv.child().getClass());
Filter f = (Filter) pv.child();
assertEquals(In.class, f.condition().getClass());
In in = (In) f.condition();
assertEquals(column, in.value());
assertEquals(Arrays.asList(L(1), L(2)), in.list());
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.planner;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.sql.TestUtils;
import org.elasticsearch.xpack.sql.analysis.analyzer.Analyzer;
import org.elasticsearch.xpack.sql.analysis.analyzer.Verifier;
import org.elasticsearch.xpack.sql.analysis.index.EsIndex;
import org.elasticsearch.xpack.sql.analysis.index.IndexResolution;
import org.elasticsearch.xpack.sql.expression.function.FunctionRegistry;
import org.elasticsearch.xpack.sql.optimizer.Optimizer;
import org.elasticsearch.xpack.sql.parser.SqlParser;
import org.elasticsearch.xpack.sql.plan.physical.PhysicalPlan;
import org.elasticsearch.xpack.sql.stats.Metrics;
import org.elasticsearch.xpack.sql.type.EsField;
import org.elasticsearch.xpack.sql.type.TypesTests;
import org.junit.After;
import org.junit.Before;
import java.util.Map;
public class PostOptimizerVerifierTests extends ESTestCase {
private SqlParser parser;
private Analyzer analyzer;
private Optimizer optimizer;
private Planner planner;
private IndexResolution indexResolution;
@Before
public void init() {
parser = new SqlParser();
Map<String, EsField> mapping = TypesTests.loadMapping("mapping-multi-field-variation.json");
EsIndex test = new EsIndex("test", mapping);
indexResolution = IndexResolution.valid(test);
analyzer = new Analyzer(TestUtils.TEST_CFG, new FunctionRegistry(), indexResolution, new Verifier(new Metrics()));
optimizer = new Optimizer();
planner = new Planner();
}
@After
public void destroy() {
parser = null;
analyzer = null;
}
private PhysicalPlan plan(String sql) {
return planner.plan(optimizer.optimize(analyzer.analyze(parser.createStatement(sql), true)), true);
}
private String error(String sql) {
return error(indexResolution, sql);
}
private String error(IndexResolution getIndexResult, String sql) {
PlanningException e = expectThrows(PlanningException.class, () -> plan(sql));
assertTrue(e.getMessage().startsWith("Found "));
String header = "Found 1 problem(s)\nline ";
return e.getMessage().substring(header.length());
}
public void testPivotInnerAgg() {
assertEquals("1:59: Aggregation [SUM_OF_SQUARES(int)] not supported (yet) by PIVOT",
error("SELECT * FROM (SELECT int, keyword, bool FROM test) " + "PIVOT(SUM_OF_SQUARES(int) FOR keyword IN ('bla'))"));
}
public void testPivotNestedInnerAgg() {
assertEquals("1:65: Aggregation [SUM_OF_SQUARES(int)] not supported (yet) by PIVOT",
error("SELECT * FROM (SELECT int, keyword, bool FROM test) " + "PIVOT(ROUND(SUM_OF_SQUARES(int)) FOR keyword IN ('bla'))"));
}
}

View File

@ -11,6 +11,7 @@ import org.elasticsearch.xpack.sql.analysis.analyzer.Analyzer;
import org.elasticsearch.xpack.sql.analysis.analyzer.Verifier;
import org.elasticsearch.xpack.sql.analysis.index.EsIndex;
import org.elasticsearch.xpack.sql.analysis.index.IndexResolution;
import org.elasticsearch.xpack.sql.expression.Expressions;
import org.elasticsearch.xpack.sql.expression.function.FunctionRegistry;
import org.elasticsearch.xpack.sql.expression.function.aggregate.AggregateFunctionAttribute;
import org.elasticsearch.xpack.sql.optimizer.Optimizer;
@ -26,8 +27,10 @@ import org.elasticsearch.xpack.sql.type.TypesTests;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import java.util.Arrays;
import java.util.Map;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.startsWith;
@ -397,4 +400,18 @@ public class QueryFolderTests extends ESTestCase {
AggregateFunctionAttribute afa = (AggregateFunctionAttribute) ee.output().get(0);
assertThat(afa.propertyPath(), endsWith("[3.0]"));
}
public void testFoldingOfPivot() {
PhysicalPlan p = plan("SELECT * FROM (SELECT int, keyword, bool FROM test) PIVOT(AVG(int) FOR keyword IN ('A', 'B'))");
assertEquals(EsQueryExec.class, p.getClass());
EsQueryExec ee = (EsQueryExec) p;
assertEquals(3, ee.output().size());
assertEquals(Arrays.asList("bool", "'A'", "'B'"), Expressions.names(ee.output()));
String q = ee.toString().replaceAll("\\s+", "");
assertThat(q, containsString("\"query\":{\"terms\":{\"keyword\":[\"A\",\"B\"]"));
String a = ee.queryContainer().aggs().asAggBuilder().toString().replaceAll("\\s+", "");
assertThat(a, containsString("\"terms\":{\"field\":\"bool\""));
assertThat(a, containsString("\"terms\":{\"field\":\"keyword\""));
assertThat(a, containsString("{\"avg\":{\"field\":\"int\"}"));
}
}