SQLParser { public static Collection<ForeignKeyDefinition> parseForeignKeys(String sqlFile) { Collection<ForeignKeyDefinition> foreignKeys = new LinkedList<>(); List<String> sqlStatements = loadStatements(sqlFile); for (String sqlStatement : sqlStatements) { SQLiteLexer lexer = new SQLiteLexer(new ANTLRInputStream(sqlStatement)); SQLiteParser parser = new SQLiteParser(new CommonTokenStream(lexer)); parser.removeErrorListeners(); parser.addErrorListener(new ANTLRErrorListener() { @Override public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) { StringBuilder sb = new StringBuilder(); sb.append("Syntax error: ").append(msg).append("\n"); int lineStart = 0, lineNumber = 1; while (lineStart != -1) { int nextLineStart = sqlStatement.indexOf('\n', lineStart + 1); String str = nextLineStart == -1 ? sqlStatement.substring(lineStart) : sqlStatement.substring(lineStart, nextLineStart); sb.append(str).append("\n"); if (lineNumber == line) { for (int i = 0; i < charPositionInLine - 2; i++) { sb.append(" "); } sb.append("^\n"); } lineNumber++; lineStart = nextLineStart; if (lineStart != -1) lineStart++; } logger.error(sb.toString()); } @Override public void reportAmbiguity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, boolean exact, BitSet ambigAlts, ATNConfigSet configs) { logger.warn("Ambiguity at {} to {}:\n{}", startIndex, stopIndex, sqlStatement); } @Override public void reportAttemptingFullContext(Parser recognizer, DFA dfa, int startIndex, int stopIndex, BitSet conflictingAlts, ATNConfigSet configs) { logger.warn("Attempting full context at {} to {}:\n{}", startIndex, stopIndex, sqlStatement); } @Override public void reportContextSensitivity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, int prediction, ATNConfigSet configs) { logger.warn("Context sensitivity at {} to {}:\n{}", startIndex, stopIndex, sqlStatement); } }); ParseTree tree = parser.sql_stmt(); ParseTreeWalker.DEFAULT.walk(new SQLiteBaseListener() { String tableName; String columnName; @Override public void enterCreate_table_stmt(SQLiteParser.Create_table_stmtContext ctx) { super.enterCreate_table_stmt(ctx); if (ctx.table_name() != null) { this.tableName = cleanIdentifier(ctx.table_name().getText()); } } @Override public void enterAlter_table_stmt(SQLiteParser.Alter_table_stmtContext ctx) { super.enterAlter_table_stmt(ctx); if (ctx.table_name() != null) { this.tableName = cleanIdentifier(ctx.table_name().getText()); } } @Override public void enterColumn_def(SQLiteParser.Column_defContext ctx) { super.enterColumn_def(ctx); this.columnName = cleanIdentifier(ctx.column_name().getText()); } @Override public void enterColumn_constraint(SQLiteParser.Column_constraintContext ctx) { super.enterColumn_constraint(ctx); if (ctx.foreign_key_clause() == null) return; if (this.tableName == null || this.columnName == null) return; foreignKeys.add(this.createForeignKeyDefinition(ctx.foreign_key_clause(), this.tableName, this.columnName)); } @Override public void enterTable_constraint(SQLiteParser.Table_constraintContext ctx) { super.enterTable_constraint(ctx); if (ctx.K_FOREIGN() == null) return; if (this.tableName == null) return; foreignKeys.add(this.createForeignKeyDefinition(ctx)); } private ForeignKeyDefinition createForeignKeyDefinition(SQLiteParser.Table_constraintContext tableConstraint) { List<String> referencedColumns = tableConstraint.column_name().stream().map(RuleContext::getText).map(SQLParser::cleanIdentifier).collect(Collectors.toList()); return this.createForeignKeyDefinition( tableConstraint.foreign_key_clause(), this.tableName, referencedColumns.toArray(new String[referencedColumns.size()]) ); } private ForeignKeyDefinition createForeignKeyDefinition( SQLiteParser.Foreign_key_clauseContext referencesClause, String dependentTable, String... dependentColumns ) { return new ForeignKeyDefinition( dependentTable, Arrays.asList(dependentColumns), cleanIdentifier(referencesClause.foreign_table().getText()), referencesClause.column_name().stream().map(RuleContext::getText).map(SQLParser::cleanIdentifier).collect(Collectors.toList()) ); } }, tree); } return foreignKeys; }  static Map<String, List<String>> parseSchema(String sqlFile); static Collection<PrimaryKeyDefinition> parsePrimaryKeys(String sqlFile); static Collection<ForeignKeyDefinition> parseForeignKeys(String sqlFile); static Collection<NotNullDefinition> parseNotNullConstraints(String sqlFile); static Collection<DataTypeDefinition> parseDataTypes(String sqlFile);  }
@Test public void testParseForeignKeyDefinitions() { String inputFile = Thread.currentThread().getContextClassLoader().getResource("foreign-keys.sql").getFile(); Collection<ForeignKeyDefinition> primaryKeyDefinitions = SQLParser.parseForeignKeys(inputFile); Assert.assertEquals( new HashSet<>(Arrays.asList( new ForeignKeyDefinition( "dep1", Collections.singletonList("that_id"), "ref1", Collections.singletonList("id") ), new ForeignKeyDefinition( "dep2", Collections.singletonList("that_id"), "ref2", Collections.singletonList("id") ), new ForeignKeyDefinition( "dep3", Arrays.asList("first_name", "last_name"), "ref3", Arrays.asList("fn", "ln") ), new ForeignKeyDefinition( "dep4", Collections.singletonList("that_oid"), "ref4", Collections.singletonList("oid") ), new ForeignKeyDefinition( "dep5", Arrays.asList("first_name", "last_name"), "ref5", Arrays.asList("fn", "ln") ) )), new HashSet<>(primaryKeyDefinitions) ); }