HSQL Trigger generates SQL Error: -458, SQLState: S1000 / java.lang.ArrayIndexOutOfBoundsException - triggers

I have an HSQL version of an Oracle database schema to perform unit tests.
I need to update a column on update with current timestamp.
The trigger I have implemented is loaded without complaint by the hsql engine, but it crashes at runtime when I try to update rows.
Here is a sample test case that you can run in a project configured with spring and junit:
public class UtSqlTriggerTest {
#Test public void testTrigger() throws SQLException {
ResourceDatabasePopulator resourceDatabasePopulator = new ResourceDatabasePopulator();
resourceDatabasePopulator.addScript(new InMemoryResource(
"CREATE TABLE TEST (ID NUMERIC NOT NULL PRIMARY KEY,DATA VARCHAR(200), LAST_UPDATE TIMESTAMP);\n" +
"CREATE TRIGGER updTimestamp AFTER UPDATE OF DATA ON TEST\n" +
"REFERENCING NEW AS newrow OLD AS oldrow\n" +
"FOR EACH ROW\n" +
"SET newrow.LAST_UPDATE = current_timestamp;"));
Connection connection = DriverManager.getConnection("jdbc:hsqldb:file:/opt/db/testdb;shutdown=true", "SA", "");
resourceDatabasePopulator.populate(connection);
JdbcTemplate tjdbc = new JdbcTemplate(new SingleConnectionDataSource(connection, true));
tjdbc.update("INSERT INTO TEST(ID, DATA) VALUES (0, 'HELLO')");
tjdbc.update("UPDATE TEST SET DATA = 'HELLO WORLD' WHERE ID = 0");
tjdbc.queryForObject("SELECT LAST_UPDATE FROM TEST WHERE ID = 0", Date.class);
}
}
What's wrong with this trigger ? Why it generated ArrayIndexOutOfBoundException ?
CREATE TRIGGER updateDateAjoutFichier AFTER UPDATE OF DATA ON TEST
REFERENCING NEW AS newrow OLD AS oldrow
FOR EACH ROW
SET newrow.LAST_UPDATE = current_timestamp;

When you run the CREATE TRIGGER in a SQL client you will see the following error message:
attempt to assign to non-updatable column: LAST_UPDATE [SQL State=0U000, DB Errorcode=-2500]
This is because you are trying to modify a column in an AFTER trigger. Changing column values is only possible in a BEFORE trigger. So you should use:
CREATE TRIGGER updTimestamp BEFORE UPDATE OF DATA ON TEST
REFERENCING NEW AS newrow OLD AS oldrow
FOR EACH ROW
SET newrow.LAST_UPDATE = current_timestamp;

Related

Executing PostgreSQL Stored Procedure using Spring Data - JdbcTemplate

I am trying to call a PostgreSQL Stored Procedure from Spring Data JdbcTemplate. The following are the error and code block. Appreciate if any one can help.
Stored procedure
CREATE or replace PROCEDURE getRecord (
IN in_id INTEGER,
OUT out_name VARCHAR(20),
OUT out_age INTEGER)
language plpgsql
as $$
BEGIN
SELECT name, age
INTO out_name, out_age
FROM Student where id = in_id;
END
$$
Springboot Code
SimpleJdbcCall simpleJdbcCall;
dataSource = jdbcTemplate.getDataSource();
simpleJdbcCall = new SimpleJdbcCall(jdbcTemplate).withProcedureName("getrecord");
SqlParameterSource in = new MapSqlParameterSource().addValue("in_id",24);
try {
Map<String, Object> out = simpleJdbcCall.execute(in);
if (out != null){
System.out.println("A record found");
}
else
{
System.out.println("No record found");
}
}
catch (Exception e){
System.out.println(e.getMessage());
}
Error
CallableStatementCallback; bad SQL grammar [{call getrecord(?, ?, ?)}]; nested exception is org.postgresql.util.PSQLException: ERROR: getrecord(integer) is a procedure
Hint: To call a procedure, use CALL.
Position: 15
Note:
The stored procedure is having three parameters - one IN and two Out Parameters.
After going through few tutorials, I had observed that, only in parameter is being passed to the stored procedure call rather than all 3 parameters, because only the first parameter is IN and the rest of two are OUT parameters.
For example:
https://www.tutorialspoint.com/springjdbc/springjdbc_stored_procedure.htm
https://mkyong.com/spring-boot/spring-boot-jdbc-stored-procedure-examples/

