I am using Spring Boot 2 with JPA, and I leave it to Hibernate to create my database from my entities, which works fine. Now I want to add a data.sql file which will seed my database. I configured JPA as follows:
spring.jpa.properties.hibernate.hbm2ddl.import_files=data.sql
However I have a problem when executing the seed SQL. In the file I have defined a couple of functions, and at the end I am executing them:
CREATE OR REPLACE FUNCTION insert_timeout_configuration() RETURNS bigint AS $$
DECLARE created_id bigint;
BEGIN
INSERT INTO timeout_configuration (id, version, timeout)
VALUES (nextval('my_sequence'), 0, 300)
RETURNING id INTO created_id;
return created_id;
END;
$$ language plpgsql;
CREATE OR REPLACE FUNCTION insert_url_configuration() RETURNS bigint AS $$
DECLARE created_id bigint;
BEGIN
INSERT INTO url_configuration (id, version, my_url)
VALUES (nextval('my_sequence'), 0,'http://localhost:8080/')
RETURNING id INTO created_id;
return created_id;
END;
$$ language plpgsql;
DO $$
INSERT INTO global_configuration(id, version, name, timeout_configuration_id, url_configuration_id)
VALUES (nextval('my_sequence'), 0, 'My global config', insert_timeout_configuration(), insert_url_configuration());
-- do some other code
END
$$;
drop function insert_timeout_configuration();
drop function insert_url_configuration();
If I execute the same code in a PostgreSQL console to read from the file it works fine. But if I run it via Spring, I keep getting the following:
org.postgresql.util.PSQLException: Unterminated dollar quote started at position 0 in SQL $$ language plpgsql. Expected terminating $$
at org.postgresql.core.Parser.checkParsePosition(Parser.java:1273) ~ [postgresql-42.2.4.jar:42.2.4]
at org.postgresql.core.Parser.parseSql(Parser.java:1172) ~[postgresql- 42.2.4.jar:42.2.4]
at org.postgresql.core.Parser.replaceProcessing(Parser.java:1124) ~ [postgresql-42.2.4.jar:42.2.4]
at org.postgresql.core.CachedQueryCreateAction.create(CachedQueryCreateAction.java:41) ~[postgresql-42.2.4.jar:42.2.4]
at org.postgresql.core.QueryExecutorBase.createQueryByKey(QueryExecutorBase.java:314) ~[postgresql-42.2.4.jar:42.2.4]
at org.postgresql.jdbc.PgStatement.executeCachedSql(PgStatement.java:285) ~[postgresql-42.2.4.jar:42.2.4]
at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:270) ~ [postgresql-42.2.4.jar:42.2.4]
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:266) ~ [postgresql-42.2.4.jar:42.2.4]
at com.zaxxer.hikari.pool.ProxyStatement.execute(ProxyStatement.java:95) ~ [HikariCP-2.7.9.jar:?]
at com.zaxxer.hikari.pool.HikariProxyStatement.execute(HikariProxyStatement.java) ~ [HikariCP-2.7.9.jar:?]
at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(Generat ionTargetToDatabase.java:54) ~[hibernate-core-5.2.17.Final.jar:5.2.17.Final]
... 33 more
[DEBUG] 2018-09-07 21:09:43.325 [main] SQL - CREATE OR REPLACE FUNCTION insert_url_configuration() RETURNS bigint AS $$
Hibernate: CREATE OR REPLACE FUNCTION insert_url_configuration() RETURNS bigint AS $$
[WARN ] 2018-09-07 21:09:43.325 [main] ExceptionHandlerLoggedImpl - GenerationTarget encountered exception accepting command : Error executing DDL
via JDBC Statement
I am using PostgreSQL 9.5 and Spring Boot 2.0.3. I read that the delimiter $$ in the function definition cannot be parsed correctly, but I cannot find how to solve this. I tried instead of $$ to have it with simple '' and escape the single-quote everywhere, but that didn't work either.
The problem was not the syntax, because the syntax was perfectly working with flyway or directly in PostgreSQL CLI. The problem was with Hibernate, specifically with parsing the import file. The way Hibernate works is that it executes each expression from the files individually, not the whole content as a single expression. I tried to put all function definitions in one line and it worked, but it was not readable. So I found that there is a configuration for Hibernate to tell it that expressions can be multi-lined, but the $$ delimiter was still unrecognized when used in multi-line.
So the solution was to define the command with ' delimiter and then escape the single quotes where needed with an additional '.
The solution is to set the spring.jpa.properties.hibernate.hbm2ddl.import_files_sql_extractor to use org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor. MultipleLinesSqlCommandExtractor extracts the SQL expression from multiple lines, and stops when a semicolon is present. That is the end of the expression. By wrapping the body of the function in single-quote string, Hibernate will treat that wrapping as a single line.
data.sql
CREATE OR REPLACE FUNCTION insert_timeout_configuration() RETURNS bigint AS '
DECLARE created_id bigint;
BEGIN
INSERT INTO timeout_configuration (id, version, timeout)
VALUES (nextval(''my_sequence''), 0, 300)
RETURNING id INTO created_id;
return created_id;
END;
' language plpgsql;
CREATE OR REPLACE FUNCTION insert_url_configuration() RETURNS bigint AS '
DECLARE created_id bigint;
BEGIN
INSERT INTO url_configuration (id, version, my_url)
VALUES (nextval(''my_sequence''), 0,''http://localhost:8080/'')
RETURNING id INTO created_id;
return created_id;
END;
' language plpgsql;
DO '
INSERT INTO global_configuration(id, version, name, timeout_configuration_id, url_configuration_id)
VALUES (nextval(''my_sequence''), 0, ''My global config'', insert_timeout_configuration(), insert_url_configuration());
-- do some other code
END
';
drop function insert_timeout_configuration();
drop function insert_url_configuration();
I have to always keep in mind to escape the single-quotes in the expressions, but now I can have a more human-readable seed file.
Related
I was developing a code to sync data between multiple servers.
I wrote this MySQL trigger, so that it would trigger a java program if any change occurs to the table and sync it over to other servers.
If I run the below SQL code, I won't get any error and the java class is not being called.
use server1;
drop trigger if exists datainsert;
DELIMITER $$
CREATE TRIGGER datainsert
AFTER INSERT ON `student` FOR EACH ROW
begin
DECLARE id_exists Boolean;
DECLARE a INT;
SELECT 1
INTO #id_exists
FROM student
WHERE student.user_name= NEW.user_name;
IF #id_exists = 1
THEN
set a = sys_exec("java -cp \"E:\\servers\\Tomcat_instance1\\webapps\\Server\\lib\\*;E:\\servers\\Tomcat_instance1\\webapps\\Server\\WEB-INF\\classes;\" Test");
END IF;
END;
$$
DELIMITER ;
Insert query
use server1;
SET GLOBAL event_scheduler = ON;
insert into student values('l5','Test#123','asd','asd','M','20','coimbatore','654321','9876543210','a#123.com')
What am I missing here?
I have correctly linked the required library (lib_mysqludf_sys) file.
I am trying to load custom database triggers from sql file in resource folder.
In my test class I added this anotation #Sql("classpath:custom_script.sql")
In this file I have triggers for PostgreSQL database. When I execute this scripts from PgAdmin's query tool they work fine, but when I loading it from before tests in Spring Boot application, I got following error:
org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement #1 of class path resource [custom_script.sql]: CREATE OR REPLACE FUNCTION MY_TRIGGER_PROC() RETURNS TRIGGER AS $CUSTOM_TRG$ DECLARE FOO_V INTEGER; nested exception is org.postgresql.util.PSQLException: Unterminated dollar quote started at position 64 in SQL CREATE OR REPLACE FUNCTION MY_TRIGGER_PROC() RETURNS TRIGGER AS $CUSTOM_TRG$ DECLARE FOO_V INTEGER. Expected terminating $$
In custom_script.sql I have three related triggers like this:
CREATE OR REPLACE FUNCTION MY_TRIGGER_PROC()
RETURNS TRIGGER AS $CUSTOM_TRG$
DECLARE FOO_V INTEGER;
BEGIN
SELECT COUNT(F.ID)
INTO FOO_V
FROM FOO_TABLE F;
IF FOO_V > 0
THEN
RAISE EXCEPTION 'CUSTOM EXCEPTION ERROR MESSAGE FOR ID ', NEW.ID
USING ERRCODE = 'restrict_violation';
END IF;
RETURN NEW;
END;
$CUSTOM_TRG$
LANGUAGE plpgsql;
CREATE TRIGGER CUSTOM_TRG
BEFORE INSERT
ON FOO_TABLE
FOR EACH ROW
EXECUTE PROCEDURE MY_TRIGGER_PROC();
I expected that error will be because of delimiter or multi-line problem so I add these properties into properties file:
spring.jpa.properties.hibernate.connection.charSet=UTF-8
spring.jpa.properties.hibernate.hbm2ddl.import_files_sql_extractor=org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor
spring.datasource.separator=^;
and also edit my script as you can see in code below but it didn't help. Can you help me with that? Thanks.
Edited script:
CREATE OR REPLACE FUNCTION MY_TRIGGER_PROC()
RETURNS TRIGGER AS $CUSTOM_TRG$
DECLARE FOO_V INTEGER;
BEGIN
SELECT COUNT(F.ID)
INTO FOO_V
FROM FOO_TABLE F;
IF FOO_V > 0
THEN
RAISE EXCEPTION 'CUSTOM EXCEPTION ERROR MESSAGE FOR ID ', NEW.ID
USING ERRCODE = 'restrict_violation';
END IF;
RETURN NEW;
END;
$CUSTOM_TRG$
LANGUAGE plpgsql^;
CREATE TRIGGER CUSTOM_TRG
BEFORE INSERT
ON FOO_TABLE
FOR EACH ROW
EXECUTE PROCEDURE MY_TRIGGER_PROC()^;
A solution is to use single quotes around the function definition instead of $CUSTOM_TRG$ as used here; in similar cases the $$ dollar-quoting should again be ditched in favor of simple single quotes. However, the single-quoting of the entire definition requires escaping quotes within the function definition, as mentioned in the Postgresql documentation:
It is often helpful to use dollar quoting (see Section 4.1.2.4) to write the function definition string, rather than the normal single quote syntax. Without dollar quoting, any single quotes or backslashes in the function definition must be escaped by doubling them.
So in your example, you would need to double the single quotes in your function definition, occurring in the logic where FOO_V > 0.
I am using JPA to generate a script for creating database tables based on my entities:
javax.persistence.schema-generation.scripts.action=create
javax.persistence.schema-generation.scripts.create-target=db_setup.sql
The file is generated with the correct tables, however the statements do not end with a semicolon, for example:
create table hibernate_sequence (next_val bigint)
insert into hibernate_sequence values ( 1 )
insert into hibernate_sequence values ( 1 )
I assume this is valid in standard SQL? However, it is not valid in MySQL. Is there a way to tell JPA to add the semicolons to the end of the line? Or what else could be the reason that it is missing?
Since Hibernate 5.1.0, the line delimiter for the generated SQL can be defined by setting the hibernate.hbm2ddl.delimiter property, e.g.
hibernate.hbm2ddl.delimiter=";"
The default is no delimiter.
See also comments on this ticket.
I am using SQuirelL client to connect to MariaDB. My OS is Ubuntu. I have downloaded the Mariadb driver (mariadb-java-client-1.5.2.jar) to the proper location and linked it in the SQuirelL client. I have setup a database, and am able to create tables in it.
But things go south when i try creating any object where i use a DELIMITER. I have even tried with the mysql driver, mysql-connector-java-5.1.38.jar. but same error.
this is my SQL -
DELIMITER //
CREATE FUNCTION FortyTwo() RETURNS TINYINT DETERMINISTIC
BEGIN
DECLARE x TINYINT;
SET x = 42;
RETURN x;
END
//
DELIMITER ;
and this is the error -
Error: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'DELIMITER //
CREATE FUNCTION FortyTwo() RETURNS TINYINT DETERMINISTIC
BEGIN
DE' at line 1
Query is : DELIMITER //
CREATE FUNCTION FortyTwo() RETURNS TINYINT DETERMINISTIC
BEGIN
DECLARE x TINYINT
SQLState: 42000
ErrorCode: 1064
Error occurred in:
DELIMITER //
CREATE FUNCTION FortyTwo() RETURNS TINYINT DETERMINISTIC
BEGIN
DECLARE x TINYINT
I will greatly appreciate any help ! Thanks
DELIMITER is not a valid MariaDB SQL command.
Look at the documentation. You won't find it there.
DELIMITER is a MySQL Client command:
mysql> help
List of all MySQL commands:
Note that all text commands must be first on line and end with ';'
...
delimiter (\d) Set statement delimiter.
...
You generally don't need delimiters, since you only execute one command at a time when using JDBC.
They can be batched if needed for performance, but they should still be submitted to the JDBC driver one at a time.
I stuck with Oracle store procedure calling. The code looks simple, but I seriously don't know how to make it work.
This is my code for creating the procedure
DELIMITER ##
CREATE OR REPLACE PROCEDURE updateAward(_total_amount in Number, _no_of_sales in Number, _agent in NUMBER, _id in NUMBER) AS
BEGIN
update Award set total_amount = _total_amount, no_of_sales = _no_of_sales, agent_id = _agent where ID = _id ##
commit ##
So, when I execute it through NetBean (it is the only tool I have at this moment), the code run well.
I also tried to run the compile statement
alter PROCEDURE updateAward compile;
and then, use
select *
from user_errors
where name = 'ORG_SPGETTYPE'
The select return empty, proving that the compile process is ok. However, when I trigger the procedure
call updateAward(1,1,1,1);
It returns the error
Package or function UPDATEAWARD is in an invalid state
and the command
SELECT object_name FROM user_objects WHERE status='INVALID';
return the name of the procedure. How can I solve this problem ?
Update 1:
if I use
BEGIN
updateAward(1,1,1,1);
End;
I got error
Error code 6550, SQL state 65000: ORA-06550: line 2, column 20:
PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following:
:= . ( % ;
Update 2:
The reason I put the deliminator is because i got error with ";" when working through some vpn to the other network (still not sure why). So, i updated the code like your answer, but then, with the End; in the end of the procedure and then, get the Invalid SQL statement1. If i remove it and execute (through Netbean), the procedure is created successfully. However, after compiling and check the user_errors, it got the
"PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following: ; "
First things first, your procedure syntax looks wrong. Don't use DELIMITER as that syntax is specific to MySQL. Instead, try something like the following.
CREATE OR REPLACE PROCEDURE updateAward(_total_amount in Number, _no_of_sales in Number, _agent in NUMBER, _id in NUMBER) AS
BEGIN
update Award set total_amount = _total_amount, no_of_sales = _no_of_sales, agent_id = _agent where ID = _id;
commit;
END;
Firstly, there are a couple of things wrong with your procedure:
You're not using delimiters correctly. Delimiters should be used to terminate the whole procedure, not each line.
The NetBeans SQL window doesn't know SQL very well so it cannot tell when the procedure ends and something else begins. Normally, it uses semicolons (;) to tell when one statement ends and another begins, but stored procedures can contain semicolons within them so that wouldn't work. Instead, we change the delimiter to something else so that the NetBeans SQL window sends the entire stored procedure to the database in one go.
Variable names are not allowed to begin with an underscore (_). In particular, rule 5 in the list of Schema Object Naming Rules at this Oracle documentation page states that
Nonquoted identifiers must begin with an alphabetic character from your database character set.
Underscores are not alphabetic characters.
I've taken your procedure, fixed the use of delimiters and added an extra p onto the front of each parameter name (p for 'parameter'), and I got the following, which ran successfully in NetBeans and created a procedure without errors:
delimiter $$
CREATE OR REPLACE PROCEDURE updateAward(p_total_amount in Number, p_no_of_sales in Number, p_agent in NUMBER, p_id in NUMBER) AS
BEGIN
update Award set total_amount = p_total_amount, no_of_sales = p_no_of_sales, agent_id = p_agent where ID = p_id;
commit;
END;
$$
delimiter ;
Secondly, you write
[...] and then, use
select *
from user_errors
where name = 'ORG_SPGETTYPE'
The select return empty, proving that the compile process is ok.
Um, no. This proves that there are no errors in the procedure ORG_SPGETTYPE (or no procedure with that name exists). Your procedure is named updateAward, which Oracle will capitalise to UPDATEAWARD. Try
select *
from user_errors
where name = 'UPDATEAWARD';
instead.