X-Original-To: pgsql-general-postgresql.org@localhost.postgresql.org Received: from localhost (unknown [200.46.204.2]) by svr1.postgresql.org (Postfix) with ESMTP id EDF78D1B1D4; Fri, 14 May 2004 15:14:33 -0300 (ADT) Received: from svr1.postgresql.org ([200.46.204.71]) by localhost (neptune.hub.org [200.46.204.2]) (amavisd-new, port 10024) with ESMTP id 74904-05; Fri, 14 May 2004 15:14:14 -0300 (ADT) Received: from mail.n2bb.com (mail.n2bb.com [66.0.13.10]) by svr1.postgresql.org (Postfix) with ESMTP id 3AD4CD1B184; Fri, 14 May 2004 15:14:12 -0300 (ADT) Received: from n2-atl-mrylander.it.n2bb.com ([10.250.12.78] RDNS failed) by mail.n2bb.com over TLS secured channel with Microsoft SMTPSVC(5.0.2195.6713); Fri, 14 May 2004 14:14:10 -0400 From: Mike Rylander To: PostgreSQL-development Subject: Re: [HACKERS] MERGE-esque Functionallity (was: Adding MERGE to the TODO list (resend with subject)) Date: Fri, 14 May 2004 14:04:20 -0400 User-Agent: KMail/1.6.1 References: <200405111344.i4BDiQj06463@candle.pha.pa.us> In-Reply-To: <200405111344.i4BDiQj06463@candle.pha.pa.us> Cc: , "postgres SQL" MIME-Version: 1.0 Content-Disposition: inline Message-Id: <200405141402.07429.miker@purplefrog.com> Reply-To: Content-Type: Multipart/Mixed; boundary="Boundary-00=_FqQpA1hLAZ6yVKJ" X-OriginalArrivalTime: 14 May 2004 18:14:10.0651 (UTC) FILETIME=[4122DAB0:01C439DF] X-Virus-Scanned: by amavisd-new at postgresql.org X-Spam-Status: No, hits=1.1 tagged_above=0.0 required=5.0 tests=REPLY_TO_EMPTY, UPPERCASE_25_50 X-Spam-Level: * X-Archive-Number: 200405/464 X-Sequence-Number: 60898 --Boundary-00=_FqQpA1hLAZ6yVKJ Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: 7bit Content-Disposition: inline On Tuesday 11 May 2004 09:44 am, Bruce Momjian wrote: [snip] > > > > Bruce Momjian kirjutas E, 10.05.2004 kell 06:58: > > > > > Added to TODO: > > > > > > > > > > * Add MERGE command that does UPDATE, or on failure, INSERT > > > > [snip] Hello all. I have been lurking here for a bit and the MERGE topic (on [HACKERS]) caught my eye, so I had a go at implementing a basic version of MERGE-on-INSERT in pl/pgsql. It is attached below, and any comments are welcome. I find it useful on "status" type tables, though it is not very nice when there are many clients (table locking to avoid race conditions). Hope someone will find it useful! -miker --Boundary-00=_FqQpA1hLAZ6yVKJ Content-Type: text/x-sql; charset="iso-8859-1"; name="merge_on_insert.sql" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="merge_on_insert.sql" -- -- Merge on INSERT functionallity for Postgres 7.3+ -- -- miker@purplefrog.com / 5-14-04 -- -- CAVEAT EMPTOR: Uses table locks to avoid concurrency issues, -- so it WILL slow down heavily loaded tables. -- This effecivly puts the table into -- TRANSACTION ISOLATION LEVEL SERIALIZABLE mode. -- CREATE OR REPLACE FUNCTION add_merge_on_insert ( TEXT, -- table name TEXT, -- key column TEXT[] -- column list to update on deduplication ) RETURNS TEXT RETURNS NULL ON NULL INPUT SECURITY INVOKER LANGUAGE 'plpgsql' AS ' DECLARE tablename ALIAS FOR $1; keycol ALIAS FOR $2; updatecols ALIAS FOR $3; trig TEXT; arraydims TEXT; BEGIN trig := \' CREATE FUNCTION "\' || tablename || \'_merge_on_insert_proc" () RETURNS TRIGGER AS \'\' DECLARE orig \' || quote_ident(tablename) || \'%ROWTYPE; BEGIN LOCK TABLE \' || quote_ident(tablename) || \' IN ROW EXCLUSIVE MODE; SELECT INTO orig * FROM \' || quote_ident(tablename) || \' WHERE \' || quote_ident(keycol) || \' = NEW.\' || quote_ident(keycol) || \'; IF NOT FOUND THEN RETURN NEW; END IF; UPDATE \' || quote_ident(tablename) || \' SET \'; arraydims := array_dims(updatecols); FOR i IN 1 .. (substring(arraydims from (position(\':\' in arraydims) + 1 ) for ( position(\']\' in arraydims) - (position(\':\' in arraydims) + 1 ) )))::INT LOOP trig := trig || quote_ident(updatecols[i]) || \' = COALESCE( NEW.\' || quote_ident(updatecols[i]) || \', orig.\' || quote_ident(updatecols[i]) || \'), \'; END LOOP; trig := substring( trig from 0 for (character_length(trig) - 1)); trig := trig || \' WHERE \' || quote_ident(keycol) || \' = NEW.\' || quote_ident(keycol) || \'; RETURN NULL; END; \'\' LANGUAGE \'\'plpgsql\'\'; \'; EXECUTE trig; EXECUTE \' CREATE TRIGGER "\' || tablename || \'_merge_on_insert_trig" BEFORE INSERT ON \' || quote_ident(tablename) || \' FOR EACH ROW EXECUTE PROCEDURE "\' || tablename || \'_merge_on_insert_proc" (); \'; RETURN \'FUNCTION \' || tablename || \'_merge_on_insert_proc (); TRIGGER \' || tablename || \'_merge_on_insert_trig;\'; END; '; CREATE OR REPLACE FUNCTION remove_merge_on_insert ( TEXT -- table name ) RETURNS TEXT RETURNS NULL ON NULL INPUT SECURITY INVOKER LANGUAGE 'plpgsql' AS ' BEGIN EXECUTE \'DROP FUNCTION "\' || $1 || \'_merge_on_insert_proc" () CASCADE;\'; RETURN \'FUNCTION \' || $1 || \'_merge_on_insert_proc (); TRIGGER \' || $1 || \'_merge_on_insert_trig;\'; END; '; --Boundary-00=_FqQpA1hLAZ6yVKJ--