How to corrupt your data by accident

advertisement
How to corrupt your data
by accident
BY: LLOYD ALBIN
9/3/2013
Create test databases
createdb -h sqltest lloyd_test_1
createdb -h sqltest lloyd_test_2
The first thing we need
to do is to create two
test databases for our
example.
Creating our test tables
CREATE TABLE public.table1 (
network VARCHAR,
PRIMARY KEY(network)
);
CREATE TABLE public.table2 (
pk SERIAL,
network VARCHAR,
PRIMARY KEY(pk)
);
Here we are creating our
test tables. We can see
that there is a possible
foreign key relationship
between the tables
using the network
column.
Creating the foreign key relationship
ALTER TABLE public.table2
ADD CONSTRAINT table2_fk FOREIGN KEY
(network)
REFERENCES public.table1(network)
ON DELETE NO ACTION
ON UPDATE CASCADE
NOT DEFERRABLE;
Here is our foreign key
relationship.
Inserting some test data
INSERT INTO public.table1 VALUES ('CROSS');
INSERT INTO public.table1 VALUES ('TEST');
INSERT INTO public.table2 (network)
VALUES ('CROSS');
INSERT INTO public.table2 (network)
VALUES ('TEST');
Here is the test data. As
you can see we have
created two records in
each table. We are using
the foreign key
relationship twice, once
for ‘TEST’ and once for
‘CROSS’.
Testing Deletion
DELETE FROM public.table1
WHERE network = 'CROSS';
ERROR: update or delete on table "table1" violates
foreign key constraint "table2_fk" on table "table2"
DETAIL: Key (network)=(CROSS) is still referenced
from table "table2".
The deletion fails just as
expected, due to the
foreign key relationship.
Creating the problem
psql –h sqltest –d lloyd_test_1 –f delete.sql
BEGIN
…
DELETE 1
…
COMMIT
We have a sync process
that can delete data
even though there is a
foreign key constraint.
The Data (Table 2)
SELECT * FROM table2;
pk | network
----+--------1 | CROSS
2 | TEST
(2 rows)
We can see that we have
two records and these
records have foreign key
relationships to table1,
so there MUST be
matching data in table1.
The Data (Table 1)
SELECT * FROM table1;
network
--------TEST
(1 row)
What ???? Where is our
CROSS network? We
have deleted it violating
the foreign key
relationship.
Dependencies
As you can see all the dependencies are still in place.
Backup
pg_dump -h sqltest -Fp lloyd_test_1
> lloyd_test_1.sql
pg_dump -h sqltest -Fc lloyd_test_1
> lloyd_test_1.dump
The backup’s won’t
complain at all, so you
would not even know
that there is anything
wrong.
Restore via psql
psql -h sqltest -d lloyd_test_2 -f lloyd_test_1.sql
…
psql:lloyd_test_1.sql:129: ERROR: insert or update
on table "table2" violates foreign key constraint
"table2_fk"
DETAIL: Key (network)=(CROSS) is not present in
table "table1".
…
When you try and
restore the database,
this is when you will
find out there is a
problem. It can’t
implement the foreign
key relationship
because of the row that
we deleted.
From this, you might
think that it was
inserting data, but it
was really applying the
foreign key relationship.
Restore via pg_restore
pg_restore -h sqltest -d lloyd_test_2 lloyd_test_1.dump
pg_restore: [archiver (db)] Error while PROCESSING TOC:
pg_restore: [archiver (db)] Error from TOC entry 2526;
2606 390088458 FK CONSTRAINT table2_fk postgres
pg_restore: [archiver (db)] could not execute query:
ERROR: insert or update on table "table2" violates foreign
key constraint "table2_fk"
DETAIL: Key (network)=(CROSS) is not present in table
"table1".
Command was: ALTER TABLE ONLY table2
ADD CONSTRAINT table2_fk FOREIGN KEY (network)
REFERENCES table1(network) ON UPDATE CASCADE;
WARNING: errors ignored on restore: 1
When you try and
restore the database,
this is when you will
find out there is a
problem. It can’t
implement the foreign
key relationship
because of the row that
we deleted.
With pg_restore, you
can tell for sure that it
failed during the
creation of the foreign
key relationship.
Hands on time
Feel free to look at this database personally at this point so that you can
personally see that the data is violating the foreign key constraint.
delete.sql
BEGIN;
SET session_replication_role = 'replica';
DELETE FROM public.table1
WHERE "network" = E'CROSS';
COMMIT;
Disables the internally
created constraint
triggers used by the
foreign key relationship
and then deletes the
row of data. The
COMMIT fails to check
the constraints.
session_replication_role
session_replication_role (enum)
Controls firing of replication-related triggers and
rules for the current session. Setting this variable
requires superuser privilege and results in discarding
any previously cached query plans. Possible values
are origin (the default), replica and local. See ALTER
TABLE for more information.
Here is the
documentation on this
command. Please note
that there is nothing
said about foreign key’s.
http://www.postgresql.
org/docs/9.2/static/run
time-configclient.html#GUCSESSIONREPLICATION-ROLE
ALTER TABLE - TRIGGER
DISABLE/ENABLE [ REPLICA | ALWAYS ] TRIGGER
These forms configure the firing of trigger(s) belonging to the table. A disabled trigger is
still known to the system, but is not executed when its triggering event occurs. For a
deferred trigger, the enable status is checked when the event occurs, not when the trigger
function is actually executed. One can disable or enable a single trigger specified by name,
or all triggers on the table, or only user triggers (this option excludes internally generated
constraint triggers such as those that are used to implement foreign key constraints or
deferrable uniqueness and exclusion constraints). Disabling or enabling internally generated
constraint triggers requires superuser privileges; it should be done with caution since of
course the integrity of the constraint cannot be guaranteed if the triggers are not executed.
The trigger firing mechanism is also affected by the configuration variable
session_replication_role. Simply enabled triggers will fire when the replication role is
"origin" (the default) or "local". Triggers configured as ENABLE REPLICA will only fire if the
session is in "replica" mode, and triggers configured as ENABLE ALWAYS will fire regardless
of the current replication mode.
ALTER TABLE - RULE
DISABLE/ENABLE [ REPLICA | ALWAYS ] RULE
These forms configure the firing of rewrite rules
belonging to the table. A disabled rule is still known
to the system, but is not applied during query
rewriting. The semantics are as for disabled/enabled
triggers. This configuration is ignored for ON SELECT
rules, which are always applied in order to keep
views working even if the current session is in a nondefault replication role.
Rule based items.
http://www.postgresql.
org/docs/9.2/static/sqlaltertable.html
How I read the docs
To me, none of the documentation talked about being able to bypass the
foreign key relationships. Especially when you read the ALTER TABLE TRIGGER information that says:
“this option excludes internally generated constraint triggers such as those that are
used to implement foreign key constraints or deferrable uniqueness and exclusion
constraints”
ENABLE/DISABLE TRIGGER ALL
BEGIN;
ALTER TABLE public.table1 DISABLE TRIGGER ALL;
DELETE FROM public.table1
WHERE network = E'CROSS';
ALTER TABLE public.table1 ENABLE TRIGGER ALL;
COMMIT;
Here is another way to
cause the same
problem. It would be
nice if they re-checked
the foreign key’s if they
were disabled and reenabled during a
transaction.
Drop/Create Foreign Key
While your application could drop and re-create the foreign key. The
drop of the foreign key will be stalled until everyone has stopped using
any tables related to the foreign key.
Download