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.
Related
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.
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 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.
I have declared package level type this way (using Oracle XE 11):
create or replace PACKAGE RM_TYPES
AS
TYPE RECPPART_ARR IS TABLE OF RM_RECEPCIONPARTIDAS%ROWTYPE;
END RM_TYPES;
I have SP like this:
create or replace PROCEDURE "RM_TRY_B" (partidas OUT RM_TYPES.RECPPART_ARR) as
begin
SELECT * BULK COLLECT INTO partidas FROM rm_recepcionpartidas;
end;
I have java code like this:
CallableStatement cstmt = conn.prepareCall("{call RM_TRY_B(?)}");
cstmt.registerOutParameter(1, OracleTypes.ARRAY, "RM_TYPES.RECPPART_ARR");
cstmt.execute();
Array a = cstmt.getArray(1);
It gives me an excepcion:
Exception in thread "main" java.sql.SQLException: invalid name pattern: RM_TYPES.RECPPART_ARR
I have already granted access to package to my user by issuing this command to oracle:
GRANT EXECUTE ON RM_TYPES TO myuser;
I used this as reference: https://docs.oracle.com/database/121/JJDBC/apxref.htm#JJDBC28913 (section named: Creating Java level objects for each row using %ROWTYPE Attribute
Where did I do wrong?
I've also try passing in this name in my java code: "RECPPART_ARR" or "MYSCHEMA.RM_TYPES.RECPPART_ARR" none of them works.
Then I read someone said this on stackoverflow: java - passing array in oracle stored procedure : "actually the problem is that any type created within a package is not visible by java. If I create the type at schema level then it works. "
Is it true?
Then maybe I should define an alias at schema level?
How? I tried "CREATE SYNONYM":
CREATE PUBLIC SYNONYM RECPPART_ARRAY FOR RM_TYPES.RECPPART_ARR;
And then (tried to modify my SP):
create or replace PROCEDURE "RM_TRY_B" (partidas OUT RECPPART_ARRAY) as
begin
SELECT * BULK COLLECT INTO partidas FROM rm_recepcionpartidas;
end;
But this time this SP wouldn't compile, with this error message in my SQLDeveloper: Error(1,36): PLS-00905: object MYSCHEMA.RECPPART_ARRAY is invalid.
Then I tried using the previous definition of my sp:
create or replace PROCEDURE "RM_TRY_B" (partidas OUT RM_TYPES.RECPPART_ARR) as
begin
SELECT * BULK COLLECT INTO partidas FROM rm_recepcionpartidas;
end;
And modified my Java code to use the synomim instead:
CallableStatement cstmt = conn.prepareCall("{call RM_TRY_B(?)}");
cstmt.registerOutParameter(1, OracleTypes.ARRAY, "RECPPART_ARRAY");
cstmt.execute();
Array a = cstmt.getArray(1);
Still, exception, with message: Fail to construct descriptor: Unable to resolve type: "MYSCHEMA.RECPPART_ARRAY"
ADDITION
Some other info I just found:
http://oracle.developer-works.com/article/5227493/%22invalid+name+pattern%22++when+trying+to+user+packaged+TYPE
Someone wrote: I had the same issue. Managed to solve it by creating public synonym and giving grants.
As you see, I did that already, but no luck for me.
ADDITION
Or ... maybe something like this in oracle (after reading this: http://docs.oracle.com/javadb/10.10.1.2/ref/rrefsqljgrant.html ):
create or replace PACKAGE RM_TYPES
AS
TYPE RECPPART_ARR IS TABLE OF RM_RECEPCIONPARTIDAS%ROWTYPE;
END RM_TYPES;
sqlplus (logged in as sys as SYSDBA)> GRANT USAGE ON TYPE RM_TYPES.RECPPART_ARR TO myuser;
CREATE PUBLIC SYNONYM RECPPART_ARRAY FOR RM_TYPES.RECPPART_ARR;
create or replace PROCEDURE "RM_TRY_B" (partidas OUT RM_TYPES.RECPPART_ARR) as
begin
SELECT * BULK COLLECT INTO partidas FROM rm_recepcionpartidas;
end;
....
I tried it..., even logged in using user "sys" as SYSDBA .... I got an error when issuing grant:
Error starting at line : 1 in command -
GRANT USAGE TYPE ON RM_TYPES.RECP_ARR TO myuser
Error report -
SQL Error: ORA-00990: missing or invalid privilege
00990. 00000 - "missing or invalid privilege"
*Cause:
*Action:
I'm running out of idea now.
JDBC Support for PL/SQL Data Types as Parameters is a new feature of Oracle 12c.
PL/SQL types look and act like regular types; they can be used in SQL and other contexts, they have a TYPE_OID and TYPECODE, and they have a data dictionary view (DBA_PLSQL_TYPES). One odd difference is that PL/SQL types do not show up in DBA_OBJECTS.
In older versions you must create a TYPE as a stand-alone object in order to use it outside of PL/SQL. Code like this can create the objects:
CREATE OR REPLACE TYPE RECPPART_REC IS OBJECT
(
--list RM_RECEPCIONPARTIDAS columns here. %ROWTYPE is not available in SQL.
);
CREATE OR REPLACE RECPPART_ARR IS TABLE OF RECPPART_REC;
You could make use of a little-known feature in PL/SQL: PIPELINED functions. An example:
create table tab (
id number(7)
);
/
insert into tab values (1);
insert into tab values (2);
create or replace package pkg
as
type typ is table of tab%rowtype;
end pkg;
/
create or replace procedure proc (param out pkg.typ) as
begin
select * bulk collect into param from tab;
end;
/
create or replace function func return pkg.typ pipelined as
begin
for rec in (select * from tab) loop
pipe row(rec);
end loop;
end;
/
select * from table(func);
The above will yield:
ID
--
1
2
So you can materialise the table type also easily from JDBC.
The reason for this is the fact that every pipelined function implicitly creates a top-level SQL type that is of the same type as your PL/SQL table type. In the above case something like:
create or replace type SYS_PLSQL_29848_13_1 as object (ID NUMBER(7));
create or replace type SYS_PLSQL_29753_9_1 as table of SYS_PLSQL_29848_13_1;
This is more of a side-note. In general, you should probably prefer the approach suggested by Jon Heller
I'm looking for a way to create a function in MySQL preferably from a script (executed from Java) but standard JDBC or Spring's JdbcTemplate would work as well.
I've managed to successfully create the function from mysql command-line console using:
DELIMITER $$
CREATE FUNCTION test() RETURNS double DETERMINISTIC
BEGIN
RETURN 63;
END$$
But since DELIMITER in not a valid SQL syntax I get an exception when running it from a script:
[42000][1064] You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DELIMITER $$
When I try to create it from Spring JDBC template using
jdbcTemplate.execute("CREATE FUNCTION some() RETURNS double DETERMINISTIC\n" +
"BEGIN\n" +
"RETURN 63;\n" +
"END;");
I get the following exception:
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near
'CREATE FUNCTION some() RETURNS double DETERMINISTIC
BEGIN
RETURN 63;
END' at line 1`
Does anyone know how to go about?
You can use jdbc to do that, but on your script don't declare any delimiter and then put your own one to separate functions or procedures declarations
your script will look like :
CREATE FUNCTION do_something() RETURNS double DETERMINISTIC
BEGIN
RETURN 63;
END;;
CREATE FUNCTION do_other_thing() RETURNS double DETERMINISTIC
BEGIN
RETURN 63;
END;;
and the code to use will be like this after getting your sql script into a StringBuilder
String[] sql = stringBuilder.toString().split(";;");
for (int i = 0; i < sql.length; i++) {
statment.executeUpdate(sql[i]);
}
I hope this ill help !
when you use Spring Framework, please use Hibernate Framework for connecting data.