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