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}