Index: doc/src/sgml/xfunc.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql/doc/src/sgml/xfunc.sgml,v
retrieving revision 1.47
diff -c -r1.47 xfunc.sgml
*** doc/src/sgml/xfunc.sgml	2002/01/20 22:19:56	1.47
--- doc/src/sgml/xfunc.sgml	2002/02/22 22:51:32
***************
*** 1297,1302 ****
--- 1297,1331 ----
      </para>
  
      <para>
+     Other options provided in the new-style interface are two
+      variants of the
+      <function>PG_GETARG_<replaceable>xxx</replaceable>()</function>
+      macros. The first of these,
+      <function>PG_GETARG_<replaceable>xxx</replaceable>_COPY()</function>
+      guarantees to return a copy of the specified parameter which is
+      safe for writing into. (The normal macros will sometimes return a
+      pointer to the value which must not be written to. Using the
+      <function>PG_GETARG_<replaceable>xxx</replaceable>_COPY()</function>
+      macros guarantees a writable result.)
+     </para>
+ 
+     <para>
+     The second variant consists of the
+     <function>PG_GETARG_<replaceable>xxx</replaceable>_SLICE()</function>
+     macros which take three parameters. The first is the number of the
+     parameter (as above). The second and third are the offset and
+     length of the segment to be returned. Offsets are counted from
+     zero, and a negative length requests that the remainder of the
+     value be returned. These routines provide more efficient access to
+     parts of large values in the case where they have storage type
+     "external". (The storage type of a column can be specified using
+     <command>ALTER TABLE <repaceable>tablename</replaceable> ALTER
+     COLUMN <replaceable>colname</replaceable> SET STORAGE
+     <replaceable>storagetype</replaceable>. Storage type is one of
+     plain, external, extended or main.)
+     </para>
+ 
+     <para>
       The version-1 function call conventions make it possible to
       return <quote>set</quote> results and implement trigger functions and
       procedural-language call handlers.  Version-1 code is also more
Index: doc/src/sgml/ref/alter_table.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql/doc/src/sgml/ref/alter_table.sgml,v
retrieving revision 1.38
diff -c -r1.38 alter_table.sgml
*** doc/src/sgml/ref/alter_table.sgml	2002/02/17 13:29:00	1.38
--- doc/src/sgml/ref/alter_table.sgml	2002/02/22 22:51:33
***************
*** 31,36 ****
--- 31,38 ----
  ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ * ]
      ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET STATISTICS <replaceable class="PARAMETER">integer</replaceable>
  ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ * ]
+     ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET STORAGE {PLAIN | EXTERNAL | EXTENDED | MAIN}
+ ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ * ]
      RENAME [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> TO <replaceable
      class="PARAMETER">newcolumn</replaceable>
  ALTER TABLE <replaceable class="PARAMETER">table</replaceable>
***************
*** 169,174 ****
--- 171,187 ----
     The <literal>ALTER COLUMN SET STATISTICS</literal> form allows you to
     set the statistics-gathering target for subsequent
     <xref linkend="sql-analyze" endterm="sql-analyze-title"> operations.
+    The <literal>ALTER COLUMN SET STORAGE</literal> form allows the
+    column storage mode to be set. This controls whether this column is
+    held inline or in a supplementary table, and whether the data
+    should be compressed or not. <literal>PLAIN</literal> must be used
+    for fixed-length values such as <literal>INTEGER</literal> and is
+    inline, uncompressed. <literal>MAIN</literal> is for inline,
+    compressible data. <literal>EXTERNAL</literal> is for external,
+    uncompressed data and <literal>EXTENDED</literal> is for external,
+    compressed data. The use of <literal>EXTERNAL</literal> will make
+    substring operations on a column faster, at the penalty of
+    increased storage space.
     The <literal>RENAME</literal> clause causes the name of a table,
     column, index, or sequence to change without changing any of the
     data. The data will remain of the same type and size after the
Index: src/backend/access/heap/tuptoaster.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/access/heap/tuptoaster.c,v
retrieving revision 1.27
diff -c -r1.27 tuptoaster.c
*** src/backend/access/heap/tuptoaster.c	2002/01/16 20:29:01	1.27
--- src/backend/access/heap/tuptoaster.c	2002/02/22 22:51:34
***************
*** 47,52 ****
--- 47,54 ----
  					   HeapTuple oldtup);
  static Datum toast_save_datum(Relation rel, Datum value);
  static varattrib *toast_fetch_datum(varattrib *attr);
