package net.sourceforge.mayfly.parser; import junit.framework.TestCase; import junitx.framework.ObjectAssert; import net.sourceforge.mayfly.MayflyException; import net.sourceforge.mayfly.UnimplementedException; import net.sourceforge.mayfly.acceptance.MayflyDialect; import net.sourceforge.mayfly.datastore.Column; import net.sourceforge.mayfly.datastore.constraint.Cascade; import net.sourceforge.mayfly.datastore.constraint.NoAction; import net.sourceforge.mayfly.datastore.constraint.SetDefault; import net.sourceforge.mayfly.datastore.constraint.SetNull; import net.sourceforge.mayfly.evaluation.Expression; import net.sourceforge.mayfly.evaluation.ValueList; import net.sourceforge.mayfly.evaluation.command.CreateTable; import net.sourceforge.mayfly.evaluation.command.UnresolvedTableReference; import net.sourceforge.mayfly.evaluation.condition.Condition; import net.sourceforge.mayfly.evaluation.condition.Greater; import net.sourceforge.mayfly.evaluation.expression.Average; import net.sourceforge.mayfly.evaluation.expression.Concatenate; import net.sourceforge.mayfly.evaluation.expression.Count; import net.sourceforge.mayfly.evaluation.expression.CountAll; import net.sourceforge.mayfly.evaluation.expression.Divide; import net.sourceforge.mayfly.evaluation.expression.Maximum; import net.sourceforge.mayfly.evaluation.expression.Minimum; import net.sourceforge.mayfly.evaluation.expression.Minus; import net.sourceforge.mayfly.evaluation.expression.Multiply; import net.sourceforge.mayfly.evaluation.expression.NullExpression; import net.sourceforge.mayfly.evaluation.expression.Plus; import net.sourceforge.mayfly.evaluation.expression.SingleColumn; import net.sourceforge.mayfly.evaluation.expression.Sum; import net.sourceforge.mayfly.evaluation.expression.literal.CellExpression; import net.sourceforge.mayfly.evaluation.expression.literal.DecimalLiteral; import net.sourceforge.mayfly.evaluation.expression.literal.IntegerLiteral; import net.sourceforge.mayfly.evaluation.expression.literal.LongLiteral; import net.sourceforge.mayfly.evaluation.expression.literal.QuotedString; import net.sourceforge.mayfly.evaluation.what.WhatElement; import net.sourceforge.mayfly.util.ImmutableByteArray; import net.sourceforge.mayfly.util.MayflyAssert; import java.util.ArrayList; import java.util.List; public class ParserTest extends TestCase { public void testEmptyString() throws Exception { expectFailure("", "expected identifier but got end of file"); } public void testIdentifier() throws Exception { Parser parser = new Parser("foo"); parser.parseFromTable(); assertEquals("", parser.remainingTokens()); } public void testRemainingTokens() throws Exception { Parser parser = new Parser("foo inner"); parser.parseFromTable(); assertEquals("INNER", parser.remainingTokens()); } public void testIdentifierDot() throws Exception { expectFailure("foo.", "expected identifier but got end of file"); } public void testSchemaDotTable() throws Exception { new Parser("mars.foo").parseFromTable(); } public void testDotIdentifier() throws Exception { expectFailure(".foo", "expected identifier but got '.'"); } public void testSchemaDotTableAlias() throws Exception { Parser parser = new Parser("mars.foo f"); parser.parseFromTable(); assertEquals("", parser.remainingTokens()); } public void testTableAlias() throws Exception { Parser parser = new Parser("foo f"); parser.parseFromTable(); assertEquals("", parser.remainingTokens()); } public void testSelect() throws Exception { Parser parser = new Parser("select * from foo"); parser.parseSelect(); assertEquals("", parser.remainingTokens()); } public void testExtraneousTokensAtEnd() throws Exception { Parser parser = new Parser("select * from foo 5"); try { parser.parse(); fail(); } catch (MayflyException e) { assertEquals("expected end of file but got 5", e.getMessage()); } } public void testExtraneousTokensAtEndForDrop() throws Exception { Parser parser = new Parser("drop table foo ("); try { parser.parse(); fail(); } catch (MayflyException e) { assertEquals("expected end of file but got '('", e.getMessage()); } } public void testExtraneousTokensExampleTwo() throws Exception { Parser parser = new Parser("select * from foo 5"); try { parser.parse(); fail(); } catch (MayflyException e) { assertEquals("expected end of file but got 5", e.getMessage()); } } public void testListOfFromItems() throws Exception { Parser parser = new Parser("select * from foo, bar b, baz"); parser.parseSelect(); assertEquals("", parser.remainingTokens()); } public void testCrossJoin() throws Exception { Parser parser = new Parser("foo, bar cross join baz, quux"); parser.parseFromItems(); assertEquals("", parser.remainingTokens()); } public void testCrossJoinWithOn() throws Exception { Parser parser = new Parser("select a, b from foo cross join bar on 1 = 1"); try { parser.parse(); fail(); } catch (ParserException e) { // In this example, we might say: // "Specify INNER JOIN, not CROSS JOIN, if you want an ON condition" // but there is a dangling ON issue. So until we understand how the // parser knows which JOIN the ON goes with, let's not get too fancy. assertEquals("expected end of file but got ON", e.getMessage()); } } public void testInnerJoin() throws Exception { Parser parser = new Parser("foo, bar inner join baz on a = b, quux"); parser.parseFromItems(); assertEquals("", parser.remainingTokens()); } public void testInnerJoinOnMissing() throws Exception { Parser parser = new Parser("foo inner join bar"); try { parser.parseFromItems(); fail(); } catch (ParserException e) { assertEquals("expected ON but got end of file", e.getMessage()); } } public void testLeftOuterJoin() throws Exception { Parser parser = new Parser("foo, bar left outer join baz on a = b"); parser.parseFromItems(); assertEquals("", parser.remainingTokens()); } public void testLeftJoin() throws Exception { Parser parser = new Parser("foo, bar left join baz on a = b"); parser.parseFromItems(); assertEquals("", parser.remainingTokens()); } public void testWhere() throws Exception { Parser parser = new Parser("select * from foo where a = b"); parser.parseSelect(); assertEquals("", parser.remainingTokens()); } public void testTableDotColumn() throws Exception { Parser parser = new Parser("f.a = b"); parser.parseWhere(); assertEquals("", parser.remainingTokens()); } public void testTableDotColumnRightHandSide() throws Exception { Parser parser = new Parser("f = g.b"); parser.parseWhere(); assertEquals("", parser.remainingTokens()); } public void testTableDot() throws Exception { try { new Parser("f. = b").parseWhere(); fail(); } catch (ParserException e) { assertEquals("expected identifier but got '='", e.getMessage()); } } public void testTableReference() throws Exception { UnresolvedTableReference reference = new Parser("foo ").parseTableReference(); assertEquals("foo", reference.tableName()); assertEquals("my_default", reference.schema("my_default")); MayflyAssert.assertLocation(1, 1, 1, 4, reference.location); } public void testTableReferenceWithSchema() throws Exception { UnresolvedTableReference reference = new Parser("mars.foo ").parseTableReference(); assertEquals("foo", reference.tableName()); assertEquals("mars", reference.schema("my_default")); MayflyAssert.assertLocation(1, 1, 1, 9, reference.location); } public void testBadTokenAfterIs() throws Exception { try { new Parser("a IS BORIng").parseWhere(); fail(); } catch (ParserException e) { assertEquals("expected NULL but got BORIng", e.getMessage()); } } public void testBadTokenAfterNot() throws Exception { try { new Parser("a NOT INTERESTING").parseWhere(); fail(); } catch (ParserException e) { assertEquals("expected IN or LIKE but got INTERESTING", e.getMessage()); } } public void testBadTokenAfterIsNot() throws Exception { try { new Parser("a IS NOT SENSIBLE").parseWhere(); fail(); } catch (ParserException e) { assertEquals("expected NULL but got SENSIBLE", e.getMessage()); } } public void testMissingOperator() throws Exception { try { new Parser("f 5").parseWhere(); fail(); } catch (ParserException e) { /* This would be a nice message, as would "expected boolean but got f" or some such. */ // assertEquals("expected boolean operator but got 5", e.getMessage()); assertEquals( "expected boolean expression but got non-boolean expression", e.getMessage()); } } public void testAnd() throws Exception { Parser parser = new Parser("a = 5 and b = c"); parser.parseWhere(); assertEquals("", parser.remainingTokens()); } public void testLiteralNumber() throws Exception { Parser parser = new Parser("f = 5"); parser.parseWhere(); assertEquals("", parser.remainingTokens()); } public void testCondition() throws Exception { Parser parser = new Parser("(y + z / 10) < 60"); Condition condition = parser.parseCondition().asBoolean(); assertEquals("", parser.remainingTokens()); Greater greater = (Greater) condition; IntegerLiteral sixty = (IntegerLiteral) greater.leftSide; assertEquals(60, sixty.value); Plus plus = (Plus) greater.rightSide; MayflyAssert.assertColumn("y", plus.left()); Divide divide = (Divide) plus.right(); MayflyAssert.assertColumn("z", divide.left()); MayflyAssert.assertInteger(10, divide.right()); } public void testExpectedBooleanGotNonBoolean() throws Exception { lookForConditionGetExpression("5 + x"); } private void lookForConditionGetExpression(String sql) { try { new Parser(sql).parseCondition().asBoolean(); fail(); } catch (ParserException e) { // Would be really nice to say what is going on with a more specific error message // assertEquals("expected boolean expression but got 5 + x", e.getMessage()); assertEquals( "expected boolean expression but got non-boolean expression", e.getMessage()); } } public void testExpectedNonBooleanGotBoolean() throws Exception { lookForExpressionGetCondition("5 = x"); lookForExpressionGetCondition( "foo.a = bar.a and (bar.a < 5 or foo.a <> bar.a)"); } private void lookForExpressionGetCondition(String sql) { try { new Parser(sql).parseCondition().asNonBoolean(); fail(); } catch (ParserException e) { assertEquals( "expected non-boolean expression but got boolean expression", e.getMessage()); // assertEquals("expected non-boolean expression but got 5 = x", e.getMessage()); } } public void testLiteralString() throws Exception { Parser parser = new Parser("'hi'"); assertEquals(new QuotedString("'hi'"), parser.parsePrimary().asNonBoolean()); assertEquals("", parser.remainingTokens()); } public void testExpressionLocation() throws Exception { checkExpression(NullExpression.class, 3, 7, " null "); checkExpression(IntegerLiteral.class, 2, 4, " 43 "); checkExpression(LongLiteral.class, 2, 12, " 4555666777 "); checkExpression(QuotedString.class, 2, 7, " 'foo' "); checkExpression(DecimalLiteral.class, 3, 10, " 3.14159 "); checkExpression(DecimalLiteral.class, 1, 3, ".5"); checkExpression(DecimalLiteral.class, 1, 3, "5."); checkExpression(DecimalLiteral.class, 2, 7, " + 0.5\n"); checkExpression(IntegerLiteral.class, 2, 6, " - 43 "); checkExpression(LongLiteral.class, 2, 13, " -4555666777 "); checkExpression(Plus.class, 2, 12, " -5 + 8 / 2 "); /* Should be 2,15. But that turns out to be hard (how to modify the ParserExpression with the right location, or some such). */ checkExpression(Plus.class, 3, 13, " (-5 + 8 / 2 ) "); checkExpression(Maximum.class, 2, 11, " max ( x ) "); checkExpression(Minimum.class, 2, 11, " min ( x ) "); checkExpression(CountAll.class, 2, 11, " count (*) "); checkExpression(Count.class, 2, 13, " count (yyy) "); checkExpression(Average.class, 2, 11, " avg ( x ) "); checkExpression(Sum.class, 1, 9, "sum( x )"); checkExpression(SingleColumn.class, 1, 2, "x "); checkExpression(SingleColumn.class, 2, 9, " foo . x "); checkExpression(NullExpression.class, 2, 6, " null "); } public void testNullExceptionHasLocation() throws Exception { Parser parser = new Parser(" null "); try { parser.parseExpression(); fail(); } catch (FoundNullLiteral e) { MayflyAssert.assertLocation(2, 6, e.location()); } } public void testRethrownNullExceptionHasLocation() throws Exception { Parser parser = new Parser(" 5 + null + 7 "); try { parser.parseExpressionOrNull(); fail(); } catch (MayflyException e) { assertEquals( "Specify a null literal rather than an expression containing one", e.getMessage()); /* This is currently from the start of the expression until the first null. Would be a bit more friendly to parse the whole expression and provide the location of the whole expression, I guess, but maybe this location is close enough. */ MayflyAssert.assertLocation(2, 10, e.location()); } } public void testBinaryLocation() throws Exception { TextToken original = new TextToken(TokenType.PARAMETER, "?", 5, 73, 5, 74); List tokens = new ArrayList(); tokens.add( new BinaryToken( new ImmutableByteArray(((byte)42)), original.location)); tokens.add(new EndOfFileToken(6, 1, null)); Parser parser = new Parser(tokens); CellExpression expression = (CellExpression) parser.parseExpressionOrNull(); assertEquals(5, expression.location.startLineNumber); assertEquals(73, expression.location.startColumn); assertEquals(5, expression.location.endLineNumber); assertEquals(74, expression.location.endColumn); } public void testParameterOutsidePreparedStatement() throws Exception { Parser realParse = new Parser("limit ? offset ?"); try { realParse.parseLimit(); fail(); } catch (MayflyException e) { assertEquals( "Attempt to specify '?' outside a prepared statement", e.getMessage()); } } public void testParameterInPreparedStatement() throws Exception { Parser syntaxCheck = new Parser( new Lexer("limit ? offset ?").tokens(), true); syntaxCheck.parseLimit(); } private void checkExpression(Class expectedClass, int expectedStartColumn, int expectedEndColumn, String input) { Parser parser = new Parser(input); Expression expression = parser.parseExpressionOrNull(); ObjectAssert.assertInstanceOf(expectedClass, expression); MayflyAssert.assertLocation( expectedStartColumn, expectedEndColumn, expression.location); } public void testValueConstructor() throws Exception { Parser parser = new Parser(" values ( 33 , null ) "); ValueList values = parser.parseValueConstructor(); MayflyAssert.assertLocation(3, 23, values.location); MayflyAssert.assertLocation(12, 14, values.location(0)); MayflyAssert.assertLocation(17, 21, values.location(1)); } public void testSyntaxErrorInValueConstructor() throws Exception { Parser parser = new Parser(" values ( a 'foo' ) "); try { parser.parseValueConstructor(); fail(); } catch (MayflyException e) { /* Probably would be nicer if this parsed the syntax frist, and then worried about whether there was a column reference. */ assertEquals("values clause may not refer to column: a", e.getMessage()); } } public void testValueConstructorNoSpaces() throws Exception { Parser parser = new Parser("values(5,'Value')"); ValueList values = parser.parseValueConstructor(); MayflyAssert.assertLocation(1, 18, values.location); MayflyAssert.assertLocation(8, 9, values.location(0)); MayflyAssert.assertLocation(10, 17, values.location(1)); } public void testSingleColumnAsWhat() throws Exception { Parser parser = new Parser("select a from foo"); parser.parseSelect(); assertEquals("", parser.remainingTokens()); } public void testSingleColumnWithTableAsWhat() throws Exception { Parser parser = new Parser("foo.a"); parser.parseWhat(); assertEquals("", parser.remainingTokens()); } public void testTwoWhatElements() throws Exception { Parser parser = new Parser("b, foo.a"); parser.parseWhat(); assertEquals("", parser.remainingTokens()); } public void testAllNotLegalWithOthers() throws Exception { Parser parser = new Parser("a, *"); try { parser.parseWhat(); fail(); } catch (ParserException e) { assertEquals("expected expression but got '*'", e.getMessage()); } } public void testConcatenate() throws Exception { Parser parser = new Parser("a || b"); parser.parseWhatElement(); assertEquals("", parser.remainingTokens()); } public void testExpressionPrecedence() throws Exception { // What about concatenate? It would seem like it can't be in the // same expression, due to differing types... Parser parser = new Parser("a * b + c / e - a"); WhatElement expression = parser.parseExpression().asNonBoolean(); assertEquals("", parser.remainingTokens()); Minus minus = (Minus) expression; Plus plus = (Plus) minus.left(); Multiply multiply = (Multiply) plus.left(); MayflyAssert.assertColumn("a", multiply.left()); MayflyAssert.assertColumn("b", multiply.right()); Divide divide = (Divide) plus.right(); MayflyAssert.assertColumn("c", divide.left()); MayflyAssert.assertColumn("e", divide.right()); MayflyAssert.assertColumn("a", minus.right()); } public void testDivideAssociativity() throws Exception { Parser parser = new Parser("a / b * c / d"); WhatElement expression = parser.parseExpression().asNonBoolean(); assertEquals("", parser.remainingTokens()); Divide outerDivide = (Divide) expression; Multiply multiply = (Multiply) outerDivide.left(); Divide innerDivide = (Divide) multiply.left(); MayflyAssert.assertColumn("a", innerDivide.left()); MayflyAssert.assertColumn("b", innerDivide.right()); MayflyAssert.assertColumn("c", multiply.right()); MayflyAssert.assertColumn("d", outerDivide.right()); } public void testMinusAssociativity() throws Exception { Parser parser = new Parser("a-b-c+d"); WhatElement expression = parser.parseExpression().asNonBoolean(); assertEquals("", parser.remainingTokens()); Plus plus = (Plus) expression; Minus outerMinus = (Minus) plus.left(); Minus innerMinus = (Minus) outerMinus.left(); MayflyAssert.assertColumn("a", innerMinus.left()); MayflyAssert.assertColumn("b", innerMinus.right()); MayflyAssert.assertColumn("c", outerMinus.right()); MayflyAssert.assertColumn("d", plus.right()); } public void testConcatenateAssociativity() throws Exception { // Not sure it matters if concatenate associates right-to-left or left-to-right. // But we'll pick the same as the other operators... Parser parser = new Parser("a || b || c || d"); WhatElement expression = parser.parseExpression().asNonBoolean(); assertEquals("", parser.remainingTokens()); Concatenate d = (Concatenate) expression; Concatenate c = (Concatenate) d.left(); Concatenate b = (Concatenate) c.left(); MayflyAssert.assertColumn("a", b.left()); MayflyAssert.assertColumn("b", b.right()); MayflyAssert.assertColumn("c", c.right()); MayflyAssert.assertColumn("d", d.right()); } public void testParenthesesInExpression() throws Exception { Parser parser = new Parser("x / (y * z)"); WhatElement expression = parser.parseExpression().asNonBoolean(); assertEquals("", parser.remainingTokens()); Divide divide = (Divide) expression; MayflyAssert.assertColumn("x", divide.left()); Multiply multiply = (Multiply) divide.right(); MayflyAssert.assertColumn("y", multiply.left()); MayflyAssert.assertColumn("z", multiply.right()); } public void testConsumeInteger() throws Exception { assertEquals(23, new Parser("23").consumeInteger()); assertEquals(2147483647, new Parser("2147483647").consumeInteger()); try { new Parser("2147483648").consumeInteger(); fail(); } catch (ParserException e) { // Might be nice to show the context. Kind of a bigger problem // (how do we show context on "expected foo got bar"?). assertEquals("2147483648 is out of range", e.getMessage()); } } public void testNumericLiteral() throws Exception { checkDecimal(5.6, "5.6"); checkDecimal(222333444555.0, "222333444555."); checkDecimal(0.03, ".03"); IntegerLiteral smallishInteger = (IntegerLiteral) new Parser("1000222333").parseNumericLiteral(Location.UNKNOWN); assertEquals(1000222333, smallishInteger.value); LongLiteral biggishInteger = (LongLiteral) new Parser("9223372036854775807").parseNumericLiteral(Location.UNKNOWN); assertEquals(9223372036854775807L, biggishInteger.value); try { new Parser("9223372036854775808").parseNumericLiteral(Location.UNKNOWN); fail(); } catch (UnimplementedException e) { assertEquals("don't yet handle BigInteger 9223372036854775808", e.getMessage()); } } public void testLeadingPeriodInDefault() throws Exception { DecimalLiteral value = (DecimalLiteral) new Parser(".07").parseDefaultValue("x"); assertEquals(0.07, value.valueAsCell().asDouble(), 0.000001); } public void testLeadingPeriodInPrimary() throws Exception { DecimalLiteral value = (DecimalLiteral) new Parser(".07").parsePrimary().asNonBoolean(); assertEquals(0.07, value.valueAsCell().asDouble(), 0.000001); } private void checkDecimal(double expected, String input) { DecimalLiteral decimal = (DecimalLiteral) new Parser(input).parseNumericLiteral(Location.UNKNOWN); assertEquals(expected, decimal.value.doubleValue(), 0.0001); } public void testExpressionInOrderBy() throws Exception { try { new Parser("x + y").parseOrderItem(); fail(); } catch (ParserException e) { /* Maybe this is no big deal as long as location is set? A bit reluctant to get too worried without user experience. */ if (new MayflyDialect().wishThisWereTrue()) { assertEquals("expected column reference in ORDER BY but got x + y", e.getMessage()); } else { assertEquals("expected column reference in ORDER BY but got expression", e.getMessage()); } assertEquals(1, e.startColumn()); assertEquals(6, e.endColumn()); } } public void testFunctionInOrderBy() throws Exception { try { new Parser("\"java.lang.Math.abs\"(y)").parseOrderItem(); fail(); } catch (ParserException e) { assertEquals("expected column reference in ORDER BY but got function call", e.getMessage()); } } public void testMultipleConstraints() throws Exception { // UNIQUE and PRIMARY KEY together don't make much sense. // The rule here is that constraints must be after DEFAULT // but can be in any order. new Parser("x integer unique primary key").parseColumnDefinition( new CreateTable("foo")); new Parser("x integer primary key unique").parseColumnDefinition( new CreateTable("foo")); } public void testParseAutoIncrement() throws Exception { Column column = new Parser("x integer auto_increment primary key") .parseColumnDefinition(new CreateTable("foo")); assertTrue(column.isSequenceOrAutoIncrement()); assertTrue(column.isAutoIncrement()); assertFalse(column.isSequence()); } public void testParseSequence() throws Exception { Column column = new Parser("x integer generated by default as identity") .parseColumnDefinition(new CreateTable("foo")); assertTrue(column.isSequenceOrAutoIncrement()); assertFalse(column.isAutoIncrement()); assertTrue(column.isSequence()); } public void testConstraint() throws Exception { new Parser("unique(x)").parseTableElement(new CreateTable("foo")); Parser namedConstraint = new Parser("constraint foo_x_constraint unique(x)"); namedConstraint.parseTableElement(new CreateTable("foo")); assertEquals("", namedConstraint.remainingTokens()); try { new Parser("select integer").parseTableElement(null); fail(); } catch (ParserException e) { assertEquals("expected column or table constraint but got SELECT", e.getMessage()); } } public void testForeignKeyActions() throws Exception { try { new Parser("on earthquake run away").parseActions(); fail(); } catch (ParserException e) { assertEquals("expected UPDATE or DELETE but got earthquake", e.getMessage()); } try { new Parser("on delete no action " + "on insert think about it").parseActions(); fail(); } catch (ParserException e) { assertEquals("expected UPDATE but got INSERT", e.getMessage()); } checkActions("on update no action on delete no action", NoAction.class, NoAction.class); checkActions("on delete set null on update cascade", SetNull.class, Cascade.class); checkActions("on delete set default", SetDefault.class, NoAction.class); checkActions("on update set default", NoAction.class, SetDefault.class); } private void checkActions(String sql, Class expectedOnDelete, Class expectedOnUpdate) { Parser parser = new Parser(sql); Parser.Actions actions = parser.parseActions(); assertEquals("", parser.remainingTokens()); ObjectAssert.assertInstanceOf(expectedOnDelete, actions.onDelete); ObjectAssert.assertInstanceOf(expectedOnUpdate, actions.onUpdate); } public void testMultipleCommands() throws Exception { List commands = new Parser("select x from foo;;select y from foo").parseCommands(); assertEquals(2, commands.size()); } public void testMultipleCommands2() throws Exception { List commands = new Parser("select x from foo;").parseCommands(); assertEquals(1, commands.size()); } public void testMultipleCommandsEmpty() throws Exception { List commands = new Parser("").parseCommands(); assertEquals(0, commands.size()); } public void testMultipleCommandsOnlySemicolons() throws Exception { List commands = new Parser(";;;;").parseCommands(); assertEquals(0, commands.size()); } public void testMissingSemicolon() throws Exception { try { new Parser( "select x from foo select x from bar").parseCommands(); fail(); } catch (ParserException e) { assertEquals("expected end of command but got SELECT", e.getMessage()); } } public void testAlterError() throws Exception { try { new Parser("alter your mommma").parseCommand(); fail(); } catch (ParserException e) { assertEquals("expected alter command but got your", e.getMessage()); } } public void testDataType() throws Exception { try { new Parser(" foobar").parseDataType(); fail(); } catch (ParserException e) { assertEquals("expected data type but got foobar", e.getMessage()); assertEquals(3, e.startColumn()); assertEquals(9, e.endColumn()); } } private void expectFailure(String sql, String expectedMessage) { try { new Parser(sql).parseFromTable(); fail(); } catch (ParserException e) { assertEquals(expectedMessage, e.getMessage()); } } }