42809 Error On Executing PostgreSQL Stored Procedure From Asp.Net C# Application

I am using PostgreSQL pgadmin4 (4.16v) with ASP.NET application. I have created a procedure as defined below:
CREATE OR REPLACE PROCEDURE public.usp_bind(
)
LANGUAGE 'plpgsql'
AS $BODY$
BEGIN
select district_id,district_name from district_master order by district_name;
END;
$BODY$;
From asp.net application I have called above procedure, code as below:
NpgsqlConnection conn = new NpgsqlConnection();
NpgsqlDataAdapter da = new NpgsqlDataAdapter();
NpgsqlCommand cmd = new NpgsqlCommand();
DataSet ds = new DataSet();
public string dbconstr = dbConnectstr.Connectionstring();
public DataSet getDatafunction(string procedure_, [Optional] string flag_)
{
using (conn = new NpgsqlConnection(dbconstr))
{
//conn.Open();
using (da = new NpgsqlDataAdapter())
{
da.SelectCommand.CommandType = CommandType.StoredProcedure;
da.SelectCommand.CommandText = "CALL usp_bind";
da.SelectCommand.Connection = conn;
using (ds = new DataSet())
{
da.Fill(ds);
}
}
//conn.Close();
}
return ds;
}
It's giving me an error as - 42809: 'usp_bind' is a procedure.
I would have called it using a CALL method too but did't worked. What is the exact way to call a procedure from ASP.NET application?
Don't set CommandType.StoredProcedure on your command.
Unfortunately, stored procedures are new, and CommandType.StoredProcedure was already used to invoke functions, and changing that would be a major breaking change at this point.

Database operation expected to affect 1 row(s) but actually affected 0 row(s) with entity framework

I have the following table:
And I have the following trigger:
CREATE TRIGGER check_insertion_to_pushes_table
BEFORE INSERT
ON "Pushes"
FOR EACH ROW
EXECUTE PROCEDURE trg_insert_failed_push();
CREATE or replace FUNCTION trg_insert_failed_push()
RETURNS trigger AS
$func$
BEGIN
IF (NEW."Sent" = false) THEN
IF EXISTS(
SELECT *
FROM "Pushes"
where "Sent" = false
and "CustomerId" = NEW."CustomerId"
and "PushTemplateId" = NEW."PushTemplateId"
)
THEN
RETURN NULL;
END IF;
RETURN NEW;
ELSE
RETURN NEW;
end if;
END
$func$ LANGUAGE plpgsql;
If there is row in the DB where CustomerId and PushTemplateId and Sent are equal to new row and Sent is false I would like to pass insertion.
And I have the following test to check how it works:
public class Tests
{
private IPushRepository _pushRepository;
[NUnit.Framework.SetUp]
public void Setup()
{
var confBuilder = new ConfigurationBuilder();
var configuration = confBuilder.AddJsonFile("/home/aleksej/projects/makeapppushernet/TestProject/appsettings.LocalToProdDb.json").Build();
_pushRepository = new PushRepository(new ApplicationDbContext(configuration));
}
[Test]
public async Task Test1()
{
var push = new Push
{
CustomerId = 69164,
Sent = false,
PackageId = "com.kek.lol",
Category = "betting",
Advertiser = "Advertiser",
TemplateType = "opened_and_not_registration",
IntervalType = "minutes",
BottomDateTimeBorder = 90,
TopDateTimeBorder = 60,
ClientStartDateTime = DateTime.Now,
FCMResponse = "hello",
CreatedAt = DateTime.Now,
LangCode = "En",
PushBody = "Hello",
PushTitle = "Hello",
PushTemplateId = 15
};
var pushesList = new List<Push>
{
push
};
await _pushRepository.SaveAsync(pushesList);
Assert.Pass();
}
}
If I set false for Sent in the test I have the following exception:
Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
If I set true I have nothing. It just passes insertion.
Update
Ok, with the help of Shay Rojansky's answer I have the following trigger code:
CREATE TRIGGER check_insertion_to_failed_pushes_table
BEFORE INSERT
ON "FailedPushes"
FOR EACH ROW
EXECUTE PROCEDURE trg_insert_failed_push();
CREATE or replace FUNCTION trg_insert_failed_push()
RETURNS trigger AS
$func$
DECLARE
push "FailedPushes"%ROWTYPE;
old_push_id numeric;
BEGIN
old_push_id = (SELECT "FailedPushId"
FROM "FailedPushes"
where "CustomerId" = NEW."CustomerId"
and "PushTemplateId" = NEW."PushTemplateId");
push := new;
IF (old_push_id != 0)
THEN
push."FailedPushId" = old_push_id;
DELETE
FROM "FailedPushes"
where "CustomerId" = NEW."CustomerId"
and "PushTemplateId" = NEW."PushTemplateId";
return push;
END IF;
push."FailedPushId" = (SELECT count(*) FROM "FailedPushes")::numeric + 1;
return push;
END
$func$ LANGUAGE plpgsql;
Maybe not very elegant but it works.
You are in effect configuring PostgreSQL to ignore the INSERT under certain conditions, but EF Core isn't aware of this in any way. When you tell EF Core to add a new row, it expects for that to actually happen in the database. If the entity has any database-generated columns (identity, serial), EF Core also expects to receive the their values for the newly-inserted row (and will populate them back into the entity's CLR instance).
So AFAIK you can't just tell the database to ignore the INSERT and expect everything to work...
See this issue on EF Core upsert support which is somewhat related.
Maybe your model parsing to Action in the controller might not have accurate values.