+ static varattrib *toast_fetch_datum_slice(varattrib *attr,
+ 										  int32 sliceoffset, int32 length);
  
  
  /* ----------
***************
*** 163,168 ****
--- 165,244 ----
  
  
  /* ----------
+  * heap_tuple_untoast_attr_slice -
+  *
+  *      Public entry point to get back part of a toasted value 
+  *      from compression or external storage.
+  * ----------
+  */
+ varattrib  *
+ heap_tuple_untoast_attr_slice(varattrib *attr, int32 sliceoffset, int32 slicelength)
+ {
+ 	varattrib  *preslice;
+ 	varattrib  *result;
+ 	int32  attrsize;
+ 	
+ 	if (VARATT_IS_COMPRESSED(attr))
+ 	{
+ 		varattrib *tmp;
+ 		
+ 		if (VARATT_IS_EXTERNAL(attr))
+ 		{
+ 			tmp = toast_fetch_datum(attr);
+ 		}
+ 		else
+ 		{
+ 			tmp = attr; /* compressed in main tuple */
+ 		}
+ 		
+ 		preslice = (varattrib *) palloc(attr->va_content.va_external.va_rawsize
+ 										+ VARHDRSZ);
+ 		VARATT_SIZEP(preslice) = attr->va_content.va_external.va_rawsize + VARHDRSZ;
+ 		pglz_decompress((PGLZ_Header *) tmp, VARATT_DATA(preslice));
+ 		
+ 		if (tmp != attr) 
+ 			pfree(tmp);
+ 	}
+ 	else 	
+ 	{
+ 		/* Plain value */
+ 		if (VARATT_IS_EXTERNAL(attr))
+ 		{   
+ 			/* fast path */
+ 			return (toast_fetch_datum_slice(attr, sliceoffset, slicelength));
+ 		}
+ 		else
+ 		{
+ 			preslice = attr;
+ 		}
+ 	}
+ 	
+ 	/* slicing of datum for compressed cases and plain value */
+ 	
+ 	attrsize = VARSIZE(preslice) - VARHDRSZ;
+ 	if (sliceoffset >= attrsize) 
+ 	{
+ 		sliceoffset = 0;
+ 		slicelength = 0;
+ 	}
+ 	
+ 	if (((sliceoffset + slicelength) > attrsize) || slicelength < 0)
+ 	{
+ 		slicelength = attrsize - sliceoffset;
+ 	}
+ 	
+ 	result = (varattrib *) palloc(slicelength + VARHDRSZ);
+ 	VARATT_SIZEP(result) = slicelength + VARHDRSZ;
+ 	
+ 	memcpy(VARDATA(result), VARDATA(preslice) + sliceoffset, slicelength);
+ 	
+ 	if (preslice != attr) pfree(preslice);
+ 	
+ 	return result;
+ }
+ 
+ 
+ /* ----------
   * toast_raw_datum_size -
   *
   *	Return the raw (detoasted) size of a varlena datum
***************
*** 981,987 ****
  		VARATT_SIZEP(result) |= VARATT_FLAG_COMPRESSED;
  
  	/*
! 	 * Open the toast relation and it's index
  	 */
  	toastrel = heap_open(attr->va_content.va_external.va_toastrelid,
  						 AccessShareLock);
--- 1057,1063 ----
  		VARATT_SIZEP(result) |= VARATT_FLAG_COMPRESSED;
  
  	/*
! 	 * Open the toast relation and its index
  	 */
  	toastrel = heap_open(attr->va_content.va_external.va_toastrelid,
  						 AccessShareLock);
***************
*** 1067,1072 ****
--- 1143,1342 ----
  	 * Final checks that we successfully fetched the datum
  	 */
  	if (nextidx != numchunks)
