Open 7.4 items

Started by Bruce Momjianover 22 years ago29 messages
#1Bruce Momjian
pgman@candle.pha.pa.us

P O S T G R E S Q L

7 . 4 O P E N I T E M S

Current at ftp://momjian.postgresql.org/pub/postgresql/open_items.

Changes
-------
Fix REVOKE ALL ON FUNCTION error when removing owner permissions
Improve speed of building of constraints during restore
What to do about exposing the list of possible SQLSTATE error codes

Documentation Changes
---------------------
Move release notes to SGML
Freeze message strings

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
#2Stephan Szabo
sszabo@megazone.bigpanda.com
In reply to: Bruce Momjian (#1)
Re: Open 7.4 items

On Sun, 5 Oct 2003, Bruce Momjian wrote:

Improve speed of building of constraints during restore

Did we get consensus on what to do with this, whether we're doing
only the superuser option to not check, only speeding up fk constraint
checks by using a statement instead of the repeated calls, both, or
something else?

#3Tom Lane
tgl@sss.pgh.pa.us
In reply to: Stephan Szabo (#2)
Re: Open 7.4 items

Stephan Szabo <sszabo@megazone.bigpanda.com> writes:

Improve speed of building of constraints during restore

Did we get consensus on what to do with this,

Not really, it was still up in the air I thought. However, the
discussion will become moot if we don't have an implementation
of the faster-checking alternative to look at pretty soon. Do you
have something nearly ready to show?

regards, tom lane

#4Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Tom Lane (#3)
Re: Open 7.4 items

Tom Lane wrote:

Stephan Szabo <sszabo@megazone.bigpanda.com> writes:

Improve speed of building of constraints during restore

Did we get consensus on what to do with this,

Not really, it was still up in the air I thought. However, the
discussion will become moot if we don't have an implementation
of the faster-checking alternative to look at pretty soon. Do you
have something nearly ready to show?

Last I remember, there was the idea to make ALTER TABLE use a query to
check all constraints at once, rather than per row, _and_ there was an
idea to turn it off by the superuser. However, if we get the first one,
and it is fast, we might not even use the second one.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
#5Stephan Szabo
sszabo@megazone.bigpanda.com
In reply to: Tom Lane (#3)
1 attachment(s)
Re: Open 7.4 items

On Sun, 5 Oct 2003, Tom Lane wrote:

Stephan Szabo <sszabo@megazone.bigpanda.com> writes:

Improve speed of building of constraints during restore

Did we get consensus on what to do with this,

Not really, it was still up in the air I thought. However, the
discussion will become moot if we don't have an implementation
of the faster-checking alternative to look at pretty soon. Do you
have something nearly ready to show?

It's not cleaned up, but yes. It appears to work for the simple tests I've
done and should fall back if the permissions don't work to do a single
query on both tables.

I wasn't sure what to do about some of the spi error conditions. For many
of them I'm just returning false now so that it will try the other
mechanism in case that might work. I'm not really sure if that's worth it,
or if I should just elog(ERROR) and give up.

Attachments:

single_check.difftext/plain; charset=US-ASCII; name=single_check.diffDownload
Index: src/backend/commands/tablecmds.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/commands/tablecmds.c,v
retrieving revision 1.85
diff -c -r1.85 tablecmds.c
*** src/backend/commands/tablecmds.c	2 Oct 2003 06:36:37 -0000	1.85
--- src/backend/commands/tablecmds.c	5 Oct 2003 18:30:43 -0000
***************
*** 3437,3442 ****
--- 3437,3443 ----
  	return indexoid;
  }
  
+ 
  /*
   * Scan the existing rows in a table to verify they meet a proposed FK
   * constraint.
***************
*** 3454,3531 ****
  	List	   *list;
  	int			count;
  
! 	/*
! 	 * Scan through each tuple, calling RI_FKey_check_ins (insert trigger)
! 	 * as if that tuple had just been inserted.  If any of those fail, it
! 	 * should ereport(ERROR) and that's that.
! 	 */
! 	MemSet(&trig, 0, sizeof(trig));
! 	trig.tgoid = InvalidOid;
! 	trig.tgname = fkconstraint->constr_name;
! 	trig.tgenabled = TRUE;
! 	trig.tgisconstraint = TRUE;
! 	trig.tgconstrrelid = RelationGetRelid(pkrel);
! 	trig.tgdeferrable = FALSE;
! 	trig.tginitdeferred = FALSE;
! 
! 	trig.tgargs = (char **) palloc(sizeof(char *) *
! 								   (4 + length(fkconstraint->fk_attrs)
! 									+ length(fkconstraint->pk_attrs)));
! 
! 	trig.tgargs[0] = trig.tgname;
! 	trig.tgargs[1] = RelationGetRelationName(rel);
! 	trig.tgargs[2] = RelationGetRelationName(pkrel);
! 	trig.tgargs[3] = fkMatchTypeToString(fkconstraint->fk_matchtype);
! 	count = 4;
! 	foreach(list, fkconstraint->fk_attrs)
! 	{
! 		char	   *fk_at = strVal(lfirst(list));
! 
! 		trig.tgargs[count] = fk_at;
! 		count += 2;
! 	}
! 	count = 5;
! 	foreach(list, fkconstraint->pk_attrs)
! 	{
! 		char	   *pk_at = strVal(lfirst(list));
! 
! 		trig.tgargs[count] = pk_at;
! 		count += 2;
! 	}
! 	trig.tgnargs = count - 1;
! 
! 	scan = heap_beginscan(rel, SnapshotNow, 0, NULL);
! 
! 	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
! 	{
! 		FunctionCallInfoData fcinfo;
! 		TriggerData trigdata;
! 
! 		/*
! 		 * Make a call to the trigger function
! 		 *
! 		 * No parameters are passed, but we do set a context
! 		 */
! 		MemSet(&fcinfo, 0, sizeof(fcinfo));
! 
  		/*
! 		 * We assume RI_FKey_check_ins won't look at flinfo...
  		 */
! 		trigdata.type = T_TriggerData;
! 		trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
! 		trigdata.tg_relation = rel;
! 		trigdata.tg_trigtuple = tuple;
! 		trigdata.tg_newtuple = NULL;
! 		trigdata.tg_trigger = &trig;
  
! 		fcinfo.context = (Node *) &trigdata;
! 
! 		RI_FKey_check_ins(&fcinfo);
  	}
- 
- 	heap_endscan(scan);
- 
- 	pfree(trig.tgargs);
  }
  
  /*
--- 3455,3533 ----
  	List	   *list;
  	int			count;
  
! 	if (!RI_Check_Table(fkconstraint, rel, pkrel)) {
  		/*
! 		 * Scan through each tuple, calling RI_FKey_check_ins (insert trigger)
! 		 * as if that tuple had just been inserted.  If any of those fail, it
! 		 * should ereport(ERROR) and that's that.
  		 */
! 		MemSet(&trig, 0, sizeof(trig));
! 		trig.tgoid = InvalidOid;
! 		trig.tgname = fkconstraint->constr_name;
! 		trig.tgenabled = TRUE;
! 		trig.tgisconstraint = TRUE;
! 		trig.tgconstrrelid = RelationGetRelid(pkrel);
! 		trig.tgdeferrable = FALSE;
! 		trig.tginitdeferred = FALSE;
! 
! 		trig.tgargs = (char **) palloc(sizeof(char *) *
! 									   (4 + length(fkconstraint->fk_attrs)
! 										+ length(fkconstraint->pk_attrs)));
! 
! 		trig.tgargs[0] = trig.tgname;
! 		trig.tgargs[1] = RelationGetRelationName(rel);
! 		trig.tgargs[2] = RelationGetRelationName(pkrel);
! 		trig.tgargs[3] = fkMatchTypeToString(fkconstraint->fk_matchtype);
! 		count = 4;
! 		foreach(list, fkconstraint->fk_attrs)
! 		{
! 			char	   *fk_at = strVal(lfirst(list));
! 
! 			trig.tgargs[count] = fk_at;
! 			count += 2;
! 		}
! 		count = 5;
! 		foreach(list, fkconstraint->pk_attrs)
! 		{
! 			char	   *pk_at = strVal(lfirst(list));
! 
! 			trig.tgargs[count] = pk_at;
! 			count += 2;
! 		}
! 		trig.tgnargs = count - 1;
! 
! 		scan = heap_beginscan(rel, SnapshotNow, 0, NULL);
! 
! 		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
! 		{
! 			FunctionCallInfoData fcinfo;
! 			TriggerData trigdata;
! 
! 			/*
! 			 * Make a call to the trigger function
! 			 *
! 			 * No parameters are passed, but we do set a context
! 			 */
! 			MemSet(&fcinfo, 0, sizeof(fcinfo));
! 
! 			/*
! 			 * We assume RI_FKey_check_ins won't look at flinfo...
! 			 */
! 			trigdata.type = T_TriggerData;
! 			trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
! 			trigdata.tg_relation = rel;
! 			trigdata.tg_trigtuple = tuple;
! 			trigdata.tg_newtuple = NULL;
! 			trigdata.tg_trigger = &trig;
! 
! 			fcinfo.context = (Node *) &trigdata;
! 
! 			RI_FKey_check_ins(&fcinfo);
! 		}
! 		heap_endscan(scan);
  
! 		pfree(trig.tgargs);
  	}
  }
  
  /*
Index: src/backend/utils/adt/ri_triggers.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/adt/ri_triggers.c,v
retrieving revision 1.61
diff -c -r1.61 ri_triggers.c
*** src/backend/utils/adt/ri_triggers.c	1 Oct 2003 21:30:52 -0000	1.61
--- src/backend/utils/adt/ri_triggers.c	5 Oct 2003 18:30:50 -0000
***************
*** 40,48 ****
  #include "rewrite/rewriteHandler.h"
  #include "utils/lsyscache.h"
  #include "utils/typcache.h"
  #include "miscadmin.h"
  
- 
  /* ----------
   * Local definitions
   * ----------
--- 40,48 ----
  #include "rewrite/rewriteHandler.h"
  #include "utils/lsyscache.h"
  #include "utils/typcache.h"
+ #include "utils/acl.h"
  #include "miscadmin.h"
  
  /* ----------
   * Local definitions
   * ----------
***************
*** 3396,3399 ****
--- 3396,3606 ----
  	 */
  	return DatumGetBool(FunctionCall2(&(typentry->eq_opr_finfo),
  									  oldvalue, newvalue));
