Firebird autoincrement column - triggers

I need help creating an auto-increment column in Firebird.
Here is my table below
I want column Seqid to be auto incrementing
create Table TS_PRODUCT_PRICEHISTORY
(
Seqid int not null,
Remarks varchar(100)
)
I created a generator
CREATE GENERATOR tsproductpricehistory_gen_id;
Then create a trigger
CREATE TRIGGER aitspph_id FOR TS_PRODUCT_PRICEHISTORY
ACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
IF (NEW.SEQID IS NULL) THEN
NEW.SEQID = GEN_ID(tsproductpricehistory_gen_id,1);
END
When I try to create a trigger via above code I get this error
Error: *** IBPP::SQLException *** Context: Statement::Prepare( CREATE
TRIGGER aitspph_id FOR TS_PRODUCT_PRICEHISTORY ACTIVE BEFORE INSERT
POSITION 0 AS BEGIN IF (NEW.SEQID IS NULL) THEN NEW.SEQID =
GEN_ID(tsproductpricehistory_gen_id,1) ) Message: isc_dsql_prepare
failed
SQL Message : -104 Invalid token
Engine Code : 335544569 Engine Message : Dynamic SQL Error SQL
error code = -104 Unexpected end of command - line 6, column 50
I can't seem to find whats wrong with the code creating the trigger.

Try this one:
CREATE TABLE TS_PRODUCT_PRICEHISTORY (
SEQID INTEGER NOT NULL,
REMARKS VARCHAR(100)
);
/* Autoincrement for field (SEQID) */
CREATE GENERATOR GEN_TS_PRODUCT_PRICEHISTORY_ID;
SET TERM ^ ;
CREATE TRIGGER TS_PRODUCT_PRICEHISTORY_BI FOR TS_PRODUCT_PRICEHISTORY
ACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
IF (NEW.SEQID IS NULL) THEN
NEW.SEQID = GEN_ID(GEN_TS_PRODUCT_PRICEHISTORY_ID,1);
END^
SET TERM ; ^

Related

postgres: How to generically make a column immutable?

