pg.ddx.io  pgsql-sql@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Jeff Frost <jeff@frostconsultingllc.com>
To: Tom Lane <tgl@sss.pgh.pa.us>
Cc: pgsql-sql@postgresql.org
Subject: Re: update before drop causes OID problems in transaction?
Date: Fri, 17 Mar 2006 20:20:33 -0800 (PST)
Message-ID: <Pine.LNX.4.64.0603172012190.11424@discord.dyndns.org> (raw)
In-Reply-To: <Pine.LNX.4.64.0603171454170.11424@discord.dyndns.org>
References: <Pine.LNX.4.64.0603171434230.11424@discord.dyndns.org>
	<10754.1142635969@sss.pgh.pa.us>
	<Pine.LNX.4.64.0603171454170.11424@discord.dyndns.org>

On Fri, 17 Mar 2006, Jeff Frost wrote:

>> Could we see a complete test case, rather than handwaving?  I'd expect
>> some issues like this if you were using any prepared statements or
>> plpgsql functions with non-EXECUTEd queries involving the dropped table,
>> but your description doesn't mention either of those risk factors.
>
> Tom, it's for a client, so let me see if they'll allow me to post the 
> transaction, if not, I'll have to write something equivalent.  More later.

Alright, they are fine with me sharing the SQL, so here goes:

I suspect I've answered my own question while preparing the test case. Is it 
the use of pg_get_serial_sequence at the bottom of the transaction?  If so, 
why does it only have a problem when there is an update to credit_card_audit 
in the transaction?

If I'm looking at this correctly, the OID referenced is credit_card_audit:

SELECT * from pg_class where relfilenode = 29976142;
       relname      | relnamespace | reltype  | relowner | relam | relfilenode 
| reltablespace | relpages | reltuples | reltoastrelid | reltoastidxid | 
relhasindex | relisshared | relkind | relnatts | relchecks | reltriggers | 
relukeys | relfkeys | relrefs | relhasoids | relhaspkey | relhasrules | 
relhassubclass | relacl
-------------------+--------------+----------+----------+-------+-------------+---------------+----------+-----------+---------------+---------------+-------------+-------------+---------+----------+-----------+-------------+----------+----------+---------+------------+------------+-------------+----------------+--------
  credit_card_audit |         2200 | 29976143 |    16387 |     0 |    29976142 
|             0 |      133 |      3329 |      29976148 |             0 | t 
| f           | r       |        9 |         1 |           6 |        0 | 
0 |       0 | t          | t          | t           | f              |
(1 row)



Below is the transaction and following that is a \d of the credit_card and 
credit_card_audit tables:

BEGIN;

DROP RULE credit_card_audit_no_update ON credit_card_audit;

-- We have a not null constraint in the new table
-- Without this UPDATE, the transaction is fine
-- but with it, we get the ERROR:  could not open relation
-- with OID 29976142

UPDATE credit_card_audit SET modified_by = 1
WHERE modified_by IS NULL;


CREATE TEMP TABLE ca_common (LIKE credit_card_audit) ON COMMIT DROP;
INSERT INTO ca_common SELECT * FROM credit_card_audit;
ALTER TABLE ca_common DROP COLUMN credit_card_old;
ALTER TABLE ca_common DROP COLUMN credit_card_new;

CREATE TEMP TABLE ca_old (credit_card_audit_id INTEGER, LIKE credit_card) ON 
COMMIT DROP;
ALTER TABLE ca_old ALTER column id drop not null;
ALTER TABLE ca_old ALTER column account_id drop not null;
ALTER TABLE ca_old ALTER column profile_id drop not null;
ALTER TABLE ca_old ALTER column expires drop not null;
ALTER TABLE ca_old ALTER column credit_card_type drop not null;
ALTER TABLE ca_old ALTER column billing_name drop not null;

INSERT INTO ca_old
SELECT
credit_card_audit_id,
(credit_card_old).*
FROM credit_card_audit;

CREATE TEMP TABLE ca_new (credit_card_audit_id INTEGER, LIKE credit_card) ON 
COMMIT DROP;
ALTER TABLE ca_new ALTER column id drop not null;
ALTER TABLE ca_new ALTER column account_id drop not null;
ALTER TABLE ca_new ALTER column profile_id drop not null;
ALTER TABLE ca_new ALTER column expires drop not null;
ALTER TABLE ca_new ALTER column credit_card_type drop not null;
ALTER TABLE ca_new ALTER column billing_name drop not null;

INSERT INTO ca_new
SELECT
credit_card_audit_id,
(credit_card_new).*
FROM credit_card_audit;

DROP TRIGGER audit_credit_card ON credit_card;
DROP TABLE credit_card_audit;

DROP VIEW cc_with_id_view;

ALTER TABLE credit_card DROP COLUMN billing_name;

-- recreate credit_card_audit