+ }
+ 
+ 
+ /* ----------
+  * RI_Check_Table -
+  *
+  *	Check an entire table for non-matching values using a single query.
+  *	We expect that an exclusive lock has been taken on rel and pkrel
+  *	such that we do not need to do row locking for the check.
+  *
+  *	If the check fails due to SPI errors or the permissions are not such
+  *	that the the current user has read permissions on both tables return
+  *	false to let our caller know that they will need to do something else
+  *	to check the constraint (or error as the case may be).
+  *
+  * ----------
+  */
+ bool
+ RI_Check_Table(FkConstraint *fkconstraint, Relation rel, Relation pkrel) {
+ 	char		querystr[MAX_QUOTED_REL_NAME_LEN * 2 + 250 +
+ 						(MAX_QUOTED_NAME_LEN + 32) * ((RI_MAX_NUMKEYS * 4)+1) ];
+ 	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
+ 	char		relname[MAX_QUOTED_REL_NAME_LEN];
+ 	char		attname[MAX_QUOTED_NAME_LEN];
+ 	char		fkattname[MAX_QUOTED_NAME_LEN];
+ 	char		*sep = "";
+ 	List		*list;
+ 	List		*list2;
+ 	int		spi_result;
+ 	void		*qplan;
+ 	Datum		vals[RI_MAX_NUMKEYS * 2];
+ 	char		nulls[RI_MAX_NUMKEYS * 2];
+ 
+ 	if (!rel || !pkrel)
+ 		return false;
+  
+ 	/*
+ 	 * Do a pre-check to see if we believe that the query below will succeed.
+ 	 * I'm not sure if these checks are sufficient, but they cover the common
+ 	 * case that would cause failures.
+ 	 */
+ 	if (pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_SELECT) != ACLCHECK_OK)
+ 		return false;
+ 	if (pg_class_aclcheck(RelationGetRelid(pkrel), GetUserId(), ACL_SELECT) != ACLCHECK_OK) 
+ 		return false;
+ 
+ 	quoteRelationName(pkrelname, pkrel);
+ 	quoteRelationName(relname, rel);
+ 
+ 	/* The query string build is:
+ 	 *  SELECT fk.keycols FROM ONLY relname fk 
+ 	 *   LEFT OUTER JOIN pkrelname pk 
+ 	 *   ON (pk.pkkeycol1=fk.keycol1 [AND ...])
+ 	 *   WHERE pk.pkkeycol1 IS NULL AND
+ 	 * For MATCH unspecified:
+ 	 *   (fk.keycol1 IS NOT NULL [AND ...])
+ 	 * For MATCH FULL:
+ 	 *   (fk.keycol1 IS NOT NULL [OR ...])
+ 	 */
+ 
+ 	snprintf(querystr, sizeof(querystr), "SELECT ");
+ 	foreach (list, fkconstraint->fk_attrs) {
+ 		quoteOneName(attname, strVal(lfirst(list)));
+ 		snprintf(querystr + strlen(querystr), sizeof(querystr), "%sfk.%s", sep, attname);
+ 		sep = ", ";
+ 	}
+ 	snprintf(querystr + strlen(querystr), sizeof(querystr), " FROM ONLY %s fk LEFT OUTER JOIN "
+ 			" %s pk ON (", relname, pkrelname);
+ 
+ 	sep="";
+ 	for (list=fkconstraint->pk_attrs, list2=fkconstraint->fk_attrs; 
+ 		list != NIL && list2 != NIL; 
+ 		list=lnext(list), list2=lnext(list2)) {
+ 		quoteOneName(attname, strVal(lfirst(list)));
+ 		quoteOneName(fkattname, strVal(lfirst(list2)));
+ 		snprintf(querystr + strlen(querystr), sizeof(querystr), " %spk.%s=fk.%s", sep, attname, fkattname);
+ 		sep = "AND ";
+ 	}
+ 	quoteOneName(fkattname, strVal(lfirst(fkconstraint->pk_attrs)));
+ 	snprintf(querystr + strlen(querystr), sizeof(querystr), ") WHERE pk.%s IS NULL AND (", fkattname);
+ 
+ 	sep="";
+ 	foreach (list, fkconstraint->fk_attrs) {
+ 		quoteOneName(attname, strVal(lfirst(list)));
+ 		snprintf(querystr + strlen(querystr), sizeof(querystr), " %sfk.%s IS NOT NULL", sep, attname);
+ 		switch (fkconstraint->fk_matchtype) {
+ 			case FKCONSTR_MATCH_UNSPECIFIED:
+ 				sep="AND ";
+ 				break;
+ 			case FKCONSTR_MATCH_FULL:
+ 				sep="OR ";
+ 				break;
+ 			case FKCONSTR_MATCH_PARTIAL:
+ 				ereport(ERROR,
+ 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						errmsg("MATCH PARTIAL not yet implemented")));
+ 				break;
+ 			default:
+ 				elog(ERROR, "Unrecognized match type: %d", fkconstraint->fk_matchtype);
+ 				break;
+ 		}
+ 	}
+ 	snprintf(querystr + strlen(querystr), sizeof(querystr), ")");
+ 
+ 	if (SPI_connect() != SPI_OK_CONNECT) {
+ 		elog(DEBUG1, "SPI_connect failed");
+ 		return false;
+ 	}
+ 
+ 	/*
+ 	 * Generate the plan.  We don't want to cache it, and there are no
+ 	 * arguments to the plan. 
+ 	 */
+ 
+ 	/* Create the plan */
+ 	qplan = SPI_prepare(querystr, 0, NULL);
+ 
+ 	if (qplan == NULL) {
+ 		elog(DEBUG1, "Unable to prepare plan");
+ 		if (SPI_finish() != SPI_OK_FINISH)
+ 			elog(ERROR, "SPI_finish failed");
+ 		return false;
+ 	}
+ 
+ 	nulls[0] = ' ';
+ 
+ 	/* Run the plan */
+ 	spi_result = SPI_execp(qplan, vals, nulls, 1);
+ 
+ 	/* Check result */
+ 	if (spi_result < 0) {
+ 		elog(DEBUG1, "SPI_execp failed %d", spi_result);
+ 		if (SPI_finish() != SPI_OK_FINISH)
+ 			elog(ERROR, "SPI_finish failed");
+ 		return false;
+ 	}
+ 
+ 	if (spi_result != SPI_OK_SELECT) {
+ 		elog(DEBUG1, "SPI_execp failed to return SPI_OK_SELECT");
+ 		if (SPI_finish() != SPI_OK_FINISH)
+ 			elog(ERROR, "SPI_finish failed");
+ 		return false;
+ 	}
+ 
+ 	if (SPI_processed>0) 
+ 	{
+ 		char		key_names[BUFLENGTH];
+ 		char		key_values[BUFLENGTH];
+ 		char	   *name_ptr = key_names;
+ 		char	   *val_ptr = key_values;
+ 		int		colpos = 1;
+ 		bool		nullsfound=false;
+ 
+ 		foreach (list, fkconstraint->fk_attrs)
+ 		{
+ 			char	*val;
+ 
+ 			quoteOneName(attname, strVal(lfirst(list)));
+ 
+ 			val = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, colpos);
+ 			if (!val) {
+ 				nullsfound=true;
+ 				break;
+ 			}
+ 
+ 			/*
+ 			 * Go to "..." if name or value doesn't fit in buffer.  We reserve
+ 			 * 5 bytes to ensure we can add comma, "...", null.
+ 			 */
+ 			if (strlen(attname) >= (key_names + BUFLENGTH - 5) - name_ptr ||
+ 				strlen(val) >= (key_values + BUFLENGTH - 5) - val_ptr)
+ 			{
+ 				sprintf(name_ptr, "...");
+ 				sprintf(val_ptr, "...");
+ 				break;
+ 			}
+ 
+ 			name_ptr += sprintf(name_ptr, "%s%s", colpos > 1 ? "," : "", attname);
+ 			val_ptr += sprintf(val_ptr, "%s%s", colpos > 1 ? "," : "", val);
+ 			colpos++;
+ 		}
+ 
+ 		if (!nullsfound) {
+ 			ereport(ERROR,
+ 				(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
+ 				 errmsg("insert or update on table %s violates foreign key constraint \"%s\"",
+ 						relname, fkconstraint->constr_name),
+ 				 errdetail("Key (%s)=(%s) is not present in table \"%s\".",
+ 						   key_names, key_values,
+ 						   RelationGetRelationName(pkrel))));
+ 		}
+ 		else {
+ 			/* 
+ 			 * If we get here currently, this means we must be MATCH FULL (since unspecified
+ 			 * couldn't have nulls in the output) and the nulls had to be mixed with non-NULLS.
+ 			 */
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
+ 					 errmsg("insert or update on table %s violates foreign key constraint \"%s\"",
+ 						relname, fkconstraint->constr_name),
+ 					 errdetail("MATCH FULL does not allow mixing of NULL and non-NULL key values.")));
+ 		}
+ 	}
+ 
+ 	if (SPI_finish() != SPI_OK_FINISH)
+ 		elog(ERROR, "SPI_finish failed");
+ 	return true;
  }
