/*
* Copyright 2013 Future Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.araqne.logdb.query.parser;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.araqne.log.api.LogParserFactoryRegistry;
import org.araqne.log.api.LogParserRegistry;
import org.araqne.logdb.AbstractQueryCommandParser;
import org.araqne.logdb.AccountService;
import org.araqne.logdb.Permission;
import org.araqne.logdb.QueryCommand;
import org.araqne.logdb.QueryContext;
import org.araqne.logdb.QueryErrorMessage;
import org.araqne.logdb.QueryParseException;
import org.araqne.logdb.Row;
import org.araqne.logdb.TimeSpan;
import org.araqne.logdb.query.command.StorageObjectName;
import org.araqne.logdb.query.command.Table;
import org.araqne.logdb.query.command.Table.TableParams;
import org.araqne.logdb.query.expr.Comma;
import org.araqne.logdb.query.expr.Expression;
import org.araqne.logdb.query.parser.ExpressionParser.FuncTerm;
import org.araqne.logdb.query.parser.ExpressionParser.TokenTerm;
import org.araqne.logstorage.LogStorage;
import org.araqne.logstorage.LogStorageStatus;
import org.araqne.logstorage.LogTableRegistry;
public class TableParser extends AbstractQueryCommandParser {
private AccountService accountService;
private LogStorage logStorage;
private LogTableRegistry tableRegistry;
private LogParserFactoryRegistry parserFactoryRegistry;
private LogParserRegistry parserRegistry;
public TableParser(AccountService accountService, LogStorage logStorage, LogTableRegistry tableRegistry,
LogParserFactoryRegistry parserFactoryRegistry, LogParserRegistry parserRegistry) {
this.accountService = accountService;
this.logStorage = logStorage;
this.tableRegistry = tableRegistry;
this.parserFactoryRegistry = parserFactoryRegistry;
this.parserRegistry = parserRegistry;
setDescriptions("Scan all tuples from tables.", "로그프레소 테이블에 저장된 데이터를 조회합니다.");
setOptions("offset", false, "Skip count", "건너 뛸 로그 갯수");
setOptions("limit", false, "Max output count", "가져올 최대 로그 갯수");
setOptions("duration", false,
"Scan only recent data. You should use s(second), m(minute), h(hour), d(day), mon(month) time unit. For example, `10s` means data from 10 seconds earlier.",
"현재 시각으로부터 일정 시간 범위 이내의 로그로 한정. s(초), m(분), h(시), d(일), mon(월) 단위로 지정할 수 있습니다. 예를 들면, 10s의 경우 현재 시각으로부터 10초 이전까지의 범위를 의미합니다.");
setOptions("from", false, "Start time of range. yyyyMMddHHmmss format. If you omit time part, it will be padded by zero.",
"yyyyMMddHHmmss 포맷으로 범위의 시작을 지정합니다. 뒷자리를 쓰지 않으면 0으로 채워집니다.");
setOptions("to", false, "End time of range. yyyyMMddHHmmss format. If you omit time part, it will be padded by zero.",
"yyyyMMddHHmmss 포맷으로 범위의 끝을 지정합니다. 뒷자리를 쓰지 않으면 0으로 채워집니다.");
setOptions("window", false,
"Receive table input in realtime for specified duration. You should use s(second), m(minute), h(hour), d(day), mon(month) time unit. For example, `10s` means real-time data for 10 seconds timeout.",
"쿼리 시작 시점으로부터 일정 시간 동안 테이블 입력을 수신합니다. s(초), m(분), h(시), d(일), mon(월) 단위로 지정할 수 있습니다. 예를 들면, 10s의 경우 쿼리 시작 후 10초 동안 입력을 수신합니다. window 옵션을 사용하는 경우 from, to, duration 옵션을 사용할 수 없습니다.");
}
@Override
public String getCommandName() {
return "table";
}
@Override
public Map<String, QueryErrorMessage> getErrorMessages() {
Map<String, QueryErrorMessage> m = new HashMap<String, QueryErrorMessage>();
m.put("10600", new QueryErrorMessage("archive-not-opened", "저장소가 닫혀 있습니다."));
m.put("10601", new QueryErrorMessage("negative-offset", "offset 값은 0보다 크거나 같아야 합니다: 입력값=[offset]."));
m.put("10602", new QueryErrorMessage("negative-limit", "입력값이 허용 범위를 벗어났습니다: limit=[offset]."));
m.put("10603", new QueryErrorMessage("invalid-table-spec", "[options]에서 [exp] 잘못된 옵션입니다."));
m.put("10604", new QueryErrorMessage("no-table-data-source", "테이블이 없습니다."));
m.put("10605", new QueryErrorMessage("table-not-found", "테이블 [table]이(가) 존재하지 않습니다."));
m.put("10606", new QueryErrorMessage("no-read-permission", "테이블 [table] 읽기 권한이 없습니다."));
m.put("10607", new QueryErrorMessage("table-not-found", "테이블 [table]이(가) 존재하지 않습니다."));
m.put("10608", new QueryErrorMessage("no-read-permission", "테이블 [table] 읽기 권한이 없습니다."));
return m;
}
@SuppressWarnings("unchecked")
@Override
public QueryCommand parse(QueryContext context, String commandString) {
if (logStorage.getStatus() != LogStorageStatus.Open)
// throw new QueryParseException("archive-not-opened", -1);
throw new QueryParseException("10600", -1, -1, null);
ParseResult r = QueryTokenizer.parseOptions(context, commandString, getCommandName().length(),
Arrays.asList("from", "to", "offset", "limit", "duration", "parser", "order", "window", "raw", "eachtable"),
getFunctionRegistry());
Map<String, String> options = (Map<String, String>) r.value;
String tableTokens = commandString.substring(r.next);
List<TableSpec> tableNames = parseTableNames(context, tableTokens);
Date from = null;
Date to = null;
long offset = 0;
long limit = 0;
String parser = null;
boolean ordered = true;
boolean asc = false;
TimeSpan window = null;
boolean eachTable = false;
if (options.containsKey("window"))
window = TimeSpan.parse(options.get("window"));
if (options.containsKey("duration")) {
String duration = options.get("duration");
int i;
for (i = 0; i < duration.length(); i++) {
char c = duration.charAt(i);
if (!('0' <= c && c <= '9'))
break;
}
int value = Integer.parseInt(duration.substring(0, i));
from = QueryTokenizer.getDuration(value, duration.substring(i));
}
if (options.containsKey("from"))
from = QueryTokenizer.getDate(options.get("from"));
if (options.containsKey("to"))
to = QueryTokenizer.getDate(options.get("to"));
if (options.containsKey("offset"))
offset = Long.parseLong(options.get("offset"));
if (offset < 0) {
// throw new QueryParseException("negative-offset", -1);
Map<String, String> param = new HashMap<String, String>();
param.put("offset", options.get("offset"));
int offsetS = QueryTokenizer.findKeyword(commandString, options.get("offset"));
throw new QueryParseException("10601", offsetS, offsetS + options.get("offset").length() - 1, param);
}
if (options.containsKey("limit"))
limit = Long.parseLong(options.get("limit"));
if (limit < 0) {
// throw new QueryParseException("negative-limit", -1);
Map<String, String> param = new HashMap<String, String>();
param.put("limit", options.get("limit"));
int offsetS = QueryTokenizer.findKeyword(commandString, options.get("limit"));
throw new QueryParseException("10602", offsetS, offsetS + options.get("limit").length() - 1, param);
}
if (options.get("parser") != null)
parser = options.get("parser");
String orderOpt = options.get("order");
if (orderOpt != null) {
ordered = !orderOpt.equals("f");
asc = orderOpt.equals("asc");
}
eachTable = CommandOptions.parseBoolean(options.get("eachtable"));
TableParams params = new TableParams();
params.setTableSpecs(tableNames);
params.setOffset(offset);
params.setLimit(limit);
params.setFrom(from);
params.setTo(to);
params.setParserName(parser);
params.setOrdered(ordered);
params.setAsc(asc);
params.setWindow(window);
params.setEachTable(eachTable);
params.setRaw(CommandOptions.parseBoolean(options.get("raw")));
if (params.isRaw()) {
if (context.getSession() == null || !context.getSession().isAdmin())
throw new QueryParseException("no-raw-permission", -1);
}
Table table = new Table(params);
table.setAccountService(accountService);
table.setTableRegistry(tableRegistry);
table.setStorage(logStorage);
table.setParserFactoryRegistry(parserFactoryRegistry);
table.setParserRegistry(parserRegistry);
return table;
}
private static enum OpTermI implements OpTerm {
Comma(",", 200), ListEndComma(",", 200), NOP("", 0, true, false, true);
private String symbol;
private int precedence;
private boolean leftAssoc;
@SuppressWarnings("unused")
private boolean unary;
private boolean isAlpha;
OpTermI(String symbol, int precedence) {
this(symbol, precedence, true, false, false);
}
OpTermI(String symbol, int precedence, boolean leftAssoc, boolean unary, boolean isAlpha) {
this.symbol = symbol;
this.precedence = precedence;
this.leftAssoc = leftAssoc;
this.unary = unary;
this.isAlpha = isAlpha;
}
@Override
public String toString() {
return symbol;
}
@Override
public OpTerm parse(String token) {
for (OpTermI op : values()) {
if (op.symbol.equals(token) && op != NOP) {
return op;
}
}
return null;
}
@Override
public boolean isInstance(Object o) {
return o instanceof OpTermI;
}
@Override
public boolean isUnary() {
return false;
}
@Override
public boolean isAlpha() {
return isAlpha;
}
@Override
public boolean isLeftAssoc() {
return leftAssoc;
}
@Override
public boolean isDelimiter(String s) {
for (OpTermI op : values()) {
if (!op.isAlpha && op.symbol.equals(s)) {
return true;
}
}
return false;
}
@Override
public String getSymbol() {
return symbol;
}
@Override
public int getPrecedence() {
return precedence;
}
@Override
public List<OpTerm> delimiters() {
List<OpTerm> delims = new ArrayList<OpTerm>();
for (OpTermI op : values()) {
if (!op.isAlpha()) {
delims.add(op);
}
}
return delims;
}
@Override
public OpTerm postProcessCloseParen() {
if (!this.equals(Comma)) {
return this;
}
return ListEndComma;
}
@Override
public boolean hasAltOp() {
return false;
}
@Override
public OpTerm getAltOp() {
return null;
}
}
private static class OpEmitterFactoryI implements OpEmitterFactory {
@Override
public void emit(Stack<Expression> exprStack, Term term) {
OpTermI op = (OpTermI) term;
Expression rhs = exprStack.pop();
Expression lhs = exprStack.pop();
switch (op) {
case Comma:
exprStack.add(new Comma(lhs, rhs));
break;
case ListEndComma:
exprStack.add(new Comma(lhs, rhs, true));
break;
default:
throw new IllegalStateException("unsupported operator " + op.toString());
}
}
};
private static class StringConstant implements Expression {
private String token;
public StringConstant(String token) {
this.token = token;
}
@Override
public Object eval(Row map) {
if (token.startsWith("\"") && token.endsWith("\""))
return token.substring(1, token.length() - 1);
else
return token.toString();
}
public String toString() {
return token;
}
}
private static class TermEmitterFactoryI implements TermEmitterFactory {
@Override
public void emit(Stack<Expression> exprStack, TokenTerm t) {
exprStack.add(new StringConstant(t.toString()));
}
}
private static class MetaS implements TableSpec {
private Expression predicate;
private TableSpec pattern;
private MetadataMatcher<TableSpec> mm;
public MetaS(Expression pred, TableSpec pat) {
this.predicate = pred;
this.pattern = pat;
this.mm = new MetadataMatcher<TableSpec>(predicate.eval(new Row()).toString(), Arrays.asList(pattern));
}
public Object clone() {
return new MetaS(predicate, pattern);
}
@Override
public String getSpec() {
return toString();
}
@Override
public List<StorageObjectName> match(LogTableRegistry logTableRegistry) {
return mm.match(logTableRegistry);
}
@Override
public String getNamespace() {
return pattern.getNamespace();
}
@Override
public void setNamespace(String ns) {
pattern.setNamespace(ns);
}
@Override
public String getTable() {
return pattern.getTable();
}
@Override
public void setTable(String tableName) {
pattern.setTable(tableName);
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("meta(");
sb.append(predicate.toString());
sb.append(", " + pattern.toString());
sb.append(")");
return sb.toString();
}
@Override
public boolean isOptional() {
return true;
}
@Override
public void setOptional(boolean optional) {
throw new UnsupportedOperationException("set table-metadata matcher as not-optional is not supported");
}
}
private static class Meta implements Expression {
private List<TableSpec> patterns;
private MetadataMatcher<TableSpec> mm;
private String predStr;
private List<Expression> args;
public Meta(List<Expression> args) {
this.args = args;
Expression predicate = args.get(0);
this.predStr = predicate.toString();
this.patterns = new ArrayList<TableSpec>();
for (Expression e : args.subList(1, args.size())) {
try {
this.patterns.add(new WildcardTableSpec(e.eval(new Row()).toString()));
} catch (IllegalArgumentException exc) {
// throw new QueryParseException("invalid-table-spec", -1,
// e.toString());
Map<String, String> param = new HashMap<String, String>();
try {
param.put("options", args.toString());
param.put("exp", e.toString());
} catch (Throwable t) {
}
throw new QueryParseException("10603", -1, -1, param);
}
}
if (args.size() < 2) {
this.patterns.add(new WildcardTableSpec("*"));
}
this.mm = new MetadataMatcher<TableSpec>(predicate.eval(new Row()).toString(), patterns);
}
public Object clone() {
return new Meta(args);
}
@Override
public Object eval(Row map) {
List<TableSpec> result = new ArrayList<TableSpec>();
for (TableSpec ts : patterns) {
result.add(new MetaS(args.get(0), ts));
}
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("meta(");
sb.append(predStr);
for (TableSpec spec : patterns) {
sb.append(", " + spec.toString());
}
sb.append(")");
return sb.toString();
}
}
private static class FuncEmitterFactoryI implements FuncEmitterFactory {
public FuncEmitterFactoryI() {
}
@Override
public void emit(QueryContext context, Stack<Expression> exprStack, FuncTerm f) {
String name = f.getName();
List<Expression> args = getArgsFromStack(f, exprStack);
if (name.equals("meta")) {
exprStack.add(new Meta(args));
}
}
private List<Expression> getArgsFromStack(FuncTerm f, Stack<Expression> exprStack) {
List<Expression> exprs = null;
if (exprStack.isEmpty() || !f.hasArgument())
return new ArrayList<Expression>();
Expression arg = exprStack.pop();
if (arg instanceof Comma) {
exprs = ((Comma) arg).getList();
} else {
exprs = new ArrayList<Expression>();
exprs.add(arg);
}
return exprs;
}
}
@SuppressWarnings("unchecked")
private List<TableSpec> parseTableNames(QueryContext context, String tableTokens) {
List<TableSpec> tableNames = new ArrayList<TableSpec>();
OpEmitterFactory of = new OpEmitterFactoryI();
FuncEmitterFactory ff = new FuncEmitterFactoryI();
TermEmitterFactory tf = new TermEmitterFactoryI();
Expression expr = ExpressionParser.parse(context, tableTokens, new ParsingRule(OpTermI.NOP, of, ff, tf));
Object evalResult = expr.eval(new Row());
if (evalResult instanceof List) {
for (Object o : (List<Object>) evalResult) {
addTableSpec(tableNames, context, o);
}
} else {
addTableSpec(tableNames, context, evalResult);
}
if (tableNames.isEmpty()) {
// throw new QueryParseException("no-table-data-source", -1);
Map<String, String> params = new HashMap<String, String>();
params.put("value", tableTokens);
throw new QueryParseException("10604", -1, -1, params);
}
return tableNames;
}
private void addTableSpec(List<TableSpec> target, QueryContext context, Object spec) {
if (spec instanceof TableSpec) {
target.add((TableSpec) spec);
} else if (spec instanceof List) {
@SuppressWarnings("unchecked")
List<Object> specs = (List<Object>) spec;
for (Object o : specs) {
addTableSpec(target, context, o);
}
} else {
WildcardTableSpec wspec = new WildcardTableSpec(spec.toString());
if (!(wspec.hasWildcard() || wspec.isOptional())) {
List<StorageObjectName> sonList = wspec.match(tableRegistry);
StorageObjectName son = sonList.get(0);
if (son.getNamespace() == null) {
// check only local tables
if (!son.isOptional() && !tableRegistry.exists(son.getTable())) {
// throw new QueryParseException("table-not-found", -1,
// "table=" + son.toString());
String table = son.toString();
Map<String, String> param = new HashMap<String, String>();
param.put("table", table);
throw new QueryParseException("10605", -1, -1, param);
}
if (!accountService.checkPermission(context.getSession(), son.getTable(), Permission.READ)) {
// throw new QueryParseException("no-read-permission",
// -1, "table=" + son.toString());
String table = son.toString();
Map<String, String> param = new HashMap<String, String>();
param.put("table", table);
throw new QueryParseException("10606", -1, -1, param);
}
}
}
target.add(wspec);
}
}
private void addTableNames(List<String> target, QueryContext context, String fqdn) {
// strip namespace
String name = fqdn;
String namespace = null;
int pos = fqdn.lastIndexOf(':');
if (pos >= 0) {
namespace = fqdn.substring(0, pos);
name = fqdn.substring(pos + 1);
// XXX
if (name.startsWith("`") && name.endsWith("`")) {
name = name.substring(1, name.length() - 1);
fqdn = namespace + ":" + name;
}
} else {
// XXX
if (name.startsWith("`") && name.endsWith("`")) {
name = name.substring(1, name.length() - 1);
fqdn = name;
}
}
if (namespace == null && !name.contains("*")) {
// check only local tables
if (!tableRegistry.exists(name)) {
// throw new QueryParseException("table-not-found", -1, "table="
// + fqdn);
Map<String, String> params = new HashMap<String, String>();
params.put("table", fqdn);
throw new QueryParseException("10607", -1, -1, params);
}
if (!accountService.checkPermission(context.getSession(), name, Permission.READ)) {
// throw new QueryParseException("no-read-permission", -1,
// "table=" + fqdn);
Map<String, String> params = new HashMap<String, String>();
params.put("table", fqdn);
throw new QueryParseException("10608", -1, -1, params);
}
}
target.add(fqdn);
}
}