package org.araqne.logdb.query.parser; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Stack; import org.araqne.logdb.AbstractQueryCommandParser; import org.araqne.logdb.DefaultQuery; import org.araqne.logdb.Query; import org.araqne.logdb.QueryCommand; import org.araqne.logdb.QueryContext; import org.araqne.logdb.QueryParseException; import org.araqne.logdb.QueryParserService; import org.araqne.logdb.QueryResultFactory; import org.araqne.logdb.Row; import org.araqne.logdb.query.command.Join; import org.araqne.logdb.query.command.Join.JoinType; import org.araqne.logdb.query.command.Sort.SortField; 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; public class JoinParser extends AbstractQueryCommandParser { private QueryParserService parserService; private QueryResultFactory resultFactory; public JoinParser(QueryParserService parserService, QueryResultFactory resultFactory) { this.parserService = parserService; this.resultFactory = resultFactory; setDescriptions("Join sub query result set. You should use `streamjoin` instead of `join` in stream query.", "서브 쿼리 결과 집합을 조인합니다. 스트림 쿼리에서는 join 명령어가 지원되지 않으므로, 이후에 설명할 streamjoin 명령어를 사용해야 합니다."); setOptions("type", OPTIONAL, "`inner`, `left`, `right`, or `full`", "조인 유형을 지정합니다. left 지정 시 조인 키가 일치하지 않더라도 입력 데이터가 출력됩니다."); } @Override public String getCommandName() { return "join"; } private JoinType getJoinType(Map<String, Object> options) { JoinType joinType = JoinType.Inner; if (options.containsKey("type")) { String type = (String) options.get("type"); if (type.equals("left")) { joinType = JoinType.Left; } else if (type.equals("inner")) { joinType = JoinType.Inner; } else if (type.equals("right")) { joinType = JoinType.Right; } else if (type.equals("full")) { joinType = JoinType.Full; } } return joinType; } @Override public QueryCommand parse(QueryContext context, String commandString) { ParseResult r = QueryTokenizer.parseOptions(context, commandString, getCommandName().length(), Arrays.asList("type"), getFunctionRegistry()); @SuppressWarnings("unchecked") Map<String, Object> options = (Map<String, Object>) r.value; JoinType joinType = getJoinType(options); // parsing rule for join OpEmitterFactory of = new JoinOpEmitterFactory(); TermEmitterFactory tf = new JoinTermEmitterFactory(); FuncEmitterFactory ff = new FuncEmitterFactory() { @Override public void emit(QueryContext context, Stack<Expression> exprStack, FuncTerm f) { throw new QueryParseException("unexpected-function", -1, "function is [" + f + "]"); } }; if (r.next < 0) r.next = 0; int b = commandString.indexOf('[', r.next); if (b < 0) throw new QueryParseException("no-subquery", -1, "join query has no subquery"); String fieldString = commandString.substring(r.next, b); Expression fExpr = ExpressionParser.parse(context, fieldString, new ParsingRule(JoinOpTerm.NOP, of, ff, tf)); SortField[] sortFieldArray = null; if (fExpr instanceof FieldTerm) sortFieldArray = new SortField[] { SortField.class.cast(fExpr.eval(null)) }; else if (fExpr instanceof Comma) { @SuppressWarnings("unchecked") List<Object> fl = (List<Object>) fExpr.eval(null); sortFieldArray = fl.toArray(new SortField[0]); } else { throw new QueryParseException("unexpected-expression", -1, "expression is [" + fieldString + "]"); } Expression sqExpr = ExpressionParser.parse(context, commandString.substring(b), new ParsingRule(JoinOpTerm.NOP, of, ff, tf)); if (!(sqExpr instanceof SubQueryTerm)) throw new QueryParseException("no-subquery", -1, "join query has no subquery"); String subQueryString = SubQueryTerm.class.cast(sqExpr).getSubQuery(); QueryContext subQueryContext = new QueryContext(context.getSession(), context); List<QueryCommand> subCommands = parserService.parseCommands(subQueryContext, subQueryString); Query subQuery = new DefaultQuery(subQueryContext, subQueryString, subCommands, resultFactory); return new Join(joinType, sortFieldArray, subQuery); } private static enum JoinOpTerm implements OpTerm { Asc("+", 500, false, true, false), Desc("-", 500, false, true, false), Comma(",", 200), ListEndComma(",", 200), NOP("", 0, true, false, true); private JoinOpTerm(String symbol, int precedence) { this(symbol, precedence, true, false, false); } private JoinOpTerm(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; } public final String symbol; public final int precedence; public final boolean leftAssoc; public final boolean unary; public final boolean isAlpha; @Override public String toString() { return symbol; } @Override public boolean isInstance(Object o) { return o instanceof JoinOpTerm; } @Override public OpTerm parse(String token) { for (JoinOpTerm op : values()) { if (op.symbol.equals(token) && op != NOP) { return op; } } return null; } @Override public boolean isDelimiter(String s) { for (JoinOpTerm op : values()) { if (!op.isAlpha && op.symbol.equals(s)) { return true; } } return false; } @Override public List<OpTerm> delimiters() { List<OpTerm> delims = new ArrayList<OpTerm>(); for (JoinOpTerm op : values()) { if (!op.isAlpha) { delims.add(op); } } return delims; } @Override public boolean isUnary() { return unary; } @Override public boolean isAlpha() { return isAlpha; } @Override public String getSymbol() { return symbol; } @Override public int getPrecedence() { return precedence; } @Override public boolean isLeftAssoc() { return leftAssoc; } @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 JoinOpEmitterFactory implements OpEmitterFactory { @Override public void emit(Stack<Expression> exprStack, Term term) { JoinOpTerm op = JoinOpTerm.class.cast(term); // is unary op? if (op.isUnary()) { Expression expr = exprStack.pop(); if (!(expr instanceof FieldTerm)) throw new QueryParseException("unexpected-expression", -1, "expression is [" + expr + "]"); FieldTerm t = FieldTerm.class.cast(expr); switch (op) { case Asc: { exprStack.push(t); break; } case Desc: { t.toggleAsc(); exprStack.push(t); break; } default: throw new QueryParseException("unsupported-operator", -1, "unsupported unary operator [" + op.toString() + "]"); } return; } // reversed order by stack if (exprStack.size() < 2) throw new QueryParseException("broken-expression", -1, "operator is [" + op + "]"); 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 QueryParseException("unsupported-operator", -1, "unsupported unary operator [" + op.toString() + "]"); } } } private static class SubQueryTerm implements Expression { private final String subquery; public SubQueryTerm(String subquery) { this.subquery = subquery; } @Override public Object eval(Row row) { return subquery; } public String getSubQuery() { return subquery; } } private static class FieldTerm implements Expression { private final String field; private boolean asc; public FieldTerm(String field) { this.field = field; this.asc = true; } public boolean toggleAsc() { asc = !asc; return asc; } @Override public Object eval(Row row) { return new SortField(field, asc); } } private static class JoinTermEmitterFactory implements TermEmitterFactory { @Override public void emit(Stack<Expression> exprStack, TokenTerm t) { if (!t.getText().equals("(") && !t.getText().equals(")")) { String token = ((TokenTerm) t).getText().trim(); Expression expr = parseTokenExpr(exprStack, token); exprStack.add(expr); } } private Expression parseTokenExpr(Stack<Expression> exprStack, String token) { // sub query if (token.startsWith("[") && token.endsWith("]")) { String subQueryString = token.substring(1, token.length() - 1).trim(); SubQueryTerm term = new SubQueryTerm(subQueryString); return term; } return new FieldTerm(token); } } }