Index: src/include/commands/trigger.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/commands/trigger.h,v
retrieving revision 1.43
diff -c -r1.43 trigger.h
*** src/include/commands/trigger.h	4 Aug 2003 02:40:13 -0000	1.43
--- src/include/commands/trigger.h	5 Oct 2003 18:30:53 -0000
***************
*** 197,201 ****
--- 197,204 ----
   * in utils/adt/ri_triggers.c
   */
  extern bool RI_FKey_keyequal_upd(TriggerData *trigdata);
+ extern bool RI_Check_Table(FkConstraint *fkconstraint, 
+                                 Relation rel, 
+                                 Relation pkrel);
  
  #endif   /* TRIGGER_H */
Index: src/test/regress/expected/alter_table.out
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/test/regress/expected/alter_table.out,v
retrieving revision 1.77
diff -c -r1.77 alter_table.out
*** src/test/regress/expected/alter_table.out	2 Oct 2003 06:32:46 -0000	1.77
--- src/test/regress/expected/alter_table.out	5 Oct 2003 18:31:02 -0000
***************
*** 313,320 ****
  ERROR:  column "b" referenced in foreign key constraint does not exist
  -- Try (and fail) to add constraint due to invalid data
  ALTER TABLE tmp3 add constraint tmpconstr foreign key (a) references tmp2 match full;
! ERROR:  insert or update on table "tmp3" violates foreign key constraint "tmpconstr"
! DETAIL:  Key (a)=(5) is not present in table "tmp2".
  -- Delete failing row
  DELETE FROM tmp3 where a=5;
  -- Try (and succeed)
--- 313,320 ----
  ERROR:  column "b" referenced in foreign key constraint does not exist
  -- Try (and fail) to add constraint due to invalid data
  ALTER TABLE tmp3 add constraint tmpconstr foreign key (a) references tmp2 match full;
! ERROR:  insert or update on table "public"."tmp3" violates foreign key constraint "tmpconstr"
! DETAIL:  Key ("a")=(5) is not present in table "tmp2".
  -- Delete failing row
  DELETE FROM tmp3 where a=5;
  -- Try (and succeed)
#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Stephan Szabo (#5)
Re: Open 7.4 items

Stephan Szabo <sszabo@megazone.bigpanda.com> writes:

On Sun, 5 Oct 2003, Tom Lane wrote:

Not really, it was still up in the air I thought. However, the
discussion will become moot if we don't have an implementation
of the faster-checking alternative to look at pretty soon. Do you
have something nearly ready to show?

It's not cleaned up, but yes. It appears to work for the simple tests I've
done and should fall back if the permissions don't work to do a single
query on both tables.

Okay, I'll look this over, make any improvements I can think of, and
post another version in a couple of hours. One thing I can see I'd
like to do is merge the error-reporting code with the main line, so
that there's not any difference in the output format (I don't like
the induced change in the regression test outputs...)

regards, tom lane

#7Tom Lane
tgl@sss.pgh.pa.us
In reply to: Stephan Szabo (#5)
Re: Open 7.4 items

Stephan Szabo <sszabo@megazone.bigpanda.com> writes:

It's not cleaned up, but yes. It appears to work for the simple tests I've
done and should fall back if the permissions don't work to do a single
query on both tables.

Here's my code-reviewed version of the patch. Anyone else want to take
a look?

I wasn't sure what to do about some of the spi error conditions. For many
of them I'm just returning false now so that it will try the other
mechanism in case that might work. I'm not really sure if that's worth it,
or if I should just elog(ERROR) and give up.

I think you may as well keep it the same as the other RI routines and
just elog() on SPI error. If SPI is broken, the trigger procedure is
gonna fail too.

I changed that, consolidated the error-reporting code, and fixed a couple
other little issues, notably:

* The generated query applied ONLY to the FK table but not the PK table.
I assume this was just an oversight.

* The query is now run using SPI_execp_current and selecting "current"
snapshot. Without this, we could fail in a serializable transaction
if someone else has already committed changes to either relation.
For example:

create pk and fk tables;

begin serializable xact;
insert into pk values(1);
insert into fk values(1);

begin;
insert into fk values(2);
commit;

alter table fk add foreign key ...;

The ALTER will not be blocked from acquiring exclusive lock, since
T2 already committed. But if we run the query in the serializable
snapshot, it won't see the violating row fk=2.

The old trigger-based check avoids this error because the scan loop uses
SnapshotNow to select live rows from the FK table. There is a dual race
condition where T2 deletes a row from the PK table. In current CVS tip
this will be detected and reported as a serialization failure, because
T1 won't be able to get SELECT FOR UPDATE lock on the deleted row. With
the proposed patch you'll instead see a "no such key" failure, which I
think is fine, even though it nominally violates serializability.

Comments? Can anyone else do a code review (Jan??)?

regards, tom lane

#8Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Tom Lane (#7)
Re: Open 7.4 items

Wow, that's a heap of code --- that's my only comment. :-)

---------------------------------------------------------------------------

Tom Lane wrote:

Stephan Szabo <sszabo@megazone.bigpanda.com> writes:

It's not cleaned up, but yes. It appears to work for the simple tests I've
done and should fall back if the permissions don't work to do a single
query on both tables.

Here's my code-reviewed version of the patch. Anyone else want to take
a look?

I wasn't sure what to do about some of the spi error conditions. For many
of them I'm just returning false now so that it will try the other
mechanism in case that might work. I'm not really sure if that's worth it,
or if I should just elog(ERROR) and give up.

I think you may as well keep it the same as the other RI routines and
just elog() on SPI error. If SPI is broken, the trigger procedure is
gonna fail too.

I changed that, consolidated the error-reporting code, and fixed a couple
other little issues, notably:

* The generated query applied ONLY to the FK table but not the PK table.
I assume this was just an oversight.

* The query is now run using SPI_execp_current and selecting "current"
snapshot. Without this, we could fail in a serializable transaction
if someone else has already committed changes to either relation.
For example:

create pk and fk tables;

begin serializable xact;
insert into pk values(1);
insert into fk values(1);

begin;
insert into fk values(2);
commit;

alter table fk add foreign key ...;

The ALTER will not be blocked from acquiring exclusive lock, since
T2 already committed. But if we run the query in the serializable
snapshot, it won't see the violating row fk=2.

The old trigger-based check avoids this error because the scan loop uses
SnapshotNow to select live rows from the FK table. There is a dual race
condition where T2 deletes a row from the PK table. In current CVS tip
this will be detected and reported as a serialization failure, because
T1 won't be able to get SELECT FOR UPDATE lock on the deleted row. With
the proposed patch you'll instead see a "no such key" failure, which I
think is fine, even though it nominally violates serializability.

Comments? Can anyone else do a code review (Jan??)?

regards, tom lane

Content-Description: RIcheck.patch

