1

I need help with DROP/CREATE of tables in my schema.sql

Setup:

  • Oracle XE
  • Spring Boot v1.4.0
  • Java 1.8

When I have the following entry in schema.sql:

DROP TABLE table_a;

CREATE TABLE table_a
(
    id                       VARCHAR(5) PRIMARY KEY,
    name                     VARCHAR(100));

I get the exception

DROP TABLE table_a; nested exception is java.sql.SQLSyntaxErrorException: ORA-00942: table or view does not exist

When I looked up some help on how to do a DROP TABLE IF EXISTS in Oracle, the best answer I got was the following (works in SQLDeveloper):

BEGIN
  EXECUTE IMMEDIATE 'DROP TABLE table_a';
  EXCEPTION
  WHEN OTHERS THEN
  IF SQLCODE != -942 THEN
    RAISE;
  END IF;

  EXECUTE IMMEDIATE 'CREATE TABLE table_a
  (
    id               VARCHAR(5) PRIMARY KEY,
    name             VARCHAR(100)
  )';
END;

However, the above code throws the following Exception:

2016-08-10 14:55:36.232 INFO 9032 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : Executing SQL script from URL [file:/C:/projects/project_a/target/classes/schema.sql] 2016-08-10 14:55:36.286 WARN 9032 --- [ main] o.s.w.c.s.GenericWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private javax.sql.DataSource org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration.dataSource; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration$NonEmbeddedConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSourceInitializer': Invocation of init method failed; nested exception is org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement #1 of URL [file:/C:/projects/project_a/target/classes/schema.sql]: BEGIN EXECUTE IMMEDIATE 'DROP TABLE table_a'; nested exception is java.sql.SQLException: ORA-06550: line 1, column 44: PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following:

  • & = - + ; < / > at in is mod remainder not rem return returning <> or != or ~= >= <= <> and or like like2 like4 likec between into using || multiset bulk member submultiset

Does anybody have a more elegant way to handle DROP/CREATE of Oracle Tables in Spring Boot?

Tora Tora Tora
  • 973
  • 3
  • 18
  • 33
  • Not sure that does quite what you want; the table creation is inside the exception handler - so if the table does exist it will be dropped and not be recreated. Anyway, your framework is seeing the first two lines, up to the first semicolon, as a standalone statement - it doesn't know the whole thing is one PL/SQL block, apparently. Can you change the statement delimiter? – Alex Poole Aug 10 '16 at 22:22
  • Possible duplicate of [create or replace table in oracle pl/sql](http://stackoverflow.com/questions/16634699/create-or-replace-table-in-oracle-pl-sql) –  Aug 10 '16 at 22:25
  • 1
    @mathguy - the question isn't really how to drop and create a table in PL/SQL (which here seems to be from a schema creation script, so the creating-tables-at-runtime warnings don't really apply), but how to call a PL/SQL block from Spring Boot? – Alex Poole Aug 10 '16 at 22:28
  • I replaced with one long sentence -> "BEGIN EXECUTE IMMEDIATE 'DROP TABLE table_a'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; EXECUTE IMMEDIATE 'CREATE TABLE table_a (id VARCHAR(5) PRIMARY KEY, name VARCHAR(100))'; END;". However, I still get the same Exception. – Tora Tora Tora Aug 10 '16 at 22:29
  • The important bit was "up to the first semicolon", not the number of lines. The semicolon is being seen - somewhere, not sure where in your code or framework - as a statement separator. It's incorrectly splitting the PL/SQL up into smaller, invalid, chunks. – Alex Poole Aug 10 '16 at 22:32
  • 1
    There's no need to run the CREATE TABLE using dynamic SQL in this case since it's static. Wrap just the DROP TABLE with the exception handler. – Jeffrey Kemp Aug 11 '16 at 06:58
  • [Possibly related question](http://stackoverflow.com/q/30753901/266304). – Alex Poole Aug 11 '16 at 09:49

4 Answers4

3

You haven't shown your Java code, but from the stack trace it looks like you're calling ScriptUtil's executeSqlScript() method, which used the default semicolon statement separator.

It isn't recognising the PL/SQL block as a single unit, and is instead trying to run everything up to the first semicolon as a standalone SQL statement - which isn't valid and causes the error you're seeing.

You can use the version of executeSqlScript() that lets you override the default and use / instead:

public static void executeSqlScript(Connection connection,
                                    EncodedResource resource,
                                    boolean continueOnError,
                                    boolean ignoreFailedDrops,
                                    String commentPrefix,
                                    String separator,
                                    String blockCommentStartDelimiter,
                                    String blockCommentEndDelimiter)
                             throws ScriptException

separator - the script statement separator; defaults to ";" if not specified and falls back to "\n" as a last resort; may be set to "^^^ END OF SCRIPT ^^^" to signal that the script contains a single statement without a separator

which would mean all the SQL statements in your script would have to use a / separator instead of a semicolon too:

BEGIN
  EXECUTE IMMEDIATE 'DROP TABLE table_a';
  EXCEPTION
  WHEN OTHERS THEN
  IF SQLCODE != -942 THEN
    RAISE;
  END IF;
END;
/

CREATE TABLE table_a
  (
    id               VARCHAR(5) PRIMARY KEY,
    name             VARCHAR(100)
  )
/

...

As noted in comments, your original block wasn't quite right anyway; and the create doesn't need to be done through PL/SQL, even if the drop needs to be.

But that method also has a ignoreFailedDrops flag, which seems to do exactly what you want (I can't test it to check though):

ignoreFailedDrops - whether or not to continue in the event of specifically an error on a DROP statement

If you use that version and pass true for that flag you don't need the PL/SQL wrapper around the drop; you can keep the semicolon separator and revert to:

DROP TABLE table_a;

CREATE TABLE table_a
(
    id                       VARCHAR(5) PRIMARY KEY,
    name                     VARCHAR(100)
);

...

If your schema script contains any other PL/SQL - trigger, packages, etc. - then you will still need to switch to using the slash separator (or any other separator of your choice; a slash is traditional though) for everything.

Alex Poole
  • 183,384
  • 11
  • 179
  • 318
  • Thanks for that elaborate suggestion. I didn't see a quick way to have Spring-Boot use the above mentioned version of the executeSqlScript() method. So, I'm using the below suggestion provided by Gary Myers. When I get the time I will investigate on how to use the above to make it work. – Tora Tora Tora Aug 12 '16 at 16:49
1

Rather than dropping and recreating tables Oracle encourages you to use global temporary tables. The advantage of a global temp table is that you don't have to go through this routine of dropping a table just to recreate it.

Now, the thing about a global temp table is that the data in it is only visible to the session which creates it, and the data is automatically deleted when either A) the transaction commits, or B) the session disconnects - you get to choose when the data should be deleted when the table is created, but the data doesn't persist for long periods of time, nor is it visible to everyone who connects to the database. It's intended for "scratchpad" tables where an application needs to put data into a table on a temporary basis, use it within a given session, and then get rid of it. If this matches your intended usage this would be a good way to go.

