Index: doc/src/sgml/xfunc.sgml =================================================================== RCS file: /projects/cvsroot/pgsql/doc/src/sgml/xfunc.sgml,v retrieving revision 1.38 diff -c -r1.38 xfunc.sgml *** doc/src/sgml/xfunc.sgml 2001/09/16 16:11:09 1.38 --- doc/src/sgml/xfunc.sgml 2001/10/15 22:50:02 *************** *** 1137,1142 **** --- 1137,1171 ---- + Other options provided in the new-style interface are two + variants of the + PG_GETARG_xxx() + macros. The first of these, + PG_GETARG_xxx_COPY() + 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 + PG_GETARG_xxx_COPY() + macros guarantees a writable result.) + + + + The second variant consists of the + PG_GETARG_xxx_SLICE() + 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 + ALTER TABLE tablename ALTER + COLUMN colname SET STORAGE + storagetype. Storage type is one of + plain, external, extended or main.) + + + The version-1 function call conventions make it possible to return set 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.29 diff -c -r1.29 alter_table.sgml *** doc/src/sgml/ref/alter_table.sgml 2001/10/12 00:07:14 1.29 --- doc/src/sgml/ref/alter_table.sgml 2001/10/15 22:50:02 *************** *** 31,36 **** --- 31,38 ---- ALTER TABLE [ ONLY ] table [ * ] ALTER [ COLUMN ] column SET STATISTICS integer ALTER TABLE [ ONLY ] table [ * ] + ALTER [ COLUMN ] column SET STORAGE {PLAIN | EXTERNAL | EXTENDED | MAIN} + ALTER TABLE [ ONLY ] table [ * ] RENAME [ COLUMN ] column TO newcolumn ALTER TABLE table *************** *** 170,175 **** --- 172,187 ---- The ALTER COLUMN SET STATISTICS form allows you to set the statistics-gathering target for subsequent operations. + The ALTER COLUMN SET STORAGE 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. PLAIN must be used + for fixed-length values such as INTEGER and is + inline, uncompressed. MAIN is for inline, + compressible data. EXTERNAL is for external, + uncompressed data and EXTENDED is for external, + compressed data. The use of EXTERNAL will make + substring and slicing operations on a column faster. The RENAME clause causes the name of a table or column to change without changing any of the data contained in the affected table. Thus, the table or column will Index: src/backend/access/heap/tuptoaster.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/access/heap/tuptoaster.c,v retrieving revision 1.24 diff -c -r1.24 tuptoaster.c *** src/backend/access/heap/tuptoaster.c 2001/08/10 18:57:33 1.24 --- src/backend/access/heap/tuptoaster.c 2001/10/15 22:50:04 *************** *** 47,54 **** --- 47,57 ---- 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); + /* ---------- * heap_tuple_toast_attrs - * *************** *** 165,171 **** --- 168,248 ---- return result; } + /* ---------- + * 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 - * *************** *** 1097,1100 **** --- 1174,1385 ---- } + /* ---------- + * 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; + int numchunks; + int startchunk; + int endchunk; + int32 startoffset; + int32 endoffset; + int totalchunks; + Pointer chunk; + bool isnull; + int32 chunksize; + int32 chcpystrt; + int32 chcpyend; + + char *chunks_found; + char *chunks_expected; + + 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; + + chunks_found = palloc(numchunks); + chunks_expected = palloc(numchunks); + memset(chunks_found, 0, numchunks); + memset(chunks_expected, 1, numchunks); + + /* + * 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 + * + * Note we will not necessarily see the chunks in sequence-number order. + */ + toastscan = index_beginscan(toastidx, false, nscankeys, &toastkey[0]); + while ((indexRes = index_getnext(toastscan, ForwardScanDirection)) != NULL) + { + toasttup.t_self = indexRes->heap_iptr; + heap_fetch(toastrel, SnapshotAny, &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 < startchunk || residx > endchunk) + elog(ERROR, "unexpected chunk number %d for toast value %u", + residx, + 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); + } + if (chunks_found[residx - startchunk]++ > 0) + elog(ERROR, "chunk %d for toast value %u appears multiple times", + 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); + } + + /* + * Final checks that we successfully fetched the datum + */ + if (memcmp(chunks_found, chunks_expected, numchunks) != 0) + elog(ERROR, "not all toast chunks found for value %u", + attr->va_content.va_external.va_valueid); + pfree(chunks_expected); + pfree(chunks_found); + + /* + * End scan and close relations + */ + index_endscan(toastscan); + index_close(toastidx); + heap_close(toastrel, AccessShareLock); + + return result; + } + + #endif /* TUPLE_TOASTER_ACTIVE */ + + + Index: src/backend/commands/command.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/commands/command.c,v retrieving revision 1.144 diff -c -r1.144 command.c *** src/backend/commands/command.c 2001/10/12 00:07:14 1.144 --- src/backend/commands/command.c 2001/10/15 22:50:06 *************** *** 56,63 **** static void drop_default(Oid relid, int16 attnum); static bool needs_toast_table(Relation rel); static bool is_relation(char *name); - /* -------------------------------- * PortalCleanup * -------------------------------- --- 56,63 ---- static void drop_default(Oid relid, int16 attnum); static bool needs_toast_table(Relation rel); static bool is_relation(char *name); + static Oid AlterTableColumnSetup(const char *relationName, int system_ok); /* -------------------------------- * PortalCleanup * -------------------------------- *************** *** 262,268 **** --- 262,348 ---- PortalDrop(portal); } + /* + * ALTER TABLE ALTER|ADD COLUMN support routines + * + */ + + /* ---------------- + * AlterTableColumnSetup + * 1. Checks that the operation is OK for the specified column. + * (not a system table*, owner ok, is a table) + * 2. Get an exclusive lock on the Relation + * 3. Returns OID of relation + * + * *if system_ok == 1, this check is skipped. + * (statistics can be changed on system tables) + * ---------------- + */ + + Oid + AlterTableColumnSetup(const char *relationName, int system_ok) + { + Relation rel; + Oid myrelid; + + /* + * permissions checking. this would normally be done in utility.c, + * but the ALTER COLUMN etc. routines are recursive. + * + * normally, only the owner of a class can change its schema. + */ + if (!allowSystemTableMods && IsSystemRelationName(relationName) + && !system_ok) + elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", + relationName); + + if (!pg_ownercheck(GetUserId(), relationName, RELNAME)) + elog(ERROR, "ALTER TABLE: permission denied"); + + /* + * Grab an exclusive lock on the target table, which we will NOT + * release until end of transaction. + */ + rel = heap_openr(relationName, AccessExclusiveLock); + + if (rel->rd_rel->relkind != RELKIND_RELATION) + elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", + relationName); + + myrelid = RelationGetRelid(rel); + heap_close(rel, NoLock); /* close rel but keep lock! */ + return myrelid; + } + /* ---------------- + * RecurseOverChildren, RecurseOverChildrenEnd + * Support macros to bracket code to be executed over all the children + * of relation relid. childrelname is set successively to each child name. + * ---------------- + */ + + #define RecurseOverChildren(relid) \ + { \ + List *child, \ + *children; \ + Relation rel; \ + children = find_all_inheritors( relid ); \ + foreach(child, children) \ + { \ + Oid childrelid = lfirsti(child); \ + char *childrelname; \ + \ + if (childrelid == relid ) \ + continue; \ + rel = heap_open(childrelid, AccessExclusiveLock); \ + childrelname = pstrdup(RelationGetRelationName(rel)); \ + heap_close(rel, AccessExclusiveLock); + + #define RecurseOverChildrenEnd pfree(childrelname); } } + + + + /* ---------------- * AlterTableAddColumn * (formerly known as PerformAddAttribute) * *************** *** 314,343 **** char *typename; int attndims; ! /* ! * permissions checking. this would normally be done in utility.c, ! * but this particular routine is recursive. ! * ! * normally, only the owner of a class can change its schema. ! */ ! if (!allowSystemTableMods && IsSystemRelationName(relationName)) ! elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog", ! relationName); ! if (!pg_ownercheck(GetUserId(), relationName, RELNAME)) ! elog(ERROR, "ALTER TABLE: permission denied"); /* ! * Grab an exclusive lock on the target table, which we will NOT ! * release until end of transaction. */ ! rel = heap_openr(relationName, AccessExclusiveLock); ! if (rel->rd_rel->relkind != RELKIND_RELATION) ! elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", ! relationName); - myrelid = RelationGetRelid(rel); - heap_close(rel, NoLock); /* close rel but keep lock! */ /* * Recurse to add the column to child classes, if requested. --- 394,412 ---- char *typename; int attndims; ! /* Standard setup */ ! ! myrelid = AlterTableColumnSetup(relationName,0); /* ! * we can't add a not null attribute */ ! if (colDef->is_not_null) ! elog(ERROR, "Can't add a NOT NULL attribute to an existing relation"); ! if (colDef->raw_default || colDef->cooked_default) ! elog(ERROR, "Adding columns with defaults is not implemented."); /* * Recurse to add the column to child classes, if requested. *************** *** 348,379 **** */ if (inherits) { ! List *child, ! *children; ! ! /* this routine is actually in the planner */ ! children = find_all_inheritors(myrelid); ! ! /* ! * find_all_inheritors does the recursive search of the ! * inheritance hierarchy, so all we have to do is process all ! * of the relids in the list that it returns. ! */ ! foreach(child, children) ! { ! Oid childrelid = lfirsti(child); ! char *childrelname; ! ! if (childrelid == myrelid) ! continue; ! rel = heap_open(childrelid, AccessExclusiveLock); ! childrelname = pstrdup(RelationGetRelationName(rel)); ! heap_close(rel, AccessExclusiveLock); ! ! AlterTableAddColumn(childrelname, false, colDef); ! ! pfree(childrelname); ! } } /* --- 417,425 ---- */ if (inherits) { ! RecurseOverChildren(myrelid); /* macro */ ! AlterTableAddColumn(childrelname, false, colDef); ! RecurseOverChildrenEnd ; /* macro */ } /* *************** *** 397,403 **** elog(ERROR, "Adding NOT NULL columns is not implemented." "\n\tAdd the column, then use ALTER TABLE ADD CONSTRAINT."); - rel = heap_openr(RelationRelationName, RowExclusiveLock); reltup = SearchSysCache(RELNAME, --- 443,448 ---- *************** *** 543,590 **** int16 attnum; Oid myrelid; ! if (!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"); ! #endif ! ! rel = heap_openr(relationName, AccessExclusiveLock); ! if (rel->rd_rel->relkind != RELKIND_RELATION) ! elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", ! relationName); ! myrelid = RelationGetRelid(rel); ! heap_close(rel, NoLock); /* * Propagate to children if desired */ if (inh) { ! List *child, ! *children; ! ! /* this routine is actually in the planner */ ! children = find_all_inheritors(myrelid); ! ! /* ! * find_all_inheritors does the recursive search of the ! * inheritance hierarchy, so all we have to do is process all of ! * the relids in the list that it returns. ! */ ! foreach(child, children) ! { ! Oid childrelid = lfirsti(child); ! ! if (childrelid == myrelid) ! continue; ! rel = heap_open(childrelid, AccessExclusiveLock); ! AlterTableAlterColumnDefault(RelationGetRelationName(rel), ! false, colName, newDefault); ! heap_close(rel, AccessExclusiveLock); ! } } /* -= now do the thing on this relation =- */ --- 588,603 ---- int16 attnum; Oid myrelid; ! myrelid = AlterTableColumnSetup(relationName, 0); /* * Propagate to children if desired */ if (inh) { ! RecurseOverChildren(myrelid) ; ! AlterTableAlterColumnDefault(childrelname, false, colName, newDefault); ! RecurseOverChildrenEnd ; } /* -= now do the thing on this relation =- */ *************** *** 700,770 **** /* ! * 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; ! #ifndef NO_SECURITY ! if (!pg_ownercheck(GetUserId(), relationName, RELNAME)) ! elog(ERROR, "ALTER TABLE: permission denied"); ! #endif ! rel = heap_openr(relationName, AccessExclusiveLock); ! if (rel->rd_rel->relkind != RELKIND_RELATION) ! elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", ! relationName); ! myrelid = RelationGetRelid(rel); ! heap_close(rel, NoLock); /* close rel, but keep lock! */ /* * Propagate to children if desired */ if (inh) { ! List *child, ! *children; ! ! /* this routine is actually in the planner */ ! children = find_all_inheritors(myrelid); ! ! /* ! * find_all_inheritors does the recursive search of the ! * inheritance hierarchy, so all we have to do is process all of ! * the relids in the list that it returns. ! */ ! foreach(child, children) ! { ! Oid childrelid = lfirsti(child); ! ! 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, --- 713,783 ---- /* ! * ALTER TABLE ALTER COLUMN SET STATISTICS / STORAGE */ void ! AlterTableAlterColumnFlags(const char *relationName, ! bool inh, const char *colName, ! Node *flagValue, ! const char *flagType) { Oid myrelid; ! int newtarget = 0; ! char *storagemode; ! char newstorage = 'x'; Relation attrelation; HeapTuple tuple; ! /* Allow statistics changes on system tables */ ! myrelid = AlterTableColumnSetup(relationName, (*flagType == 'S')); ! /* 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, "AlterTable: \"%s\" storage not recognized", ! storagemode); ! } ! else ! { ! elog(ERROR,"ALTER TABLE: Invalid column flag: %c", (int) *flagType); ! } /* * Propagate to children if desired */ if (inh) { ! RecurseOverChildren(myrelid); ! AlterTableAlterColumnFlags(childrelname, ! false, colName, flagValue, ! flagType); ! RecurseOverChildrenEnd; } /* -= now do the thing on this relation =- */ attrelation = heap_openr(AttributeRelationName, RowExclusiveLock); tuple = SearchSysCacheCopy(ATTNAME, *************** *** 779,785 **** 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); --- 792,814 ---- 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); *************** *** 1040,1072 **** if (inh) elog(ERROR, "ALTER TABLE / DROP COLUMN with inherit option is not supported yet"); ! /* ! * permissions checking. this would normally be done in utility.c, ! * but this particular routine is recursive. ! * ! * normally, only the owner of a class can change its schema. ! */ ! if (!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"); ! #endif ! ! /* ! * Grab an exclusive lock on the target table, which we will NOT ! * release until end of transaction. ! */ ! rel = heap_openr(relationName, AccessExclusiveLock); ! ! if (rel->rd_rel->relkind != RELKIND_RELATION) ! elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table", ! relationName); - myrelid = RelationGetRelid(rel); - heap_close(rel, NoLock); /* close rel but keep lock! */ - /* * What to do when rel has inheritors ? */ --- 1069,1076 ---- if (inh) elog(ERROR, "ALTER TABLE / DROP COLUMN with inherit option is not supported yet"); ! myrelid = AlterTableColumnSetup(relationName,0); /* * What to do when rel has inheritors ? */ *************** *** 1336,1344 **** * the constraint against tuples already in * the table. */ AddRelationRawConstraints(rel, NIL, makeList1(constr)); ! break; } case CONSTR_UNIQUE: --- 1340,1349 ---- * the constraint against tuples already in * the table. */ + AddRelationRawConstraints(rel, NIL, makeList1(constr)); ! break; } case CONSTR_UNIQUE: Index: src/backend/parser/gram.y =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/parser/gram.y,v retrieving revision 2.262 diff -c -r2.262 gram.y *** src/backend/parser/gram.y 2001/10/10 00:02:42 2.262 --- src/backend/parser/gram.y 2001/10/15 22:50:12 *************** *** 353,359 **** 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 --- 353,359 ---- 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 *************** *** 1027,1032 **** --- 1027,1043 ---- n->def = (Node *) makeInteger($9); $$ = (Node *)n; } + /* ALTER TABLE ALTER [COLUMN] SET STORAGE */ + | 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 DROP [COLUMN] {RESTRICT|CASCADE} */ | ALTER TABLE relation_expr DROP opt_column ColId drop_behavior { *************** *** 5732,5737 **** --- 5743,5749 ---- | 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.99 diff -c -r1.99 keywords.c *** src/backend/parser/keywords.c 2001/10/10 00:02:42 1.99 --- src/backend/parser/keywords.c 2001/10/15 22:50:12 *************** *** 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.120 diff -c -r1.120 utility.c *** src/backend/tcop/utility.c 2001/10/12 00:07:14 1.120 --- src/backend/tcop/utility.c 2001/10/15 22:50:14 *************** *** 435,445 **** stmt->name, 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, --- 435,446 ---- stmt->name, stmt->def); break; ! case 'S': case 'M': /* ALTER COLUMN STATISTICS/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.73 diff -c -r1.73 varlena.c *** src/backend/utils/adt/varlena.c 2001/09/14 17:46:40 1.73 --- src/backend/utils/adt/varlena.c 2001/10/15 22:50:15 *************** *** 355,404 **** * 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); --- 355,434 ---- * 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 2001-10-10 */ Datum text_substr(PG_FUNCTION_ARGS) { ! text *string; int32 m = PG_GETARG_INT32(1); int32 n = PG_GETARG_INT32(2); ! int eml = 1; ! int32 sm; ! int32 sn; #ifdef MULTIBYTE int i; char *p; + text *ret; + int len; + #endif MULTIBYTE ! /* ! * starting position before the start of the string? then offset into ! * the string per SQL92 spec... ! */ ! ! if (m < 1) ! { ! n += (m - 1); ! m = 1; ! } ! /* Checks for m > octet length are made by toast routines */ ! sm = m - 1; /* m is 1-based, access routine is 0-based */ ! sn = n; ! #ifdef MULTIBYTE ! eml = pg_database_encoding_max_length(); ! ! if (eml > 1) { ! sm = 0; ! sn = (m + n)*eml + 3; ! /* +3 to avoid problems with mb chars which overhang the slice end*/ } + #endif ! string = PG_GETARG_TEXT_P_SLICE(0,sm,sn); ! ! if (eml == 1) /* Single byte representation */ { ! PG_RETURN_TEXT_P(string); ! } ! ! #ifndef MULTIBYTE ! PG_RETURN_NULL(); /* notreached: just to remove compiler warning */ ! #endif ! ! /* Remainder only used in multibyte ("true multibyte") case. */ ! ! #ifdef MULTIBYTE ! ! len = pg_mbstrlen_with_len(VARDATA(string), sn - 3); ! ! if (m > len) { m = 1; + n = 0; } /* m will now become a zero-based starting position */ + m--; if (((m + n) > len) || (n < 0)) n = (len - m); p = VARDATA(string); for (i = 0; i < m; i++) p += pg_mblen(p); *************** *** 406,412 **** 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; --- 436,441 ---- *************** *** 414,419 **** --- 443,450 ---- memcpy(VARDATA(ret), VARDATA(string) + m, n); PG_RETURN_TEXT_P(ret); + #endif + } /* *************** *** 765,790 **** 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; --- 796,809 ---- 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; *************** *** 792,806 **** /* 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); } /* --- 811,818 ---- /* m will now become a zero-based starting position */ m--; ! PG_RETURN_BYTEA_P(PG_GETARG_BYTEA_P_SLICE(0,m,n)); } /* *************** *** 863,869 **** len = VARSIZE(v) - VARHDRSZ; ! if (n < 0 || n >= len) elog(ERROR, "byteaGetByte: index %d out of range [0..%d]", n, len - 1); --- 875,881 ---- len = VARSIZE(v) - VARHDRSZ; ! if (n < 0 || n > len) elog(ERROR, "byteaGetByte: index %d out of range [0..%d]", n, len - 1); *************** *** 1218,1220 **** --- 1230,1235 ---- PG_RETURN_INT32(cmp); } + + + Index: src/backend/utils/fmgr/fmgr.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/utils/fmgr/fmgr.c,v retrieving revision 1.55 diff -c -r1.55 fmgr.c *** src/backend/utils/fmgr/fmgr.c 2001/10/06 23:21:44 1.55 --- src/backend/utils/fmgr/fmgr.c 2001/10/15 22:50:16 *************** *** 1519,1521 **** --- 1519,1528 ---- return result; } } + + struct varlena * + pg_detoast_datum_slice(struct varlena * datum, int32 first, int32 count) + { + /* Only get the specified proportion 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.15 diff -c -r1.15 fmgr.h *** src/include/fmgr.h 2001/10/06 23:21:44 1.15 --- src/include/fmgr.h 2001/10/15 22:50:18 *************** *** 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)) + /* Variants which return a b-byte slice from a(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.11 diff -c -r1.11 tuptoaster.h *** src/include/access/tuptoaster.h 2001/05/07 00:43:24 1.11 --- src/include/access/tuptoaster.h 2001/10/15 22:50:18 *************** *** 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.28 diff -c -r1.28 command.h *** src/include/commands/command.h 2001/10/12 00:07:15 1.28 --- src/include/commands/command.h 2001/10/15 22:50:18 *************** *** 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,56 ---- 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.146 diff -c -r1.146 parsenodes.h *** src/include/nodes/parsenodes.h 2001/10/12 00:07:15 1.146 --- src/include/nodes/parsenodes.h 2001/10/15 22:50:20 *************** *** 121,126 **** --- 121,127 ---- * A = add column * T = alter column default * S = alter column statistics + * M = alter column storage (TOAST) mode * D = drop column * C = add constraint * X = drop constraint