*** src/backend/commands/tablecmds.c.orig	Thu Oct  2 15:24:52 2003
--- src/backend/commands/tablecmds.c	Sun Oct  5 16:29:51 2003
***************
*** 3455,3460 ****
--- 3455,3467 ----
int			count;
/*
+ 	 * See if we can do it with a single LEFT JOIN query.  A FALSE result
+ 	 * indicates we must proceed with the fire-the-trigger method.
+ 	 */
+ 	if (RI_Initial_Check(fkconstraint, rel, pkrel))
+ 		return;
+ 
+ 	/*
* Scan through each tuple, calling RI_FKey_check_ins (insert trigger)
* as if that tuple had just been inserted.  If any of those fail, it
* should ereport(ERROR) and that's that.
*** src/backend/utils/adt/ri_triggers.c.orig	Wed Oct  1 17:30:52 2003
--- src/backend/utils/adt/ri_triggers.c	Sun Oct  5 16:42:37 2003
***************
*** 40,45 ****
--- 40,46 ----
#include "rewrite/rewriteHandler.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"
+ #include "utils/acl.h"
#include "miscadmin.h"

***************
*** 164,170 ****
Datum *vals, char *nulls);
static void ri_ReportViolation(RI_QueryKey *qkey, const char *constrname,
Relation pk_rel, Relation fk_rel,
! HeapTuple violator, bool spi_err);

/* ----------
--- 165,172 ----
Datum *vals, char *nulls);
static void ri_ReportViolation(RI_QueryKey *qkey, const char *constrname,
Relation pk_rel, Relation fk_rel,
! 				   HeapTuple violator, TupleDesc tupdesc,
! 				   bool spi_err);
/* ----------
***************
*** 2540,2546 ****
--- 2542,2743 ----
}
+ /* ----------
+  * RI_Initial_Check -
+  *
+  *	Check an entire table for non-matching values using a single query.
+  *	This is not a trigger procedure, but is called during ALTER TABLE
+  *	ADD FOREIGN KEY to validate the initial table contents.
+  *
+  *	We expect that an exclusive lock has been taken on rel and pkrel;
+  *	hence, we do not need to lock individual rows for the check.
+  *
+  *	If the check fails because the current user doesn't have permissions
+  *	to read both tables, return false to let our caller know that they will
+  *	need to do something else to check the constraint.
+  * ----------
+  */
+ bool
+ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel)
+ {
+ 	const char *constrname = fkconstraint->constr_name;
+ 	char		querystr[MAX_QUOTED_REL_NAME_LEN * 2 + 250 +
+ 						(MAX_QUOTED_NAME_LEN + 32) * ((RI_MAX_NUMKEYS * 4)+1)];
+ 	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
+ 	char		relname[MAX_QUOTED_REL_NAME_LEN];
+ 	char		attname[MAX_QUOTED_NAME_LEN];
+ 	char		fkattname[MAX_QUOTED_NAME_LEN];
+ 	const char *sep;
+ 	List		*list;
+ 	List		*list2;
+ 	int			spi_result;
+ 	void		*qplan;
+ 
+ 	/*
+ 	 * Check to make sure current user has enough permissions to do the
+ 	 * test query.  (If not, caller can fall back to the trigger method,
+ 	 * which works because it changes user IDs on the fly.)
+ 	 *
+ 	 * XXX are there any other show-stopper conditions to check?
+ 	 */
+ 	if (pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_SELECT) != ACLCHECK_OK)
+ 		return false;
+ 	if (pg_class_aclcheck(RelationGetRelid(pkrel), GetUserId(), ACL_SELECT) != ACLCHECK_OK) 
+ 		return false;
+ 
+ 	/*----------
+ 	 * The query string built is:
+ 	 *  SELECT fk.keycols FROM ONLY relname fk 
+ 	 *   LEFT OUTER JOIN ONLY pkrelname pk 
+ 	 *   ON (pk.pkkeycol1=fk.keycol1 [AND ...])
+ 	 *   WHERE pk.pkkeycol1 IS NULL AND
+ 	 * For MATCH unspecified:
+ 	 *   (fk.keycol1 IS NOT NULL [AND ...])
+ 	 * For MATCH FULL:
+ 	 *   (fk.keycol1 IS NOT NULL [OR ...])
+ 	 *----------
+ 	 */
+ 
+ 	sprintf(querystr, "SELECT ");
+ 	sep="";
+ 	foreach(list, fkconstraint->fk_attrs)
+ 	{
+ 		quoteOneName(attname, strVal(lfirst(list)));
+ 		snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr),
+ 				 "%sfk.%s", sep, attname);
+ 		sep = ", ";
+ 	}
+ 
+ 	quoteRelationName(pkrelname, pkrel);
+ 	quoteRelationName(relname, rel);
+ 	snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr),
+ 			 " FROM ONLY %s fk LEFT OUTER JOIN ONLY %s pk ON (",
+ 			 relname, pkrelname);
+ 
+ 	sep="";
+ 	for (list=fkconstraint->pk_attrs, list2=fkconstraint->fk_attrs; 
+ 		 list != NIL && list2 != NIL; 
+ 		 list=lnext(list), list2=lnext(list2))
+ 	{
+ 		quoteOneName(attname, strVal(lfirst(list)));
+ 		quoteOneName(fkattname, strVal(lfirst(list2)));
+ 		snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr),
+ 				 "%spk.%s=fk.%s",
+ 				 sep, attname, fkattname);
+ 		sep = " AND ";
+ 	}
+ 	/*
+ 	 * It's sufficient to test any one pk attribute for null to detect a
+ 	 * join failure.
+ 	 */
+ 	quoteOneName(attname, strVal(lfirst(fkconstraint->pk_attrs)));
+ 	snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr),
+ 			 ") WHERE pk.%s IS NULL AND (", attname);
+ 
+ 	sep="";
+ 	foreach(list, fkconstraint->fk_attrs)
+ 	{
+ 		quoteOneName(attname, strVal(lfirst(list)));
+ 		snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr),
+ 				 "%sfk.%s IS NOT NULL",
+ 				 sep, attname);
+ 		switch (fkconstraint->fk_matchtype)
+ 		{
+ 			case FKCONSTR_MATCH_UNSPECIFIED:
+ 				sep=" AND ";
+ 				break;
+ 			case FKCONSTR_MATCH_FULL:
+ 				sep=" OR ";
+ 				break;
+ 			case FKCONSTR_MATCH_PARTIAL:
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("MATCH PARTIAL not yet implemented")));
+ 				break;
+ 			default:
+ 				elog(ERROR, "unrecognized match type: %d",
+ 					 fkconstraint->fk_matchtype);
+ 				break;
+ 		}
+ 	}
+ 	snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr),
+ 			 ")");
+ 
+ 	if (SPI_connect() != SPI_OK_CONNECT)
+ 		elog(ERROR, "SPI_connect failed");
+ 
+ 	/*
+ 	 * Generate the plan.  We don't need to cache it, and there are no
+ 	 * arguments to the plan. 
+ 	 */
+ 	qplan = SPI_prepare(querystr, 0, NULL);
+ 
+ 	/*
+ 	 * Run the plan.  For safety we force a current query snapshot to be
+ 	 * used.  (In serializable mode, this arguably violates serializability,
+ 	 * but we really haven't got much choice.)  We need at most one tuple
+ 	 * returned, so pass limit = 1.
+ 	 */
+ 	spi_result = SPI_execp_current(qplan, NULL, NULL, true, 1);
+ 	/* Check result */
+ 	if (spi_result != SPI_OK_SELECT)
+ 		elog(ERROR, "SPI_execp_current returned %d", spi_result);
+ 
+ 	/* Did we find a tuple violating the constraint? */
+ 	if (SPI_processed > 0)
+ 	{
+ 		HeapTuple	tuple = SPI_tuptable->vals[0];
+ 		TupleDesc	tupdesc = SPI_tuptable->tupdesc;
+ 		int			nkeys = length(fkconstraint->fk_attrs);
+ 		int			i;
+ 		RI_QueryKey	qkey;
+ 
+ 		/*
+ 		 * If it's MATCH FULL, and there are any nulls in the FK keys,
+ 		 * complain about that rather than the lack of a match.  MATCH FULL
+ 		 * disallows partially-null FK rows.
+ 		 */
+ 		if (fkconstraint->fk_matchtype == FKCONSTR_MATCH_FULL)
+ 		{
+ 			bool	isnull = false;
+ 
+ 			for (i = 1; i <= nkeys; i++)
+ 			{
+ 				(void) SPI_getbinval(tuple, tupdesc, i, &isnull);
+ 				if (isnull)
+ 					break;
+ 			}
+ 			if (isnull)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
+ 						 errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
+ 								RelationGetRelationName(rel),
+ 								constrname),
+ 						 errdetail("MATCH FULL does not allow mixing of null and nonnull key values.")));
+ 		}
+ 
+ 		/*
+ 		 * Although we didn't cache the query, we need to set up a fake
+ 		 * query key to pass to ri_ReportViolation.
+ 		 */
+ 		MemSet(&qkey, 0, sizeof(qkey));
+ 		qkey.constr_queryno = RI_PLAN_CHECK_LOOKUPPK;
+ 		qkey.nkeypairs = nkeys;
+ 		for (i = 0; i < nkeys; i++)
+ 			qkey.keypair[i][RI_KEYPAIR_FK_IDX] = i + 1;
+ 
+ 		ri_ReportViolation(&qkey, constrname,
+ 						   pkrel, rel,
+ 						   tuple, tupdesc,
+ 						   false);
+ 	}
+ 
+ 	if (SPI_finish() != SPI_OK_FINISH)
+ 		elog(ERROR, "SPI_finish failed");
+ 
+ 	return true;
+ }
/* ----------
***************
*** 2905,2910 ****
--- 3102,3108 ----
ri_ReportViolation(qkey, constrname ? constrname : "",
pk_rel, fk_rel,
new_tuple ? new_tuple : old_tuple,
+ 						   NULL,
true);
/* XXX wouldn't it be clearer to do this part at the caller? */
***************
*** 2913,2918 ****
--- 3111,3117 ----
ri_ReportViolation(qkey, constrname,
pk_rel, fk_rel,
new_tuple ? new_tuple : old_tuple,
+ 						   NULL,
false);
return SPI_processed != 0;
***************
*** 2950,2956 ****
static void
ri_ReportViolation(RI_QueryKey *qkey, const char *constrname,
Relation pk_rel, Relation fk_rel,
! 				   HeapTuple violator, bool spi_err)
{
#define BUFLENGTH	512
char		key_names[BUFLENGTH];
--- 3149,3156 ----
static void
ri_ReportViolation(RI_QueryKey *qkey, const char *constrname,
Relation pk_rel, Relation fk_rel,
! 				   HeapTuple violator, TupleDesc tupdesc,
! 				   bool spi_err)
{
#define BUFLENGTH	512
char		key_names[BUFLENGTH];
***************
*** 2958,2964 ****
char	   *name_ptr = key_names;
char	   *val_ptr = key_values;
bool		onfk;
- 	Relation	rel;
int			idx,
key_idx;
--- 3158,3163 ----
***************
*** 2972,2989 ****
errhint("This is most likely due to a rule having rewritten the query.")));

