views:

4372

answers:

8

i have a bunch of sql scripts that should upgrade the database when the java web application starts up.

i tried using the ibatis scriptrunner, but it fails gloriously when defining triggers, where the ";" character does not mark an end of statement.

now i have written my own version of a script runner, which basically does the job, but destroys possible formatting and comments, especially in "create or replace view".

public class ScriptRunner {
private final DataSource ds;


public ScriptRunner(DataSource ds) {
 this.ds = ds;
}

public void run(InputStream sqlStream) throws SQLException, IOException {
 sqlStream.reset();
 final Statement statement = ds.getConnection().createStatement();
 List<String> sqlFragments = createSqlfragments(sqlStream);
 for (String toRun : sqlFragments) {
  if (toRun.length() > 0) {
   statement.execute(toRun);
  }
 }
}

private static List<String> createSqlfragments(InputStream sqlStream) throws IOException {
 BufferedReader br = new BufferedReader(new InputStreamReader(sqlStream));

 List<String> ret = new ArrayList<String>();
 String line;
 StringBuilder script = new StringBuilder();
 while ((line = br.readLine()) != null) {
  if (line.equals("/")) {
   ret.add(removeMultilineComments(script));
   script = new StringBuilder();
  } else {
   //strip comments
   final int indexComment = line.indexOf("--");
   String lineWithoutComments = (indexComment != -1) ? line.substring(0, indexComment) : line;
   script.append(lineWithoutComments).append(" ");
  }
 }
 if (script.length() > 0) {
  ret.add(removeMultilineComments(script));
 }
 return ret;
}

private static String removeMultilineComments(StringBuilder script) {
 return script.toString().replaceAll("/\\*(.*?)\\*/", "").trim();
}

is there a clean way to acieve this? is there something in hibernate i have not seen? or can i pass an inputstream to sqlplus somehow? besides my worries about the formatting, i doubt that this code is error-free, since i have limited knowledge about the pl/sql syntax.

+1  A: 

sqlplus : yes you can. I run sqlplus from within Xemacs(editor) all the time. So, you can run sqlplus in an interpreted mode and then provide it commands and read the output as well.

Another ways is to download the free java based SQL developer tool from oracle (http://www.oracle.com/technology/software/products/sql/index.html). it comes with a sqlcli.bat utility which is a wrapper over a java program. You might want to use this command line utility to do your work.

summary, I would try running sqlplus in the background and provide it's input and reading its output (like emacs does).

anjanb
A: 

If you want to write your own script runner, you can use Spring JDBC's SimpleJdbcTemplate (http://static.springframework.org/spring/docs/2.0.x/reference/jdbc.html).

You can, of course, load the scripts as you would any resource in Spring as well.

tunaranch
A: 

You can see other people's implementations. See this resource: "Open Source SQL Clients in Java" http://java-source.net/open-source/sql-clients

dt
+3  A: 

The iBATIS ScriptRunner has a setDelimiter(String, boolean) method. This allows you to have a string other than ";" to be the separator between SQL statements.

In your Oracle SQL script, separate the statements with a "/" (slash).

In your Java code, before calling the runScript do a setDelimter("/", false) which will instruct the ScriptRunner to recognize "/" as statement separator.

+1  A: 

Hi All,

Use below solution for your reference , i have tried and tested and running successfully.

private static String script_location = "";
private static String file_extension = ".sql";
private static ProcessBuilder processBuilder =null;

public static void main(String[] args) {
    try {
        File file = new File("C:/Script_folder");
        File [] list_files= file.listFiles(new FileFilter() {

            public boolean accept(File f) {
                if (f.getName().toLowerCase().endsWith(file_extension))
                    return true;
                return false;
            }
        });
        for (int i = 0; i<list_files.length;i++){
            script_location = "@" + list_files[i].getAbsolutePath();//ORACLE
            processBuilder = new ProcessBuilder("sqlplus",        "UserName/Password@database_name", script_location); //ORACLE
            //script_location = "-i" + list_files[i].getAbsolutePath();
            //  processBuilder = new ProcessBuilder("sqlplus", "-Udeep-Pdumbhead-Spc-de-deep\\sqlexpress-de_com",script_location);
            processBuilder.redirectErrorStream(true);
            Process process = processBuilder.start();
            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String currentLine = null;
            while ((currentLine = in.readLine()) != null) {
                System.out.println(" "  + currentLine);
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }catch(Exception ex){
        ex.printStackTrace();
    }
}

Use this snippet code and try and run.

Thanx to user mentioned the solution in the below link:

http://forums.sun.com/thread.jspa?threadID=5413026

Regards | Nitin

Nitin
this looks like a good alternative :) thanks for sharing
Andreas Petersson
A: 

@Nitin,

your solution sounds good to me. But what if I have remote database? Is there any way to do so for remote database?

Please let me know if anyone knows.

Thanks.

Babban Shikaari
A: 

Baban, i don't think solution will care if db is remote - the @database is a ref to tns style name which can refer to any server??

bitkahuna
A: 

Nitin, your answer does launch scripts, but it hangs in the while loop reading the errors (or after) presumably because sqlplus is stuck at the SQL> prompt waiting input. help???

bitkahuna