CREATE TABLE public.credit_card_audit (
credit_card_audit_id BIGSERIAL PRIMARY KEY
, actor TEXT NOT NULL DEFAULT current_user
, action TEXT NOT NULL CHECK(action IN ('INSERT', 'UPDATE', 'DELETE'))
, credit_card_action_time TIMESTAMP WITH TIME ZONE NOT NULL
DEFAULT CURRENT_TIMESTAMP
, event_type TEXT
, modified_by INTEGER NOT NULL REFERENCES accounts_basics(id)
, credit_card_old public.credit_card
, credit_card_new public.credit_card
);

COMMENT ON TABLE public.credit_card_audit IS $$
Timestamp, old and new column sets for auditing.
This gets written on any change to public.credit_card.

It was created via
/home/jeff.frost/p4/ops-dbtools/set_up_audit.pl -t credit_card -e 
modified_by=INTEGER NOT NULL REFERENCES accounts_basics(id) -e event_type=TEXT
$$;

CREATE RULE credit_card_audit_no_delete AS
ON DELETE TO public.credit_card_audit
DO INSTEAD NOTHING;

CREATE RULE credit_card_audit_no_update AS
ON UPDATE TO public.credit_card_audit
DO INSTEAD NOTHING;


CREATE INDEX credit_card_audit_event_type_idx
ON public.credit_card_audit(event_type);

CREATE INDEX credit_card_audit_modified_by_idx
ON public.credit_card_audit(modified_by);

CREATE OR REPLACE FUNCTION public.audit_credit_card ()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
rows_affected INTEGER;
BEGIN
PERFORM tablename
FROM pg_tables
WHERE tablename = TG_RELNAME || '_audit';
IF NOT FOUND THEN
     RAISE EXCEPTION 'No audit table found for %', TG_RELNAME;
END IF;
IF TG_OP = 'INSERT' THEN
     INSERT INTO public.credit_card_audit (action, event_type, modified_by, 
credit_card_new)
     VALUES ('INSERT', NEW.event_type, NEW.modified_by, NEW );
ELSIF TG_OP = 'UPDATE' THEN
     INSERT INTO public.credit_card_audit (action, event_type, modified_by, 
credit_card_old, credit_card_new)
     VALUES ('UPDATE', NEW.event_type, NEW.modified_by, OLD , NEW );
ELSIF TG_OP = 'DELETE' THEN
     INSERT INTO public.credit_card_audit (action, event_type, modified_by, 
credit_card_old)
     VALUES ('DELETE', OLD.event_type, OLD.modified_by, OLD );
ELSE
     RAISE EXCEPTION 'TG_OP is none of INSERT, UPDATE or DELETE.';
END IF;
GET DIAGNOSTICS rows_affected = ROW_COUNT;
IF rows_affected = 1 THEN
     IF TG_OP IN ('INSERT', 'UPDATE') THEN
         RETURN NEW;
     ELSE
         RETURN OLD;
     END IF;
ELSE
     RAISE EXCEPTION 'INSERT failed on public.credit_card_audit';
END IF;
END;
$$;

COMMENT ON FUNCTION public.audit_credit_card ()
IS $$
Trigger function that logs actions on the public.credit_card table to
public.credit_card_audit for auditing purposes.

It was created via
/home/jeff.frost/p4/ops-dbtools/set_up_audit.pl -t credit_card -e 
modified_by=INTEGER NOT NULL REFERENCES accounts_basics(id) -e event_type=TEXT
$$;

CREATE TRIGGER audit_credit_card
BEFORE INSERT OR UPDATE OR DELETE ON public.credit_card
FOR EACH ROW EXECUTE PROCEDURE public.audit_credit_card ();

COMMENT ON TRIGGER audit_credit_card ON public.credit_card IS
$$
Trigger that calls public.audit_credit_card().

It was created via
/home/jeff.frost/p4/ops-dbtools/set_up_audit.pl -t credit_card -e 
modified_by=INTEGER NOT NULL REFERENCES accounts_basics(id) -e event_type=TEXT
$$;


--end of credit_card_audit setup

INSERT INTO credit_card_audit (credit_card_audit_id,
  actor,
  action,
  credit_card_action_time,
  event_type,
  modified_by,
  credit_card_old,
  credit_card_new)
SELECT c.credit_card_audit_id,
   c.actor,c.action,
   c.credit_card_action_time,
   c.event_type,
   c.modified_by,
(o.id,
  o.account_id,
  o.profile_id,
  o.expires,
  o.active,
  o.cc_number,
  o.credit_card_type,
  o.modified_by,
  o.event_type)::credit_card,
(n.id,
  n.account_id,
  n.profile_id,
  n.expires,
  n.active,
  n.cc_number,
  n.credit_card_type,
  n.modified_by,
  n.event_type)::credit_card
FROM
     ca_common c
JOIN
     ca_old o
     ON (c.credit_card_audit_id = o.credit_card_audit_id)
JOIN
     ca_new n
     ON (c.credit_card_audit_id = n.credit_card_audit_id);