Here's the problem.
create table customer (
customer_id int generated by default as identity (start with 100) primary key
);
create table cart (
cart_id int generated by default as identity (start with 100) primary key
);
I want to protect customer_id and cart_id from updating generically once they are inserted. How?
UPD: While I was writing the question I found the answer to my original question. Here it is:
create table cart (
cart_id int generated by default as identity (start with 100) primary key,
name text not null,
at timestamp with time zone
);
create or replace function table_update_guard() returns trigger
language plpgsql immutable parallel safe cost 1 as $body$
begin
raise exception
'trigger %: updating is prohibited for %',
tg_name, tg_argv[0]
using errcode = 'restrict_violation';
return null;
end;
$body$;
create or replace trigger cart_update_guard
before update of cart_id, name on cart for each row
-- NOTE: the WHEN clause below is optional
when (
old.cart_id is distinct from new.cart_id
or old.name is distinct from new.name
)
execute function table_update_guard('cart_id, name');
> insert into cart (cart_id, name) values (0, 'prado');
INSERT 0 1
> update cart set cart_id = -1 where cart_id = 0;
ERROR: trigger cart_update_guard: updating is prohibited for cart_id, name
CONTEXT: PL/pgSQL function table_update_guard() line 3 at RAISE
> update cart set name = 'nasa' where cart_id = 0;
ERROR: trigger cart_update_guard: updating is prohibited for cart_id, name
CONTEXT: PL/pgSQL function table_update_guard() line 3 at RAISE
> update cart set at = now() where cart_id = 0;
UPDATE 1
The WHEN clause was suggested by Belayer in his answer. The full explanation is in my research. Additionally I examined the approach with playing with privileges. NOTE: Some people say that triggers like here are performance killers. They are wrong. How do you think postgres implements constraints internally? — Using implicit triggers like defined here.
If I understand correctly you want to prevent any user from modifying the the table id once it is established and to have a generic function produce the exception, while still allowing other updates. You can accomplish this by modifying the trigger rather than the function. Specify the WHEN predicate on the trigger itself. For the cart table then:
create or replace trigger cart_id_guard
before update of cart_id
on cart for each row
when (old.cart_id is distinct from new.cart_id)
execute function surrogate_id_guard('cart_id');
The for the customer table the trigger becomes:
create or replace trigger customer_id_guard
before update of customer_id
on customer for each row
when (old.customer_id is distinct from new.customer_id)
execute function surrogate_id_guard('customer_id');
The trigger function itself does not change. (demo here)
TL;DR
What did I try? Revoking UPDATE privilege doesn't work.
# \c danissimo danissimo
You are now connected to database "danissimo" as user "danissimo".
> revoke update (customer_id) on customer from danissimo;
REVOKE
> insert into customer (customer_id) values (0);
INSERT 0 1
> update customer set customer_id = 0 where customer_id = 0;
UPDATE 1
> update customer set customer_id = -1 where customer_id = 0;
UPDATE 1
Okay, let's put a guard on it.
create or replace function customer_id_guard() returns trigger
language plpgsql as $body$
begin
if old.customer_id != new.customer_id then
raise exception
'trigger %: updating is prohibited for %',
tg_name, 'customer_id' using
errcode = 'restrict_violation';
end if;
return new;
end;
$body$;
create or replace trigger customer_id_guard
after update on customer for each row
execute function customer_id_guard();
Now let's give them some work.
> update customer set customer_id = -1 where customer_id = -1;
UPDATE 1
Right, I didn't change the value. What about this:
> update customer set customer_id = 0 where customer_id = -1;
ERROR: trigger customer_id_guard: updating is prohibited for customer_id
CONTEXT: PL/pgSQL function customer_id_guard() line 4 at RAISE
Yeah, here it goes. Good, let's protect cart_id as well. I don't want to copy–paste trigger functions, so I let's try to generalize it:
create or replace function generated_id_guard() returns trigger
language plpgsql as $body$
declare
id_col_name text := tg_argv[0];
equal boolean;
begin
execute format('old.%1$I = new.%1$I', id_col_name) into equal;
if not equal then
raise exception
'trigger %: updating is prohibited for %',
tg_name, id_col_name using
errcode = 'restrict_violation';
end if;
return new;
end;
$body$;
create or replace trigger cart_id_guard
after update on cart for each row
execute function generated_id_guard('cart_id');
As you might notice I pass the column name to the trigger function and generate an expression and put the result of that expression into equal which then test.
> insert into cart (cart_id) values (0);
INSERT 0 1
> update cart set cart_id = 0 where cart_id = 0;
ERROR: syntax error at or near "old"
LINE 1: old.cart_id = new.cart_id
^
QUERY: old.cart_id = new.cart_id
CONTEXT: PL/pgSQL function generated_id_guard() line 6 at EXECUTE
Hmmm... He's right, what the dangling old.cart_id = new.cart_id? What if I write
execute format('select old.%1$I = new.%1$I', id_col_name) into equal;
> update cart set cart_id = 0 where cart_id = 0;
ERROR: missing FROM-clause entry for table "old"
LINE 1: select old.cart_id = new.cart_id
^
QUERY: select old.cart_id = new.cart_id
CONTEXT: PL/pgSQL function generated_id_guard() line 6 at EXECUTE
Right, right... What if I write
declare
id_old int;
id_new int;
begin
execute format('select %I from old', id_col_name) into id_old;
execute format('select %I from new', id_col_name) into id_new;
if id_old != id_new then
> update cart set cart_id = 0 where cart_id = 0;
ERROR: relation "old" does not exist
LINE 1: select cart_id from old
^
QUERY: select cart_id from old
CONTEXT: PL/pgSQL function generated_id_guard() line 7 at EXECUTE
Aha, «relation "old" does not exist»...
Well, here's the last resort:
drop table cart;
create table cart (
cart_id int generated by default as identity (start with 100) primary key,
at timestamp with time zone
);
insert into cart (cart_id) values (0);
create or replace function surrogate_id_guard() returns trigger
language plpgsql immutable parallel safe cost 1 as $body$
begin
raise exception
'trigger %: updating is prohibited for %',
tg_name, tg_argv[0] using
errcode = 'restrict_violation';
return null;
end;
$body$;
create or replace trigger cart_id_guard
before update of cart_id on cart for each row
execute function surrogate_id_guard('cart_id');
I just make it trigger on any attempt to update cart_id. Let's check:
> update cart set cart_id = 0 where cart_id = 0;
ERROR: trigger cart_id_guard: updating is prohibited for cart_id
CONTEXT: PL/pgSQL function surrogate_id_guard() line 3 at RAISE
> update cart set at = now() where cart_id = 0;
UPDATE 1
Well, finally I answered my original question at this point. But another question is still arisen: How to apply the same algorithm encoded in a function to columns given in args to that function?
The very first attempt in my previous research was to revoke privileges. As Laurenz Albe pointed in his comment I had to revoke the privilege to update the whole table instead of revoking the privilege to update a certain column. Here's the code:
# \c danissimo danissimo
You are now connected to database "danissimo" as user "danissimo".
create table cart (
cart_id int generated by default as identity (start with 100) primary key,
at timestamp with time zone default now()
);
insert into cart default values;
revoke update on cart from danissimo;
Can I update the table now?
> update cart set at = at - interval '1 day';
ERROR: permission denied for table cart
Okay, let's grant the privilege to update columns other than cart_id:
> grant update (at) on cart to danissimo;
> update cart set at = at - interval '1 day';
UPDATE 1
So far, so good. Now time ticks and eventually danissimo adds another column item_ids:
alter table cart add column item_ids int[];
Can danissimo update the new column now? Keep in mind that the privilege to update the whole table was revoked from him and the privilege to update the new column was not granted:
> update cart set item_ids = array[1, 3, 7 ,5];
ERROR: permission denied for table cart
And if I grant him the privilege?
> grant update (item_ids) on cart to danissimo;
> update cart set item_ids = array[1, 3, 7 ,5];
UPDATE 1
What does that all mean? I considered two approaches. One is to prohibit updates of a column once a value is given to the column. Another is to play with privileges. In our projects it's usual that we add new columns while the projects evolve. If I stick with privileges I have to grant the privilege to update the new column each time I add a new one. On the other hand, if I protect some columns with a trigger I just add new columns and bother no more.
CONCLUSION: Use triggers as shown above 👆🏼.