+ 		elog(ERROR, "missing chunk number %d for toast value %u",
+ 			 nextidx,
+ 			 attr->va_content.va_external.va_valueid);
+ 
+ 	/*
+ 	 * End scan and close relations
+ 	 */
+ 	index_endscan(toastscan);
+ 	index_close(toastidx);
+ 	heap_close(toastrel, AccessShareLock);
+ 
+ 	return result;
+ }
+ 
+ /* ----------
+  * toast_fetch_datum_slice -
+  *
+  *	Reconstruct a segment of a varattrib from the chunks saved
+  *	in the toast relation
+  * ----------
+  */
+ static varattrib *
+ toast_fetch_datum_slice(varattrib *attr, int32 sliceoffset, int32 length)
+ {
+ 	Relation	toastrel;
+ 	Relation	toastidx;
+ 	ScanKeyData toastkey[3];
+ 	IndexScanDesc toastscan;
+ 	HeapTupleData toasttup;
+ 	HeapTuple	ttup;
+ 	TupleDesc	toasttupDesc;
+ 	RetrieveIndexResult indexRes;
+ 	Buffer		buffer;
+ 
+ 	varattrib  *result;
+ 	int32		attrsize;
+ 	int32       nscankeys;
+ 	int32		residx;
+ 	int32       nextidx;
+ 	int		    numchunks;
+ 	int		    startchunk;
+ 	int		    endchunk;
+ 	int32		startoffset;
+ 	int32		endoffset;
+ 	int         totalchunks;
+ 	Pointer		chunk;
+ 	bool		isnull;
+ 	int32		chunksize;
+ 	int32       chcpystrt;
+ 	int32       chcpyend;
+ 
+ 	attrsize = attr->va_content.va_external.va_extsize;
+ 	totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
+ 
+ 	if (sliceoffset >= attrsize) 
+ 	  {
+ 	    sliceoffset = 0;
+ 	    length = 0;
+ 	  }
+ 
+ 	if (((sliceoffset + length) > attrsize) || length < 0)
+ 	  {
+ 	    length = attrsize - sliceoffset;
+ 	  }
+ 
+ 	result = (varattrib *) palloc(length + VARHDRSZ);
+ 	VARATT_SIZEP(result) = length + VARHDRSZ;
+ 
+ 	if (VARATT_IS_COMPRESSED(attr))
+ 		VARATT_SIZEP(result) |= VARATT_FLAG_COMPRESSED;
+ 	
+ 	if (length == 0) return (result); /* Can save a lot of work at this point! */
+ 
+ 	startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
+ 	endchunk = (sliceoffset + length - 1) / TOAST_MAX_CHUNK_SIZE;
+ 	numchunks = (endchunk - startchunk ) + 1;
+  
+ 	startoffset = sliceoffset % TOAST_MAX_CHUNK_SIZE;
+ 	endoffset = (sliceoffset + length - 1) % TOAST_MAX_CHUNK_SIZE;
+ 
+ 	/*
+ 	 * Open the toast relation and it's index
+ 	 */
+ 	toastrel = heap_open(attr->va_content.va_external.va_toastrelid,
+ 						 AccessShareLock);
+ 	toasttupDesc = toastrel->rd_att;
+ 	toastidx = index_open(toastrel->rd_rel->reltoastidxid);
+ 
+ 	/*
+ 	 * Setup a scan key to fetch from the index. This is either two keys
+ 	 * or three depending on the number of chunks.
+ 	 */
+ 	ScanKeyEntryInitialize(&toastkey[0],
+ 						   (bits16) 0,
+ 						   (AttrNumber) 1,
+ 						   (RegProcedure) F_OIDEQ,
+ 						   ObjectIdGetDatum(attr->va_content.va_external.va_valueid));
+ 	/*
+ 	 * Now dependent on number of chunks:
+ 	 */
+ 	
+ 	if (numchunks == 1) 
+ 	{
+ 	    ScanKeyEntryInitialize(&toastkey[1],
+ 							   (bits16) 0,
+ 							   (AttrNumber) 2,
+ 							   (RegProcedure) F_INT4EQ,
+ 							   Int32GetDatum(startchunk));
+ 	    nscankeys = 2;
+ 	}
+ 	else
+ 	{
+ 	    ScanKeyEntryInitialize(&toastkey[1],
+ 							   (bits16) 0,
+ 							   (AttrNumber) 2,
+ 							   (RegProcedure) F_INT4GE,
+ 							   Int32GetDatum(startchunk));
+ 	    ScanKeyEntryInitialize(&toastkey[2],
+ 							   (bits16) 0,
+ 							   (AttrNumber) 2,
+ 							   (RegProcedure) F_INT4LE,
+ 							   Int32GetDatum(endchunk));
+ 	    nscankeys = 3;
+ 	}
+ 
+ 	/*
+ 	 * Read the chunks by index
+ 	 *
+ 	 * The index is on (valueid, chunkidx) so they will come in order
+ 	 */
+ 	nextidx = startchunk;
+ 	toastscan = index_beginscan(toastidx, false, nscankeys, &toastkey[0]);
+ 	while ((indexRes = index_getnext(toastscan, ForwardScanDirection)) != NULL)
+ 	{
+ 		toasttup.t_self = indexRes->heap_iptr;
+ 		heap_fetch(toastrel, SnapshotToast, &toasttup, &buffer, toastscan);
+ 		pfree(indexRes);
+ 
+ 		if (toasttup.t_data == NULL)
+ 			continue;
+ 		ttup = &toasttup;
+ 
+ 		/*
+ 		 * Have a chunk, extract the sequence number and the data
+ 		 */
+ 		residx = DatumGetInt32(heap_getattr(ttup, 2, toasttupDesc, &isnull));
+ 		Assert(!isnull);
+ 		chunk = DatumGetPointer(heap_getattr(ttup, 3, toasttupDesc, &isnull));
+ 		Assert(!isnull);
+ 		chunksize = VARATT_SIZE(chunk) - VARHDRSZ;
+ 
+ 		/*
+ 		 * Some checks on the data we've found
+ 		 */
+ 		if ((residx != nextidx) || (residx > endchunk) || (residx < startchunk))
+ 			elog(ERROR, "unexpected chunk number %d (expected %d) for toast value %u",
+ 				 residx, nextidx,
+ 				 attr->va_content.va_external.va_valueid);
+ 		if (residx < totalchunks - 1)
+ 		{
+ 			if (chunksize != TOAST_MAX_CHUNK_SIZE)
+ 				elog(ERROR, "unexpected chunk size %d in chunk %d for toast value %u",
+ 					 chunksize, residx,
+ 					 attr->va_content.va_external.va_valueid);
+ 		}
+ 		else
+ 		{
+ 			if ((residx * TOAST_MAX_CHUNK_SIZE + chunksize) != attrsize)
+ 				elog(ERROR, "unexpected chunk size %d in chunk %d for toast value %u",
+ 					 chunksize, residx,
+ 					 attr->va_content.va_external.va_valueid);
+ 		}
+ 
+ 		/*
+ 		 * Copy the data into proper place in our result
+ 		 */
+ 		chcpystrt = 0;
+ 		chcpyend = chunksize - 1;
+ 		if (residx == startchunk) chcpystrt = startoffset;
+ 		if (residx == endchunk) chcpyend = endoffset;
+ 		
+ 		memcpy(((char *) VARATT_DATA(result)) + 
+ 		       (residx * TOAST_MAX_CHUNK_SIZE - sliceoffset) +chcpystrt,
+ 			   VARATT_DATA(chunk) + chcpystrt,
+ 			   (chcpyend - chcpystrt) + 1);
+ 		
+ 		ReleaseBuffer(buffer);
+ 		nextidx++;
+ 	}
+ 
+ 	/*
+ 	 * Final checks that we successfully fetched the datum
+ 	 */
+ 	if ( nextidx != (endchunk + 1))
  		elog(ERROR, "missing chunk number %d for toast value %u",
  			 nextidx,
  			 attr->va_content.va_external.va_valueid);
Index: src/backend/commands/command.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/commands/command.c,v
retrieving revision 1.154
diff -c -r1.154 command.c
*** src/backend/commands/command.c	2002/02/19 20:11:12	1.154
--- src/backend/commands/command.c	2002/02/22 22:51:36
***************
*** 718,737 ****
  
  
  /*
!  * ALTER TABLE ALTER COLUMN SET STATISTICS
   */
  void
! AlterTableAlterColumnStatistics(const char *relationName,
  								bool inh, const char *colName,
! 								Node *statsTarget)
  {
  	Relation	rel;
  	Oid			myrelid;
! 	int			newtarget;
  	Relation	attrelation;
  	HeapTuple	tuple;
  
! 	/* we allow this on system tables */
  #ifndef NO_SECURITY
  	if (!pg_ownercheck(GetUserId(), relationName, RELNAME))
  		elog(ERROR, "ALTER TABLE: permission denied");
--- 718,744 ----
  
  
  /*
!  * ALTER TABLE ALTER COLUMN SET STATISTICS / STORAGE
   */
  void
! AlterTableAlterColumnFlags(const char *relationName,
  								bool inh, const char *colName,
! 								Node *flagValue, const char *flagType)
  {
  	Relation	rel;
  	Oid			myrelid;
! 	int			newtarget = 1;
! 	char        newstorage = 'x';
! 	char        *storagemode;
  	Relation	attrelation;
  	HeapTuple	tuple;
  
! 	/* we allow statistics case for system tables */
! 
! 	if (*flagType =='M' && !allowSystemTableMods && IsSystemRelationName(relationName))
! 		elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
! 			 relationName);
! 
  #ifndef NO_SECURITY
  	if (!pg_ownercheck(GetUserId(), relationName, RELNAME))
  		elog(ERROR, "ALTER TABLE: permission denied");
***************
*** 746,751 ****
--- 753,802 ----
  	myrelid = RelationGetRelid(rel);
  	heap_close(rel, NoLock);	/* close rel, but keep lock! */
  
+ 	
+ 	/*
+ 	 * Check the supplied parameters before anything else
+ 	 */
+ 	if (*flagType == 'S')           /*
+ 									 * STATISTICS
+ 									 */
+ 	{
+ 		Assert(IsA(flagValue, Integer));
+ 		newtarget = intVal(flagValue);
+ 		
+ 		/*
+ 		 * Limit target to sane range (should we raise an error instead?)
+ 		 */
+ 		if (newtarget < 0)
+ 			newtarget = 0;
+ 		else if (newtarget > 1000)
+ 			newtarget = 1000;
+ 	}
+ 	else if (*flagType == 'M')      /*
+ 									 * STORAGE
+ 									 */
+ 	{
+ 		Assert(IsA(flagValue, Value));
+ 		
+ 		storagemode = strVal(flagValue);
+ 		if (strcasecmp(storagemode, "plain") == 0)
+ 			newstorage = 'p';
+ 		else if (strcasecmp(storagemode, "external") == 0)
+ 			newstorage = 'e';
+ 		else if (strcasecmp(storagemode, "extended") == 0)
+ 			newstorage = 'x';
+ 		else if (strcasecmp(storagemode, "main") == 0)
+ 			newstorage = 'm';
+ 		else
+ 			elog(ERROR, "ALTER TABLE: \"%s\" storage not recognized",
+ 				 storagemode);
+ 	}
+ 	else
+ 	{
+ 		elog(ERROR, "ALTER TABLE: Invalid column flag: %c",
+ 			 (int) *flagType);
+ 	}
+ 
  	/*
  	 * Propagate to children if desired
  	 */
***************
*** 769,791 ****
  			if (childrelid == myrelid)
  				continue;
  			rel = heap_open(childrelid, AccessExclusiveLock);
! 			AlterTableAlterColumnStatistics(RelationGetRelationName(rel),
! 											false, colName, statsTarget);
  			heap_close(rel, AccessExclusiveLock);
  		}
  	}
  
  	/* -= now do the thing on this relation =- */
  