/*
! * rel is set to where the tuple description is coming from.
*/
onfk = (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK);
if (onfk)
{
- rel = fk_rel;
key_idx = RI_KEYPAIR_FK_IDX;
}
else
{
- rel = pk_rel;
key_idx = RI_KEYPAIR_PK_IDX;
}

/*
--- 3171,3191 ----
errhint("This is most likely due to a rule having rewritten the query.")));

/*
! * Determine which relation to complain about. If tupdesc wasn't
! * passed by caller, assume the violator tuple came from there.
*/
onfk = (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK);
if (onfk)
{
key_idx = RI_KEYPAIR_FK_IDX;
+ if (tupdesc == NULL)
+ tupdesc = fk_rel->rd_att;
}
else
{
key_idx = RI_KEYPAIR_PK_IDX;
+ if (tupdesc == NULL)
+ tupdesc = pk_rel->rd_att;
}

/*
***************
*** 3008,3015 ****
char *name,
*val;

! name = SPI_fname(rel->rd_att, fnum);
! val = SPI_getvalue(violator, rel->rd_att, fnum);
if (!val)
val = "null";

--- 3210,3217 ----
char	   *name,
*val;

! name = SPI_fname(tupdesc, fnum);
! val = SPI_getvalue(violator, tupdesc, fnum);
if (!val)
val = "null";

*** src/include/commands/trigger.h.orig	Sun Aug  3 23:01:31 2003
--- src/include/commands/trigger.h	Sun Oct  5 16:11:44 2003
***************
*** 197,201 ****
--- 197,204 ----
* in utils/adt/ri_triggers.c
*/
extern bool RI_FKey_keyequal_upd(TriggerData *trigdata);
+ extern bool RI_Initial_Check(FkConstraint *fkconstraint, 
+ 							 Relation rel, 
+ 							 Relation pkrel);

#endif /* TRIGGER_H */

---------------------------(end of broadcast)---------------------------
TIP 9: the planner will ignore your desire to choose an index scan if your
joining column's datatypes do not match

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
#9Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#8)
Re: Open 7.4 items

Bruce Momjian <pgman@candle.pha.pa.us> writes:

Wow, that's a heap of code --- that's my only comment. :-)

Most of it is pretty direct cribbing of code that already exists in
the other routines in ri_triggers.c, so it's not really completely
new code, just boilerplate.

regards, tom lane

#10Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Tom Lane (#9)
Re: Open 7.4 items

Tom Lane wrote:

Bruce Momjian <pgman@candle.pha.pa.us> writes:

Wow, that's a heap of code --- that's my only comment. :-)

Most of it is pretty direct cribbing of code that already exists in
the other routines in ri_triggers.c, so it's not really completely
new code, just boilerplate.

Oh, that makes me feel better. Do we have timings for this code?

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
#11Marc G. Fournier
scrappy@postgresql.org
In reply to: Bruce Momjian (#8)
Re: Open 7.4 items

On Sun, 5 Oct 2003, Bruce Momjian wrote:

Wow, that's a heap of code --- that's my only comment. :-)

And you reposted the *whole* patch for that?? *tsk* *tsk*

#12Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Marc G. Fournier (#11)
Re: Open 7.4 items

Marc G. Fournier wrote:

On Sun, 5 Oct 2003, Bruce Momjian wrote:

Wow, that's a heap of code --- that's my only comment. :-)

And you reposted the *whole* patch for that?? *tsk* *tsk*