Trigger LMD - PL/SQL

I would like to execute the trigger below in order for the error to be raised("You are not allowed to change the date"), that should be triggered when someone tries to change the date in the table, but I can't manage to find out how I can call the trigger. I did try SET DataValiditate = TO_DATE(DataValiditate, 'yyyy/mm/dd') + '2023/10/14';, but it doesn't work. Please could you help me? Thank you!
CREATE TABLE ProduseAchizitionate (
ID_Achizitii NUMBER(9) NOT NULL,
CodProdus NUMBER(9) NOT NULL,
Cantitate NUMBER(9) NOT NULL,
DataValiditate DATE NOT NULL,
CONSTRAINT pk_ProduseAchizitionate PRIMARY KEY(ID_Achizitii)
);
ALTER TABLE ProduseAchizitionate
ADD CONSTRAINT "include" FOREIGN KEY (ID_Achizitii)
REFERENCES Achizitii(ID_Achizitii) ON DELETE CASCADE;
ALTER TABLE ProduseAchizitionate
ADD CONSTRAINT "cuprinde" FOREIGN KEY (CodProdus)
REFERENCES Medicament(CodProdus) ON DELETE CASCADE;
INSERT ALL
INTO ProduseAchizitionate(ID_Achizitii, CodProdus, Cantitate,
DataValiditate)
VALUES(01,04,40,TO_DATE('2023/10/10','yyyy/mm/dd'))
SELECT * FROM dual;
CREATE OR REPLACE TRIGGER produseAchizitionate_update
BEFORE UPDATE OF DataValiditate ON ProduseAchizitionate
FOR EACH ROW
BEGIN
IF (:NEW.DataValiditate <> :OLD.DataValiditate) THEN
RAISE_APPLICATION_ERROR(-20201,'Data expirarii produsului nu poate fi modificata!');
END IF;
END;
/
UPDATE ProduseAchizitionate
SET DataValiditate = TO_DATE(DataValiditate, 'yyyy/mm/dd');
As shown, your UPDATE isn't changing the value of the date at all, so the trigger won't evaluate the error condition. Try replacing your to_date with SYSDATE, or TO_DATE('2023/10/14', 'yyyy/mm/dd'):
CREATE TABLE ProduseAchizitionate (
ID_Achizitii NUMBER(9) NOT NULL,
CodProdus NUMBER(9) NOT NULL,
Cantitate NUMBER(9) NOT NULL,
DataValiditate DATE NOT NULL,
CONSTRAINT pk_ProduseAchizitionate PRIMARY KEY(ID_Achizitii)
);
INSERT ALL
INTO ProduseAchizitionate(ID_Achizitii, CodProdus, Cantitate,
DataValiditate)
VALUES(01,04,40,TO_DATE('2023/10/10','yyyy/mm/dd'))
SELECT * FROM dual;
CREATE OR REPLACE TRIGGER produseAchizitionate_update
BEFORE UPDATE OF DataValiditate ON ProduseAchizitionate
FOR EACH ROW
BEGIN
IF (:NEW.DataValiditate <> :OLD.DataValiditate) THEN
RAISE_APPLICATION_ERROR(-20201,'Data expirarii produsului nu poate fi modificata!');
END IF;
END;
/
UPDATE ProduseAchizitionate SET DataValiditate = SYSDATE;
Output:
Table PRODUSEACHIZITIONATE created.
1 row inserted.
Trigger PRODUSEACHIZITIONATE_UPDATE compiled
Error starting at line : 37 in command -
UPDATE ProduseAchizitionate SET DataValiditate = SYSDATE
Error report -
ORA-20201: Data expirarii produsului nu poate fi modificata!
ORA-06512: at "PETE.PRODUSEACHIZITIONATE_UPDATE", line 3
ORA-04088: error during execution of trigger 'PETE.PRODUSEACHIZITIONATE_UPDATE'

