001package org.avaje.dbmigration.ddl; 002 003import java.io.BufferedReader; 004import java.io.IOException; 005import java.io.StringReader; 006import java.util.ArrayList; 007import java.util.List; 008 009/** 010 * Parses string content into separate SQL/DDL statements. 011 */ 012public class DdlParser { 013 014 /** 015 * Break up the sql in reader into a list of statements using the semi-colon and $$ delimiters; 016 */ 017 public List<String> parse(StringReader reader) { 018 019 try { 020 BufferedReader br = new BufferedReader(reader); 021 StatementsSeparator statements = new StatementsSeparator(); 022 023 String s; 024 while ((s = br.readLine()) != null) { 025 s = s.trim(); 026 statements.nextLine(s); 027 } 028 029 return statements.statements; 030 031 } catch (IOException e) { 032 throw new DdlRunnerException(e); 033 } 034 } 035 036 037 /** 038 * Local utility used to detect the end of statements / separate statements. 039 * This is often just the semicolon character but for trigger/procedures this 040 * detects the $$ demarcation used in the history DDL generation for MySql and 041 * Postgres. 042 */ 043 static class StatementsSeparator { 044 045 ArrayList<String> statements = new ArrayList<String>(); 046 047 boolean trimDelimiter; 048 049 boolean inDbProcedure; 050 051 StringBuilder sb = new StringBuilder(); 052 053 void lineContainsDollars(String line) { 054 if (inDbProcedure) { 055 if (trimDelimiter) { 056 line = line.replace("$$",""); 057 } 058 endOfStatement(line); 059 } else { 060 // MySql style delimiter needs to be trimmed/removed 061 trimDelimiter = line.equals("delimiter $$"); 062 if (!trimDelimiter) { 063 sb.append(line).append(" "); 064 } 065 } 066 inDbProcedure = !inDbProcedure; 067 } 068 069 void endOfStatement(String line) { 070 // end of Db procedure 071 sb.append(line); 072 statements.add(sb.toString().trim()); 073 sb = new StringBuilder(); 074 } 075 076 void nextLine(String line) { 077 078 if (line.contains("$$")) { 079 lineContainsDollars(line); 080 return; 081 } 082 083 if (sb.length() == 0 && (line.isEmpty() || line.startsWith("--"))) { 084 // ignore leading empty lines and sql comments 085 return; 086 } 087 088 if (inDbProcedure) { 089 sb.append(line).append(" "); 090 return; 091 } 092 093 int semiPos = line.indexOf(';'); 094 if (semiPos == -1) { 095 sb.append(line).append(" "); 096 097 } else if (semiPos == line.length() - 1) { 098 // semicolon at end of line 099 endOfStatement(line); 100 101 } else { 102 // semicolon in middle of line 103 String preSemi = line.substring(0, semiPos); 104 endOfStatement(preSemi); 105 sb.append(line.substring(semiPos + 1)); 106 } 107 } 108 } 109}