To create your table as a global temp table you'd specify the following:

CREATE GLOBAL TEMPORARY TABLE table_a
  (id                       VARCHAR(5) PRIMARY KEY,
   name                     VARCHAR(100))
  ON COMMIT PRESERVE ROWS;

(And as a side comment - you should get in the habit of using VARCHAR2 instead of VARCHAR with Oracle. VARCHAR is an ANSI type which Oracle does not correctly support. In Oracle VARCHAR is currently a synonym for VARCHAR2, but the rumor is that Some Day Oracle will change VARCHAR so it's fully ANSI-compliant, and if you've used it in your table the behavior of your database may quietly change without warning. So this is your warning :-).

Best of luck.

  • The `schema.sql` script being run *suggests* this is initial schema creation, and a protective drop isn't unreasonable in that case. You have to hope the script isn't accidentally rerun one day of course... Not sure GTT is relevant if that is the case though. – Alex Poole Aug 10 '16 at 22:34
  • Good news -> The above script works. However, after the application has terminated I expected the tables to get dropped. They stayed. Second, when I ran the application again I expected them to be overwritten. However, I get the Exception "ORA-00955: name is already used by an existing object" – Tora Tora Tora Aug 10 '16 at 22:58
  • The advantage of a global temporary table is that you only have to create it once and then it continues to exist. You don't have to drop and re-create the table over and over. – Bob Jarvis - Слава Україні Aug 11 '16 at 11:35
  • @BobJarvis, I see your point. However, what we are doing here is setup the DB for Unit Tests every time we do a build {mvn clean package}. Hence, we want a script that drops everything and recreates the tables and subsequently populates them. – Tora Tora Tora Aug 11 '16 at 20:23
1

Create a stored procedure

create or replace procedure recreate_table 
  (i_table_name in varchar2, i_create_stmt in varchar2) 
is
BEGIN
  BEGIN
    EXECUTE IMMEDIATE 'DROP TABLE '||upper(i_table_name);
  EXCEPTION
    WHEN OTHERS THEN
      IF SQLCODE != -942 THEN
        RAISE;
      END IF;
  END;
  EXECUTE IMMEDIATE i_create_stmt;
END;

Then your schema.sql can use the SQL statement:

call recreate_table('TABLE_A','CREATE TABLE TABLE_A (ID NUMBER, VAL VARCHAR2(10))');

rather than including PL/SQL

Gary Myers
  • 34,963
  • 3
  • 49
  • 74
0

It's old thread but I've stumbled upon the same problem in latest Spring Boot - 1.5.4 and I think I've found an answer (also thanks to Alex Poole above). By default Spring Boot uses property

spring.datasource.separator=;

So as You can see the ';' is used as separator for SQL commands. So in the process of trying to put PL/SQL procedure into 'schema.sql' Oracle DB recieves only first SQL-command and tries to write it ot the DB. So having code:

BEGIN
  EXECUTE IMMEDIATE 'DROP TABLE table_a';
  EXCEPTION
  WHEN OTHERS THEN
  IF SQLCODE != -942 THEN
    RAISE;
  END IF;
END;

In DB only BEGIN and EXECUTE IMMEDIATE 'DROP TABLE table_a'; is being stored - You can see that in eg. SQL Developer. Changing the separator to eg. ';;' helps - and also using it in SQL code instead of ';'. I do not recommend using '/' as separator as this is the sign used to create multiline-SQL-comments so it can cause problems when someone uses them in SQL file.

Chlebik
  • 646
  • 1
  • 9
  • 27