- 	Assert(IsA(statsTarget, Integer));
- 	newtarget = intVal(statsTarget);
- 
- 	/* Limit target to sane range (should we raise an error instead?) */
- 	if (newtarget < 0)
- 		newtarget = 0;
- 	else if (newtarget > 1000)
- 		newtarget = 1000;
- 
  	attrelation = heap_openr(AttributeRelationName, RowExclusiveLock);
  
  	tuple = SearchSysCacheCopy(ATTNAME,
--- 820,833 ----
  			if (childrelid == myrelid)
  				continue;
  			rel = heap_open(childrelid, AccessExclusiveLock);
! 			AlterTableAlterColumnFlags(RelationGetRelationName(rel),
! 											false, colName, flagValue, flagType);
  			heap_close(rel, AccessExclusiveLock);
  		}
  	}
  
  	/* -= now do the thing on this relation =- */
  
  	attrelation = heap_openr(AttributeRelationName, RowExclusiveLock);
  
  	tuple = SearchSysCacheCopy(ATTNAME,
***************
*** 799,807 ****
  	if (((Form_pg_attribute) GETSTRUCT(tuple))->attnum < 0)
  		elog(ERROR, "ALTER TABLE: cannot change system attribute \"%s\"",
  			 colName);
! 
! 	((Form_pg_attribute) GETSTRUCT(tuple))->attstattarget = newtarget;
! 
  	simple_heap_update(attrelation, &tuple->t_self, tuple);
  
  	/* keep system catalog indices current */
--- 841,862 ----
  	if (((Form_pg_attribute) GETSTRUCT(tuple))->attnum < 0)
  		elog(ERROR, "ALTER TABLE: cannot change system attribute \"%s\"",
  			 colName);
! 	/*
! 	 * Now change the appropriate field
! 	 */
! 	if (*flagType == 'S')
! 		((Form_pg_attribute) GETSTRUCT(tuple))->attstattarget = newtarget;
! 	else
! 	{
! 		if ((newstorage == 'p') ||
! 			(((Form_pg_attribute) GETSTRUCT(tuple))->attlen == -1))
! 			((Form_pg_attribute) GETSTRUCT(tuple))->attstorage = newstorage;
! 		else
! 		{
! 			elog(ERROR,
! 				 "ALTER TABLE: Fixed-length columns can only have storage \"plain\"");
! 		}
! 	}
  	simple_heap_update(attrelation, &tuple->t_self, tuple);
  
  	/* keep system catalog indices current */
Index: src/backend/parser/gram.y
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/parser/gram.y,v
retrieving revision 2.278
diff -c -r2.278 gram.y
*** src/backend/parser/gram.y	2002/02/18 23:11:17	2.278
--- src/backend/parser/gram.y	2002/02/22 22:51:41
***************
*** 363,369 ****
  		OFFSET, OIDS, OPERATOR, OWNER, PASSWORD, PROCEDURAL,
  		REINDEX, RENAME, RESET, RETURNS, ROW, RULE,
  		SEQUENCE, SETOF, SHARE, SHOW, START, STATEMENT,
! 		STATISTICS, STDIN, STDOUT, SYSID,
  		TEMP, TEMPLATE, TOAST, TRUNCATE, TRUSTED, 
  		UNLISTEN, UNTIL, VACUUM, VALID, VERBOSE, VERSION
  
--- 363,369 ----
  		OFFSET, OIDS, OPERATOR, OWNER, PASSWORD, PROCEDURAL,
  		REINDEX, RENAME, RESET, RETURNS, ROW, RULE,
  		SEQUENCE, SETOF, SHARE, SHOW, START, STATEMENT,
! 		STATISTICS, STDIN, STDOUT, STORAGE, SYSID,
  		TEMP, TEMPLATE, TOAST, TRUNCATE, TRUSTED, 
  		UNLISTEN, UNTIL, VACUUM, VALID, VERBOSE, VERSION
  
***************
*** 1093,1098 ****
--- 1093,1109 ----
  					n->def = (Node *) makeInteger($9);
  					$$ = (Node *)n;
  				}