how to create SQL procedure which contains compile time errors?

I am writing a one time SQL procedure which will add an identity column to a table, then set the primary key of the table to that new identity column. But I cannot compile the SQL procedure because the CREATE PROCEDURE statement returns a compile error.
How to tell SQL to create the procedure despite the compile error?
CREATE or replace PROCEDURE TESTSQL(
)
LANGUAGE SQL
SPECIFIC ORDHEADP5T
SET OPTION DATFMT = *ISO, DLYPRP = *YES, DBGVIEW = *SOURCE,
USRPRF = *OWNER, DYNUSRPRF = *OWNER, COMMIT = *CHG
BEGIN
alter table ordheader
add column ordidnum int generated always as identity ;
alter table ordheader
add constraint pk_ordheader primary key ( ordidnum ) ;
end
SQL0205 30 14 Position 44 Column ORDIDNUM not in table ORDHEADER
You can do this one if you reduce it to one alter table like :
CREATE or replace PROCEDURE TESTSQL()
LANGUAGE SQL
SPECIFIC ORDHEADP5T
SET OPTION DATFMT = *ISO, DLYPRP = *YES, DBGVIEW = *SOURCE,
USRPRF = *OWNER, DYNUSRPRF = *OWNER, COMMIT = *CHG
BEGIN
alter table ordheader
add column ordidnum int generated always as identity
add constraint pk_ordheader primary key ( ordidnum ) ;
end

Firebird Error in Creating Trigger

Preparing query: CREATE TRIGGER autoincrementor_id FOR ID
ACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
IF (NEW.OID IS NULL) THEN
NEW.OID = GEN_ID(MY_GEN_ID,1)
Error: *** IBPP::SQLException ***
Context: Statement::Prepare( CREATE TRIGGER autoincrementor_id FOR ID
ACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
IF (NEW.OID IS NULL) THEN
NEW.OID = GEN_ID(MY_GEN_ID,1) )
Message: isc_dsql_prepare failed
SQL Message : -104
Invalid token
Engine Code : 335544569
Engine Message :
Dynamic SQL Error
SQL error code = -104
Unexpected end of command - line 6, column 33
I get this error while creating the trigger below:
CREATE TRIGGER autoincrementor_id FOR ID
ACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
IF (NEW.OID IS NULL) THEN
NEW.OID = GEN_ID(MY_GEN_ID,1);
END
Where am I missing? I already have my table ID created with a primary id oid, that must be auto incremented. I also created my generator function MY_GEN_ID.
You have to set the statement terminator and use it to terminate the CREATE TRIGGER statement, ie
SET TERM ^;
CREATE TRIGGER autoincrementor_id FOR ID
... rest of the trigger's body
END^
SET TERM ;^