Oops, sorry.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
#13Stephan Szabo
sszabo@megazone.bigpanda.com
In reply to: Tom Lane (#7)
Re: Open 7.4 items

On Sun, 5 Oct 2003, Tom Lane wrote:

Stephan Szabo <sszabo@megazone.bigpanda.com> writes:

I wasn't sure what to do about some of the spi error conditions. For many
of them I'm just returning false now so that it will try the other
mechanism in case that might work. I'm not really sure if that's worth it,
or if I should just elog(ERROR) and give up.

I think you may as well keep it the same as the other RI routines and
just elog() on SPI error. If SPI is broken, the trigger procedure is
gonna fail too.

Okay.

I changed that, consolidated the error-reporting code, and fixed a couple
other little issues, notably:

* The generated query applied ONLY to the FK table but not the PK table.
I assume this was just an oversight.

Yep, dumb oversight.

* The query is now run using SPI_execp_current and selecting "current"
snapshot. Without this, we could fail in a serializable transaction
if someone else has already committed changes to either relation.

You'd think I'd have remembered this could happen given the recent
discussions. I was wondering if we could get the serialization failure
with for update, but that's disallowed on the nullable side of the outer
join. It's probably okay to give the no such key error in the delete case
(at some point it'd be nice to make it give serialization failure, but
that might take alot more work than is warrented at this time for 7.4)

#14Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#10)
Re: Open 7.4 items

Bruce Momjian <pgman@candle.pha.pa.us> writes:

Oh, that makes me feel better. Do we have timings for this code?

This is just a single data point, but I made a table of 1 million
rows containing just the int4 primary key column (values 0-1million
in a somewhat random order). Then I copied the same data, sans index,
to produce a foreign key table. Then I tried ALTER ADD PRIMARY KEY.

The results were:

Time to load the 1 million rows: 8 sec

Time to create the PK index: 10 sec

Time to ADD PRIMARY KEY:

with CVS-tip code (fire trigger per row): 78 sec

with proposed patch: anywhere from 5 to 25 sec depending on plan

The default plan if there is no index on the FK table (meaning the
planner will not know its true size) is a nestloop with inner index
scan taking about 17 sec.

If any index has been created on the FK table, you'll probably get a
merge or hash join. I found these took about 20 sec with the default
sort_mem setting, but with sort_mem boosted to 50000 or more, the
hash join got lots faster --- down in the 6-7 second range ---
presumably because it didn't need multiple hash batches.

It'd clearly be worth our while to mention boosting sort_mem as a
helpful thing to do during bulk data load --- it should speed up
btree index creation too. I don't think that tip appears anywhere
in the docs at the moment.

So the patch definitely seems worthwhile, but someone might still care
to argue that there should be a bypass switch available too.

regards, tom lane

#15Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Tom Lane (#14)
Re: Open 7.4 items

Tom Lane wrote:

It'd clearly be worth our while to mention boosting sort_mem as a
helpful thing to do during bulk data load --- it should speed up
btree index creation too. I don't think that tip appears anywhere
in the docs at the moment.

Added recently, see last sentence:

<term><varname>sort_mem</varname> (<type>integer</type>)</term>
<listitem>
<para>
Specifies the amount of memory to be used by internal sort operations and
hash tables before switching to temporary disk files. The value is
specified in kilobytes, and defaults to 1024 kilobytes (1 MB).
Note that for a complex query, several sort or hash operations might be
running in parallel; each one will be allowed to use as much memory
as this value specifies before it starts to put data into temporary
files. Also, several running sessions could be doing
sort operations simultaneously. So the total memory used could be many
times the value of <varname>sort_mem</varname>. Sort operations are used
by <literal>ORDER BY</>, merge joins, and <command>CREATE INDEX</>.
Hash tables are used in hash joins, hash-based aggregation, and
hash-based processing of <literal>IN</> subqueries. Because
<command>CREATE INDEX</> is used when restoring a database, it might
be good to temporarily increase this value during a restore.
</para>

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
#16Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#15)
Re: Open 7.4 items

Bruce Momjian <pgman@candle.pha.pa.us> writes:

Tom Lane wrote:

It'd clearly be worth our while to mention boosting sort_mem as a
helpful thing to do during bulk data load --- it should speed up
btree index creation too. I don't think that tip appears anywhere
in the docs at the moment.

Added recently, see last sentence:

That's a fairly useless place to put it, though, since someone would
only think to look at sort_mem if they already had a clue. It should
be mentioned under bulk data load (in performance tips chapter) and
perhaps also in dump/restore procedures.

regards, tom lane

#17Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Tom Lane (#16)
Re: Open 7.4 items

Tom Lane wrote:

Bruce Momjian <pgman@candle.pha.pa.us> writes:

Tom Lane wrote:

It'd clearly be worth our while to mention boosting sort_mem as a
helpful thing to do during bulk data load --- it should speed up
btree index creation too. I don't think that tip appears anywhere
in the docs at the moment.

Added recently, see last sentence:

That's a fairly useless place to put it, though, since someone would
only think to look at sort_mem if they already had a clue. It should
be mentioned under bulk data load (in performance tips chapter) and
perhaps also in dump/restore procedures.

There were several places it is needed, so I just hit the one place ---
feel free to add some more.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
#18Neil Conway
neilc@samurai.com
In reply to: Tom Lane (#16)
1 attachment(s)
Re: [HACKERS] Open 7.4 items

On Sun, 2003-10-05 at 19:58, Tom Lane wrote:

That's a fairly useless place to put it, though, since someone would
only think to look at sort_mem if they already had a clue. It should
be mentioned under bulk data load (in performance tips chapter)

Attached is a doc patch that does this. The way I've worded it may not
be the best, though.

and perhaps also in dump/restore procedures.

It's already mentioned there.

Should we also suggest turning off fsync when doing restores?

(BTW, is there a reason the docs consistently call them "B-tree
indexes", not "B+-tree indexes"?)

-Neil

Attachments:

sort_mem_restore_docs-2.patchtext/x-patch; charset=ANSI_X3.4-1968; name=sort_mem_restore_docs-2.patchDownload
Index: doc/src/sgml/perform.sgml
===================================================================
RCS file: /var/lib/cvs/pgsql-server/doc/src/sgml/perform.sgml,v
retrieving revision 1.33
diff -c -r1.33 perform.sgml
*** doc/src/sgml/perform.sgml	11 Sep 2003 18:30:38 -0000	1.33
--- doc/src/sgml/perform.sgml	6 Oct 2003 00:21:48 -0000
***************
*** 751,761 ****
  
     <para>
      Use <command>COPY FROM STDIN</command> to load all the rows in one
!     command, instead of using
!     a series of <command>INSERT</command> commands.  This reduces parsing,
!     planning, etc.
!     overhead a great deal. If you do this then it is not necessary to turn
!     off autocommit, since it is only one command anyway.
     </para>
    </sect2>
  
--- 751,760 ----
  
     <para>
      Use <command>COPY FROM STDIN</command> to load all the rows in one
!     command, instead of using a series of <command>INSERT</command>
!     commands.  This reduces parsing, planning, etc.  overhead a great
!     deal. If you do this then it is not necessary to turn off
!     autocommit, since it is only one command anyway.
     </para>
    </sect2>
  
***************
*** 764,772 ****
  
     <para>
      If you are loading a freshly created table, the fastest way is to
!     create the table, bulk-load with <command>COPY</command>, then create any
!     indexes needed 
!     for the table.  Creating an index on pre-existing data is quicker than
      updating it incrementally as each row is loaded.
     </para>
  
--- 763,771 ----
  
     <para>
      If you are loading a freshly created table, the fastest way is to
!     create the table, bulk load the table's data using
!     <command>COPY</command>, then create any indexes needed for the
!     table.  Creating an index on pre-existing data is quicker than
      updating it incrementally as each row is loaded.
     </para>
  
***************
*** 780,785 ****
--- 779,797 ----
     </para>
    </sect2>
  
+   <sect2 id="populate-sort-mem">
+    <title>Increase <varname>sort_mem</varname></title>
+ 
+    <para>
+     Temporarily increasing the <varname>sort_mem</varname>
+     configuration variable when restoring large amounts of data can
+     lead to improved performance. This is because when a B-tree index
+     is created from scratch, the existing content of the table needs
+     to be sorted. Allowing the merge sort to use more buffer pages
+     means that fewer merge passes will be required.
+    </para>
+   </sect2>
+ 
    <sect2 id="populate-analyze">
     <title>Run <command>ANALYZE</command> Afterwards</title>
  
Index: doc/src/sgml/runtime.sgml
===================================================================
RCS file: /var/lib/cvs/pgsql-server/doc/src/sgml/runtime.sgml,v
retrieving revision 1.210
diff -c -r1.210 runtime.sgml
*** doc/src/sgml/runtime.sgml	3 Oct 2003 19:26:49 -0000	1.210
--- doc/src/sgml/runtime.sgml	6 Oct 2003 00:06:45 -0000
***************
*** 928,935 ****
          by <literal>ORDER BY</>, merge joins, and <command>CREATE INDEX</>.
          Hash tables are used in hash joins, hash-based aggregation, and
          hash-based processing of <literal>IN</> subqueries.  Because 
!         <command>CREATE INDEX</> is used when restoring a database, it might
!         be good to temporarily increase this value during a restore.
         </para>
        </listitem>
       </varlistentry>
--- 928,936 ----
          by <literal>ORDER BY</>, merge joins, and <command>CREATE INDEX</>.
          Hash tables are used in hash joins, hash-based aggregation, and
          hash-based processing of <literal>IN</> subqueries.  Because 
!         <command>CREATE INDEX</> is used when restoring a database,
!         increasing <varname>sort_mem</varname> before doing a large
!         restore operation can improve performance.
         </para>
        </listitem>
       </varlistentry>
#19Tom Lane
tgl@sss.pgh.pa.us
In reply to: Neil Conway (#18)
Re: [HACKERS] Open 7.4 items

Neil Conway <neilc@samurai.com> writes:

(BTW, is there a reason the docs consistently call them "B-tree
indexes", not "B+-tree indexes"?)

The latter might be technically more correct, but most people are going
to think it's a typo. I think B-tree is fine for the purposes of our docs.

regards, tom lane

#20Tom Lane
tgl@sss.pgh.pa.us
In reply to: Stephan Szabo (#13)
Re: Open 7.4 items

Stephan Szabo <sszabo@megazone.bigpanda.com> writes:

It's probably okay to give the no such key error in the delete case
(at some point it'd be nice to make it give serialization failure, but
that might take alot more work than is warrented at this time for 7.4)

Per prior discussion, I think the "no such key" error is more useful
than the serialization error, even if it's the wrong thing according
to a narrow interpretation; so I really don't feel much need to revisit
this later.

regards, tom lane

#21Jan Wieck
JanWieck@Yahoo.com
In reply to: Bruce Momjian (#8)
Re: Open 7.4 items

Bruce Momjian wrote:

Wow, that's a heap of code --- that's my only comment. :-)

Not really.

I'm not sure what conditions could possibley cause SPI_prepare to return
NULL, but it'd be certainly better to check that. Other than that, looks
good to me.

Jan

---------------------------------------------------------------------------

Tom Lane wrote:

Stephan Szabo <sszabo@megazone.bigpanda.com> writes:

It's not cleaned up, but yes. It appears to work for the simple tests I've
done and should fall back if the permissions don't work to do a single
query on both tables.

Here's my code-reviewed version of the patch. Anyone else want to take
a look?

I wasn't sure what to do about some of the spi error conditions. For many
of them I'm just returning false now so that it will try the other
mechanism in case that might work. I'm not really sure if that's worth it,
or if I should just elog(ERROR) and give up.

I think you may as well keep it the same as the other RI routines and
just elog() on SPI error. If SPI is broken, the trigger procedure is
gonna fail too.

I changed that, consolidated the error-reporting code, and fixed a couple
other little issues, notably:

* The generated query applied ONLY to the FK table but not the PK table.
I assume this was just an oversight.

* The query is now run using SPI_execp_current and selecting "current"
snapshot. Without this, we could fail in a serializable transaction
if someone else has already committed changes to either relation.
For example:

create pk and fk tables;

begin serializable xact;
insert into pk values(1);
insert into fk values(1);

begin;
insert into fk values(2);
commit;

alter table fk add foreign key ...;

The ALTER will not be blocked from acquiring exclusive lock, since
T2 already committed. But if we run the query in the serializable
snapshot, it won't see the violating row fk=2.

The old trigger-based check avoids this error because the scan loop uses
SnapshotNow to select live rows from the FK table. There is a dual race
condition where T2 deletes a row from the PK table. In current CVS tip
this will be detected and reported as a serialization failure, because
T1 won't be able to get SELECT FOR UPDATE lock on the deleted row. With
the proposed patch you'll instead see a "no such key" failure, which I
think is fine, even though it nominally violates serializability.

Comments? Can anyone else do a code review (Jan??)?

regards, tom lane

Content-Description: RIcheck.patch

*** src/backend/commands/tablecmds.c.orig	Thu Oct  2 15:24:52 2003
--- src/backend/commands/tablecmds.c	Sun Oct  5 16:29:51 2003
***************
*** 3455,3460 ****
--- 3455,3467 ----
int			count;
/*
+ 	 * See if we can do it with a single LEFT JOIN query.  A FALSE result
+ 	 * indicates we must proceed with the fire-the-trigger method.
+ 	 */
+ 	if (RI_Initial_Check(fkconstraint, rel, pkrel))
+ 		return;
+ 
+ 	/*
* Scan through each tuple, calling RI_FKey_check_ins (insert trigger)
* as if that tuple had just been inserted.  If any of those fail, it
* should ereport(ERROR) and that's that.
*** src/backend/utils/adt/ri_triggers.c.orig	Wed Oct  1 17:30:52 2003
--- src/backend/utils/adt/ri_triggers.c	Sun Oct  5 16:42:37 2003
***************
*** 40,45 ****
--- 40,46 ----
#include "rewrite/rewriteHandler.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"
+ #include "utils/acl.h"
#include "miscadmin.h"

***************
*** 164,170 ****
Datum *vals, char *nulls);
static void ri_ReportViolation(RI_QueryKey *qkey, const char *constrname,
Relation pk_rel, Relation fk_rel,
! HeapTuple violator, bool spi_err);

/* ----------
--- 165,172 ----
Datum *vals, char *nulls);
static void ri_ReportViolation(RI_QueryKey *qkey, const char *constrname,
Relation pk_rel, Relation fk_rel,
! 				   HeapTuple violator, TupleDesc tupdesc,
! 				   bool spi_err);
/* ----------
***************
*** 2540,2546 ****
--- 2542,2743 ----
}
+ /* ----------
+  * RI_Initial_Check -
+  *
+  *	Check an entire table for non-matching values using a single query.
+  *	This is not a trigger procedure, but is called during ALTER TABLE
+  *	ADD FOREIGN KEY to validate the initial table contents.
+  *
+  *	We expect that an exclusive lock has been taken on rel and pkrel;
+  *	hence, we do not need to lock individual rows for the check.
+  *
+  *	If the check fails because the current user doesn't have permissions
+  *	to read both tables, return false to let our caller know that they will
+  *	need to do something else to check the constraint.
+  * ----------
+  */
+ bool
+ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel)
+ {
+ 	const char *constrname = fkconstraint->constr_name;
+ 	char		querystr[MAX_QUOTED_REL_NAME_LEN * 2 + 250 +
+ 						(MAX_QUOTED_NAME_LEN + 32) * ((RI_MAX_NUMKEYS * 4)+1)];
+ 	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
+ 	char		relname[MAX_QUOTED_REL_NAME_LEN];
+ 	char		attname[MAX_QUOTED_NAME_LEN];
+ 	char		fkattname[MAX_QUOTED_NAME_LEN];
+ 	const char *sep;
+ 	List		*list;
+ 	List		*list2;
+ 	int			spi_result;
+ 	void		*qplan;
+ 
+ 	/*
+ 	 * Check to make sure current user has enough permissions to do the
+ 	 * test query.  (If not, caller can fall back to the trigger method,
+ 	 * which works because it changes user IDs on the fly.)
+ 	 *
+ 	 * XXX are there any other show-stopper conditions to check?
+ 	 */
+ 	if (pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_SELECT) != ACLCHECK_OK)
+ 		return false;
+ 	if (pg_class_aclcheck(RelationGetRelid(pkrel), GetUserId(), ACL_SELECT) != ACLCHECK_OK) 
+ 		return false;
+ 
+ 	/*----------
+ 	 * The query string built is:
+ 	 *  SELECT fk.keycols FROM ONLY relname fk 
+ 	 *   LEFT OUTER JOIN ONLY pkrelname pk 
+ 	 *   ON (pk.pkkeycol1=fk.keycol1 [AND ...])
+ 	 *   WHERE pk.pkkeycol1 IS NULL AND
+ 	 * For MATCH unspecified:
+ 	 *   (fk.keycol1 IS NOT NULL [AND ...])
+ 	 * For MATCH FULL:
+ 	 *   (fk.keycol1 IS NOT NULL [OR ...])
+ 	 *----------
+ 	 */
+ 
+ 	sprintf(querystr, "SELECT ");
+ 	sep="";
+ 	foreach(list, fkconstraint->fk_attrs)
+ 	{
+ 		quoteOneName(attname, strVal(lfirst(list)));
+ 		snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr),
+ 				 "%sfk.%s", sep, attname);
+ 		sep = ", ";
+ 	}
+ 
+ 	quoteRelationName(pkrelname, pkrel);
+ 	quoteRelationName(relname, rel);
+ 	snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr),
+ 			 " FROM ONLY %s fk LEFT OUTER JOIN ONLY %s pk ON (",
+ 			 relname, pkrelname);
+ 
+ 	sep="";
+ 	for (list=fkconstraint->pk_attrs, list2=fkconstraint->fk_attrs; 
+ 		 list != NIL && list2 != NIL; 
+ 		 list=lnext(list), list2=lnext(list2))
+ 	{
+ 		quoteOneName(attname, strVal(lfirst(list)));
+ 		quoteOneName(fkattname, strVal(lfirst(list2)));
+ 		snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr),
+ 				 "%spk.%s=fk.%s",
+ 				 sep, attname, fkattname);
+ 		sep = " AND ";
+ 	}
+ 	/*
+ 	 * It's sufficient to test any one pk attribute for null to detect a
+ 	 * join failure.
+ 	 */
+ 	quoteOneName(attname, strVal(lfirst(fkconstraint->pk_attrs)));
+ 	snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr),
+ 			 ") WHERE pk.%s IS NULL AND (", attname);
+ 
+ 	sep="";
+ 	foreach(list, fkconstraint->fk_attrs)
+ 	{
+ 		quoteOneName(attname, strVal(lfirst(list)));
+ 		snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr),
+ 				 "%sfk.%s IS NOT NULL",
+ 				 sep, attname);
+ 		switch (fkconstraint->fk_matchtype)
+ 		{
+ 			case FKCONSTR_MATCH_UNSPECIFIED:
+ 				sep=" AND ";
+ 				break;
+ 			case FKCONSTR_MATCH_FULL:
+ 				sep=" OR ";
+ 				break;
+ 			case FKCONSTR_MATCH_PARTIAL:
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("MATCH PARTIAL not yet implemented")));
+ 				break;
+ 			default:
+ 				elog(ERROR, "unrecognized match type: %d",
+ 					 fkconstraint->fk_matchtype);
+ 				break;
+ 		}
+ 	}
+ 	snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr),
+ 			 ")");
+ 
+ 	if (SPI_connect() != SPI_OK_CONNECT)
+ 		elog(ERROR, "SPI_connect failed");
+ 
+ 	/*
+ 	 * Generate the plan.  We don't need to cache it, and there are no
+ 	 * arguments to the plan. 
+ 	 */
+ 	qplan = SPI_prepare(querystr, 0, NULL);
+ 
+ 	/*
+ 	 * Run the plan.  For safety we force a current query snapshot to be
+ 	 * used.  (In serializable mode, this arguably violates serializability,
+ 	 * but we really haven't got much choice.)  We need at most one tuple
+ 	 * returned, so pass limit = 1.
+ 	 */
+ 	spi_result = SPI_execp_current(qplan, NULL, NULL, true, 1);
+ 	/* Check result */
+ 	if (spi_result != SPI_OK_SELECT)
+ 		elog(ERROR, "SPI_execp_current returned %d", spi_result);
+ 
+ 	/* Did we find a tuple violating the constraint? */
+ 	if (SPI_processed > 0)
+ 	{
+ 		HeapTuple	tuple = SPI_tuptable->vals[0];
+ 		TupleDesc	tupdesc = SPI_tuptable->tupdesc;
+ 		int			nkeys = length(fkconstraint->fk_attrs);
+ 		int			i;
+ 		RI_QueryKey	qkey;
+ 
+ 		/*
+ 		 * If it's MATCH FULL, and there are any nulls in the FK keys,
+ 		 * complain about that rather than the lack of a match.  MATCH FULL
+ 		 * disallows partially-null FK rows.
+ 		 */
+ 		if (fkconstraint->fk_matchtype == FKCONSTR_MATCH_FULL)
+ 		{
+ 			bool	isnull = false;
+ 
+ 			for (i = 1; i <= nkeys; i++)
+ 			{
+ 				(void) SPI_getbinval(tuple, tupdesc, i, &isnull);
+ 				if (isnull)
+ 					break;
+ 			}
+ 			if (isnull)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
+ 						 errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
+ 								RelationGetRelationName(rel),
+ 								constrname),
+ 						 errdetail("MATCH FULL does not allow mixing of null and nonnull key values.")));
+ 		}
+ 
+ 		/*
+ 		 * Although we didn't cache the query, we need to set up a fake
+ 		 * query key to pass to ri_ReportViolation.
+ 		 */
+ 		MemSet(&qkey, 0, sizeof(qkey));
+ 		qkey.constr_queryno = RI_PLAN_CHECK_LOOKUPPK;
+ 		qkey.nkeypairs = nkeys;
+ 		for (i = 0; i < nkeys; i++)
+ 			qkey.keypair[i][RI_KEYPAIR_FK_IDX] = i + 1;
+ 
+ 		ri_ReportViolation(&qkey, constrname,
+ 						   pkrel, rel,
+ 						   tuple, tupdesc,
+ 						   false);
+ 	}
+ 
+ 	if (SPI_finish() != SPI_OK_FINISH)
+ 		elog(ERROR, "SPI_finish failed");
+ 
+ 	return true;
+ }
/* ----------
***************
*** 2905,2910 ****
--- 3102,3108 ----
ri_ReportViolation(qkey, constrname ? constrname : "",
pk_rel, fk_rel,
new_tuple ? new_tuple : old_tuple,
+ 						   NULL,
true);
/* XXX wouldn't it be clearer to do this part at the caller? */
***************
*** 2913,2918 ****
--- 3111,3117 ----
ri_ReportViolation(qkey, constrname,
pk_rel, fk_rel,
new_tuple ? new_tuple : old_tuple,
+ 						   NULL,
false);
return SPI_processed != 0;
***************
*** 2950,2956 ****
static void
ri_ReportViolation(RI_QueryKey *qkey, const char *constrname,
Relation pk_rel, Relation fk_rel,
! 				   HeapTuple violator, bool spi_err)
{
#define BUFLENGTH	512
char		key_names[BUFLENGTH];
--- 3149,3156 ----
static void
ri_ReportViolation(RI_QueryKey *qkey, const char *constrname,
Relation pk_rel, Relation fk_rel,
! 				   HeapTuple violator, TupleDesc tupdesc,
! 				   bool spi_err)
{
#define BUFLENGTH	512
char		key_names[BUFLENGTH];
***************
*** 2958,2964 ****
char	   *name_ptr = key_names;
char	   *val_ptr = key_values;
bool		onfk;
- 	Relation	rel;
int			idx,
key_idx;
--- 3158,3163 ----
***************
*** 2972,2989 ****
errhint("This is most likely due to a rule having rewritten the query.")));