+ /* ALTER TABLE <relation> ALTER [COLUMN] <colname> SET STORAGE <storagemode> */
+         | ALTER TABLE relation_expr ALTER opt_column ColId SET STORAGE ColId
+                 {
+ 					AlterTableStmt *n = makeNode(AlterTableStmt);
+ 					n->subtype = 'M';
+ 					n->relname = $3->relname;
+ 					n->inhOpt = $3->inhOpt;
+ 					n->name = $6;
+ 					n->def = (Node *) makeString($9);
+ 					$$ = (Node *)n;
+ 				}
  /* ALTER TABLE <relation> DROP [COLUMN] <colname> {RESTRICT|CASCADE} */
  		| ALTER TABLE relation_expr DROP opt_column ColId drop_behavior
  				{
***************
*** 5888,5893 ****
--- 5899,5905 ----
  		| STATISTICS					{ $$ = "statistics"; }
  		| STDIN							{ $$ = "stdin"; }
  		| STDOUT						{ $$ = "stdout"; }
+         | STORAGE                       { $$ = "storage"; }
  		| SYSID							{ $$ = "sysid"; }
  		| TEMP							{ $$ = "temp"; }
  		| TEMPLATE						{ $$ = "template"; }
Index: src/backend/parser/keywords.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/parser/keywords.c,v
retrieving revision 1.100
diff -c -r1.100 keywords.c
*** src/backend/parser/keywords.c	2002/02/18 23:11:18	1.100
--- src/backend/parser/keywords.c	2002/02/22 22:51:42
***************
*** 243,248 ****
--- 243,249 ----
  	{"statistics", STATISTICS},
  	{"stdin", STDIN},
  	{"stdout", STDOUT},
+ 	{"storage", STORAGE},
  	{"substring", SUBSTRING},
  	{"sysid", SYSID},
  	{"table", TABLE},
Index: src/backend/tcop/utility.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/tcop/utility.c,v
retrieving revision 1.125
diff -c -r1.125 utility.c
*** src/backend/tcop/utility.c	2002/02/07 00:27:30	1.125
--- src/backend/tcop/utility.c	2002/02/22 22:51:43
***************
*** 433,442 ****
  													 stmt->def);
  						break;
  					case 'S':	/* ALTER COLUMN STATISTICS */
