package edu.caltech.nanodb.sqlparse; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import edu.caltech.nanodb.commands.ShowSystemStatsCommand; import edu.caltech.nanodb.expressions.ArithmeticOperator; import edu.caltech.nanodb.expressions.BooleanOperator; import edu.caltech.nanodb.expressions.ColumnName; import edu.caltech.nanodb.expressions.ColumnValue; import edu.caltech.nanodb.expressions.CompareOperator; import edu.caltech.nanodb.expressions.DateTimeUtils; import edu.caltech.nanodb.expressions.ExistsOperator; import edu.caltech.nanodb.expressions.InSubqueryOperator; import edu.caltech.nanodb.expressions.InValuesOperator; import edu.caltech.nanodb.expressions.IsNullOperator; import edu.caltech.nanodb.expressions.LiteralValue; import edu.caltech.nanodb.expressions.NegateOperator; import edu.caltech.nanodb.expressions.ScalarSubquery; import edu.caltech.nanodb.expressions.StringMatchOperator; import edu.caltech.nanodb.functions.FunctionDirectory; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.tree.TerminalNode; import edu.caltech.nanodb.commands.AnalyzeCommand; import edu.caltech.nanodb.commands.BeginTransactionCommand; import edu.caltech.nanodb.commands.Command; import edu.caltech.nanodb.commands.CommandProperties; import edu.caltech.nanodb.commands.CommitTransactionCommand; import edu.caltech.nanodb.commands.ConstraintDecl; import edu.caltech.nanodb.commands.CrashCommand; import edu.caltech.nanodb.commands.CreateIndexCommand; import edu.caltech.nanodb.commands.CreateTableCommand; import edu.caltech.nanodb.commands.DeleteCommand; import edu.caltech.nanodb.commands.DropIndexCommand; import edu.caltech.nanodb.commands.DropTableCommand; import edu.caltech.nanodb.commands.DumpIndexCommand; import edu.caltech.nanodb.commands.DumpTableCommand; import edu.caltech.nanodb.commands.ExitCommand; import edu.caltech.nanodb.commands.ExplainCommand; import edu.caltech.nanodb.commands.FlushCommand; import edu.caltech.nanodb.commands.InsertCommand; import edu.caltech.nanodb.commands.OptimizeCommand; import edu.caltech.nanodb.commands.RollbackTransactionCommand; import edu.caltech.nanodb.commands.SelectCommand; import edu.caltech.nanodb.commands.SetPropertyCommand; import edu.caltech.nanodb.commands.ShowTableStatsCommand; import edu.caltech.nanodb.commands.ShowTablesCommand; import edu.caltech.nanodb.commands.ShowPropertiesCommand; import edu.caltech.nanodb.commands.TableColumnDecl; import edu.caltech.nanodb.commands.UpdateCommand; import edu.caltech.nanodb.commands.VerifyCommand; import edu.caltech.nanodb.expressions.Expression; import edu.caltech.nanodb.expressions.FunctionCall; import edu.caltech.nanodb.expressions.OrderByExpression; import edu.caltech.nanodb.queryast.FromClause; import edu.caltech.nanodb.queryast.SelectClause; import edu.caltech.nanodb.queryast.SelectValue; import edu.caltech.nanodb.relations.ColumnInfo; import edu.caltech.nanodb.relations.ColumnType; import edu.caltech.nanodb.relations.ForeignKeyValueChangeOption; import edu.caltech.nanodb.relations.JoinType; import edu.caltech.nanodb.relations.SQLDataType; import edu.caltech.nanodb.relations.TableConstraintType; /** * This class translates NanoSQL parse trees generated by the ANTLR4 grammar * into the corresponding hierarchy of {@link Command} and {@link Expression} * objects used by the server for command execution. */ public class NanoSQLTranslator extends NanoSQLBaseVisitor<Object> { /** A function directory for resolving function calls. */ private FunctionDirectory functionDirectory; public NanoSQLTranslator(FunctionDirectory functionDirectory) { this.functionDirectory = functionDirectory; } public NanoSQLTranslator() { this(null); } /*======================================================================*/ /* ALL COMMAND RULES */ /*======================================================================*/ //=== COMMANDS =========================================================== @Override public Object visitCommands(NanoSQLParser.CommandsContext ctx) { ArrayList<Command> commands = new ArrayList<>(); for (NanoSQLParser.CommandContext cc : ctx.command()) commands.add((Command) visit(cc)); return commands; } @Override public Object visitCommand(NanoSQLParser.CommandContext ctx) { return visit(ctx.commandNoSemicolon()); } //=== TRANSACTIONS ======================================================= @Override public Object visitBeginTxnStmt(NanoSQLParser.BeginTxnStmtContext ctx) { return new BeginTransactionCommand(); } @Override public Object visitCommitTxnStmt(NanoSQLParser.CommitTxnStmtContext ctx) { return new CommitTransactionCommand(); } @Override public Object visitRollbackTxnStmt(NanoSQLParser.RollbackTxnStmtContext ctx) { return new RollbackTransactionCommand(); } //=== GENERAL UTILITY COMMANDS =========================================== @Override public Object visitFlushStmt(NanoSQLParser.FlushStmtContext ctx) { return new FlushCommand(); } @Override public Object visitExitStmt(NanoSQLParser.ExitStmtContext ctx) { return new ExitCommand(); } @Override public Object visitCrashStmt(NanoSQLParser.CrashStmtContext ctx) { int secs = 0; if (ctx.INT_LITERAL() != null) secs = Integer.parseInt(ctx.INT_LITERAL().getText()); return new CrashCommand(secs); } @Override public Object visitShowPropsStmt(NanoSQLParser.ShowPropsStmtContext ctx) { ShowPropertiesCommand cmd = new ShowPropertiesCommand(); if (ctx.pattern != null) cmd.setFilter(ctx.pattern.getText().toLowerCase()); return cmd; } @Override public Object visitSetPropStmt(NanoSQLParser.SetPropStmtContext ctx) { String propName = ctx.name.getText(); // Remove the quotes from around the property name propName = propName.substring(1, propName.length() - 1); return new SetPropertyCommand(propName, (Expression) visit(ctx.expression())); } @Override public Object visitShowSystemStatsStmt(NanoSQLParser.ShowSystemStatsStmtContext ctx) { String systemName = ctx.name.getText(); // Remove the quotes from around the subsystem name systemName = systemName.substring(1, systemName.length() - 1); return new ShowSystemStatsCommand(systemName); } //=== TABLE/INDEX UTILITY COMMANDS ======================================= @Override public Object visitShowTablesStmt(NanoSQLParser.ShowTablesStmtContext ctx) { return new ShowTablesCommand(); } @Override public Object visitAnalyzeStmt(NanoSQLParser.AnalyzeStmtContext ctx) { AnalyzeCommand cmd = new AnalyzeCommand(); for (TerminalNode n : ctx.IDENT()) cmd.addTable(n.getText().toLowerCase()); return cmd; } @Override public Object visitOptimizeStmt(NanoSQLParser.OptimizeStmtContext ctx) { OptimizeCommand cmd = new OptimizeCommand(); for (TerminalNode n : ctx.IDENT()) cmd.addTable(n.getText().toLowerCase()); return cmd; } @Override public Object visitVerifyStmt(NanoSQLParser.VerifyStmtContext ctx) { VerifyCommand cmd = new VerifyCommand(); for (TerminalNode n : ctx.IDENT()) cmd.addTable(n.getText().toLowerCase()); return cmd; } @Override public Object visitDumpTableStmt(NanoSQLParser.DumpTableStmtContext ctx) { String filename = null; if (ctx.fileName != null) { filename = ctx.fileName.getText(); // Remove the quotes from around the filename filename = filename.substring(1, filename.length() - 1); } String format = null; if (ctx.format != null) { format = ctx.format.getText().toLowerCase(); // Remove the quotes from around the format format = format.substring(1, format.length() - 1); } return new DumpTableCommand(ctx.tableName.getText().toLowerCase(), filename, format); } @Override public Object visitDumpIndexStmt(NanoSQLParser.DumpIndexStmtContext ctx) { String filename = null; if (ctx.fileName != null) { filename = ctx.fileName.getText(); // Remove the quotes from around the filename filename = filename.substring(1, filename.length() - 1); } String format = null; if (ctx.format != null) { format = ctx.format.getText().toLowerCase(); // Remove the quotes from around the format format = format.substring(1, format.length() - 1); } return new DumpIndexCommand(ctx.indexName.getText().toLowerCase(), ctx.tableName.getText().toLowerCase(), filename, format); } @Override public Object visitShowTableStatsStmt(NanoSQLParser.ShowTableStatsStmtContext ctx) { return new ShowTableStatsCommand(ctx.tableName.getText().toLowerCase()); } //=== DDL OPERATIONS ===================================================== @Override public Object visitCmdProperties(NanoSQLParser.CmdPropertiesContext ctx) { CommandProperties cmdProps = new CommandProperties(); for (int i = 0; i < ctx.IDENT().size(); i++) { String name = ctx.IDENT(i).getText().toLowerCase(); Object value = visit(ctx.literalValue(i)); cmdProps.set(name, value); } return cmdProps; } @Override public Object visitCreateTableStmt(NanoSQLParser.CreateTableStmtContext ctx) { String tableName = ctx.tableName.getText().toLowerCase(); boolean temporary = (ctx.TEMPORARY() != null); boolean ifNotExists = (ctx.EXISTS() != null); CreateTableCommand cmd = new CreateTableCommand(tableName); cmd.setTemporary(temporary); cmd.setIfNotExists(ifNotExists); for (NanoSQLParser.TableColDeclContext tcdCtx : ctx.tableColDecl()) { TableColumnDecl colDecl = (TableColumnDecl) visit(tcdCtx); cmd.addColumn(colDecl); } for (NanoSQLParser.TableConstraintContext tcCtx : ctx.tableConstraint()) { ConstraintDecl constraint = (ConstraintDecl) visit(tcCtx); cmd.addConstraint(constraint); } if (ctx.cmdProperties() != null) cmd.setProperties((CommandProperties) visit(ctx.cmdProperties())); return cmd; } @Override public Object visitTableColDecl(NanoSQLParser.TableColDeclContext ctx) { String columnName = ctx.columnName.getText().toLowerCase(); ColumnType columnType = (ColumnType) visit(ctx.columnType()); ColumnInfo colInfo = new ColumnInfo(columnName, columnType); TableColumnDecl colDecl = new TableColumnDecl(colInfo); for (NanoSQLParser.ColumnConstraintContext ccc : ctx.columnConstraint()) { ConstraintDecl constraint = (ConstraintDecl) visit(ccc); constraint.addColumn(columnName); colDecl.addConstraint(constraint); } return colDecl; } @Override public Object visitColTypeInt(NanoSQLParser.ColTypeIntContext ctx) { return ColumnType.INTEGER; } @Override public Object visitColTypeBigInt(NanoSQLParser.ColTypeBigIntContext ctx) { return ColumnType.BIGINT; } @Override public Object visitColTypeDecimal(NanoSQLParser.ColTypeDecimalContext ctx) { ColumnType colType = new ColumnType(SQLDataType.NUMERIC); if (ctx.precision != null) colType.setPrecision(Integer.parseInt(ctx.precision.getText())); if (ctx.scale != null) colType.setScale(Integer.parseInt(ctx.scale.getText())); return colType; } @Override public Object visitColTypeFloat(NanoSQLParser.ColTypeFloatContext ctx) { return ColumnType.FLOAT; } @Override public Object visitColTypeDouble(NanoSQLParser.ColTypeDoubleContext ctx) { return ColumnType.DOUBLE; } @Override public Object visitColTypeChar(NanoSQLParser.ColTypeCharContext ctx) { ColumnType colType = new ColumnType(SQLDataType.CHAR); colType.setLength(Integer.parseInt(ctx.length.getText())); return colType; } @Override public Object visitColTypeVarChar(NanoSQLParser.ColTypeVarCharContext ctx) { ColumnType colType = new ColumnType(SQLDataType.VARCHAR); colType.setLength(Integer.parseInt(ctx.length.getText())); return colType; } @Override public Object visitColTypeDate(NanoSQLParser.ColTypeDateContext ctx) { return ColumnType.DATE; } @Override public Object visitColTypeTime(NanoSQLParser.ColTypeTimeContext ctx) { return ColumnType.TIME; } @Override public Object visitColTypeDateTime(NanoSQLParser.ColTypeDateTimeContext ctx) { return ColumnType.DATETIME; } @Override public Object visitColTypeTimestamp(NanoSQLParser.ColTypeTimestampContext ctx) { return ColumnType.TIMESTAMP; } @Override public Object visitColConstraintNotNull(NanoSQLParser.ColConstraintNotNullContext ctx) { String name = null; if (ctx.constraintName != null) name = ctx.constraintName.getText().toLowerCase(); return new ConstraintDecl(TableConstraintType.NOT_NULL, name, true); } @Override public Object visitColConstraintUnique(NanoSQLParser.ColConstraintUniqueContext ctx) { String name = null; if (ctx.constraintName != null) name = ctx.constraintName.getText().toLowerCase(); return new ConstraintDecl(TableConstraintType.UNIQUE, name, true); } @Override public Object visitColConstraintPrimaryKey(NanoSQLParser.ColConstraintPrimaryKeyContext ctx) { String name = null; if (ctx.constraintName != null) name = ctx.constraintName.getText().toLowerCase(); return new ConstraintDecl(TableConstraintType.PRIMARY_KEY, name, true); } @Override public Object visitColConstraintForeignKey(NanoSQLParser.ColConstraintForeignKeyContext ctx) { String name = null; if (ctx.constraintName != null) name = ctx.constraintName.getText().toLowerCase(); ConstraintDecl decl = new ConstraintDecl(TableConstraintType.FOREIGN_KEY, name, true); decl.setRefTable(ctx.refTableName.getText().toLowerCase()); if (ctx.refColumnName != null) decl.addRefColumn(ctx.refColumnName.getText().toLowerCase()); if (ctx.DELETE() != null) decl.setOnDeleteOption((ForeignKeyValueChangeOption) visit(ctx.delOpt)); if (ctx.UPDATE() != null) decl.setOnUpdateOption((ForeignKeyValueChangeOption) visit(ctx.updOpt)); return decl; } @Override public Object visitTblConstraintUnique(NanoSQLParser.TblConstraintUniqueContext ctx) { String name = null; if (ctx.constraintName != null) name = ctx.constraintName.getText().toLowerCase(); ConstraintDecl decl = new ConstraintDecl(TableConstraintType.UNIQUE, name, false); for (Token colName : ctx.columnName) decl.addColumn(colName.getText().toLowerCase()); return decl; } @Override public Object visitTblConstraintPrimaryKey(NanoSQLParser.TblConstraintPrimaryKeyContext ctx) { String name = null; if (ctx.constraintName != null) name = ctx.constraintName.getText().toLowerCase(); ConstraintDecl decl = new ConstraintDecl(TableConstraintType.PRIMARY_KEY, name, false); for (Token colName : ctx.columnName) decl.addColumn(colName.getText().toLowerCase()); return decl; } @Override public Object visitTblConstraintForeignKey(NanoSQLParser.TblConstraintForeignKeyContext ctx) { String name = null; if (ctx.constraintName != null) name = ctx.constraintName.getText().toLowerCase(); ConstraintDecl decl = new ConstraintDecl(TableConstraintType.FOREIGN_KEY, name, false); for (Token colName : ctx.columnName) decl.addColumn(colName.getText().toLowerCase()); decl.setRefTable(ctx.refTableName.getText().toLowerCase()); for (Token refColName : ctx.refColumnName) decl.addRefColumn(refColName.getText().toLowerCase()); if (ctx.DELETE() != null) decl.setOnDeleteOption((ForeignKeyValueChangeOption) visit(ctx.delOpt)); if (ctx.UPDATE() != null) decl.setOnUpdateOption((ForeignKeyValueChangeOption) visit(ctx.updOpt)); return decl; } @Override public Object visitCascadeOptRestrict(NanoSQLParser.CascadeOptRestrictContext ctx) { return ForeignKeyValueChangeOption.RESTRICT; } @Override public Object visitCascadeOptCascade(NanoSQLParser.CascadeOptCascadeContext ctx) { return ForeignKeyValueChangeOption.CASCADE; } @Override public Object visitCascadeOptSetNull(NanoSQLParser.CascadeOptSetNullContext ctx) { return ForeignKeyValueChangeOption.SET_NULL; } @Override public Object visitDropTableStmt(NanoSQLParser.DropTableStmtContext ctx) { String tableName = ctx.tableName.getText().toLowerCase(); boolean ifExists = (ctx.EXISTS() != null); return new DropTableCommand(tableName, ifExists); } @Override public Object visitCreateIndexStmt(NanoSQLParser.CreateIndexStmtContext ctx) { String indexName = ctx.indexName.getText().toLowerCase(); String tableName = ctx.tableName.getText().toLowerCase(); boolean unique = (ctx.UNIQUE() != null); boolean ifNotExists = (ctx.EXISTS() != null); CreateIndexCommand cmd = new CreateIndexCommand(indexName, tableName, unique); cmd.setIfNotExists(ifNotExists); for (Token n : ctx.columnName) cmd.addColumn(n.getText().toLowerCase()); if (ctx.cmdProperties() != null) cmd.setProperties((CommandProperties) visit(ctx.cmdProperties())); return cmd; } @Override public Object visitDropIndexStmt(NanoSQLParser.DropIndexStmtContext ctx) { String indexName = ctx.indexName.getText().toLowerCase(); String tableName = ctx.tableName.getText().toLowerCase(); boolean ifExists = (ctx.EXISTS() != null); return new DropIndexCommand(indexName, tableName, ifExists); } //=== DML OPERATIONS ===================================================== @Override public Object visitSelectStmt(NanoSQLParser.SelectStmtContext ctx) { SelectClause selectClause = new SelectClause(); selectClause.setDistinct(ctx.DISTINCT() != null); for (NanoSQLParser.SelectValueContext svc : ctx.selectValue()) selectClause.addSelectValue((SelectValue) visit(svc)); if (ctx.FROM() != null) selectClause.setFromClause((FromClause) visit(ctx.fromExpr())); if (ctx.WHERE() != null) selectClause.setWhereExpr((Expression) visit(ctx.wherePred)); if (ctx.GROUP() != null) { for (NanoSQLParser.ExpressionContext gec : ctx.groupExpr) selectClause.addGroupByExpr((Expression) visit(gec)); if (ctx.HAVING() != null) selectClause.setHavingExpr((Expression) visit(ctx.havingPred)); } if (ctx.ORDER() != null) { for (NanoSQLParser.OrderByExprContext oc : ctx.orderByExpr()) selectClause.addOrderByExpr((OrderByExpression) visit(oc)); } if (ctx.LIMIT() != null) selectClause.setLimit(Integer.parseInt(ctx.limit.getText())); if (ctx.OFFSET() != null) selectClause.setOffset(Integer.parseInt(ctx.offset.getText())); return new SelectCommand(selectClause); } @Override public Object visitSelectValue(NanoSQLParser.SelectValueContext ctx) { Expression e = (Expression) visit(ctx.expression()); String alias = null; if (ctx.alias != null) alias = ctx.alias.getText().toLowerCase(); return new SelectValue(e, alias); } @Override public Object visitJoinTypeInner(NanoSQLParser.JoinTypeInnerContext ctx) { return JoinType.INNER; } @Override public Object visitJoinTypeLeftOuter(NanoSQLParser.JoinTypeLeftOuterContext ctx) { return JoinType.LEFT_OUTER; } @Override public Object visitJoinTypeRightOuter(NanoSQLParser.JoinTypeRightOuterContext ctx) { return JoinType.RIGHT_OUTER; } @Override public Object visitJoinTypeFullOuter(NanoSQLParser.JoinTypeFullOuterContext ctx) { return JoinType.FULL_OUTER; } @Override public Object visitFromCrossJoin(NanoSQLParser.FromCrossJoinContext ctx) { FromClause lhs = (FromClause) visit(ctx.fromExpr(0)); FromClause rhs = (FromClause) visit(ctx.fromExpr(1)); return new FromClause(lhs, rhs, JoinType.CROSS); } @Override public Object visitFromNaturalJoin(NanoSQLParser.FromNaturalJoinContext ctx) { FromClause lhs = (FromClause) visit(ctx.fromExpr(0)); FromClause rhs = (FromClause) visit(ctx.fromExpr(1)); JoinType type = JoinType.INNER; if (ctx.joinType() != null) type = (JoinType) visit(ctx.joinType()); FromClause fc = new FromClause(lhs, rhs, type); fc.setConditionType(FromClause.JoinConditionType.NATURAL_JOIN); return fc; } @Override public Object visitFromJoinOn(NanoSQLParser.FromJoinOnContext ctx) { FromClause lhs = (FromClause) visit(ctx.fromExpr(0)); FromClause rhs = (FromClause) visit(ctx.fromExpr(1)); JoinType type = JoinType.INNER; if (ctx.joinType() != null) type = (JoinType) visit(ctx.joinType()); FromClause fc = new FromClause(lhs, rhs, type); fc.setConditionType(FromClause.JoinConditionType.JOIN_ON_EXPR); fc.setOnExpression((Expression) visit(ctx.expression())); return fc; } @Override public Object visitFromJoinUsing(NanoSQLParser.FromJoinUsingContext ctx) { FromClause lhs = (FromClause) visit(ctx.fromExpr(0)); FromClause rhs = (FromClause) visit(ctx.fromExpr(1)); JoinType type = JoinType.INNER; if (ctx.joinType() != null) type = (JoinType) visit(ctx.joinType()); FromClause fc = new FromClause(lhs, rhs, type); fc.setConditionType(FromClause.JoinConditionType.JOIN_USING); for (Token columnName : ctx.columnName) fc.addUsingName(columnName.getText().toLowerCase()); return fc; } @Override public Object visitFromImplicitCrossJoin(NanoSQLParser.FromImplicitCrossJoinContext ctx) { FromClause lhs = (FromClause) visit(ctx.fromExpr(0)); FromClause rhs = (FromClause) visit(ctx.fromExpr(1)); return new FromClause(lhs, rhs, JoinType.CROSS); } @Override public Object visitFromTable(NanoSQLParser.FromTableContext ctx) { String tableName = ctx.tableName.getText().toLowerCase(); String alias = null; if (ctx.alias != null) alias = ctx.alias.getText().toLowerCase(); return new FromClause(tableName, alias); } @Override public Object visitFromTableFunction(NanoSQLParser.FromTableFunctionContext ctx) { FunctionCall functionCall = (FunctionCall) visit(ctx.functionCall()); String alias = null; if (ctx.alias != null) alias = ctx.alias.getText().toLowerCase(); return new FromClause(functionCall, alias); } @Override public Object visitFromNestedSelect(NanoSQLParser.FromNestedSelectContext ctx) { SelectCommand selectCmd = (SelectCommand) visit(ctx.selectStmt()); return new FromClause(selectCmd.getSelectClause(), ctx.alias.getText().toLowerCase()); } @Override public Object visitFromParens(NanoSQLParser.FromParensContext ctx) { return visit(ctx.fromExpr()); } @Override public Object visitOrderByExpr(NanoSQLParser.OrderByExprContext ctx) { boolean ascending = true; if (ctx.DESC() != null || ctx.DESCENDING() != null) ascending = false; return new OrderByExpression( (Expression) visit(ctx.expression()), ascending); } @Override @SuppressWarnings("unchecked") public Object visitInsertStmt(NanoSQLParser.InsertStmtContext ctx) { String tableName = ctx.tableName.getText().toLowerCase(); ArrayList<String> colNames = new ArrayList<>(); for (Token cn : ctx.columnName) colNames.add(cn.getText().toLowerCase()); InsertCommand cmd; if (ctx.expressionList() != null) { ArrayList<Expression> exprs = (ArrayList<Expression>) visit(ctx.expressionList()); cmd = new InsertCommand(tableName, colNames, exprs); } else { SelectCommand selectCmd = (SelectCommand) visit(ctx.selectStmt()); SelectClause selectClause = selectCmd.getSelectClause(); cmd = new InsertCommand(tableName, colNames, selectClause); } return cmd; } @Override public Object visitUpdateStmt(NanoSQLParser.UpdateStmtContext ctx) { String tableName = ctx.tableName.getText().toLowerCase(); UpdateCommand cmd = new UpdateCommand(tableName); for (int i = 0; i < ctx.columnName.size(); i++) { String columnName = ctx.columnName.get(i).getText().toLowerCase(); Expression expr = (Expression) visit(ctx.expression(i)); cmd.addValue(columnName, expr); } if (ctx.WHERE() != null) cmd.setWhereExpr((Expression) visit(ctx.predicate)); return cmd; } @Override public Object visitDeleteStmt(NanoSQLParser.DeleteStmtContext ctx) { String tableName = ctx.tableName.getText().toLowerCase(); Expression expr = null; if (ctx.WHERE() != null) expr = (Expression) visit(ctx.expression()); return new DeleteCommand(tableName, expr); } @Override public Object visitExplainSelect(NanoSQLParser.ExplainSelectContext ctx) { SelectCommand cmdToExplain = (SelectCommand) visit(ctx.selectStmt()); return new ExplainCommand(cmdToExplain); } @Override public Object visitExplainInsert(NanoSQLParser.ExplainInsertContext ctx) { InsertCommand cmdToExplain = (InsertCommand) visit(ctx.insertStmt()); return new ExplainCommand(cmdToExplain); } @Override public Object visitExplainUpdate(NanoSQLParser.ExplainUpdateContext ctx) { UpdateCommand cmdToExplain = (UpdateCommand) visit(ctx.updateStmt()); return new ExplainCommand(cmdToExplain); } @Override public Object visitExplainDelete(NanoSQLParser.ExplainDeleteContext ctx) { DeleteCommand cmdToExplain = (DeleteCommand) visit(ctx.deleteStmt()); return new ExplainCommand(cmdToExplain); } /*======================================================================*/ /* ALL EXPRESSION RULES */ /*======================================================================*/ //=== LITERALS =========================================================== @Override public Object visitLiteralNull(NanoSQLParser.LiteralNullContext ctx) { return null; } @Override public Object visitLiteralTrue(NanoSQLParser.LiteralTrueContext ctx) { return Boolean.TRUE; } @Override public Object visitLiteralFalse(NanoSQLParser.LiteralFalseContext ctx) { return Boolean.FALSE; } @Override public Object visitLiteralInteger(NanoSQLParser.LiteralIntegerContext ctx) { String s = ctx.INT_LITERAL().getText(); BigInteger bigInt = new BigInteger(s); try { return bigInt.intValueExact(); } catch (ArithmeticException e) { // Too big to fit in an integer. } try { return bigInt.longValueExact(); } catch (ArithmeticException e) { // Too big to fit in a long. } return bigInt; } @Override public Object visitLiteralDecimal(NanoSQLParser.LiteralDecimalContext ctx) { return new BigDecimal(ctx.DECIMAL_LITERAL().getText()); } @Override public Object visitLiteralString(NanoSQLParser.LiteralStringContext ctx) { // Make sure to lop off the quotes at the start and end of the string String s = ctx.STRING_LITERAL().getText(); return s.substring(1, s.length() - 1); } @Override public Object visitLiteralInterval(NanoSQLParser.LiteralIntervalContext ctx) { // Get the text. Chop off the quotes at the start/end of the string String s = ctx.STRING_LITERAL().getText(); s = s.substring(1, s.length() - 1); // Parse the string into an interval value. return DateTimeUtils.parseInterval(s); } //=== COLUMN REFERENCES ================================================== @Override public Object visitColRefTable(NanoSQLParser.ColRefTableContext ctx) { return new ColumnName(ctx.tableName.getText().toLowerCase(), ctx.columnName.getText().toLowerCase()); } @Override public Object visitColRefNoTable(NanoSQLParser.ColRefNoTableContext ctx) { return new ColumnName(ctx.columnName.getText().toLowerCase()); } @Override public Object visitColRefWildcardTable(NanoSQLParser.ColRefWildcardTableContext ctx) { return new ColumnName(ctx.tableName.getText().toLowerCase(), null); } @Override public Object visitColRefWildcardNoTable(NanoSQLParser.ColRefWildcardNoTableContext ctx) { return new ColumnName(null, null); } //=== FUNCTION CALLS ===================================================== @Override public Object visitFunctionCall(NanoSQLParser.FunctionCallContext ctx) { String functionName = ctx.functionName.getText().toUpperCase(); boolean distinct = (ctx.DISTINCT() != null); ArrayList<Expression> args = new ArrayList<>(); for (NanoSQLParser.ExpressionContext ec : ctx.expression()) args.add((Expression) visit(ec)); // We handle "DISTINCT" and wildcard arguments in a special way, by // modifying the function name to indicate these characteristics. if (args.size() == 1) { Expression e = args.get(0); if (e instanceof ColumnValue) { ColumnName colName = ((ColumnValue) e).getColumnName(); if (colName.isColumnWildcard()) functionName += "#STAR"; else if (distinct) functionName += "#DISTINCT"; } } // TODO: Report error if someone uses DISTINCT with multiple args. FunctionCall fnCall = new FunctionCall(functionName, distinct, args); if (functionDirectory != null) fnCall.resolve(functionDirectory); return fnCall; } //=== EXPRESSION LISTS =================================================== @Override public Object visitExpressionList(NanoSQLParser.ExpressionListContext ctx) { ArrayList<Expression> exprList = new ArrayList<>(); for (NanoSQLParser.ExpressionContext ec : ctx.expression()) exprList.add((Expression) visit(ec)); return exprList; } //=== EXPRESSIONS ======================================================== @Override public Object visitExprLiteral(NanoSQLParser.ExprLiteralContext ctx) { return new LiteralValue(visit(ctx.literalValue())); } @Override public Object visitExprColumnRef(NanoSQLParser.ExprColumnRefContext ctx) { return new ColumnValue((ColumnName) visit(ctx.columnRef())); } @Override public Object visitExprFunctionCall(NanoSQLParser.ExprFunctionCallContext ctx) { // Function calls are already represented as expressions, so we can // just call the functionCall() translation and return its result. return visit(ctx.functionCall()); } @Override public Object visitExprUnarySign(NanoSQLParser.ExprUnarySignContext ctx) { Expression expr = (Expression) visit(ctx.expression()); if ("-".equals(ctx.op.getText())) expr = new NegateOperator(expr); return expr; } @Override public Object visitExprMul(NanoSQLParser.ExprMulContext ctx) { ArithmeticOperator.Type type = ArithmeticOperator.Type.find(ctx.op.getText()); Expression lhs = (Expression) visit(ctx.expression(0)); Expression rhs = (Expression) visit(ctx.expression(1)); return new ArithmeticOperator(type, lhs, rhs); } @Override public Object visitExprAdd(NanoSQLParser.ExprAddContext ctx) { String op = ctx.op.getText(); ArithmeticOperator.Type type = ArithmeticOperator.Type.find(op); Expression lhs = (Expression) visit(ctx.expression(0)); Expression rhs = (Expression) visit(ctx.expression(1)); return new ArithmeticOperator(type, lhs, rhs); } @Override public Object visitExprCompare(NanoSQLParser.ExprCompareContext ctx) { String op = ctx.op.getText(); CompareOperator.Type type = CompareOperator.Type.find(op); Expression lhs = (Expression) visit(ctx.expression(0)); Expression rhs = (Expression) visit(ctx.expression(1)); return new CompareOperator(type, lhs, rhs); } @Override public Object visitExprIsNull(NanoSQLParser.ExprIsNullContext ctx) { boolean invert = (ctx.NOT() != null); return new IsNullOperator((Expression) visit(ctx.expression()), invert); } @Override public Object visitExprBetween(NanoSQLParser.ExprBetweenContext ctx) { boolean invert = (ctx.NOT() != null); CompareOperator.Type lowOp = CompareOperator.Type.GREATER_OR_EQUAL; CompareOperator.Type highOp = CompareOperator.Type.LESS_OR_EQUAL; if (invert) { lowOp = CompareOperator.Type.LESS_THAN; highOp = CompareOperator.Type.GREATER_THAN; } Expression expr = (Expression) visit(ctx.expression(0)); Expression lowExpr = (Expression) visit(ctx.expression(1)); Expression highExpr = (Expression) visit(ctx.expression(2)); BooleanOperator andExpr = new BooleanOperator(BooleanOperator.Type.AND_EXPR); andExpr.addTerm(new CompareOperator(lowOp, expr, lowExpr)); // Clone the expression before using it again, so that if it is // mutated in subsequent steps, we won't get weird side effects. expr = expr.duplicate(); andExpr.addTerm(new CompareOperator(highOp, expr, highExpr)); return andExpr; } @Override public Object visitExprLike(NanoSQLParser.ExprLikeContext ctx) { Expression lhs = (Expression) visit(ctx.expression(0)); Expression rhs = (Expression) visit(ctx.expression(1)); return new StringMatchOperator(StringMatchOperator.Type.LIKE, lhs, rhs); } @Override public Object visitExprSimilarTo(NanoSQLParser.ExprSimilarToContext ctx) { Expression lhs = (Expression) visit(ctx.expression(0)); Expression rhs = (Expression) visit(ctx.expression(1)); return new StringMatchOperator(StringMatchOperator.Type.REGEX, lhs, rhs); } @Override @SuppressWarnings("unchecked") public Object visitExprOneColInValues(NanoSQLParser.ExprOneColInValuesContext ctx) { Expression expr = (Expression) visit(ctx.expression()); ArrayList<Expression> values = (ArrayList<Expression>) visit(ctx.expressionList()); InValuesOperator op = new InValuesOperator(expr, values); op.setInvert(ctx.NOT() != null); return op; } @Override public Object visitExprOneColInSubquery(NanoSQLParser.ExprOneColInSubqueryContext ctx) { Expression expr = (Expression) visit(ctx.expression()); SelectCommand selectCmd = (SelectCommand) visit(ctx.selectStmt()); InSubqueryOperator op = new InSubqueryOperator(expr, selectCmd.getSelectClause()); op.setInvert(ctx.NOT() != null); return op; } @Override @SuppressWarnings("unchecked") public Object visitExprMultiColInSubquery(NanoSQLParser.ExprMultiColInSubqueryContext ctx) { ArrayList<Expression> exprList = (ArrayList<Expression>) visit(ctx.expressionList()); SelectCommand selectCmd = (SelectCommand) visit(ctx.selectStmt()); InSubqueryOperator op = new InSubqueryOperator(exprList, selectCmd.getSelectClause()); op.setInvert(ctx.NOT() != null); return op; } @Override public Object visitExprExists(NanoSQLParser.ExprExistsContext ctx) { SelectCommand selectCmd = (SelectCommand) visit(ctx.selectStmt()); return new ExistsOperator(selectCmd.getSelectClause()); } @Override public Object visitExprNot(NanoSQLParser.ExprNotContext ctx) { BooleanOperator op = new BooleanOperator(BooleanOperator.Type.NOT_EXPR); op.addTerm((Expression) visit(ctx.expression())); return op; } @Override public Object visitExprAnd(NanoSQLParser.ExprAndContext ctx) { BooleanOperator op = new BooleanOperator(BooleanOperator.Type.AND_EXPR); op.addTerm((Expression) visit(ctx.expression(0))); op.addTerm((Expression) visit(ctx.expression(1))); return op; } @Override public Object visitExprOr(NanoSQLParser.ExprOrContext ctx) { BooleanOperator op = new BooleanOperator(BooleanOperator.Type.OR_EXPR); op.addTerm((Expression) visit(ctx.expression(0))); op.addTerm((Expression) visit(ctx.expression(1))); return op; } @Override public Object visitExprScalarSubquery(NanoSQLParser.ExprScalarSubqueryContext ctx) { SelectCommand selectCmd = (SelectCommand) visit(ctx.selectStmt()); return new ScalarSubquery(selectCmd.getSelectClause()); } @Override public Object visitExprParen(NanoSQLParser.ExprParenContext ctx) { return visit(ctx.expression()); } }