postgresql/src/backend/utils/adt/tid.c
Tom Lane 0144eb92bb Add the full set of comparison functions for type TID, including a btree
opclass.  This is not so much because anyone's likely to create an index
on TID, as that sorting TIDs can be useful.  Also added max and min
aggregates while at it, so that one can investigate the clusteredness of
a table with queries like SELECT min(ctid), max(ctid) FROM tab WHERE ...
Greg Stark and Tom Lane
2006-07-21 20:51:33 +00:00

395 lines
9.3 KiB
C

/*-------------------------------------------------------------------------
*
* tid.c
* Functions for the built-in type tuple id
*
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/tid.c,v 1.54 2006/07/21 20:51:32 tgl Exp $
*
* NOTES
* input routine largely stolen from boxin().
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <math.h>
#include <limits.h>
#include "access/heapam.h"
#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "libpq/pqformat.h"
#include "parser/parsetree.h"
#include "utils/builtins.h"
#define DatumGetItemPointer(X) ((ItemPointer) DatumGetPointer(X))
#define ItemPointerGetDatum(X) PointerGetDatum(X)
#define PG_GETARG_ITEMPOINTER(n) DatumGetItemPointer(PG_GETARG_DATUM(n))
#define PG_RETURN_ITEMPOINTER(x) return ItemPointerGetDatum(x)
#define LDELIM '('
#define RDELIM ')'
#define DELIM ','
#define NTIDARGS 2
/* ----------------------------------------------------------------
* tidin
* ----------------------------------------------------------------
*/
Datum
tidin(PG_FUNCTION_ARGS)
{
char *str = PG_GETARG_CSTRING(0);
char *p,
*coord[NTIDARGS];
int i;
ItemPointer result;
BlockNumber blockNumber;
OffsetNumber offsetNumber;
char *badp;
int hold_offset;
for (i = 0, p = str; *p && i < NTIDARGS && *p != RDELIM; p++)
if (*p == DELIM || (*p == LDELIM && !i))
coord[i++] = p + 1;
if (i < NTIDARGS)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type tid: \"%s\"",
str)));
errno = 0;
blockNumber = strtoul(coord[0], &badp, 10);
if (errno || *badp != DELIM)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type tid: \"%s\"",
str)));
hold_offset = strtol(coord[1], &badp, 10);
if (errno || *badp != RDELIM ||
hold_offset > USHRT_MAX || hold_offset < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type tid: \"%s\"",
str)));
offsetNumber = hold_offset;
result = (ItemPointer) palloc(sizeof(ItemPointerData));
ItemPointerSet(result, blockNumber, offsetNumber);
PG_RETURN_ITEMPOINTER(result);
}
/* ----------------------------------------------------------------
* tidout
* ----------------------------------------------------------------
*/
Datum
tidout(PG_FUNCTION_ARGS)
{
ItemPointer itemPtr = PG_GETARG_ITEMPOINTER(0);
BlockNumber blockNumber;
OffsetNumber offsetNumber;
char buf[32];
blockNumber = BlockIdGetBlockNumber(&(itemPtr->ip_blkid));
offsetNumber = itemPtr->ip_posid;
/* Perhaps someday we should output this as a record. */
snprintf(buf, sizeof(buf), "(%u,%u)", blockNumber, offsetNumber);
PG_RETURN_CSTRING(pstrdup(buf));
}
/*
* tidrecv - converts external binary format to tid
*/
Datum
tidrecv(PG_FUNCTION_ARGS)
{
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
ItemPointer result;
BlockNumber blockNumber;
OffsetNumber offsetNumber;
blockNumber = pq_getmsgint(buf, sizeof(blockNumber));
offsetNumber = pq_getmsgint(buf, sizeof(offsetNumber));
result = (ItemPointer) palloc(sizeof(ItemPointerData));
ItemPointerSet(result, blockNumber, offsetNumber);
PG_RETURN_ITEMPOINTER(result);
}
/*
* tidsend - converts tid to binary format
*/
Datum
tidsend(PG_FUNCTION_ARGS)
{
ItemPointer itemPtr = PG_GETARG_ITEMPOINTER(0);
BlockId blockId;
BlockNumber blockNumber;
OffsetNumber offsetNumber;
StringInfoData buf;
blockId = &(itemPtr->ip_blkid);
blockNumber = BlockIdGetBlockNumber(blockId);
offsetNumber = itemPtr->ip_posid;
pq_begintypsend(&buf);
pq_sendint(&buf, blockNumber, sizeof(blockNumber));
pq_sendint(&buf, offsetNumber, sizeof(offsetNumber));
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}
/*****************************************************************************
* PUBLIC ROUTINES *
*****************************************************************************/
static int32
tid_cmp_internal(ItemPointer arg1, ItemPointer arg2)
{
/*
* Don't use ItemPointerGetBlockNumber or ItemPointerGetOffsetNumber here,
* because they assert ip_posid != 0 which might not be true for a
* user-supplied TID.
*/
BlockNumber b1 = BlockIdGetBlockNumber(&(arg1->ip_blkid));
BlockNumber b2 = BlockIdGetBlockNumber(&(arg2->ip_blkid));
if (b1 < b2)
return -1;
else if (b1 > b2)
return 1;
else if (arg1->ip_posid < arg2->ip_posid)
return -1;
else if (arg1->ip_posid > arg2->ip_posid)
return 1;
else
return 0;
}
Datum
tideq(PG_FUNCTION_ARGS)
{
ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
PG_RETURN_BOOL(tid_cmp_internal(arg1,arg2) == 0);
}
Datum
tidne(PG_FUNCTION_ARGS)
{
ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
PG_RETURN_BOOL(tid_cmp_internal(arg1,arg2) != 0);
}
Datum
tidlt(PG_FUNCTION_ARGS)
{
ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
PG_RETURN_BOOL(tid_cmp_internal(arg1,arg2) < 0);
}
Datum
tidle(PG_FUNCTION_ARGS)
{
ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
PG_RETURN_BOOL(tid_cmp_internal(arg1,arg2) <= 0);
}
Datum
tidgt(PG_FUNCTION_ARGS)
{
ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
PG_RETURN_BOOL(tid_cmp_internal(arg1,arg2) > 0);
}
Datum
tidge(PG_FUNCTION_ARGS)
{
ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
PG_RETURN_BOOL(tid_cmp_internal(arg1,arg2) >= 0);
}
Datum
bttidcmp(PG_FUNCTION_ARGS)
{
ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
PG_RETURN_INT32(tid_cmp_internal(arg1, arg2));
}
Datum
tidlarger(PG_FUNCTION_ARGS)
{
ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
PG_RETURN_ITEMPOINTER(tid_cmp_internal(arg1,arg2) >= 0 ? arg1 : arg2);
}
Datum
tidsmaller(PG_FUNCTION_ARGS)
{
ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
PG_RETURN_ITEMPOINTER(tid_cmp_internal(arg1,arg2) <= 0 ? arg1 : arg2);
}
/*
* Functions to get latest tid of a specified tuple.
*
* Maybe these implementations should be moved to another place
*/
static ItemPointerData Current_last_tid = {{0, 0}, 0};
void
setLastTid(const ItemPointer tid)
{
Current_last_tid = *tid;
}
/*
* Handle CTIDs of views.
* CTID should be defined in the view and it must
* correspond to the CTID of a base relation.
*/
static Datum
currtid_for_view(Relation viewrel, ItemPointer tid)
{
TupleDesc att = RelationGetDescr(viewrel);
RuleLock *rulelock;
RewriteRule *rewrite;
int i,
natts = att->natts,
tididx = -1;
for (i = 0; i < natts; i++)
{
if (strcmp(NameStr(att->attrs[i]->attname), "ctid") == 0)
{
if (att->attrs[i]->atttypid != TIDOID)
elog(ERROR, "ctid isn't of type TID");
tididx = i;
break;
}
}
if (tididx < 0)
elog(ERROR, "currtid cannot handle views with no CTID");
rulelock = viewrel->rd_rules;
if (!rulelock)
elog(ERROR, "the view has no rules");
for (i = 0; i < rulelock->numLocks; i++)
{
rewrite = rulelock->rules[i];
if (rewrite->event == CMD_SELECT)
{
Query *query;
TargetEntry *tle;
if (list_length(rewrite->actions) != 1)
elog(ERROR, "only one select rule is allowed in views");
query = (Query *) linitial(rewrite->actions);
tle = get_tle_by_resno(query->targetList, tididx + 1);
if (tle && tle->expr && IsA(tle->expr, Var))
{
Var *var = (Var *) tle->expr;
RangeTblEntry *rte;
if (var->varno > 0 && var->varno < INNER &&
var->varattno == SelfItemPointerAttributeNumber)
{
rte = rt_fetch(var->varno, query->rtable);
if (rte)
{
heap_close(viewrel, AccessShareLock);
return DirectFunctionCall2(currtid_byreloid, ObjectIdGetDatum(rte->relid), PointerGetDatum(tid));
}
}
}
break;
}
}
elog(ERROR, "currtid cannot handle this view");
return (Datum) 0;
}
Datum
currtid_byreloid(PG_FUNCTION_ARGS)
{
Oid reloid = PG_GETARG_OID(0);
ItemPointer tid = PG_GETARG_ITEMPOINTER(1);
ItemPointer result;
Relation rel;
result = (ItemPointer) palloc(sizeof(ItemPointerData));
if (!reloid)
{
*result = Current_last_tid;
PG_RETURN_ITEMPOINTER(result);
}
rel = heap_open(reloid, AccessShareLock);
if (rel->rd_rel->relkind == RELKIND_VIEW)
return currtid_for_view(rel, tid);
ItemPointerCopy(tid, result);
heap_get_latest_tid(rel, SnapshotNow, result);
heap_close(rel, AccessShareLock);
PG_RETURN_ITEMPOINTER(result);
}
Datum
currtid_byrelname(PG_FUNCTION_ARGS)
{
text *relname = PG_GETARG_TEXT_P(0);
ItemPointer tid = PG_GETARG_ITEMPOINTER(1);
ItemPointer result;
RangeVar *relrv;
Relation rel;
relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
rel = heap_openrv(relrv, AccessShareLock);
if (rel->rd_rel->relkind == RELKIND_VIEW)
return currtid_for_view(rel, tid);
result = (ItemPointer) palloc(sizeof(ItemPointerData));
ItemPointerCopy(tid, result);
heap_get_latest_tid(rel, SnapshotNow, result);
heap_close(rel, AccessShareLock);
PG_RETURN_ITEMPOINTER(result);
}