/*
! * rel is set to where the tuple description is coming from.
*/
onfk = (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK);
if (onfk)
{
- rel = fk_rel;
key_idx = RI_KEYPAIR_FK_IDX;
}
else
{
- rel = pk_rel;
key_idx = RI_KEYPAIR_PK_IDX;
}

/*
--- 3171,3191 ----
errhint("This is most likely due to a rule having rewritten the query.")));

/*
! * Determine which relation to complain about. If tupdesc wasn't
! * passed by caller, assume the violator tuple came from there.
*/
onfk = (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK);
if (onfk)
{
key_idx = RI_KEYPAIR_FK_IDX;
+ if (tupdesc == NULL)
+ tupdesc = fk_rel->rd_att;
}
else
{
key_idx = RI_KEYPAIR_PK_IDX;
+ if (tupdesc == NULL)
+ tupdesc = pk_rel->rd_att;
}

/*
***************
*** 3008,3015 ****
char *name,
*val;

! name = SPI_fname(rel->rd_att, fnum);
! val = SPI_getvalue(violator, rel->rd_att, fnum);
if (!val)
val = "null";

--- 3210,3217 ----
char	   *name,
*val;

! name = SPI_fname(tupdesc, fnum);
! val = SPI_getvalue(violator, tupdesc, fnum);
if (!val)
val = "null";

*** src/include/commands/trigger.h.orig	Sun Aug  3 23:01:31 2003
--- src/include/commands/trigger.h	Sun Oct  5 16:11:44 2003
***************
*** 197,201 ****
--- 197,204 ----
* in utils/adt/ri_triggers.c
*/
extern bool RI_FKey_keyequal_upd(TriggerData *trigdata);
+ extern bool RI_Initial_Check(FkConstraint *fkconstraint, 
+ 							 Relation rel, 
+ 							 Relation pkrel);