! 						AlterTableAlterColumnStatistics(stmt->relname,
  										interpretInhOption(stmt->inhOpt),
  														stmt->name,
! 														stmt->def);
  						break;
  					case 'D':	/* DROP COLUMN */
  						AlterTableDropColumn(stmt->relname,
--- 433,444 ----
  													 stmt->def);
  						break;
  					case 'S':	/* ALTER COLUMN STATISTICS */
! 					case 'M':   /* ALTER COLUMN STORAGE */
! 						AlterTableAlterColumnFlags(stmt->relname,
  										interpretInhOption(stmt->inhOpt),
  														stmt->name,
! 														stmt->def,
! 												        &(stmt->subtype));
  						break;
  					case 'D':	/* DROP COLUMN */
  						AlterTableDropColumn(stmt->relname,
Index: src/backend/utils/adt/varlena.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/utils/adt/varlena.c,v
retrieving revision 1.78
diff -c -r1.78 varlena.c
*** src/backend/utils/adt/varlena.c	2001/11/19 19:15:07	1.78
--- src/backend/utils/adt/varlena.c	2002/02/22 22:51:44
***************
*** 332,380 ****
   * Changed behavior if starting position is less than one to conform to SQL92 behavior.
   * Formerly returned the entire string; now returns a portion.
   * - Thomas Lockhart 1998-12-10
   */
  Datum
  text_substr(PG_FUNCTION_ARGS)
  {
! 	text	   *string = PG_GETARG_TEXT_P(0);
  	int32		m = PG_GETARG_INT32(1);
  	int32		n = PG_GETARG_INT32(2);
! 	text	   *ret;
! 	int			len;
! 
  #ifdef MULTIBYTE
  	int			i;
  	char	   *p;
! #endif
  
- 	len = VARSIZE(string) - VARHDRSZ;
- #ifdef MULTIBYTE
- 	len = pg_mbstrlen_with_len(VARDATA(string), len);
- #endif
- 
- 	/* starting position after the end of the string? */
- 	if (m > len)
- 	{
- 		m = 1;
- 		n = 0;
- 	}
- 
  	/*
  	 * starting position before the start of the string? then offset into
  	 * the string per SQL92 spec...
  	 */
! 	else if (m < 1)
  	{
  		n += (m - 1);
  		m = 1;
  	}
  
  	/* m will now become a zero-based starting position */
  	m--;
  	if (((m + n) > len) || (n < 0))
  		n = (len - m);
  
- #ifdef MULTIBYTE
  	p = VARDATA(string);
  	for (i = 0; i < m; i++)
  		p += pg_mblen(p);
--- 332,402 ----
   * Changed behavior if starting position is less than one to conform to SQL92 behavior.
   * Formerly returned the entire string; now returns a portion.
   * - Thomas Lockhart 1998-12-10
+  * Now uses faster TOAST-slicing interface
+  * - John Gray 2002-02-22
   */
  Datum
  text_substr(PG_FUNCTION_ARGS)
  {
! 	text	   *string;
  	int32		m = PG_GETARG_INT32(1);
  	int32		n = PG_GETARG_INT32(2);
! 	int32       sm;
! 	int32       sn;
! 	int         eml = 1;
  #ifdef MULTIBYTE
  	int			i;
+ 	int			len;
+ 	text	   *ret;
  	char	   *p;
! #endif 
  
  	/*
  	 * starting position before the start of the string? then offset into
  	 * the string per SQL92 spec...
  	 */
! 	if (m < 1)
  	{
  		n += (m - 1);
  		m = 1;
  	}
+ 	/* Check for m > octet length is made in TOAST access routine */
  
  	/* m will now become a zero-based starting position */
+ 	sm = m - 1;
+ 	sn = n;
+ 
+ #ifdef MULTIBYTE
+ 	eml = pg_database_encoding_max_length ();
+ 
+ 	if (eml > 1)
+ 	{
+ 		sm = 0;
+ 		sn = (m + n) * eml + 3; /* +3 to avoid mb characters overhanging slice end */
+ 	}
+ #endif 
+ 
+ 	string = PG_GETARG_TEXT_P_SLICE (0, sm, sn);
+ 
+ 	if (eml == 1) 
+ 	{
+ 		PG_RETURN_TEXT_P (string);
+ 	}
+ #ifndef MULTIBYTE
+ 	PG_RETURN_NULL();   /* notreached: suppress compiler warning */
+ #endif
+ #ifdef MULTIBYTE
+ 	len = pg_mbstrlen_with_len (VARDATA (string), sn - 3);
+ 
+ 	if (m > len)
+ 	{
+ 		m = 1;
+ 		n = 0;
+ 	}
  	m--;
  	if (((m + n) > len) || (n < 0))
  		n = (len - m);
  
  	p = VARDATA(string);
  	for (i = 0; i < m; i++)
  		p += pg_mblen(p);
***************
*** 382,388 ****
  	for (i = 0; i < n; i++)
  		p += pg_mblen(p);
  	n = p - (VARDATA(string) + m);
- #endif
  
  	ret = (text *) palloc(VARHDRSZ + n);
  	VARATT_SIZEP(ret) = VARHDRSZ + n;
--- 404,409 ----
***************
*** 390,395 ****
--- 411,417 ----
  	memcpy(VARDATA(ret), VARDATA(string) + m, n);
  
  	PG_RETURN_TEXT_P(ret);
+ #endif
  }
  
  /*
***************
*** 740,765 ****
  Datum
  bytea_substr(PG_FUNCTION_ARGS)
  {
- 	bytea	   *string = PG_GETARG_BYTEA_P(0);
  	int32		m = PG_GETARG_INT32(1);
  	int32		n = PG_GETARG_INT32(2);
- 	bytea	   *ret;
- 	int			len;
- 
- 	len = VARSIZE(string) - VARHDRSZ;
  
- 	/* starting position after the end of the string? */
- 	if (m > len)
- 	{
- 		m = 1;
- 		n = 0;
- 	}
- 
  	/*
  	 * starting position before the start of the string? then offset into
  	 * the string per SQL92 spec...
  	 */
! 	else if (m < 1)
  	{
  		n += (m - 1);
  		m = 1;
--- 762,775 ----
  Datum
  bytea_substr(PG_FUNCTION_ARGS)
  {
  	int32		m = PG_GETARG_INT32(1);
  	int32		n = PG_GETARG_INT32(2);
  
  	/*
  	 * starting position before the start of the string? then offset into
  	 * the string per SQL92 spec...
  	 */
! 	if (m < 1)
  	{
  		n += (m - 1);
  		m = 1;
***************
*** 767,781 ****
  
  	/* m will now become a zero-based starting position */
  	m--;
- 	if (((m + n) > len) || (n < 0))
- 		n = (len - m);
- 
- 	ret = (bytea *) palloc(VARHDRSZ + n);
- 	VARATT_SIZEP(ret) = VARHDRSZ + n;
- 
- 	memcpy(VARDATA(ret), VARDATA(string) + m, n);
  
! 	PG_RETURN_BYTEA_P(ret);
  }
  
  /*
--- 777,784 ----
  
  	/* m will now become a zero-based starting position */
  	m--;
  
! 	PG_RETURN_BYTEA_P(PG_GETARG_BYTEA_P_SLICE (0, m, n));
  }
  
  /*
Index: src/backend/utils/fmgr/fmgr.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/utils/fmgr/fmgr.c,v
retrieving revision 1.57
diff -c -r1.57 fmgr.c
*** src/backend/utils/fmgr/fmgr.c	2001/11/05 17:46:30	1.57
--- src/backend/utils/fmgr/fmgr.c	2002/02/22 22:51:45
***************
*** 1520,1522 ****
--- 1520,1529 ----
  		return result;
  	}
  }
+ 
+ struct varlena *
+ pg_detoast_datum_slice(struct varlena * datum, int32 first, int32 count)
+ {
+ 	/* Only get the specified portion from the toast rel */
+ 	return (struct varlena *) heap_tuple_untoast_attr_slice((varattrib *) datum, first, count);
+ }
Index: src/include/fmgr.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/fmgr.h,v
retrieving revision 1.18
diff -c -r1.18 fmgr.h
*** src/include/fmgr.h	2001/11/05 17:46:31	1.18
--- src/include/fmgr.h	2002/02/22 22:51:46
***************
*** 135,145 ****
--- 135,150 ----
   */
  extern struct varlena *pg_detoast_datum(struct varlena * datum);
  extern struct varlena *pg_detoast_datum_copy(struct varlena * datum);
+ extern struct varlena *pg_detoast_datum_slice(struct varlena * datum, 
+ 											  int32 first, int32 count); 
  
  #define PG_DETOAST_DATUM(datum) \
  	pg_detoast_datum((struct varlena *) DatumGetPointer(datum))
  #define PG_DETOAST_DATUM_COPY(datum) \
  	pg_detoast_datum_copy((struct varlena *) DatumGetPointer(datum))
+ #define PG_DETOAST_DATUM_SLICE(datum,f,c) \
+         pg_detoast_datum_slice((struct varlena *) DatumGetPointer(datum), \
+         (int32) f, (int32) c)
  
  /*
   * Support for cleaning up detoasted copies of inputs.	This must only
***************
*** 187,192 ****
--- 192,202 ----
  #define DatumGetTextPCopy(X)		((text *) PG_DETOAST_DATUM_COPY(X))
  #define DatumGetBpCharPCopy(X)		((BpChar *) PG_DETOAST_DATUM_COPY(X))
  #define DatumGetVarCharPCopy(X)		((VarChar *) PG_DETOAST_DATUM_COPY(X))
+ /* Variants which return n bytes starting at pos. m */
+ #define DatumGetByteaPSlice(X,m,n)  ((bytea *) PG_DETOAST_DATUM_SLICE(X,m,n))
+ #define DatumGetTextPSlice(X,m,n)   ((text *) PG_DETOAST_DATUM_SLICE(X,m,n))
+ #define DatumGetBpCharPSlice(X,m,n) ((BpChar *) PG_DETOAST_DATUM_SLICE(X,m,n))
+ #define DatumGetVarCharPSlice(X,m,n) ((VarChar *) PG_DETOAST_DATUM_SLICE(X,m,n))
  /* GETARG macros for varlena types will typically look like this: */
  #define PG_GETARG_BYTEA_P(n)		DatumGetByteaP(PG_GETARG_DATUM(n))
  #define PG_GETARG_TEXT_P(n)			DatumGetTextP(PG_GETARG_DATUM(n))
***************
*** 197,202 ****
--- 207,217 ----
  #define PG_GETARG_TEXT_P_COPY(n)	DatumGetTextPCopy(PG_GETARG_DATUM(n))
  #define PG_GETARG_BPCHAR_P_COPY(n)	DatumGetBpCharPCopy(PG_GETARG_DATUM(n))
  #define PG_GETARG_VARCHAR_P_COPY(n) DatumGetVarCharPCopy(PG_GETARG_DATUM(n))
+ /* And a b-byte slice from position a -also OK to write */
+ #define PG_GETARG_BYTEA_P_SLICE(n,a,b) DatumGetByteaPSlice(PG_GETARG_DATUM(n),a,b)
+ #define PG_GETARG_TEXT_P_SLICE(n,a,b)  DatumGetTextPSlice(PG_GETARG_DATUM(n),a,b)
+ #define PG_GETARG_BPCHAR_P_SLICE(n,a,b) DatumGetBpCharPSlice(PG_GETARG_DATUM(n),a,b)
+ #define PG_GETARG_VARCHAR_P_SLICE(n,a,b) DatumGetVarCharPSlice(PG_GETARG_DATUM(n),a,b)
  
  /* To return a NULL do this: */
  #define PG_RETURN_NULL()  \
Index: src/include/access/tuptoaster.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/access/tuptoaster.h,v
retrieving revision 1.13
diff -c -r1.13 tuptoaster.h
*** src/include/access/tuptoaster.h	2001/11/05 17:46:31	1.13
--- src/include/access/tuptoaster.h	2002/02/22 22:51:46
***************
*** 100,105 ****
--- 100,116 ----
  extern varattrib *heap_tuple_untoast_attr(varattrib *attr);
  
  /* ----------
+  * heap_tuple_untoast_attr_slice() -
+  *
+  *      Fetches only the specified portion of an attribute.
+  *      (Handles all cases for attribute storage)
+  * ----------
+  */
+ extern varattrib *heap_tuple_untoast_attr_slice(varattrib *attr, 
+ 												int32 sliceoffset,
+ 												int32 slicelength);
+ 
+ /* ----------
   * toast_compress_datum -
   *
   *	Create a compressed version of a varlena datum, if possible
Index: src/include/commands/command.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/commands/command.h,v
retrieving revision 1.31
diff -c -r1.31 command.h
*** src/include/commands/command.h	2001/11/05 17:46:33	1.31
--- src/include/commands/command.h	2002/02/22 22:51:47
***************
*** 47,55 ****
  							 bool inh, const char *colName,
  							 Node *newDefault);
  
! extern void AlterTableAlterColumnStatistics(const char *relationName,
  								bool inh, const char *colName,
! 								Node *statsTarget);
  
  extern void AlterTableDropColumn(const char *relationName,
  					 bool inh, const char *colName,
--- 47,55 ----
  							 bool inh, const char *colName,
  							 Node *newDefault);
  
! extern void AlterTableAlterColumnFlags(const char *relationName,
  								bool inh, const char *colName,
! 								Node *flagValue, const char *flagType);
  
  extern void AlterTableDropColumn(const char *relationName,
  					 bool inh, const char *colName,
Index: src/include/nodes/parsenodes.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/nodes/parsenodes.h,v
retrieving revision 1.152
diff -c -r1.152 parsenodes.h
*** src/include/nodes/parsenodes.h	2002/02/18 23:11:41	1.152
--- src/include/nodes/parsenodes.h	2002/02/22 22:51:48
***************
*** 121,126 ****
--- 121,127 ----
  								 *	A = add column
  								 *	T = alter column default
  								 *	S = alter column statistics
+ 								 *  M = alter column storage
  								 *	D = drop column
  								 *	C = add constraint
  								 *	X = drop constraint