How to use Store procedure in entity frame work

I have created store procedure in Sql server its working.when i imliment it into my entity framework, its throws exception, I am new to this, kindly suggest
//SQL//
create procedure sp_getUserID
#deviceID int,
#userID int out
as
Begin
Select #userID= userId from UserTable
where deviceID = #deviceID
End
// C#
var UserID =0;
// This line error ERROR: //The specified parameter name '#userID' is not valid.
System.Data.Entity.Core.Objects.ObjectParameter UserOutput = new System.Data.Entity.Core.Objects.ObjectParameter("#userID", typeof(int));
var objStoredProcd = dbContext.sp_getUserID(UserOutput, UserLogin.DeviceUUID);
UserID = Convert.ToInt32(UserOutput.Value);

get primary key of last inserted record with JPA

I've been using JPA to insert entities into a database but I've run up against a problem where I need to do an insert and get the primary key of the record last inserted.
Using PostgreSQL I would use an INSERT RETURNING statement which would return the record id, but with an entity manager doing all this, the only way I know is to use SELECT CURRVAL.
So the problem becomes, I have several data sources sending data into a message driven bean (usually 10-100 messages at once from each source) via OpenMQ and inside this MDB I persists this to PostgreSQL via the entity manager. It's at this point I think there will be a "race condition like" effect of having so many inserts that I won't necessarily get the last record id using SELECT CURRVAL.
My MDB persists 3 entity beans via an entity manager like below.
Any help on how to better do this much appreciated.
public void onMessage(Message msg) {
Integer agPK = 0;
Integer scanPK = 0;
Integer lookPK = 0;
Iterator iter = null;
List<Ag> agKeys = null;
List<Scan> scanKeys = null;
try {
iag = (IAgBean) (new InitialContext()).lookup(
"java:comp/env/ejb/AgBean");
TextMessage tmsg = (TextMessage) msg;
// insert this into table only if doesn't exists
Ag ag = new Ag(msg.getStringProperty("name"));
agKeys = (List) (iag.getPKs(ag));
iter = agKeys.iterator();
if (iter.hasNext()) {
agPK = ((Ag) iter.next()).getId();
}
else {
// no PK found so not in dbase, insert new
iag.addAg(ag);
agKeys = (List) (iag.getPKs(ag));
iter = agKeys.iterator();
if (iter.hasNext()) {
agPK = ((Ag) iter.next()).getId();
}
}
// insert this into table always
iscan = (IScanBean) (new InitialContext()).lookup(
"java:comp/env/ejb/ScanBean");
Scan scan = new Scan();
scan.setName(msg.getStringProperty("name"));
scan.setCode(msg.getIntProperty("code"));
iscan.addScan(scan);
scanKeys = (List) iscan.getPKs(scan);
iter = scanKeys.iterator();
if (iter.hasNext()) {
scanPK = ((Scan) iter.next()).getId();
}
// insert into this table the two primary keys above
ilook = (ILookBean) (new InitialContext()).lookup(
"java:comp/env/ejb/LookBean");
Look look = new Look();
if (agPK.intValue() != 0 && scanPK.intValue() != 0) {
look.setAgId(agPK);
look.setScanId(scanPK);
ilook.addLook(look);
}
// ...
The JPA spec requires that after persist, the entity be populated with a valid ID if an ID generation strategy is being used. You don't have to do anything.