#endif /* TRIGGER_H */

---------------------------(end of broadcast)---------------------------
TIP 9: the planner will ignore your desire to choose an index scan if your
joining column's datatypes do not match

--
#======================================================================#
# It's easier to get forgiveness for being wrong than for being right. #
# Let's break this rule - forgive me. #
#================================================== JanWieck@Yahoo.com #

#22Jan Wieck
JanWieck@Yahoo.com
In reply to: Tom Lane (#14)
Re: Open 7.4 items

Tom Lane wrote:

Bruce Momjian <pgman@candle.pha.pa.us> writes:

Oh, that makes me feel better. Do we have timings for this code?

This is just a single data point, but I made a table of 1 million
rows containing just the int4 primary key column (values 0-1million
in a somewhat random order). Then I copied the same data, sans index,
to produce a foreign key table. Then I tried ALTER ADD PRIMARY KEY.

The results were:

Time to load the 1 million rows: 8 sec

Time to create the PK index: 10 sec

Time to ADD PRIMARY KEY:

with CVS-tip code (fire trigger per row): 78 sec

with proposed patch: anywhere from 5 to 25 sec depending on plan

The default plan if there is no index on the FK table (meaning the
planner will not know its true size) is a nestloop with inner index
scan taking about 17 sec.

Does an ANALYZE run between index creation and bulk FK checking improve
planning? It's not doing a full DB scan, so it shouldn't do too much
harm, does it?

Jan

--
#======================================================================#
# It's easier to get forgiveness for being wrong than for being right. #
# Let's break this rule - forgive me. #
#================================================== JanWieck@Yahoo.com #

#23Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jan Wieck (#21)
Re: Open 7.4 items

Jan Wieck <JanWieck@Yahoo.com> writes:

I'm not sure what conditions could possibley cause SPI_prepare to return
NULL, but it'd be certainly better to check that.

Good thought. I was looking at the other SPI_prepare calls in
ri_triggers.c, which don't check for NULL either, but clearly they
should. Will fix all.

regards, tom lane

#24Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jan Wieck (#22)
Re: Open 7.4 items

Jan Wieck <JanWieck@Yahoo.com> writes:

Does an ANALYZE run between index creation and bulk FK checking improve
planning?

It almost certainly would, but I was assuming we had to consider this in
the context of loading existing dump files. We could think about having
pg_dump emit an automatic ANALYZE after the data loading step in the
future though.

regards, tom lane

#25Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Tom Lane (#24)
Re: Open 7.4 items

Tom Lane wrote:

Jan Wieck <JanWieck@Yahoo.com> writes:

Does an ANALYZE run between index creation and bulk FK checking improve
planning?

It almost certainly would, but I was assuming we had to consider this in
the context of loading existing dump files. We could think about having
pg_dump emit an automatic ANALYZE after the data loading step in the
future though.

I don't have a problem with having it be slower for 7.4 upgrades if we
can make it reasonable for future loads.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
#26Rod Taylor
rbt@rbt.ca
In reply to: Tom Lane (#24)
Re: Open 7.4 items

It almost certainly would, but I was assuming we had to consider this in
the context of loading existing dump files. We could think about having
pg_dump emit an automatic ANALYZE after the data loading step in the
future though.

Rather than running ANALYZE, how about simply dumping out and restoring
current statistics? The old stats should be close enough, even with the
lack of empty space.

#27Tom Lane
tgl@sss.pgh.pa.us
In reply to: Rod Taylor (#26)
Re: Open 7.4 items

Rod Taylor <rbt@rbt.ca> writes:

Rather than running ANALYZE, how about simply dumping out and restoring
current statistics?

Nope. That would assume that the stats are the same across revisions.
Not to mention requiring superuser privileges.

regards, tom lane

#28Gaetano Mendola
mendola@bigfoot.com
In reply to: Bruce Momjian (#1)
Re: Open 7.4 items

Bruce Momjian wrote:

P O S T G R E S Q L

7 . 4 O P E N I T E M S

Current at ftp://momjian.postgresql.org/pub/postgresql/open_items.

On the same folder there is: PITR_20020822_02.gz

tell me that we are near to have it :-)

Regards
Gaetano Mendola

#29Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Neil Conway (#18)
Re: [HACKERS] Open 7.4 items

Patch applied. Thanks.

---------------------------------------------------------------------------

Neil Conway wrote:

On Sun, 2003-10-05 at 19:58, Tom Lane wrote:

That's a fairly useless place to put it, though, since someone would
only think to look at sort_mem if they already had a clue. It should
be mentioned under bulk data load (in performance tips chapter)

Attached is a doc patch that does this. The way I've worded it may not
be the best, though.

and perhaps also in dump/restore procedures.

It's already mentioned there.

Should we also suggest turning off fsync when doing restores?

(BTW, is there a reason the docs consistently call them "B-tree
indexes", not "B+-tree indexes"?)

-Neil

[ Attachment, skipping... ]

---------------------------(end of broadcast)---------------------------
TIP 5: Have you checked our extensive FAQ?

http://www.postgresql.org/docs/faqs/FAQ.html

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073