how to create a column constant in Postgresql

create table test(
t_id SERIAL primary key,
t_date CONSTANT date default CURRENT_DATE
);
ERROR: syntax error at or near "date"
LINE 3: t_date CONSTANT date default CURRENT_DATE
^
********** Error **********
ERROR: syntax error at or near "date"
SQL state: 42601
For a default value you can use a function,
CREATE TABLE test(
t_id SERIAL primary key,
t_date date DEFAULT now()
);
about constant, I never used, even other SQL (!), only in a PL/SQL context ...
If you need a "no update" constraint, you can use a trigger. Example:
CREATE FUNCTION correct_update() RETURNS trigger AS $$
BEGIN
NEW.t_date=OLD.t_date;
RETURN NEW;
END
$$ LANGUAGE plpgsql;
CREATE TRIGGER no_date_update
BEFORE BEFORE UPDATE ON test
FOR EACH ROW
WHEN (OLD.t_date IS DISTINCT FROM NEW.t_date)
EXECUTE PROCEDURE correct_update();
For a complete control, you need also trigg the INSERT event, (and does not need a default value anymore because insert trigger will do):
create table test(
t_id SERIAL primary key,
t_date date -- a default will be redundant
);
CREATE FUNCTION correct_date() RETURNS trigger AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
NEW.t_date=now(); -- default value
ELSIF TG_OP = 'UPDATE' THEN -- optional AND OLD.t_date != NEW.t_date
NEW.t_date=OLD.t_date; -- "constant" behaviour
END IF;
RETURN NEW;
END
$$ LANGUAGE plpgsql;
CREATE TRIGGER constant_date
BEFORE INSERT OR UPDATE ON test
FOR EACH ROW
EXECUTE PROCEDURE correct_date();
The OLD.t_date != NEW.t_date comparison is optional, because not affects performance... But is a good practice to use it. Another way is to check in the trigger, by WHEN, but only update triggers can use OLD... So, the best create-triggers for the same correct_date() function (with no old/new comparison) are:
CREATE TRIGGER constant_date_ins
BEFORE INSERT ON test
FOR EACH ROW
EXECUTE PROCEDURE correct_date();
CREATE TRIGGER constant_date_upd
BEFORE UPDATE ON test
FOR EACH ROW
WHEN (OLD.t_date IS DISTINCT FROM NEW.t_date)
EXECUTE PROCEDURE correct_date();
Contextualizing in a scenario
As commented above in the question, there are a lack of contextualization , ex. explaining "why you think this should work and what it should do".
Scenario-1: the db-master need to block careless programmers
We can imagine a framework like CakePHP with a "created" field and a database-master that wants that this field have a "constant behaviour", preventing that careless programmers affects this "expected constraint".
That kind of scenario was used in the anwser.
Scenario-2: the project decision is to alert by error
This is the suggestion #IgorRomanchenko ...
... now here as a Wiki, you can EDIT and add new solution/example ...
You want a check constraint
create table test(
t_id SERIAL primary key,
t_date date default CURRENT_DATE check(t_date = current_date)
);
insert into test(t_date) values (default);
INSERT 0 1
insert into test(t_date) values ('2014-01-01');
ERROR: new row for relation "test" violates check constraint "test_t_date_check"
DETAIL: Failing row contains (2, 2014-01-01).
Or may be a foreign key constraint which allows multiple possible values and can be updated without altering the table's schema
create table date_constraint (
date_constraint date primary key
);
insert into date_constraint (date_constraint) values (current_date);
create table test(
t_id SERIAL primary key,
t_date date
default CURRENT_DATE
references date_constraint(date_constraint)
);
insert into test(t_date) values (default);
INSERT 0 1
insert into test(t_date) values ('2014-01-01');
ERROR: insert or update on table "test" violates foreign key constraint "test_t_date_fkey"
DETAIL: Key (t_date)=(2014-01-01) is not present in table "date_constraint".
http://www.postgresql.org/docs/current/static/ddl-constraints.html