SELECT
     setval(
         pg_get_serial_sequence(
             'credit_card_audit',
             'credit_card_audit_id'
         ),
     max(credit_card_audit_id)
)
FROM credit_card_audit;


CREATE OR REPLACE VIEW cc_with_id_view AS
  SELECT cc.id, cc.account_id, cc.profile_id, cc.expires, cc.active, 
cc.cc_number, cct.id AS credit_card_type_id
    FROM credit_card cc
    JOIN credit_card_type cct USING (credit_card_type);

ALTER TABLE cc_with_id_view OWNER TO perpetual;

COMMIT;

\d credit_card
                                      Table "public.credit_card"
       Column      |         Type          |                        Modifiers
------------------+-----------------------+----------------------------------------------------------
  id               | integer               | not null default 
nextval('credit_card_id_seq'::regclass)
  account_id       | integer               | not null
  profile_id       | integer               | not null
  expires          | date                  | not null
  active           | boolean               |
  cc_number        | character varying(64) |
  credit_card_type | text                  | not null
  billing_name     | character varying(30) | not null
  modified_by      | integer               |
  event_type       | text                  |
Indexes:
     "credit_cards_pkey" PRIMARY KEY, btree (id)
     "account_cc_uniq" UNIQUE, btree (account_id, cc_number) WHERE active = 
true
     "credit_cards_account_id" btree (account_id)
Foreign-key constraints:
     "$1" FOREIGN KEY (account_id) REFERENCES accounts_basics(id) ON DELETE 
CASCADE
     "$2" FOREIGN KEY (profile_id) REFERENCES billing_profile(id) ON DELETE 
CASCADE
     "credit_card_event_type_fkey" FOREIGN KEY (event_type) REFERENCES 
event_type(event_type) MATCH FULL
     "credit_card_modified_by_fkey" FOREIGN KEY (modified_by) REFERENCES 
accounts_basics(id) MATCH FULL
     "fk_cc_type" FOREIGN KEY (credit_card_type) REFERENCES 
credit_card_type(credit_card_type)
Triggers:
     audit_credit_card BEFORE INSERT OR DELETE OR UPDATE ON credit_card FOR 
EACH ROW EXECUTE PROCEDURE audit_credit_card()



\d credit_card_audit
                                                      Table 
"public.credit_card_audit"
          Column          |            Type             | 
Modifiers
-------------------------+-----------------------------+----------------------------------------------------------------------------------
  actor                   | text                        | not null default 
"current_user"()
  action                  | text                        | not null
  credit_card_action_time | timestamp without time zone | not null default 
('now'::text)::timestamp(6) with time zone
  credit_card_old         | credit_card                 |
  credit_card_new         | credit_card                 |
  credit_card_audit_id    | bigint                      | not null default 
nextval('credit_card_audit_credit_card_audit_id_seq'::regclass)
  account_id              | integer                     |
  event_type              | text                        |
  modified_by             | integer                     |
Indexes:
     "credit_card_audit_pkey" PRIMARY KEY, btree (credit_card_audit_id)
     "credit_card_audit_account_id_idx" btree (account_id)
     "credit_card_audit_event_type_idx" btree (event_type)
     "credit_card_audit_modified_by_idx" btree (modified_by)
Check constraints:
     "credit_card_audit_action_check" CHECK ("action" = 'INSERT'::text OR 
"action" = 'UPDATE'::text OR "action" = 'DELETE'::text)
Foreign-key constraints:
     "credit_card_audit_account_id_fkey" FOREIGN KEY (account_id) REFERENCES 
accounts_basics(id) MATCH FULL DEFERRABLE INITIALLY DEFERRED
     "credit_card_audit_event_type_fkey" FOREIGN KEY (event_type) REFERENCES 
event_type(event_type) MATCH FULL
     "credit_card_audit_modified_by_fkey" FOREIGN KEY (modified_by) REFERENCES 
accounts_basics(id) MATCH FULL
Rules:
     credit_card_audit_no_delete AS
     ON DELETE TO credit_card_audit DO INSTEAD NOTHING
     credit_card_audit_no_update AS
     ON UPDATE TO credit_card_audit DO INSTEAD NOTHING



-- 
Jeff Frost, Owner 	<jeff@frostconsultingllc.com>
Frost Consulting, LLC 	http://www.frostconsultingllc.com/
Phone: 650-780-7908	FAX: 650-649-1954



view thread (14+ messages)  latest in thread

Message-ID: <Pine.LNX.4.64.0603172012190.11424@discord.dyndns.org>
Permalink:  ../Pine.LNX.4.64.0603172012190.11424@discord.dyndns.org/
Also on:    postgresql.org/message-id/Pine.LNX.4.64.0603172012190.11424@discord.dyndns.org

 · 

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-sql@postgresql.org
  Cc: jeff@frostconsultingllc.com, tgl@sss.pgh.pa.us
  Subject: Re: update before drop causes OID problems in transaction?
  In-Reply-To: <Pine.LNX.4.64.0603172012190.11424@discord.dyndns.org>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox