Am 16.12.2016 um 07:17 schrieb David Fetter:
On Wed, Dec 07, 2016 at 03:57:33PM +0100, Peter Moser wrote:
Am 05.12.2016 um 06:11 schrieb Haribabu Kommi:
On Tue, Oct 25, 2016 at 8:44 PM, Peter Moser <pitiz...@gmail.com
<mailto:pitiz...@gmail.com>> wrote:
We decided to follow your recommendation and add the patch to the
commitfest.
Path is not applying properly to HEAD.
Moved to next CF with "waiting on author" status.
We updated our patch. We tested it with the latest
commit dfe530a09226a9de80f2b4c3d5f667bf51481c49.
This looks neat, but it no longer applies to master. Is a rebase in
the offing?
We rebased our patch on top of HEAD, that is, commit
93513d1b6559b2d0805f0b02d312ee550e3d010b.
Best regards,
Anton, Johann, Michael, Peter
diff --git src/backend/commands/explain.c src/backend/commands/explain.c
index 0a669d9..09406d4 100644
--- src/backend/commands/explain.c
+++ src/backend/commands/explain.c
@@ -875,6 +875,12 @@ ExplainNode(PlanState *planstate, List *ancestors,
case T_SeqScan:
pname = sname = "Seq Scan";
break;
+ case T_TemporalAdjustment:
+ if(((TemporalAdjustment *) plan)->temporalCl->temporalType == TEMPORAL_TYPE_ALIGNER)
+ pname = sname = "Adjustment(for ALIGN)";
+ else
+ pname = sname = "Adjustment(for NORMALIZE)";
+ break;
case T_SampleScan:
pname = sname = "Sample Scan";
break;
diff --git src/backend/executor/Makefile src/backend/executor/Makefile
index 51edd4c..42801d3 100644
--- src/backend/executor/Makefile
+++ src/backend/executor/Makefile
@@ -25,6 +25,8 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execIndexing.o execJunk.o \
nodeSamplescan.o nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \
nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \
nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
- nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o
+ nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o \
+ nodeTemporalAdjustment.o
+
include $(top_srcdir)/src/backend/common.mk
diff --git src/backend/executor/execProcnode.c src/backend/executor/execProcnode.c
index 554244f..610d753 100644
--- src/backend/executor/execProcnode.c
+++ src/backend/executor/execProcnode.c
@@ -114,6 +114,7 @@
#include "executor/nodeValuesscan.h"
#include "executor/nodeWindowAgg.h"
#include "executor/nodeWorktablescan.h"
+#include "executor/nodeTemporalAdjustment.h"
#include "nodes/nodeFuncs.h"
#include "miscadmin.h"
@@ -334,6 +335,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
estate, eflags);
break;
+ case T_TemporalAdjustment:
+ result = (PlanState *) ExecInitTemporalAdjustment((TemporalAdjustment *) node,
+ estate, eflags);
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
result = NULL; /* keep compiler quiet */
@@ -531,6 +537,10 @@ ExecProcNode(PlanState *node)
result = ExecLimit((LimitState *) node);
break;
+ case T_TemporalAdjustmentState:
+ result = ExecTemporalAdjustment((TemporalAdjustmentState *) node);
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
result = NULL;
@@ -779,6 +789,10 @@ ExecEndNode(PlanState *node)
ExecEndLimit((LimitState *) node);
break;
+ case T_TemporalAdjustmentState:
+ ExecEndTemporalAdjustment((TemporalAdjustmentState *) node);
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
break;
@@ -812,3 +826,4 @@ ExecShutdownNode(PlanState *node)
return planstate_tree_walker(node, ExecShutdownNode, NULL);
}
+
diff --git src/backend/executor/nodeTemporalAdjustment.c src/backend/executor/nodeTemporalAdjustment.c
new file mode 100644
index 0000000..95d58a3
--- /dev/null
+++ src/backend/executor/nodeTemporalAdjustment.c
@@ -0,0 +1,537 @@
+#include "postgres.h"
+#include "executor/executor.h"
+#include "executor/nodeTemporalAdjustment.h"
+#include "utils/memutils.h"
+#include "access/htup_details.h" /* for heap_getattr */
+#include "utils/lsyscache.h"
+#include "nodes/print.h" /* for print_slot */
+#include "utils/datum.h" /* for datumCopy */
+
+/*
+ * #define TEMPORAL_DEBUG
+ * XXX PEMOSER Maybe we should use execdebug.h stuff here?
+ */
+#ifdef TEMPORAL_DEBUG
+static char*
+datumToString(Oid typeinfo, Datum attr)
+{
+ Oid typoutput;
+ bool typisvarlena;
+ getTypeOutputInfo(typeinfo, &typoutput, &typisvarlena);
+ return OidOutputFunctionCall(typoutput, attr);
+}
+
+#define TPGdebug(...) { printf(__VA_ARGS__); printf("\n"); fflush(stdout); }
+#define TPGdebugDatum(attr, typeinfo) TPGdebug("%s = %s %ld\n", #attr, datumToString(typeinfo, attr), attr)
+#define TPGdebugSlot(slot) { printf("Printing Slot '%s'\n", #slot); print_slot(slot); fflush(stdout); }
+
+#else
+#define datumToString(typeinfo, attr)
+#define TPGdebug(...)
+#define TPGdebugDatum(attr, typeinfo)
+#define TPGdebugSlot(slot)
+#endif
+
+/*
+ * isLessThan
+ * We must check if the sweepline is before a timepoint, or if a timepoint
+ * is smaller than another. We initialize the function call info during
+ * ExecInit phase.
+ */
+static bool
+isLessThan(Datum a, Datum b, TemporalAdjustmentState* node)
+{
+ node->ltFuncCallInfo.arg[0] = a;
+ node->ltFuncCallInfo.arg[1] = b;
+ node->ltFuncCallInfo.argnull[0] = false;
+ node->ltFuncCallInfo.argnull[1] = false;
+
+ /* Return value is never null, due to the pre-defined sub-query output */
+ return DatumGetBool(FunctionCallInvoke(&node->ltFuncCallInfo));
+}
+
+/*
+ * isEqual
+ * We must check if two timepoints are equal. We initialize the function
+ * call info during ExecInit phase.
+ */
+static bool
+isEqual(Datum a, Datum b, TemporalAdjustmentState* node)
+{
+ node->eqFuncCallInfo.arg[0] = a;
+ node->eqFuncCallInfo.arg[1] = b;
+ node->eqFuncCallInfo.argnull[0] = false;
+ node->eqFuncCallInfo.argnull[1] = false;
+
+ /* Return value is never null, due to the pre-defined sub-query output */
+ return DatumGetBool(FunctionCallInvoke(&node->eqFuncCallInfo));
+}
+
+/*
+ * makeRange
+ * We split range types into two scalar boundary values (i.e., upper and
+ * lower bound). Due to this splitting, we can keep a single version of
+ * the algorithm with for two separate boundaries. However, we must combine
+ * these two scalars at the end to return the same datatypes as we got for
+ * the input. The drawback of this approach is that we loose boundary types
+ * here, i.e., we do not know if a bound was inclusive or exclusive. We
+ * initialize the function call info during ExecInit phase.
+ */
+static Datum
+makeRange(Datum l, Datum u, TemporalAdjustmentState* node)
+{
+ node->rcFuncCallInfo.arg[0] = l;
+ node->rcFuncCallInfo.arg[1] = u;
+ node->rcFuncCallInfo.argnull[0] = false;
+ node->rcFuncCallInfo.argnull[1] = false;
+
+ /* Return value is never null, due to the pre-defined sub-query output */
+ return FunctionCallInvoke(&node->rcFuncCallInfo);
+}
+
+/*
+ * temporalAdjustmentStoreTuple
+ * While we store result tuples, we must add the newly calculated temporal
+ * boundaries as two scalar fields or create a single range-typed field
+ * with the two given boundaries.
+ */
+static void
+temporalAdjustmentStoreTuple(TemporalAdjustmentState* node,
+ TupleTableSlot* slotToModify,
+ TupleTableSlot* slotToStoreIn,
+ Datum newTs,
+ Datum newTe)
+{
+ MemoryContext oldContext;
+ HeapTuple t;
+
+ node->newValues[node->temporalCl->attNumTs - 1] = newTs;
+ node->newValues[node->temporalCl->attNumTe - 1] = newTe;
+ if(node->temporalCl->attNumTr != -1)
+ node->newValues[node->temporalCl->attNumTr - 1] = makeRange(newTs,
+ newTe,
+ node);
+
+ oldContext = MemoryContextSwitchTo(node->ss.ps.ps_ResultTupleSlot->tts_mcxt);
+ t = heap_modify_tuple(slotToModify->tts_tuple,
+ slotToModify->tts_tupleDescriptor,
+ node->newValues,
+ node->nullMask,
+ node->tsteMask);
+ MemoryContextSwitchTo(oldContext);
+ slotToStoreIn = ExecStoreTuple(t, slotToStoreIn, InvalidBuffer, true);
+
+ TPGdebug("Storing tuple:");
+ TPGdebugSlot(slotToStoreIn);
+}
+
+/*
+ * slotGetAttrNotNull
+ * Same as slot_getattr, but throws an error if NULL is returned.
+ */
+static Datum
+slotGetAttrNotNull(TupleTableSlot *slot, int attnum)
+{
+ bool isNull;
+ Datum result;
+
+ result = slot_getattr(slot, attnum, &isNull);
+
+ if(isNull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NOT_NULL_VIOLATION),
+ errmsg("Attribute \"%s\" at position %d is null. Temporal " \
+ "adjustment not possible.",
+ NameStr(slot->tts_tupleDescriptor->attrs[attnum - 1]->attname),
+ attnum)));
+
+ return result;
+}
+
+/*
+ * heapGetAttrNotNull
+ * Same as heap_getattr, but throws an error if NULL is returned.
+ */
+static Datum
+heapGetAttrNotNull(TupleTableSlot *slot, int attnum)
+{
+ bool isNull;
+ Datum result;
+
+ result = heap_getattr(slot->tts_tuple,
+ attnum,
+ slot->tts_tupleDescriptor,
+ &isNull);
+ if(isNull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NOT_NULL_VIOLATION),
+ errmsg("Attribute \"%s\" at position %d is null. Temporal " \
+ "adjustment not possible.",
+ NameStr(slot->tts_tupleDescriptor->attrs[attnum - 1]->attname),
+ attnum)));
+
+ return result;
+}
+
+#define setSweepline(datum) \
+ node->sweepline = datumCopy(datum, node->datumFormat->attbyval, node->datumFormat->attlen)
+
+#define freeSweepline() \
+ if (! node->datumFormat->attbyval) pfree(DatumGetPointer(node->sweepline))
+
+/*
+ * ExecTemporalAdjustment
+ *
+ * At this point we get an input, which is splitted into so-called temporal
+ * groups. Each of these groups satisfy the theta-condition (see below), has
+ * overlapping periods, and a row number as ID. The input is ordered by temporal
+ * group ID, and the start and ending timepoints, i.e., P1 and P2. Temporal
+ * normalizers do not make a distinction between start and end timepoints while
+ * grouping, therefore we have only one timepoint attribute there (i.e., P1),
+ * which is the union of start and end timepoints.
+ *
+ * This executor function implements both temporal primitives, namely temporal
+ * aligner and temporal normalizer. We keep a sweep line which starts from
+ * the lowest start point, and proceeds to the right. Please note, that
+ * both algorithms need a different input to work.
+ *
+ * (1) TEMPORAL ALIGNER
+ * Temporal aligners are used to build temporal joins. The general idea of
+ * alignment is to split each tuple of its right argument r with respect to
+ * each tuple in the group of tuples in the left argument s that satisfies
+ * theta, and has overlapping timestamp intervals.
+ *
+ * Example:
+ * ... FROM (r ALIGN s ON theta WITH (r.ts, r.te, s.ts, s.te)) x
+ *
+ * Input: x(r_1, ..., r_n, RN, P1, P2)
+ * where r_1,...,r_n are all attributes from relation r. Two of these
+ * attributes are temporal boundaries, namely TS and TE. The interval
+ * [TS,TE) represents the VALID TIME of each tuple. RN is the
+ * temporal group ID or row number, P1 is the greatest starting
+ * timepoint, and P2 is the least ending timepoint of corresponding
+ * temporal attributes of the relations r and s. The interval [P1,P2)
+ * holds the already computed intersection between r- and s-tuples.
+ *
+ * (2) TEMPORAL NORMALIZER
+ * Temporal normalizers are used to build temporal set operations,
+ * temporal aggregations, and temporal projections (i.e., DISTINCT).
+ * The general idea of normalization is to split each tuple in r with
+ * respect to the group of tuples in s that match on the grouping
+ * attributes in B (i.e., the USING clause, which can also be empty, or
+ * contain more than one attribute). In addition, also non-equality
+ * comparisons can be made by substituting USING with "ON theta".
+ *
+ * Example:
+ * ... FROM (r NORMALIZE s USING(B) WITH (r.ts, r.te, s.ts, s.te)) x
+ * or
+ * ... FROM (r NORMALIZE s ON theta WITH (r.ts, r.te, s.ts, s.te)) x
+ *
+ * Input: x(r_1, ..., r_n, RN, P1)
+ * where r_1,...,r_n are all attributes from relation r. Two of these
+ * attributes are temporal boundaries, namely TS and TE. The interval
+ * [TS,TE) represents the VALID TIME of each tuple. RN is the
+ * temporal group ID or row number, and P1 is union of both
+ * timepoints TS and TE of relation s.
+ */
+TupleTableSlot *
+ExecTemporalAdjustment(TemporalAdjustmentState *node)
+{
+ PlanState *outerPlan = outerPlanState(node);
+ TupleTableSlot *out = node->ss.ps.ps_ResultTupleSlot;
+ TupleTableSlot *curr = outerPlan->ps_ResultTupleSlot;
+ TupleTableSlot *prev = node->ss.ss_ScanTupleSlot;
+ TemporalClause *tc = node->temporalCl;
+ bool produced;
+ bool isNull;
+ Datum currP1;
+ Datum currP2;
+ Datum currRN;
+ Datum prevRN;
+ Datum prevTe;
+
+ if(node->firstCall)
+ {
+ curr = ExecProcNode(outerPlan);
+ if(TupIsNull(curr))
+ return NULL;
+
+ prev = ExecCopySlot(prev, curr);
+ node->sameleft = true;
+ node->firstCall = false;
+ node->outrn = 0;
+ node->datumFormat = curr->tts_tupleDescriptor->attrs[tc->attNumTs - 1];
+ setSweepline(slotGetAttrNotNull(curr, tc->attNumTs));
+ }
+
+ TPGdebugSlot(curr);
+ TPGdebugDatum(node->sweepline, node->datumFormat->atttypid);
+ TPGdebug("node->sameleft = %d", node->sameleft);
+
+ produced = false;
+ while(!produced && !TupIsNull(prev))
+ {
+ if(node->sameleft)
+ {
+ currRN = slotGetAttrNotNull(curr, tc->attNumRN);
+
+ /*
+ * The right-hand-side of the LEFT OUTER JOIN can produce
+ * null-values, however we must produce a result tuple anyway with
+ * the attributes of the left-hand-side, if this happens.
+ */
+ currP1 = slot_getattr(curr, tc->attNumP1, &isNull);
+ if (isNull)
+ node->sameleft = false;
+
+ if(!isNull && isLessThan(node->sweepline, currP1, node))
+ {
+ temporalAdjustmentStoreTuple(node, curr, out,
+ node->sweepline, currP1);
+ produced = true;
+ freeSweepline();
+ setSweepline(currP1);
+ node->outrn = DatumGetInt64(currRN);
+ }
+ else
+ {
+ /*
+ * Temporal aligner: currP1/2 can never be NULL, therefore we
+ * never enter this block. We do not have to check for currP1/2
+ * equal NULL.
+ */
+ if(node->alignment)
+ {
+ /* We fetched currP1 and currRN already */
+ currP2 = slotGetAttrNotNull(curr, tc->attNumP2);
+
+ /* If alignment check to not produce the same tuple again */
+ if(TupIsNull(out)
+ || !isEqual(heapGetAttrNotNull(out, tc->attNumTs),
+ currP1,
+ node)
+ || !isEqual(heapGetAttrNotNull(out, tc->attNumTe),
+ currP2,
+ node)
+ || node->outrn != DatumGetInt64(currRN))
+ {
+ temporalAdjustmentStoreTuple(node, curr, out,
+ currP1, currP2);
+
+ /* sweepline = max(sweepline, curr.P2) */
+ if (isLessThan(node->sweepline, currP2, node))
+ {
+ freeSweepline();
+ setSweepline(currP2);
+ }
+
+ node->outrn = DatumGetInt64(currRN);
+ produced = true;
+ }
+ }
+
+ prev = ExecCopySlot(prev, curr);
+ curr = ExecProcNode(outerPlan);
+
+ if(TupIsNull(curr))
+ node->sameleft = false;
+ else
+ {
+ currRN = slotGetAttrNotNull(curr, tc->attNumRN);
+ prevRN = slotGetAttrNotNull(prev, tc->attNumRN);
+ node->sameleft =
+ DatumGetInt64(currRN) == DatumGetInt64(prevRN);
+ }
+ }
+ }
+ else
+ {
+ prevTe = heapGetAttrNotNull(prev, tc->attNumTe);
+
+ if(isLessThan(node->sweepline, prevTe, node))
+ {
+ temporalAdjustmentStoreTuple(node, prev, out,
+ node->sweepline, prevTe);
+
+ /*
+ * We fetch the row number from the previous tuple slot,
+ * since it is possible that the current one is NULL, if we
+ * arrive here from sameleft = false set when curr = NULL.
+ */
+ currRN = heapGetAttrNotNull(prev, tc->attNumRN);
+ node->outrn = DatumGetInt64(currRN);
+ produced = true;
+ }
+
+ if(TupIsNull(curr))
+ prev = ExecClearTuple(prev);
+ else
+ {
+ prev = ExecCopySlot(prev, curr);
+ freeSweepline();
+ setSweepline(slotGetAttrNotNull(curr, tc->attNumTs));
+ }
+ node->sameleft = true;
+ }
+ }
+
+ if(!produced) {
+ ExecClearTuple(out);
+ return NULL;
+ }
+
+ return out;
+}
+
+/*
+ * ExecInitTemporalAdjustment
+ * Initializes the tuple memory context, outer plan node, and function call
+ * infos for makeRange, lessThan, and isEqual including collation types.
+ * A range constructor function is only initialized if temporal boundaries
+ * are given as range types.
+ */
+TemporalAdjustmentState *
+ExecInitTemporalAdjustment(TemporalAdjustment *node, EState *estate, int eflags)
+{
+ TemporalAdjustmentState *state;
+ FmgrInfo *eqFunctionInfo = palloc(sizeof(FmgrInfo));
+ FmgrInfo *ltFunctionInfo = palloc(sizeof(FmgrInfo));
+ FmgrInfo *rcFunctionInfo = palloc(sizeof(FmgrInfo));
+
+ /* check for unsupported flags */
+ Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
+
+ /*
+ * create state structure
+ */
+ state = makeNode(TemporalAdjustmentState);
+ state->ss.ps.plan = (Plan *) node;
+ state->ss.ps.state = estate;
+
+ /*
+ * Miscellaneous initialization
+ *
+ * Unique nodes have no ExprContext initialization because they never call
+ * ExecQual or ExecProject. But they do need a per-tuple memory context
+ * anyway for calling execTuplesMatch.
+ */
+ state->tempContext =
+ AllocSetContextCreate(CurrentMemoryContext,
+ "TemporalAdjustment",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ /*
+ * Tuple table initialization
+ */
+ ExecInitResultTupleSlot(estate, &state->ss.ps);
+ ExecInitScanTupleSlot(estate, &state->ss);
+
+ /*
+ * then initialize outer plan
+ */
+ outerPlanState(state) = ExecInitNode(outerPlan(node), estate, eflags);
+
+ /*
+ * initialize source tuple type.
+ */
+ ExecAssignScanTypeFromOuterPlan(&state->ss);
+
+ /*
+ * Temporal align nodes do no projections, so initialize projection info for
+ * this node appropriately
+ */
+ ExecAssignResultTypeFromTL(&state->ss.ps);
+ state->ss.ps.ps_ProjInfo = NULL;
+ state->ss.ps.ps_TupFromTlist = false;
+
+ state->alignment = node->temporalCl->temporalType == TEMPORAL_TYPE_ALIGNER;
+ state->temporalCl = copyObject(node->temporalCl);
+ state->firstCall = true;
+ state->sweepline = (Datum) 0;
+
+ /*
+ * Init masks
+ */
+ state->nullMask = palloc0(sizeof(bool) * node->numCols);
+ state->tsteMask = palloc0(sizeof(bool) * node->numCols);
+
+ state->tsteMask[state->temporalCl->attNumTs - 1] = true;
+ state->tsteMask[state->temporalCl->attNumTe - 1] = true;
+
+ /*
+ * Init buffer values for heap_modify_tuple
+ */
+ state->newValues = palloc0(sizeof(Datum) * node->numCols);
+
+ /* the parser should have made sure of this */
+ Assert(OidIsValid(node->ltOperatorID));
+ Assert(OidIsValid(node->eqOperatorID));
+
+ /*
+ * Precompute fmgr lookup data for inner loop. We use "less than", "equal",
+ * and "range_constructor2" operators on columns with indexes "tspos",
+ * "tepos", and "trpos" respectively. To construct a range type we also
+ * assign the original range information from the targetlist entry which
+ * holds the range type from the input to the function call info expression.
+ * This expression is then used to determine the correct type and collation.
+ */
+ fmgr_info(get_opcode(node->eqOperatorID), eqFunctionInfo);
+ fmgr_info(get_opcode(node->ltOperatorID), ltFunctionInfo);
+
+ InitFunctionCallInfoData(state->eqFuncCallInfo, eqFunctionInfo, 2,
+ node->sortCollationID, NULL, NULL);
+ InitFunctionCallInfoData(state->ltFuncCallInfo, ltFunctionInfo, 2,
+ node->sortCollationID, NULL, NULL);
+
+ /*
+ * Range types in boundaries need special treatment:
+ * - there is an extra column in each tuple that must be changed
+ * - and a range constructor method that must be called
+ */
+ if(node->temporalCl->attNumTr != -1)
+ {
+ state->tsteMask[state->temporalCl->attNumTr - 1] = true;
+ fmgr_info(fmgr_internal_function("range_constructor2"), rcFunctionInfo);
+ rcFunctionInfo->fn_expr = (fmNodePtr) node->rangeVar;
+ InitFunctionCallInfoData(state->rcFuncCallInfo, rcFunctionInfo, 2,
+ node->rangeVar->varcollid, NULL, NULL);
+ }
+
+#ifdef TEMPORAL_DEBUG
+ printf("TEMPORAL ADJUSTMENT EXECUTOR INIT...\n");
+ pprint(node->temporalCl);
+ fflush(stdout);
+#endif
+
+ return state;
+}
+
+void
+ExecEndTemporalAdjustment(TemporalAdjustmentState *node)
+{
+ /* clean up tuple table */
+ ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+ ExecClearTuple(node->ss.ss_ScanTupleSlot);
+
+ MemoryContextDelete(node->tempContext);
+
+ /* shut down the subplans */
+ ExecEndNode(outerPlanState(node));
+}
+
+
+void
+ExecReScanTemporalAdjustment(TemporalAdjustmentState *node)
+{
+ /* must clear result tuple so first input tuple is returned */
+ ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+
+ /*
+ * if chgParam of subnode is not null then plan will be re-scanned by
+ * first ExecProcNode.
+ */
+ if (node->ss.ps.lefttree->chgParam == NULL)
+ ExecReScan(node->ss.ps.lefttree);
+}
diff --git src/backend/nodes/copyfuncs.c src/backend/nodes/copyfuncs.c
index d973225..14c02e9 100644
--- src/backend/nodes/copyfuncs.c
+++ src/backend/nodes/copyfuncs.c
@@ -1949,6 +1949,9 @@ _copyJoinExpr(const JoinExpr *from)
COPY_NODE_FIELD(quals);
COPY_NODE_FIELD(alias);
COPY_SCALAR_FIELD(rtindex);
+ COPY_NODE_FIELD(temporalBounds);
+ COPY_SCALAR_FIELD(inTmpPrimTempType);
+ COPY_SCALAR_FIELD(inTmpPrimHasRangeT);
return newnode;
}
@@ -2308,6 +2311,45 @@ _copyOnConflictClause(const OnConflictClause *from)
return newnode;
}
+static TemporalClause *
+_copyTemporalClause(const TemporalClause *from)
+{
+ TemporalClause *newnode = makeNode(TemporalClause);
+
+ COPY_SCALAR_FIELD(temporalType);
+ COPY_SCALAR_FIELD(attNumTs);
+ COPY_SCALAR_FIELD(attNumTe);
+ COPY_SCALAR_FIELD(attNumTr);
+ COPY_SCALAR_FIELD(attNumP1);
+ COPY_SCALAR_FIELD(attNumP2);
+ COPY_SCALAR_FIELD(attNumRN);
+ COPY_STRING_FIELD(colnameTs);
+ COPY_STRING_FIELD(colnameTe);
+ COPY_STRING_FIELD(colnameTr);
+
+ return newnode;
+}
+
+static TemporalAdjustment *
+_copyTemporalAdjustment(const TemporalAdjustment *from)
+{
+ TemporalAdjustment *newnode = makeNode(TemporalAdjustment);
+
+ /*
+ * copy node superclass fields
+ */
+ CopyPlanFields((const Plan *) from, (Plan *) newnode);
+
+ COPY_SCALAR_FIELD(numCols);
+ COPY_SCALAR_FIELD(eqOperatorID);
+ COPY_SCALAR_FIELD(ltOperatorID);
+ COPY_SCALAR_FIELD(sortCollationID);
+ COPY_NODE_FIELD(temporalCl);
+ COPY_NODE_FIELD(rangeVar);
+
+ return newnode;
+}
+
static CommonTableExpr *
_copyCommonTableExpr(const CommonTableExpr *from)
{
@@ -2767,6 +2809,7 @@ _copyQuery(const Query *from)
COPY_NODE_FIELD(setOperations);
COPY_NODE_FIELD(constraintDeps);
COPY_NODE_FIELD(withCheckOptions);
+ COPY_NODE_FIELD(temporalClause);
return newnode;
}
@@ -2834,6 +2877,7 @@ _copySelectStmt(const SelectStmt *from)
COPY_NODE_FIELD(limitCount);
COPY_NODE_FIELD(lockingClause);
COPY_NODE_FIELD(withClause);
+ COPY_NODE_FIELD(temporalClause);
COPY_SCALAR_FIELD(op);
COPY_SCALAR_FIELD(all);
COPY_NODE_FIELD(larg);
@@ -5167,6 +5211,12 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_TemporalClause:
+ retval = _copyTemporalClause(from);
+ break;
+ case T_TemporalAdjustment:
+ retval = _copyTemporalAdjustment(from);
+ break;
case T_TriggerTransition:
retval = _copyTriggerTransition(from);
break;
diff --git src/backend/nodes/equalfuncs.c src/backend/nodes/equalfuncs.c
index edc1797..db36e77 100644
--- src/backend/nodes/equalfuncs.c
+++ src/backend/nodes/equalfuncs.c
@@ -755,6 +755,9 @@ _equalJoinExpr(const JoinExpr *a, const JoinExpr *b)
COMPARE_NODE_FIELD(quals);
COMPARE_NODE_FIELD(alias);
COMPARE_SCALAR_FIELD(rtindex);
+ COMPARE_NODE_FIELD(temporalBounds);
+ COMPARE_SCALAR_FIELD(inTmpPrimTempType);
+ COMPARE_SCALAR_FIELD(inTmpPrimHasRangeT);
return true;
}
diff --git src/backend/nodes/makefuncs.c src/backend/nodes/makefuncs.c
index 20e2dbd..ff6feab 100644
--- src/backend/nodes/makefuncs.c
+++ src/backend/nodes/makefuncs.c
@@ -22,6 +22,89 @@
#include "nodes/nodeFuncs.h"
#include "utils/lsyscache.h"
+/*
+ * makeColumnRef1 -
+ * makes an ColumnRef node with a single element field-list
+ */
+ColumnRef *
+makeColumnRef1(Node *field1)
+{
+ ColumnRef *ref;
+
+ ref = makeNode(ColumnRef);
+ ref->fields = list_make1(field1);
+ ref->location = -1; /* Unknown location */
+
+ return ref;
+}
+
+/*
+ * makeColumnRef2 -
+ * makes an ColumnRef node with a two elements field-list
+ */
+ColumnRef *
+makeColumnRef2(Node *field1, Node *field2)
+{
+ ColumnRef *ref;
+
+ ref = makeNode(ColumnRef);
+ ref->fields = list_make2(field1, field2);
+ ref->location = -1; /* Unknown location */
+
+ return ref;
+}
+
+/*
+ * makeResTarget -
+ * makes an ResTarget node
+ */
+ResTarget *
+makeResTarget(Node *val, char *name)
+{
+ ResTarget *rt;
+
+ rt = makeNode(ResTarget);
+ rt->location = -1; /* unknown location */
+ rt->indirection = NIL;
+ rt->name = name;
+ rt->val = val;
+
+ return rt;
+}
+
+/*
+ * makeAliasFromArgument -
+ * Selects and returns an arguments' alias, if any. Or creates a new one
+ * from a given RangeVar relation name.
+ */
+Alias *
+makeAliasFromArgument(Node *arg)
+{
+ Alias *alias = NULL;
+
+ /* Find aliases of arguments */
+ switch(nodeTag(arg))
+ {
+ case T_RangeSubselect:
+ alias = ((RangeSubselect *) arg)->alias;
+ break;
+ case T_RangeVar:
+ {
+ RangeVar *v = (RangeVar *) arg;
+ if (v->alias != NULL)
+ alias = v->alias;
+ else
+ alias = makeAlias(v->relname, NIL);
+ break;
+ }
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("Argument has no alias or is not supported.")));
+ }
+
+ return alias;
+}
/*
* makeA_Expr -
@@ -610,3 +693,4 @@ makeGroupingSet(GroupingSetKind kind, List *content, int location)
n->location = location;
return n;
}
+
diff --git src/backend/nodes/outfuncs.c src/backend/nodes/outfuncs.c
index 7258c03..696bfb2 100644
--- src/backend/nodes/outfuncs.c
+++ src/backend/nodes/outfuncs.c
@@ -1548,6 +1548,9 @@ _outJoinExpr(StringInfo str, const JoinExpr *node)
WRITE_NODE_FIELD(quals);
WRITE_NODE_FIELD(alias);
WRITE_INT_FIELD(rtindex);
+ WRITE_NODE_FIELD(temporalBounds);
+ WRITE_ENUM_FIELD(inTmpPrimTempType, TemporalType);
+ WRITE_BOOL_FIELD(inTmpPrimHasRangeT);
}
static void
@@ -1816,6 +1819,18 @@ _outSortPath(StringInfo str, const SortPath *node)
}
static void
+_outTemporalAdjustmentPath(StringInfo str, const TemporalAdjustmentPath *node)
+{
+ WRITE_NODE_TYPE("TEMPORALADJUSTMENTPATH");
+
+ _outPathInfo(str, (const Path *) node);
+
+ WRITE_NODE_FIELD(subpath);
+ WRITE_NODE_FIELD(sortClause);
+ WRITE_NODE_FIELD(temporalClause);
+}
+
+static void
_outGroupPath(StringInfo str, const GroupPath *node)
{
WRITE_NODE_TYPE("GROUPPATH");
@@ -2498,6 +2513,7 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
WRITE_NODE_FIELD(limitCount);
WRITE_NODE_FIELD(lockingClause);
WRITE_NODE_FIELD(withClause);
+ WRITE_NODE_FIELD(temporalClause);
WRITE_ENUM_FIELD(op, SetOperation);
WRITE_BOOL_FIELD(all);
WRITE_NODE_FIELD(larg);
@@ -2574,6 +2590,38 @@ _outTriggerTransition(StringInfo str, const TriggerTransition *node)
}
static void
+_outTemporalAdjustment(StringInfo str, const TemporalAdjustment *node)
+{
+ WRITE_NODE_TYPE("TEMPORALADJUSTMENT");
+
+ WRITE_INT_FIELD(numCols);
+ WRITE_OID_FIELD(eqOperatorID);
+ WRITE_OID_FIELD(ltOperatorID);
+ WRITE_OID_FIELD(sortCollationID);
+ WRITE_NODE_FIELD(temporalCl);
+ WRITE_NODE_FIELD(rangeVar);
+
+ _outPlanInfo(str, (const Plan *) node);
+}
+
+static void
+_outTemporalClause(StringInfo str, const TemporalClause *node)
+{
+ WRITE_NODE_TYPE("TEMPORALCLAUSE");
+
+ WRITE_ENUM_FIELD(temporalType, TemporalType);
+ WRITE_INT_FIELD(attNumTs);
+ WRITE_INT_FIELD(attNumTe);
+ WRITE_INT_FIELD(attNumTr);
+ WRITE_INT_FIELD(attNumP1);
+ WRITE_INT_FIELD(attNumP2);
+ WRITE_INT_FIELD(attNumRN);
+ WRITE_STRING_FIELD(colnameTs);
+ WRITE_STRING_FIELD(colnameTe);
+ WRITE_STRING_FIELD(colnameTr);
+}
+
+static void
_outColumnDef(StringInfo str, const ColumnDef *node)
{
WRITE_NODE_TYPE("COLUMNDEF");
@@ -2705,6 +2753,7 @@ _outQuery(StringInfo str, const Query *node)
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(setOperations);
WRITE_NODE_FIELD(constraintDeps);
+ WRITE_NODE_FIELD(temporalClause);
}
static void
@@ -3671,6 +3720,9 @@ outNode(StringInfo str, const void *obj)
case T_SortPath:
_outSortPath(str, obj);
break;
+ case T_TemporalAdjustmentPath:
+ _outTemporalAdjustmentPath(str, obj);
+ break;
case T_GroupPath:
_outGroupPath(str, obj);
break;
@@ -3768,6 +3820,12 @@ outNode(StringInfo str, const void *obj)
case T_ExtensibleNode:
_outExtensibleNode(str, obj);
break;
+ case T_TemporalAdjustment:
+ _outTemporalAdjustment(str, obj);
+ break;
+ case T_TemporalClause:
+ _outTemporalClause(str, obj);
+ break;
case T_CreateStmt:
_outCreateStmt(str, obj);
@@ -3924,7 +3982,6 @@ outNode(StringInfo str, const void *obj)
break;
default:
-
/*
* This should be an ERROR, but it's too useful to be able to
* dump structures that outNode only understands part of.
diff --git src/backend/nodes/print.c src/backend/nodes/print.c
index a1f2941..ff24370 100644
--- src/backend/nodes/print.c
+++ src/backend/nodes/print.c
@@ -25,7 +25,7 @@
#include "optimizer/clauses.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"
-
+#include "parser/parse_node.h"
/*
* print
@@ -492,3 +492,27 @@ print_slot(TupleTableSlot *slot)
debugtup(slot, NULL);
}
+
+/*
+ * print_namespace
+ * print out all name space items' RTEs.
+ */
+void
+print_namespace(const List *namespace)
+{
+ ListCell *lc;
+
+ if (list_length(namespace) == 0)
+ {
+ printf("No namespaces in list.\n");
+ fflush(stdout);
+ return;
+ }
+
+ foreach(lc, namespace)
+ {
+ ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(lc);
+ pprint(nsitem->p_rte);
+ }
+
+}
diff --git src/backend/nodes/readfuncs.c src/backend/nodes/readfuncs.c
index d608530..de52ebf 100644
--- src/backend/nodes/readfuncs.c
+++ src/backend/nodes/readfuncs.c
@@ -262,6 +262,7 @@ _readQuery(void)
READ_NODE_FIELD(rowMarks);
READ_NODE_FIELD(setOperations);
READ_NODE_FIELD(constraintDeps);
+ READ_NODE_FIELD(temporalClause);
READ_DONE();
}
@@ -423,6 +424,28 @@ _readSetOperationStmt(void)
READ_DONE();
}
+/*
+ * _readTemporalClause
+ */
+static TemporalClause *
+_readTemporalClause(void)
+{
+ READ_LOCALS(TemporalClause);
+
+ READ_ENUM_FIELD(temporalType, TemporalType);
+ READ_INT_FIELD(attNumTs);
+ READ_INT_FIELD(attNumTe);
+ READ_INT_FIELD(attNumTr);
+ READ_INT_FIELD(attNumP1);
+ READ_INT_FIELD(attNumP2);
+ READ_INT_FIELD(attNumRN);
+ READ_STRING_FIELD(colnameTs);
+ READ_STRING_FIELD(colnameTe);
+ READ_STRING_FIELD(colnameTr);
+
+ READ_DONE();
+}
+
/*
* Stuff from primnodes.h.
@@ -457,6 +480,17 @@ _readRangeVar(void)
READ_DONE();
}
+static ColumnRef *
+_readColumnRef(void)
+{
+ READ_LOCALS(ColumnRef);
+
+ READ_NODE_FIELD(fields);
+ READ_LOCATION_FIELD(location);
+
+ READ_DONE();
+}
+
static IntoClause *
_readIntoClause(void)
{
@@ -1238,6 +1272,9 @@ _readJoinExpr(void)
READ_NODE_FIELD(quals);
READ_NODE_FIELD(alias);
READ_INT_FIELD(rtindex);
+ READ_NODE_FIELD(temporalBounds);
+ READ_ENUM_FIELD(inTmpPrimTempType, TemporalType);
+ READ_BOOL_FIELD(inTmpPrimHasRangeT);
READ_DONE();
}
@@ -2439,6 +2476,10 @@ parseNodeString(void)
return_value = _readDefElem();
else if (MATCH("DECLARECURSOR", 13))
return_value = _readDeclareCursorStmt();
+ else if (MATCH("TEMPORALCLAUSE", 14))
+ return_value = _readTemporalClause();
+ else if (MATCH("COLUMNREF", 9))
+ return_value = _readColumnRef();
else if (MATCH("PLANNEDSTMT", 11))
return_value = _readPlannedStmt();
else if (MATCH("PLAN", 4))
diff --git src/backend/optimizer/path/allpaths.c src/backend/optimizer/path/allpaths.c
index 9753a26..077c502 100644
--- src/backend/optimizer/path/allpaths.c
+++ src/backend/optimizer/path/allpaths.c
@@ -44,6 +44,7 @@
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
+#include "utils/fmgroids.h"
/* results of subquery_is_pushdown_safe */
@@ -126,6 +127,7 @@ static void subquery_push_qual(Query *subquery,
static void recurse_push_qual(Node *setOp, Query *topquery,
RangeTblEntry *rte, Index rti, Node *qual);
static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel);
+static bool allWindowFuncsHaveRowId(List *targetList);
/*
@@ -2307,7 +2309,8 @@ subquery_is_pushdown_safe(Query *subquery, Query *topquery,
/* Check points 3, 4, and 5 */
if (subquery->distinctClause ||
subquery->hasWindowFuncs ||
- subquery->hasTargetSRFs)
+ subquery->hasTargetSRFs ||
+ subquery->temporalClause)
safetyInfo->unsafeVolatile = true;
/*
@@ -2417,6 +2420,7 @@ static void
check_output_expressions(Query *subquery, pushdown_safety_info *safetyInfo)
{
ListCell *lc;
+ bool wfsafe = allWindowFuncsHaveRowId(subquery->targetList);
foreach(lc, subquery->targetList)
{
@@ -2455,12 +2459,35 @@ check_output_expressions(Query *subquery, pushdown_safety_info *safetyInfo)
/* If subquery uses window functions, check point 4 */
if (subquery->hasWindowFuncs &&
- !targetIsInAllPartitionLists(tle, subquery))
+ !targetIsInAllPartitionLists(tle, subquery) &&
+ !wfsafe)
{
/* not present in all PARTITION BY clauses, so mark it unsafe */
safetyInfo->unsafeColumns[tle->resno] = true;
continue;
}
+
+ /*
+ * If subquery uses temporal primitives, mark all columns that are
+ * used as temporal attributes as unsafe, since they may be changed.
+ */
+ if (subquery->temporalClause)
+ {
+ AttrNumber resnoTs =
+ ((TemporalClause *)subquery->temporalClause)->attNumTs;
+ AttrNumber resnoTe =
+ ((TemporalClause *)subquery->temporalClause)->attNumTe;
+ AttrNumber resnoRangeT =
+ ((TemporalClause *)subquery->temporalClause)->attNumTr;
+
+ if (tle->resno == resnoTs
+ || tle->resno == resnoTe
+ || tle->resno == resnoRangeT)
+ {
+ safetyInfo->unsafeColumns[tle->resno] = true;
+ continue;
+ }
+ }
}
}
@@ -2530,6 +2557,32 @@ targetIsInAllPartitionLists(TargetEntry *tle, Query *query)
}
/*
+ * allWindowFuncsHaveRowId
+ * True if all window functions are row_id(), otherwise false. We use this
+ * to have unique numbers for each tuple. It is push-down-safe, because we
+ * accept gaps between numbers.
+ */
+static bool
+allWindowFuncsHaveRowId(List *targetList)
+{
+ ListCell *lc;
+
+ foreach(lc, targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ if (tle->resjunk)
+ continue;
+
+ if(IsA(tle->expr, WindowFunc)
+ && ((WindowFunc *) tle->expr)->winfnoid != F_WINDOW_ROW_ID)
+ return false;
+ }
+
+ return true;
+}
+
+/*
* qual_is_pushdown_safe - is a particular qual safe to push down?
*
* qual is a restriction clause applying to the given subquery (whose RTE
@@ -2813,6 +2866,13 @@ remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel)
return;
/*
+ * If there's a sub-query belonging to a temporal primitive, do not remove
+ * any entries, because we need all of them.
+ */
+ if (subquery->temporalClause)
+ return;
+
+ /*
* Run through the tlist and zap entries we don't need. It's okay to
* modify the tlist items in-place because set_subquery_pathlist made a
* copy of the subquery.
diff --git src/backend/optimizer/plan/createplan.c src/backend/optimizer/plan/createplan.c
index ad49674..a3f590f 100644
--- src/backend/optimizer/plan/createplan.c
+++ src/backend/optimizer/plan/createplan.c
@@ -113,6 +113,9 @@ static LockRows *create_lockrows_plan(PlannerInfo *root, LockRowsPath *best_path
static ModifyTable *create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path);
static Limit *create_limit_plan(PlannerInfo *root, LimitPath *best_path,
int flags);
+static TemporalAdjustment *create_temporaladjustment_plan(PlannerInfo *root,
+ TemporalAdjustmentPath *best_path,
+ int flags);
static SeqScan *create_seqscan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
static SampleScan *create_samplescan_plan(PlannerInfo *root, Path *best_path,
@@ -270,6 +273,9 @@ static ModifyTable *make_modifytable(PlannerInfo *root,
List *resultRelations, List *subplans,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict, int epqParam);
+static TemporalAdjustment *make_temporalAdjustment(Plan *lefttree,
+ TemporalClause *temporalClause,
+ List *sortClause);
/*
@@ -463,6 +469,11 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
(LimitPath *) best_path,
flags);
break;
+ case T_TemporalAdjustment:
+ plan = (Plan *) create_temporaladjustment_plan(root,
+ (TemporalAdjustmentPath *) best_path,
+ flags);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) best_path->pathtype);
@@ -2246,6 +2257,33 @@ create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags)
return plan;
}
+/*
+ * create_temporaladjustment_plan
+ *
+ * Create a Temporal Adjustment plan for 'best_path' and (recursively) plans
+ * for its subpaths. Depending on the type of the temporal clause, we create
+ * a temporal normalize or a temporal aligner node.
+ */
+static TemporalAdjustment *
+create_temporaladjustment_plan(PlannerInfo *root,
+ TemporalAdjustmentPath *best_path,
+ int flags)
+{
+ TemporalAdjustment *plan;
+ Plan *subplan;
+
+ /* Limit doesn't project, so tlist requirements pass through */
+ subplan = create_plan_recurse(root, best_path->subpath, flags);
+
+ plan = make_temporalAdjustment(subplan,
+ best_path->temporalClause,
+ best_path->sortClause);
+
+ copy_generic_path_info(&plan->plan, (Path *) best_path);
+
+ return plan;
+}
+
/*****************************************************************************
*
@@ -4826,6 +4864,57 @@ make_subqueryscan(List *qptlist,
return node;
}
+static TemporalAdjustment *
+make_temporalAdjustment(Plan *lefttree,
+ TemporalClause *temporalClause,
+ List *sortClause)
+{
+ TemporalAdjustment *node = makeNode(TemporalAdjustment);
+ Plan *plan = &node->plan;
+ SortGroupClause *sgc;
+ TargetEntry *tle;
+
+ plan->targetlist = lefttree->targetlist;
+ plan->qual = NIL;
+ plan->lefttree = lefttree;
+ plan->righttree = NULL;
+
+ node->numCols = list_length(lefttree->targetlist);
+ node->temporalCl = copyObject(temporalClause);
+
+ /*
+ * Fetch the targetlist entry of the given range type, s.t. we have all
+ * needed information to call range_constructor inside the executor.
+ */
+ node->rangeVar = NULL;
+ if (temporalClause->attNumTr != -1)
+ {
+ TargetEntry *tle = get_tle_by_resno(plan->targetlist,
+ temporalClause->attNumTr);
+ node->rangeVar = copyObject(tle->expr);
+ }
+
+ /*
+ * The last element in the sort clause is one of the temporal attributes
+ * P1 or P2, which have the same attribute type as the valid timestamps of
+ * both relations. Hence, we can fetch equality, sort operator, and
+ * collation Oids from them.
+ */
+ sgc = (SortGroupClause *) llast(sortClause);
+
+ /* the parser should have made sure of this */
+ Assert(OidIsValid(sgc->sortop));
+ Assert(OidIsValid(sgc->eqop));
+
+ node->eqOperatorID = sgc->eqop;
+ node->ltOperatorID = sgc->sortop;
+
+ tle = get_sortgroupclause_tle(sgc, plan->targetlist);
+ node->sortCollationID = exprCollation((Node *) tle->expr);
+
+ return node;
+}
+
static FunctionScan *
make_functionscan(List *qptlist,
List *qpqual,
diff --git src/backend/optimizer/plan/planner.c src/backend/optimizer/plan/planner.c
index 41dde50..0f76232 100644
--- src/backend/optimizer/plan/planner.c
+++ src/backend/optimizer/plan/planner.c
@@ -1987,6 +1987,20 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
Path *path = (Path *) lfirst(lc);
/*
+ * If there is a NORMALIZE or ALIGN clause, i.e., temporal primitive,
+ * add the TemporalAdjustment node with type TemporalAligner or
+ * TemporalNormalizer.
+ */
+ if (parse->temporalClause)
+ {
+ path = (Path *) create_temporaladjustment_path(root,
+ final_rel,
+ path,
+ parse->sortClause,
+ (TemporalClause *)parse->temporalClause);
+ }
+
+ /*
* If there is a FOR [KEY] UPDATE/SHARE clause, add the LockRows node.
* (Note: we intentionally test parse->rowMarks not root->rowMarks
* here. If there are only non-locking rowmarks, they should be
@@ -4358,7 +4372,6 @@ create_ordered_paths(PlannerInfo *root,
return ordered_rel;
}
-
/*
* make_group_input_target
* Generate appropriate PathTarget for initial input to grouping nodes.
diff --git src/backend/optimizer/plan/setrefs.c src/backend/optimizer/plan/setrefs.c
index 2fe1c8c..0ff19aa 100644
--- src/backend/optimizer/plan/setrefs.c
+++ src/backend/optimizer/plan/setrefs.c
@@ -612,6 +612,7 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
case T_Sort:
case T_Unique:
case T_SetOp:
+ case T_TemporalAdjustment:
/*
* These plan types don't actually bother to evaluate their
diff --git src/backend/optimizer/plan/subselect.c src/backend/optimizer/plan/subselect.c
index 3171743..a1c9733 100644
--- src/backend/optimizer/plan/subselect.c
+++ src/backend/optimizer/plan/subselect.c
@@ -2687,6 +2687,7 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
case T_Gather:
case T_SetOp:
case T_Group:
+ case T_TemporalAdjustment:
break;
default:
diff --git src/backend/optimizer/util/pathnode.c src/backend/optimizer/util/pathnode.c
index 6d3ccfd..e6f92a0 100644
--- src/backend/optimizer/util/pathnode.c
+++ src/backend/optimizer/util/pathnode.c
@@ -2362,6 +2362,66 @@ create_sort_path(PlannerInfo *root,
return pathnode;
}
+TemporalAdjustmentPath *
+create_temporaladjustment_path(PlannerInfo *root,
+ RelOptInfo *rel,
+ Path *subpath,
+ List *sortClause,
+ TemporalClause *temporalClause)
+{
+ TemporalAdjustmentPath *pathnode = makeNode(TemporalAdjustmentPath);
+
+ pathnode->path.pathtype = T_TemporalAdjustment;
+ pathnode->path.parent = rel;
+ /* TemporalAdjustment doesn't project, so use source path's pathtarget */
+ pathnode->path.pathtarget = subpath->pathtarget;
+ /* For now, assume we are above any joins, so no parameterization */
+ pathnode->path.param_info = NULL;
+
+ /* Currently we assume that temporal adjustment is not parallelizable */
+ pathnode->path.parallel_aware = false;
+ pathnode->path.parallel_safe = false;
+ pathnode->path.parallel_workers = 0;
+
+ /* Temporal Adjustment does not change the sort order */
+ pathnode->path.pathkeys = subpath->pathkeys;
+
+ pathnode->subpath = subpath;
+
+ /* Special information needed by temporal adjustment plan node */
+ pathnode->sortClause = copyObject(sortClause);
+ pathnode->temporalClause = copyObject(temporalClause);
+
+ /* Path's cost estimations */
+ pathnode->path.startup_cost = subpath->startup_cost;
+ pathnode->path.total_cost = subpath->total_cost;
+ pathnode->path.rows = subpath->rows;
+
+ if(temporalClause->temporalType == TEMPORAL_TYPE_ALIGNER)
+ {
+ /*
+ * Every tuple from the sub-node can produce up to three tuples in the
+ * algorithm. In addition we make up to three attribute comparisons for
+ * each result tuple.
+ */
+ pathnode->path.total_cost = subpath->total_cost +
+ (cpu_tuple_cost + 3 * cpu_operator_cost) * subpath->rows * 3;
+ }
+ else /* TEMPORAL_TYPE_NORMALIZER */
+ {
+ /*
+ * For each split point in the sub-node we can have up to two result
+ * tuples. The total cost is the cost of the sub-node plus for each
+ * result tuple the cost to produce it and one attribute comparison
+ * (different from alignment since we omit the intersection part).
+ */
+ pathnode->path.total_cost = subpath->total_cost +
+ (cpu_tuple_cost + cpu_operator_cost) * subpath->rows * 2;
+ }
+
+ return pathnode;
+}
+
/*
* create_group_path
* Creates a pathnode that represents performing grouping of presorted input
diff --git src/backend/parser/Makefile src/backend/parser/Makefile
index fdd8485..35c20fa 100644
--- src/backend/parser/Makefile
+++ src/backend/parser/Makefile
@@ -15,7 +15,8 @@ override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
OBJS= analyze.o gram.o scan.o parser.o \
parse_agg.o parse_clause.o parse_coerce.o parse_collate.o parse_cte.o \
parse_expr.o parse_func.o parse_node.o parse_oper.o parse_param.o \
- parse_relation.o parse_target.o parse_type.o parse_utilcmd.o scansup.o
+ parse_relation.o parse_target.o parse_type.o parse_utilcmd.o scansup.o \
+ parse_temporal.o
include $(top_srcdir)/src/backend/common.mk
diff --git src/backend/parser/analyze.c src/backend/parser/analyze.c
index 5e65fe7..45f3ac2 100644
--- src/backend/parser/analyze.c
+++ src/backend/parser/analyze.c
@@ -40,6 +40,7 @@
#include "parser/parse_param.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
+#include "parser/parse_temporal.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/rel.h"
@@ -1188,6 +1189,9 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
/* mark column origins */
markTargetListOrigins(pstate, qry->targetList);
+ /* transform inner parts of a temporal primitive node */
+ qry->temporalClause = transformTemporalClause(pstate, qry, stmt);
+
/* transform WHERE */
qual = transformWhereClause(pstate, stmt->whereClause,
EXPR_KIND_WHERE, "WHERE");
@@ -1262,6 +1266,9 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
parseCheckAggregates(pstate, qry);
+ /* transform TEMPORAL PRIMITIVES */
+ qry->temporalClause = transformTemporalClauseResjunk(qry);
+
foreach(l, stmt->lockingClause)
{
transformLockingClause(pstate, qry,
diff --git src/backend/parser/gram.y src/backend/parser/gram.y
index 2ed7b52..e600840 100644
--- src/backend/parser/gram.y
+++ src/backend/parser/gram.y
@@ -410,6 +410,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <boolean> all_or_distinct
%type <node> join_outer join_qual
+%type <node> normalizer_qual
%type <jtype> join_type
%type <list> extract_list overlay_list position_list
@@ -461,11 +462,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <value> NumericOnly
%type <list> NumericOnly_list
%type <alias> alias_clause opt_alias_clause
+%type <list> temporal_bounds
%type <list> func_alias_clause
%type <sortby> sortby
%type <ielem> index_elem
%type <node> table_ref
%type <jexpr> joined_table
+%type <jexpr> aligned_table
+%type <jexpr> normalized_table
%type <range> relation_expr
%type <range> relation_expr_opt_alias
%type <node> tablesample_clause opt_repeatable_clause
@@ -559,6 +563,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> partbound_datum_list
%type <partrange_datum> PartitionRangeDatum
%type <list> range_datum_list
+%type <list> temporal_bounds_list
+
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -583,7 +589,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
- AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
+ AGGREGATE ALIGN ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
@@ -629,7 +635,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
- NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NONE
+ NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NONE NORMALIZE
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
NULLS_P NUMERIC
@@ -10816,6 +10822,19 @@ first_or_next: FIRST_P { $$ = 0; }
| NEXT { $$ = 0; }
;
+temporal_bounds: WITH '(' temporal_bounds_list ')' { $$ = $3; }
+ ;
+
+temporal_bounds_list:
+ columnref
+ {
+ $$ = list_make1($1);
+ }
+ | temporal_bounds_list ',' columnref
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
/*
* This syntax for group_clause tries to follow the spec quite closely.
@@ -11072,6 +11091,94 @@ table_ref: relation_expr opt_alias_clause
$2->alias = $4;
$$ = (Node *) $2;
}
+ | '(' aligned_table ')' alias_clause
+ {
+ $2->alias = $4;
+ $$ = (Node *) $2;
+ }
+ | '(' normalized_table ')' alias_clause
+ {
+ $2->alias = $4;
+ $$ = (Node *) $2;
+ }
+ ;
+
+aligned_table:
+ table_ref ALIGN table_ref ON a_expr temporal_bounds
+ {
+ JoinExpr *n = makeNode(JoinExpr);
+ n->jointype = TEMPORAL_ALIGN;
+ n->isNatural = FALSE;
+ n->larg = $1;
+ n->rarg = $3;
+
+ /* No USING clause, we use only ON as join qualifier. */
+ n->usingClause = NIL;
+
+ /*
+ * A list for our period boundaries with 4 comparable values
+ * or two range typed values,
+ * i.e. [lts, lte) is the left argument period, and
+ * [rts, rte) is the right argument period,
+ * or two compatible range types with bounds like '[)'
+ */
+ if(list_length($6) == 4 || list_length($6) == 2)
+ n->temporalBounds = $6;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("Temporal adjustment boundaries must " \
+ "have 2 range typed values, or four " \
+ "single values."),
+ parser_errposition(@6)));
+
+ n->quals = $5; /* ON clause */
+ $$ = n;
+ }
+ ;
+
+normalizer_qual:
+ USING '(' name_list ')' { $$ = (Node *) $3; }
+ | USING '(' ')' { $$ = (Node *) NIL; }
+ | ON a_expr { $$ = $2; }
+ ;
+
+normalized_table:
+ table_ref NORMALIZE table_ref normalizer_qual temporal_bounds
+ {
+ JoinExpr *n = makeNode(JoinExpr);
+ n->jointype = TEMPORAL_NORMALIZE;
+ n->isNatural = FALSE;
+ n->larg = $1;
+ n->rarg = $3;
+
+ n->usingClause = NIL;
+ n->quals = NULL;
+
+ if ($4 != NULL && IsA($4, List))
+ n->usingClause = (List *) $4; /* USING clause */
+ else
+ n->quals = $4; /* ON clause */
+
+ /*
+ * A list for our period boundaries with 4 comparable values
+ * or two range typed values,
+ * i.e. [lts, lte) is the left argument period, and
+ * [rts, rte) is the right argument period,
+ * or two compatible range types with bounds like '[)'
+ */
+ if(list_length($5) == 4 || list_length($5) == 2)
+ n->temporalBounds = $5;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("Temporal adjustment boundaries must " \
+ "have 2 range typed values, or four " \
+ "single values."),
+ parser_errposition(@5)));
+
+ $$ = n;
+ }
;
@@ -14382,7 +14489,8 @@ type_func_name_keyword:
* forced to.
*/
reserved_keyword:
- ALL
+ ALIGN
+ | ALL
| ANALYSE
| ANALYZE
| AND
@@ -14430,6 +14538,7 @@ reserved_keyword:
| LIMIT
| LOCALTIME
| LOCALTIMESTAMP
+ | NORMALIZE
| NOT
| NULL_P
| OFFSET
diff --git src/backend/parser/parse_clause.c src/backend/parser/parse_clause.c
index 751de4b..47d3391 100644
--- src/backend/parser/parse_clause.c
+++ src/backend/parser/parse_clause.c
@@ -39,6 +39,7 @@
#include "parser/parse_func.h"
#include "parser/parse_oper.h"
#include "parser/parse_relation.h"
+#include "parser/parse_temporal.h"
#include "parser/parse_target.h"
#include "parser/parse_type.h"
#include "rewrite/rewriteManip.h"
@@ -967,6 +968,43 @@ transformFromClauseItem(ParseState *pstate, Node *n,
int k;
/*
+ * If this is a temporal primitive, rewrite it into a sub-query using
+ * the given join quals and the alias. We need this as temporal
+ * primitives.
+ */
+ if(j->jointype == TEMPORAL_ALIGN || j->jointype == TEMPORAL_NORMALIZE)
+ {
+ RangeSubselect *rss;
+ RangeTblRef *rtr;
+ RangeTblEntry *rte;
+ int rtindex;
+
+ if(j->jointype == TEMPORAL_ALIGN)
+ {
+ /* Rewrite the temporal aligner into a sub-SELECT */
+ rss = (RangeSubselect *) transformTemporalAligner(pstate, j);
+ }
+ else
+ {
+ /* Rewrite the temporal normalizer into a sub-SELECT */
+ rss = (RangeSubselect *) transformTemporalNormalizer(pstate, j);
+ }
+
+ /* Transform the sub-SELECT */
+ rte = transformRangeSubselect(pstate, rss);
+
+ /* assume new rte is at end */
+ rtindex = list_length(pstate->p_rtable);
+ Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
+ *top_rte = rte;
+ *top_rti = rtindex;
+ *namespace = list_make1(makeDefaultNSItem(rte));
+ rtr = makeNode(RangeTblRef);
+ rtr->rtindex = rtindex;
+ return (Node *) rtr;
+ }
+
+ /*
* Recursively process the left subtree, then the right. We must do
* it in this order for correct visibility of LATERAL references.
*/
@@ -1029,6 +1067,16 @@ transformFromClauseItem(ParseState *pstate, Node *n,
&r_colnames, &r_colvars);
/*
+ * Rename columns automatically to unique not-in-use column names, if
+ * column names clash with internal-use-only columns of temporal
+ * primitives.
+ */
+ transformTemporalClauseAmbiguousColumns(pstate, j,
+ l_colnames, r_colnames,
+ l_colvars, r_colvars,
+ l_rte, r_rte);
+
+ /*
* Natural join does not explicitly specify columns; must generate
* columns to join. Need to run through the list of columns from each
* table or join result and match up the column names. Use the first
diff --git src/backend/parser/parse_temporal.c src/backend/parser/parse_temporal.c
new file mode 100644
index 0000000..3a3f86f
--- /dev/null
+++ src/backend/parser/parse_temporal.c
@@ -0,0 +1,1620 @@
+/*-------------------------------------------------------------------------
+ *
+ * parse_temporal.c
+ * handle temporal operators in parser
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/parser/parse_temporal.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "parser/parse_temporal.h"
+#include "parser/parsetree.h"
+#include "parser/parser.h"
+#include "parser/parse_type.h"
+#include "nodes/makefuncs.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
+#include "utils/syscache.h"
+#include "utils/builtins.h"
+#include "access/htup_details.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/print.h"
+
+/*
+ * Enumeration of temporal boundary IDs. We can have four elements in a boundary
+ * list (i.e., WITH-clause of a temporal primitive) when we have two argument
+ * relations with scalar boundaries, or two entries if we have range-type
+ * boundaries, that is, VALID-TIME-attributes. In the future, we could even have
+ * a list with only one item. For instance, when we calculate temporal
+ * aggregations with a single attribute relation.
+ */
+typedef enum
+{
+ TPB_LARGTST = 0,
+ TPB_LARGTE,
+ TPB_RARGTST,
+ TPB_RARGTE
+} TemporalBoundID;
+
+typedef enum
+{
+ TPB_ONERROR_NULL,
+ TPB_ONERROR_FAIL
+
+} TemporalBoundOnError;
+
+static void
+getColumnCounter(const char *colname,
+ const char *prefix,
+ bool *found,
+ int *counter);
+
+static char *
+addTemporalAlias(ParseState *pstate,
+ char *name,
+ int counter);
+
+static SelectStmt *
+makeTemporalQuerySkeleton(JoinExpr *j,
+ char **nameRN,
+ char **nameP1,
+ char **nameP2,
+ bool *hasRangeTypes,
+ Alias **largAlias,
+ Alias **rargAlias);
+
+static ColumnRef *
+temporalBoundGet(List *bounds,
+ TemporalBoundID id,
+ TemporalBoundOnError oe);
+
+static char *
+temporalBoundGetName(List *bounds,
+ TemporalBoundID id);
+
+static ColumnRef *
+temporalBoundGetCopyFQN(List *bounds,
+ TemporalBoundID id,
+ char *relname);
+
+static void
+temporalBoundCheckRelname(ColumnRef *bound,
+ char *relname);
+
+static List *
+temporalBoundGetLeftBounds(List *bounds);
+
+static List *
+temporalBoundGetRightBounds(List *bounds);
+
+static void
+temporalBoundCheckIntegrity(ParseState *pstate,
+ List *bounds,
+ List *colnames,
+ List *colvars,
+ TemporalType tmpType);
+
+static Form_pg_type
+typeGet(Oid id);
+
+static List *
+internalUseOnlyColumnNames(ParseState *pstate,
+ bool hasRangeTypes,
+ TemporalType tmpType);
+
+/*
+ * tpprint
+ * Temporal PostgreSQL print: pprint with surroundings to cut out pieces
+ * from long debug prints.
+ */
+void
+tpprint(const void *obj, const char *marker)
+{
+ printf("--------------------------------------SSS-%s\n", marker);
+ pprint(obj);
+ printf("--------------------------------------EEE-%s\n", marker);
+ fflush(stdout);
+}
+
+/*
+ * temporalBoundGetLeftBounds -
+ * Return the left boundaries of a temporal bounds list. These are either
+ * two scalar values for TS and TE, or a single range type value T holding
+ * both bounds.
+ */
+static List *
+temporalBoundGetLeftBounds(List *bounds)
+{
+ switch(list_length(bounds))
+ {
+ case 2: return list_make1(linitial(bounds));
+ case 4: return list_make2(linitial(bounds), lsecond(bounds));
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_ARGUMENT_FOR_TEMPORAL_ADJUSTMENT),
+ errmsg("Invalid temporal bound list length."),
+ errhint("Specify four scalar columns for the " \
+ "temporal boundaries, or two range-typed "\
+ "columns.")));
+
+ /* Keep compiler quiet */
+ return NIL;
+}
+
+/*
+ * temporalBoundGetRightBounds -
+ * Return the right boundaries of a temporal bounds list. These are either
+ * two scalar values for TS and TE, or a single range type value T holding
+ * both bounds.
+ */
+static List *
+temporalBoundGetRightBounds(List *bounds)
+{
+ switch(list_length(bounds))
+ {
+ case 2: return list_make1(lsecond(bounds));
+ case 4: return list_make2(lthird(bounds), lfourth(bounds));
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_ARGUMENT_FOR_TEMPORAL_ADJUSTMENT),
+ errmsg("Invalid temporal bound list length."),
+ errhint("Specify four scalar columns for the " \
+ "temporal boundaries, or two range-typed "\
+ "columns.")));
+
+ /* Keep compiler quiet */
+ return NIL;
+}
+
+/*
+ * temporalBoundCheckRelname -
+ * Check if full-qualified names within a boundary list (i.e., WITH-clause
+ * of a temporal primitive) match with the right or left argument
+ * respectively.
+ */
+static void
+temporalBoundCheckRelname(ColumnRef *bound, char *relname)
+{
+ char *givenRelname;
+ int l = list_length(bound->fields);
+
+ if(l == 1)
+ return;
+
+ givenRelname = strVal((Value *) list_nth(bound->fields, l - 2));
+
+ if(strcmp(relname, givenRelname) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("The temporal bound \"%s\" does not match with " \
+ "the argument \"%s\" of the temporal primitive.",
+ NameListToString(bound->fields), relname)));
+}
+
+/*
+ * temporalBoundGetCopyFQN -
+ * Creates a copy of a temporal bound from the boundary list identified
+ * with the given id. If it does not contain a full-qualified column
+ * reference, the last argument "relname" is used to build a new one.
+ */
+static ColumnRef *
+temporalBoundGetCopyFQN(List *bounds, TemporalBoundID id, char *relname)
+{
+ ColumnRef *bound = copyObject(temporalBoundGet(bounds, id,
+ TPB_ONERROR_FAIL));
+ int l = list_length(bound->fields);
+
+ if(l == 1)
+ bound->fields = lcons(makeString(relname), bound->fields);
+ else
+ temporalBoundCheckRelname(bound, relname);
+
+ return bound;
+}
+
+/*
+ * temporalBoundGetName -
+ * Returns the name (that is, not the full-qualified column reference) of
+ * a bound.
+ */
+static char *
+temporalBoundGetName(List *bounds, TemporalBoundID id)
+{
+ ColumnRef *bound = temporalBoundGet(bounds, id, TPB_ONERROR_FAIL);
+ return strVal((Value *) llast(bound->fields));
+}
+
+/*
+ * temporalBoundGet -
+ * Returns a single bound with a given bound ID. See comments below for
+ * further details.
+ */
+static ColumnRef *
+temporalBoundGet(List *bounds, TemporalBoundID id, TemporalBoundOnError oe)
+{
+ int l = list_length(bounds);
+
+ switch(l)
+ {
+ /*
+ * Four boundary entries means that we have 2x two scalar boundaries.
+ * Which means the first two entries are start and end of the first
+ * bound, and the 3th and 4th entry are start and end of the second
+ * bound.
+ */
+ case 4:
+ return list_nth(bounds, id);
+
+ /*
+ * Two boundary entries are either two range-typed bounds, or a single
+ * bound with two scalar values defining start and end (the later is
+ * used for GROUP BY PERIOD for instance)
+ */
+ case 2:
+ if(id == TPB_LARGTST)
+ return linitial(bounds);
+ if(id == TPB_RARGTST || id == TPB_LARGTE)
+ return lsecond(bounds);
+ break;
+
+ /*
+ * One boundary entry is a range-typed bound for GROUP BY PERIOD or
+ * DISTINCT PERIOD bounds.
+ */
+ case 1:
+ if(id == TPB_LARGTST)
+ return linitial(bounds);
+ }
+
+ if (oe == TPB_ONERROR_FAIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("Invalid temporal bound list with length \"%d\" " \
+ "and index at \"%d\".", l, id),
+ errhint("Specify four scalar columns for the " \
+ "temporal boundaries, or two range-typed "\
+ "columns.")));
+
+ return NULL;
+}
+
+/*
+ * transformTemporalClause -
+ * If we have a temporal primitive query, we must find all attribute
+ * numbers for p1, p2, rn, ts, te, and t columns. If the names of these
+ * internal-use-only columns are already occupied, we must rename them
+ * in order to not have an ambiguous column error.
+ *
+ * Please note: We cannot simply use resjunk columns here, because the
+ * subquery has already been build and parsed. We need these columns then
+ * for more than a single recursion step. This means, that we would loose
+ * resjunk columns too early. XXX PEMOSER Is there another possibility?
+ */
+Node *
+transformTemporalClause(ParseState *pstate, Query* qry, SelectStmt *stmt)
+{
+ ListCell *lc = NULL;
+ bool foundTsTe = false;
+ TemporalClause *tc = stmt->temporalClause;
+ int pos;
+
+ /* No temporal clause given, do nothing */
+ if(!tc)
+ return NULL;
+
+ /* To start, all attribute numbers for temporal boundaries are unknown */
+ tc->attNumTr = -1;
+ tc->attNumTe = -1;
+ tc->attNumTs = -1;
+
+ /*
+ * Find attribute numbers for each attribute that is used during
+ * temporal adjustment.
+ */
+ pos = list_length(qry->targetList);
+ if (tc->temporalType == TEMPORAL_TYPE_ALIGNER)
+ {
+ tc->attNumP2 = pos--;
+ tc->attNumP1 = pos--;
+ }
+ else /* Temporal normalizer */
+ {
+ /* This entry gets added during the sort-by transformation */
+ tc->attNumP1 = pos + 1;
+
+ /* Unknown and unused */
+ tc->attNumP2 = -1;
+ }
+
+ /*
+ * If we have range types the subquery splits it into separate
+ * columns, called ts and te which are in between the p1- and
+ * rn-column.
+ */
+ if(tc->colnameTr)
+ {
+ tc->attNumTe = pos--;
+ tc->attNumTs = pos--;
+ }
+
+ tc->attNumRN = pos;
+
+ /*
+ * If we have temporal aliases stored in the current parser state, then we
+ * got ambiguous columns. We resolve this problem by renaming parts of the
+ * query tree with new unique column names.
+ */
+ foreach(lc, pstate->p_temporal_aliases)
+ {
+ SortBy *sb = NULL;
+ char *key = strVal(linitial((List *) lfirst(lc)));
+ char *value = strVal(lsecond((List *) lfirst(lc)));
+ TargetEntry *tle = NULL;
+
+ if(strcmp(key, "rn") == 0)
+ {
+ sb = (SortBy *) linitial(stmt->sortClause);
+ tle = get_tle_by_resno(qry->targetList, tc->attNumRN);
+ }
+ else if(strcmp(key, "p1") == 0)
+ {
+ sb = (SortBy *) lsecond(stmt->sortClause);
+ tle = get_tle_by_resno(qry->targetList, tc->attNumP1);
+ }
+ else if(strcmp(key, "p2") == 0)
+ {
+ sb = (SortBy *) lthird(stmt->sortClause);
+ tle = get_tle_by_resno(qry->targetList, tc->attNumP2);
+ }
+ else if(strcmp(key, "ts") == 0)
+ {
+ tc->colnameTs = pstrdup(value);
+ tle = get_tle_by_resno(qry->targetList, tc->attNumTs);
+ foundTsTe = true;
+ }
+ else if(strcmp(key, "te") == 0)
+ {
+ tc->colnameTe = pstrdup(value);
+ tle = get_tle_by_resno(qry->targetList, tc->attNumTe);
+ foundTsTe = true;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("Invalid column name \"%s\" for alias " \
+ "renames of temporal adjustment primitives.",
+ key)));
+
+ /*
+ * Rename the order-by entry.
+ * Just change the name if it is a column reference, nothing to do
+ * for constants, i.e. if the group-by field has been specified by
+ * a column attribute number (ex. 1 for the first column)
+ */
+ if(sb && IsA(sb->node, ColumnRef))
+ {
+ ColumnRef *cr = (ColumnRef *) sb->node;
+ cr->fields = list_make1(makeString(value));
+ }
+
+ /*
+ * Rename the targetlist entry for "p1", "p2", or "rn" iff aligner, and
+ * rename it for both temporal primitives, if it is "ts" or "te".
+ */
+ if(tle && (foundTsTe
+ || tc->temporalType == TEMPORAL_TYPE_ALIGNER))
+ {
+ tle->resname = pstrdup(value);
+ }
+ }
+
+ /*
+ * Find column attribute numbers of the two temporal attributes from
+ * the left argument of the inner join, or the single temporal attribute if
+ * it is a range type.
+ */
+ foreach(lc, qry->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ if(!tle->resname)
+ continue;
+
+ /* Temporal boundary is a range type */
+ if (tc->colnameTr)
+ {
+ if (strcmp(tle->resname, tc->colnameTr) == 0)
+ tc->attNumTr = tle->resno;
+ }
+ else /* Two scalar columns for boundaries */
+ {
+ if (strcmp(tle->resname, tc->colnameTs) == 0)
+ tc->attNumTs = tle->resno;
+ else if (strcmp(tle->resname, tc->colnameTe) == 0)
+ tc->attNumTe = tle->resno;
+ }
+ }
+
+ /* We need column attribute numbers for all temporal boundaries */
+ if(tc->attNumTs == -1
+ || tc->attNumTe == -1
+ || (tc->colnameTr && tc->attNumTr == -1))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_ARGUMENT_FOR_TEMPORAL_ADJUSTMENT),
+ errmsg("Needed columns for temporal adjustment not found.")));
+ }
+
+ return (Node *) tc;
+}
+
+/*
+ * transformTemporalClauseResjunk -
+ * If we have a temporal primitive query, the last three columns are P1,
+ * P2, and row_id or RN, which we do not need anymore after temporal
+ * adjustment operations have been accomplished.
+ * However, if the temporal boundaries are range typed columns we split
+ * the range [ts, te) into two separate columns ts and te, which must be
+ * marked as resjunk too.
+ * XXX PEMOSER Use a single loop inside!
+ */
+Node *
+transformTemporalClauseResjunk(Query *qry)
+{
+ TemporalClause *tc = (TemporalClause *) qry->temporalClause;
+
+ /* No temporal clause given, do nothing */
+ if(!tc)
+ return NULL;
+
+ /* Mark P1 and RN columns as junk, we do not need them afterwards. */
+ get_tle_by_resno(qry->targetList, tc->attNumP1)->resjunk = true;
+ get_tle_by_resno(qry->targetList, tc->attNumRN)->resjunk = true;
+
+ /* An aligner has also a P2 column, that must be marked as junk. */
+ if (tc->temporalType == TEMPORAL_TYPE_ALIGNER)
+ get_tle_by_resno(qry->targetList, tc->attNumP2)->resjunk = true;
+
+ /* We use range types, remove splitted columns, i.e. upper/lower bounds */
+ if(tc->colnameTr) {
+ get_tle_by_resno(qry->targetList, tc->attNumTs)->resjunk = true;
+ get_tle_by_resno(qry->targetList, tc->attNumTe)->resjunk = true;
+ }
+
+ /*
+ * Pass the temporal primitive node to the optimizer, to be used later,
+ * to mark unsafe columns, and add attribute indexes.
+ */
+ return (Node *) tc;
+}
+
+/*
+ * addTemporalAlias -
+ * We use internal-use-only columns to store some information used for
+ * temporal primitives. Since we need them over several sub-queries, we
+ * cannot use simply resjunk columns here. We must rename parts of the
+ * parse tree to handle ambiguous columns. In order to reference the right
+ * columns after renaming, we store them inside the current parser state,
+ * and use them afterwards to rename fields. Such attributes could be for
+ * example: P1, P2, or RN.
+ */
+static char *
+addTemporalAlias(ParseState *pstate, char *name, int counter)
+{
+ char *newName = palloc(64);
+
+ /*
+ * Column name for <name> alternative is <name>_N, where N is 0 if no
+ * other column with that pattern has been found, or N + 1 if
+ * the highest number for a <name>_N column is N. N stand for the <counter>.
+ */
+ counter++;
+ sprintf(newName, "%s_%d", name, counter);
+
+ /*
+ * Changed aliases must be remembered by the parser state in
+ * order to use them on nodes above, i.e. if they are used in targetlists,
+ * group-by or order-by clauses outside.
+ */
+ pstate->p_temporal_aliases =
+ lappend(pstate->p_temporal_aliases,
+ list_make2(makeString(name),
+ makeString(newName)));
+
+ return newName;
+}
+
+/*
+ * getColumnCounter -
+ * Check if a column name starts with a certain prefix. If it ends after
+ * the prefix, return found (we ignore the counter in this case). However,
+ * if it continuous with an underscore check if it has a tail after it that
+ * is a string representation of an integer. If so, return this number as
+ * integer (keep the parameter "found" as is).
+ * We use this function to rename "internal-use-only" columns on an
+ * ambiguity error with user-specified columns.
+ */
+static void
+getColumnCounter(const char *colname, const char *prefix,
+ bool *found, int *counter)
+{
+ if(memcmp(colname, prefix, strlen(prefix)) == 0)
+ {
+ colname += strlen(prefix);
+ if(*colname == '\0')
+ *found = true;
+ else if (*colname++ == '_')
+ {
+ char *pos;
+ int n = -1;
+
+ errno = 0;
+ n = strtol(colname, &pos, 10);
+
+ /*
+ * No error and fully parsed (i.e., string contained
+ * only an integer) => save it if it is bigger than
+ * the last.
+ */
+ if(errno == 0 && *pos == 0 && n > *counter)
+ *counter = n;
+ }
+ }
+}
+
+/*
+ * Creates a skeleton query that can be filled with needed fields from both
+ * temporal primitives. This is the common part of both generated to re-use
+ * the same code. It also returns palloc'd names for p1, p2, and rn, where p2
+ * is optional (omit it by passing NULL).
+ *
+ * OUTPUT:
+ * (
+ * SELECT r.*
+ * FROM
+ * (
+ * SELECT *, row_id() OVER () rn FROM r
+ * ) r
+ * LEFT OUTER JOIN
+ * <not set yet>
+ * ON <not set yet>
+ * ORDER BY rn, p1
+ * ) x
+ */
+static SelectStmt *
+makeTemporalQuerySkeleton(JoinExpr *j, char **nameRN, char **nameP1,
+ char **nameP2, bool *hasRangeTypes, Alias **largAlias,
+ Alias **rargAlias)
+{
+ const int UNKNOWN_LOCATION = -1;
+
+ SelectStmt *ssJoinLarg;
+ SelectStmt *ssRowNumber;
+ SelectStmt *ssResult;
+ RangeSubselect *rssJoinLarg;
+ RangeSubselect *rssRowNumber;
+ ResTarget *rtRowNumber;
+ ResTarget *rtAStar;
+ ResTarget *rtAStarWithR;
+ ColumnRef *crAStarWithR;
+ ColumnRef *crAStar;
+ WindowDef *wdRowNumber;
+ FuncCall *fcRowNumber;
+ JoinExpr *joinExpr;
+ SortBy *sb1;
+ SortBy *sb2;
+
+ /*
+ * We can have 2 or 4 column references, i.e. if we have 4, the first two
+ * form the left argument period [largTs, largTe), and the last two the
+ * right argument period respectively. Otherwise, we have two range typed
+ * values of the form '[)' where the first argument contains the boundaries
+ * of the left-hand-side, and the second argument contains the boundaries
+ * of the RHS respectively. The parser checked already if there was another
+ * number of arguments (not equal to 2 or 4) given.
+ */
+ *hasRangeTypes = list_length(j->temporalBounds) == 2;
+
+ /*
+ * These attribute names could cause conflicts, if the left or right
+ * relation has column names like these. We solve this later by renaming
+ * column names when we know which columns are in use, in order to create
+ * unique column names.
+ */
+ *nameRN = pstrdup("rn");
+ *nameP1 = pstrdup("p1");
+ if(nameP2) *nameP2 = pstrdup("p2");
+
+ /* Find aliases of arguments */
+ *largAlias = makeAliasFromArgument(j->larg);
+ *rargAlias = makeAliasFromArgument(j->rarg);
+
+ /*
+ * Build "(SELECT row_id() OVER (), * FROM r) r".
+ * We start with building the resource target for "*".
+ */
+ crAStar = makeColumnRef1((Node *) makeNode(A_Star));
+ rtAStar = makeResTarget((Node *) crAStar, NULL);
+
+ /* Build an empty window definition clause, i.e. "OVER ()" */
+ wdRowNumber = makeNode(WindowDef);
+ wdRowNumber->frameOptions = FRAMEOPTION_DEFAULTS;
+ wdRowNumber->startOffset = NULL;
+ wdRowNumber->endOffset = NULL;
+
+ /*
+ * Build a target for "row_id() OVER ()", row_id() enumerates each tuple
+ * similar to row_number().
+ * The rowid-function is push-down-safe, because we need only unique ids for
+ * each tuple, and do not care about gaps between numbers.
+ */
+ fcRowNumber = makeFuncCall(SystemFuncName("row_id"),
+ NIL,
+ UNKNOWN_LOCATION);
+ fcRowNumber->over = wdRowNumber;
+ rtRowNumber = makeResTarget((Node *) fcRowNumber, NULL);
+ rtRowNumber->name = *nameRN;
+
+ /*
+ * Build sub-select clause with from- and where-clause from the
+ * outer query. Add "row_id() OVER ()" to the target list.
+ */
+ ssRowNumber = makeNode(SelectStmt);
+ ssRowNumber->fromClause = list_make1(j->larg);
+ ssRowNumber->groupClause = NIL;
+ ssRowNumber->whereClause = NULL;
+ ssRowNumber->targetList = list_make2(rtAStar, rtRowNumber);
+
+ /* Build range sub-select */
+ rssRowNumber = makeNode(RangeSubselect);
+ rssRowNumber->subquery = (Node *) ssRowNumber;
+ rssRowNumber->alias = *largAlias;
+ rssRowNumber->lateral = false;
+
+ /* Build resource target for "r.*" */
+ crAStarWithR = makeColumnRef2((Node *) makeString((*largAlias)->aliasname),
+ (Node *) makeNode(A_Star));
+ rtAStarWithR = makeResTarget((Node *) crAStarWithR, NULL);
+
+ /* Build the outer range sub-select */
+ ssJoinLarg = makeNode(SelectStmt);
+ ssJoinLarg->fromClause = list_make1(rssRowNumber);
+ ssJoinLarg->groupClause = NIL;
+ ssJoinLarg->whereClause = NULL;
+
+ /* Build range sub-select */
+ rssJoinLarg = makeNode(RangeSubselect);
+ rssJoinLarg->subquery = (Node *) ssJoinLarg;
+ rssJoinLarg->lateral = false;
+
+ /* Build a join expression */
+ joinExpr = makeNode(JoinExpr);
+ joinExpr->isNatural = false;
+ joinExpr->larg = (Node *) rssRowNumber;
+ joinExpr->jointype = JOIN_LEFT; /* left outer join */
+
+ /*
+ * Copy temporal bounds into temporal primitive subquery join in order to
+ * compare temporal bound var types with actual target list var types. We
+ * do this to trigger an error on type mismatch, before a subquery function
+ * fails and triggers an non-meaningful error (as for example, "operator
+ * does not exists, or similar").
+ */
+ joinExpr->temporalBounds = copyObject(j->temporalBounds);
+
+ sb1 = makeNode(SortBy);
+ sb1->location = UNKNOWN_LOCATION;
+ sb1->node = (Node *) makeColumnRef1((Node *) makeString(*nameRN));
+
+ sb2 = makeNode(SortBy);
+ sb2->location = UNKNOWN_LOCATION;
+ sb2->node = (Node *) makeColumnRef1((Node *) makeString(*nameP1));
+
+ ssResult = makeNode(SelectStmt);
+ ssResult->withClause = NULL;
+ ssResult->fromClause = list_make1(joinExpr);
+ ssResult->targetList = list_make1(rtAStarWithR);
+ ssResult->sortClause = list_make2(sb1, sb2);
+
+ ssResult->temporalClause = makeNode(TemporalClause);
+ if(*hasRangeTypes)
+ {
+ /*
+ * Hardcoded column names for ts and te. We handle ambiguous column
+ * names during the transformation of temporal primitive clauses.
+ */
+ ssResult->temporalClause->colnameTs = "ts";
+ ssResult->temporalClause->colnameTe = "te";
+ ssResult->temporalClause->colnameTr =
+ temporalBoundGetName(j->temporalBounds, TPB_LARGTST);
+ }
+ else
+ {
+ ssResult->temporalClause->colnameTs =
+ temporalBoundGetName(j->temporalBounds, TPB_LARGTST);
+ ssResult->temporalClause->colnameTe =
+ temporalBoundGetName(j->temporalBounds, TPB_LARGTE);
+ ssResult->temporalClause->colnameTr = NULL;
+ }
+
+ /*
+ * We mark the outer sub-query with the current temporal adjustment type,
+ * s.t. the optimizer understands that we need the corresponding temporal
+ * adjustment node above.
+ */
+ ssResult->temporalClause->temporalType =
+ j->jointype == TEMPORAL_ALIGN ? TEMPORAL_TYPE_ALIGNER
+ : TEMPORAL_TYPE_NORMALIZER;
+
+ /* Let the join inside a temporal primitive know which type its parent has */
+ joinExpr->inTmpPrimTempType = ssResult->temporalClause->temporalType;
+ joinExpr->inTmpPrimHasRangeT = *hasRangeTypes;
+
+ return ssResult;
+}
+
+/*
+ * transformTemporalAligner -
+ * transform a TEMPORAL ALIGN clause into standard SQL
+ *
+ * INPUT:
+ * (r ALIGN s ON q WITH (r.ts, r.te, s.ts, s.te)) c
+ * where q can be any join qualifier, and r.ts, r.te, s.ts, and s.te
+ * can be any column name.
+ *
+ * OUTPUT:
+ * (
+ * SELECT r.*, GREATEST(r.ts, s.ts) P1, LEAST(r.te, s.te) P2
+ * FROM
+ * (
+ * SELECT *, row_id() OVER () rn FROM r
+ * ) r
+ * LEFT OUTER JOIN
+ * s
+ * ON q AND r.ts < s.te AND r.te > s.ts
+ * ORDER BY rn, P1, P2
+ * ) c
+ */
+Node *
+transformTemporalAligner(ParseState *pstate, JoinExpr *j)
+{
+ const int UNKNOWN_LOCATION = -1;
+
+ bool hasRangeTypes;
+ SelectStmt *ssResult;
+ RangeSubselect *rssResult;
+ ResTarget *rtGreatest;
+ ResTarget *rtLeast;
+ ResTarget *rtLowerLarg;
+ ResTarget *rtUpperLarg;
+ ColumnRef *crLargTs;
+ ColumnRef *crRargTs;
+ ColumnRef *crLargTe;
+ ColumnRef *crRargTe;
+ MinMaxExpr *mmeGreatest;
+ MinMaxExpr *mmeLeast;
+ FuncCall *fcLowerLarg;
+ FuncCall *fcLowerRarg;
+ FuncCall *fcUpperLarg;
+ FuncCall *fcUpperRarg;
+ List *mmeGreatestArgs;
+ List *mmeLeastArgs;
+ List *boundariesExpr;
+ JoinExpr *joinExpr;
+ A_Expr *lowerBoundExpr;
+ A_Expr *upperBoundExpr;
+ A_Expr *overlapExpr;
+ Node *boolExpr;
+ SortBy *sb3;
+ Alias *largAlias = NULL;
+ Alias *rargAlias = NULL;
+ char *colnameRN;
+ char *colnameP1;
+ char *colnameP2;
+
+ /* Create a select statement skeleton to be filled here */
+ ssResult = makeTemporalQuerySkeleton(j, &colnameRN, &colnameP1,
+ &colnameP2, &hasRangeTypes,
+ &largAlias, &rargAlias);
+
+ /* Temporal aligners do not support the USING-clause */
+ Assert(j->usingClause == NIL);
+
+ /*
+ * Build column references, for use later. If we need only two range types
+ * only Ts columnrefs are used.
+ */
+ if (hasRangeTypes)
+ {
+ crLargTs = temporalBoundGetCopyFQN(j->temporalBounds, TPB_LARGTST,
+ largAlias->aliasname);
+ crRargTs = temporalBoundGetCopyFQN(j->temporalBounds, TPB_RARGTST,
+ rargAlias->aliasname);
+
+ /* Create argument list for function call to "greatest" and "least" */
+ fcLowerLarg = makeFuncCall(SystemFuncName("lower"),
+ list_make1(crLargTs),
+ UNKNOWN_LOCATION);
+ fcLowerRarg = makeFuncCall(SystemFuncName("lower"),
+ list_make1(crRargTs),
+ UNKNOWN_LOCATION);
+ fcUpperLarg = makeFuncCall(SystemFuncName("upper"),
+ list_make1(crLargTs),
+ UNKNOWN_LOCATION);
+ fcUpperRarg = makeFuncCall(SystemFuncName("upper"),
+ list_make1(crRargTs),
+ UNKNOWN_LOCATION);
+ mmeGreatestArgs = list_make2(fcLowerLarg, fcLowerRarg);
+ mmeLeastArgs = list_make2(fcUpperLarg, fcUpperRarg);
+
+ overlapExpr = makeSimpleA_Expr(AEXPR_OP,
+ "&&",
+ copyObject(crLargTs),
+ copyObject(crRargTs),
+ UNKNOWN_LOCATION);
+
+ boundariesExpr = list_make1(overlapExpr);
+
+ rtLowerLarg = makeResTarget((Node *) fcLowerLarg,
+ ssResult->temporalClause->colnameTs);
+ rtUpperLarg = makeResTarget((Node *) fcUpperLarg,
+ ssResult->temporalClause->colnameTe);
+
+ ssResult->targetList = list_concat(ssResult->targetList,
+ list_make2(rtLowerLarg, rtUpperLarg));
+ }
+ else
+ {
+ crLargTs = temporalBoundGetCopyFQN(j->temporalBounds, TPB_LARGTST,
+ largAlias->aliasname);
+ crLargTe = temporalBoundGetCopyFQN(j->temporalBounds, TPB_LARGTE,
+ largAlias->aliasname);
+ crRargTs = temporalBoundGetCopyFQN(j->temporalBounds, TPB_RARGTST,
+ rargAlias->aliasname);
+ crRargTe = temporalBoundGetCopyFQN(j->temporalBounds, TPB_RARGTE,
+ rargAlias->aliasname);
+
+ /* Create argument list for function call to "greatest" and "least" */
+ mmeGreatestArgs = list_make2(crLargTs, crRargTs);
+ mmeLeastArgs = list_make2(crLargTe, crRargTe);
+
+ /*
+ * Build Boolean expressions, i.e. "r.ts < s.te AND r.te > s.ts"
+ * and concatenate it with q (=theta)
+ */
+ lowerBoundExpr = makeSimpleA_Expr(AEXPR_OP,
+ "<",
+ copyObject(crLargTs),
+ copyObject(crRargTe),
+ UNKNOWN_LOCATION);
+ upperBoundExpr = makeSimpleA_Expr(AEXPR_OP,
+ ">",
+ copyObject(crLargTe),
+ copyObject(crRargTs),
+ UNKNOWN_LOCATION);
+
+ boundariesExpr = list_make2(lowerBoundExpr, upperBoundExpr);
+ }
+
+ /* Concatenate all Boolean expressions by AND */
+ boolExpr = (Node *) makeBoolExpr(AND_EXPR,
+ lappend(boundariesExpr, j->quals),
+ UNKNOWN_LOCATION);
+
+ /* Build the function call "greatest(r.ts, s.ts) P1" */
+ mmeGreatest = makeNode(MinMaxExpr);
+ mmeGreatest->args = mmeGreatestArgs;
+ mmeGreatest->location = UNKNOWN_LOCATION;
+ mmeGreatest->op = IS_GREATEST;
+ rtGreatest = makeResTarget((Node *) mmeGreatest, NULL);
+ rtGreatest->name = colnameP1;
+
+ /* Build the function call "least(r.te, s.te) P2" */
+ mmeLeast = makeNode(MinMaxExpr);
+ mmeLeast->args = mmeLeastArgs;
+ mmeLeast->location = UNKNOWN_LOCATION;
+ mmeLeast->op = IS_LEAST;
+ rtLeast = makeResTarget((Node *) mmeLeast, NULL);
+ rtLeast->name = colnameP2;
+
+ sb3 = makeNode(SortBy);
+ sb3->location = UNKNOWN_LOCATION;
+ sb3->node = (Node *) makeColumnRef1((Node *) makeString(colnameP2));
+
+ ssResult->targetList = list_concat(ssResult->targetList,
+ list_make2(rtGreatest, rtLeast));
+ ssResult->sortClause = lappend(ssResult->sortClause, sb3);
+
+ joinExpr = (JoinExpr *) linitial(ssResult->fromClause);
+ joinExpr->rarg = copyObject(j->rarg);
+ joinExpr->quals = boolExpr;
+
+ /* Build range sub-select */
+ rssResult = makeNode(RangeSubselect);
+ rssResult->subquery = (Node *) ssResult;
+ rssResult->alias = copyObject(j->alias);
+ rssResult->lateral = false;
+
+ return copyObject(rssResult);
+}
+
+/*
+ * transformTemporalNormalizer -
+ * transform a TEMPORAL NORMALIZE clause into standard SQL
+ *
+ * INPUT:
+ * (r NORMALIZE s ON q WITH (r.ts, r.te, s.ts, s.te)) c
+ *
+ * -- or --
+ *
+ * (r NORMALIZE s USING(atts) WITH (r.ts, r.te, s.ts, s.te)) c
+ * where q can be any join qualifier and r.ts, r.te, s.ts, and s.te
+ * can be any column name.
+ *
+ * OUTPUT:
+ * (
+ * SELECT r.*,
+ * FROM
+ * (
+ * SELECT *, row_id() OVER () rn FROM r
+ * ) r
+ * LEFT OUTER JOIN
+ * (
+ * SELECT s.*, ts P1 FROM s
+ * UNION ALL
+ * SELECT s.*, te P1 FROM s
+ * ) s
+ * ON q AND P1 >= r.ts AND P1 < r.te
+ * ORDER BY rn, P1
+ * ) c
+ *
+ * -- or --
+ *
+ * (
+ * SELECT r.*,
+ * FROM
+ * (
+ * SELECT *, row_id() OVER () rn FROM r
+ * ) r
+ * LEFT OUTER JOIN
+ * (
+ * SELECT atts, ts P1 FROM s
+ * UNION
+ * SELECT atts, te P1 FROM s
+ * ) s
+ * ON r.atts = s.atts AND P1 >= r.ts AND P1 < r.te
+ * ORDER BY rn, P1
+ * ) c
+ */
+Node *
+transformTemporalNormalizer(ParseState *pstate, JoinExpr *j)
+{
+ const int UNKNOWN_LOCATION = -1;
+
+ SelectStmt *ssTsP1;
+ SelectStmt *ssTeP1;
+ SelectStmt *ssUnionAll;
+ SelectStmt *ssResult;
+ RangeSubselect *rssUnionAll;
+ RangeSubselect *rssResult;
+ ResTarget *rtRargStar;
+ ResTarget *rtTsP1;
+ ResTarget *rtTeP1;
+ ResTarget *rtLowerLarg;
+ ResTarget *rtUpperLarg;
+ ColumnRef *crRargStar;
+ ColumnRef *crLargTsT = NULL;
+ ColumnRef *crRargTsT = NULL;
+ ColumnRef *crLargTe = NULL;
+ ColumnRef *crRargTe = NULL;
+ ColumnRef *crP1;
+ JoinExpr *joinExpr;
+ A_Expr *lowerBoundExpr;
+ A_Expr *upperBoundExpr;
+ A_Expr *containsExpr;
+ Node *boolExpr;
+ Alias *largAlias;
+ Alias *rargAlias;
+ char *colnameRN;
+ char *colnameP1;
+ bool hasRangeTypes;
+ FuncCall *fcLowerLarg = NULL;
+ FuncCall *fcUpperLarg = NULL;
+ FuncCall *fcLowerRarg = NULL;
+ FuncCall *fcUpperRarg = NULL;
+ List *boundariesExpr;
+
+ /* Create a select statement skeleton to be filled here */
+ ssResult = makeTemporalQuerySkeleton(j, &colnameRN, &colnameP1,
+ NULL, &hasRangeTypes,
+ &largAlias, &rargAlias);
+
+ /* Build resource target for "s.*" to use it later. */
+ crRargStar = makeColumnRef2((Node *) makeString(rargAlias->aliasname),
+ (Node *) makeNode(A_Star));
+
+ crP1 = makeColumnRef1((Node *) makeString(colnameP1));
+
+ /*
+ * Build column references, for use later. If we need only two range types
+ * only Ts columnrefs are used.
+ */
+ if (hasRangeTypes)
+ {
+ crLargTsT = temporalBoundGetCopyFQN(j->temporalBounds, TPB_LARGTST,
+ largAlias->aliasname);
+ crRargTsT = temporalBoundGetCopyFQN(j->temporalBounds, TPB_RARGTST,
+ rargAlias->aliasname);
+
+ /* Create argument list for function call to "greatest" and "least" */
+ fcLowerLarg = makeFuncCall(SystemFuncName("lower"),
+ list_make1(crLargTsT),
+ UNKNOWN_LOCATION);
+ fcLowerRarg = makeFuncCall(SystemFuncName("lower"),
+ list_make1(crRargTsT),
+ UNKNOWN_LOCATION);
+ fcUpperLarg = makeFuncCall(SystemFuncName("upper"),
+ list_make1(crLargTsT),
+ UNKNOWN_LOCATION);
+ fcUpperRarg = makeFuncCall(SystemFuncName("upper"),
+ list_make1(crRargTsT),
+ UNKNOWN_LOCATION);
+
+ /* Build resource target "lower(s.t) P1" and "upper(s.t) P1" */
+ rtTsP1 = makeResTarget((Node *) fcLowerRarg, colnameP1);
+ rtTeP1 = makeResTarget((Node *) fcUpperRarg, colnameP1);
+
+ rtLowerLarg = makeResTarget((Node *) fcLowerLarg,
+ ssResult->temporalClause->colnameTs);
+ rtUpperLarg = makeResTarget((Node *) fcUpperLarg,
+ ssResult->temporalClause->colnameTe);
+
+ ssResult->targetList = list_concat(ssResult->targetList,
+ list_make2(rtLowerLarg, rtUpperLarg));
+ /*
+ * Build "contains" expression for range types, i.e. "P1 <@ t"
+ * and concatenate it with q (=theta)
+ */
+ containsExpr = makeSimpleA_Expr(AEXPR_OP,
+ "<@",
+ copyObject(crP1),
+ copyObject(crLargTsT),
+ UNKNOWN_LOCATION);
+
+ boundariesExpr = list_make1(containsExpr);
+ }
+ else
+ {
+ /*
+ * Build column references, for use later.
+ */
+ crLargTsT = temporalBoundGetCopyFQN(j->temporalBounds, TPB_LARGTST,
+ largAlias->aliasname);
+ crLargTe = temporalBoundGetCopyFQN(j->temporalBounds, TPB_LARGTE,
+ largAlias->aliasname);
+ crRargTsT = temporalBoundGetCopyFQN(j->temporalBounds, TPB_RARGTST,
+ rargAlias->aliasname);
+ crRargTe = temporalBoundGetCopyFQN(j->temporalBounds, TPB_RARGTE,
+ rargAlias->aliasname);
+
+ /* Build resource target "ts P1" and "te P1" */
+ rtTsP1 = makeResTarget((Node *) crRargTsT, colnameP1);
+ rtTeP1 = makeResTarget((Node *) crRargTe, colnameP1);
+ /*
+ * Build "contains" expressions, i.e. "P1 >= ts AND P1 < te"
+ * and concatenate it with q (=theta)
+ */
+ lowerBoundExpr = makeSimpleA_Expr(AEXPR_OP,
+ ">=",
+ copyObject(crP1),
+ copyObject(crLargTsT),
+ UNKNOWN_LOCATION);
+ upperBoundExpr = makeSimpleA_Expr(AEXPR_OP,
+ "<",
+ copyObject(crP1),
+ copyObject(crLargTe),
+ UNKNOWN_LOCATION);
+
+ boundariesExpr = list_make2(lowerBoundExpr, upperBoundExpr);
+ }
+
+ /*
+ * Build "SELECT s.*, ts P1 FROM s" and "SELECT s.*, te P1 FROM s", iff we
+ * have a ON-clause.
+ * If we have an USING-clause with a name-list 'atts' build "SELECT atts,
+ * ts P1 FROM s" and "SELECT atts, te P1 FROM s"
+ */
+
+ ssTsP1 = makeNode(SelectStmt);
+ ssTsP1->fromClause = list_make1(j->rarg);
+ ssTsP1->groupClause = NIL;
+ ssTsP1->whereClause = NULL;
+
+ ssTeP1 = copyObject(ssTsP1);
+
+ if (j->usingClause)
+ {
+ ListCell *usingItem;
+ A_Expr *expr;
+ List *qualList = NIL;
+ char *colnameTs = ssResult->temporalClause->colnameTs;
+ char *colnameTe = ssResult->temporalClause->colnameTe;
+ char *colnameTr = ssResult->temporalClause->colnameTr;
+
+ Assert(j->quals == NULL); /* shouldn't have ON() too */
+
+ foreach(usingItem, j->usingClause)
+ {
+ char *usingItemName = strVal(lfirst(usingItem));
+ ColumnRef *crUsingItemL =
+ makeColumnRef2((Node *) makeString(largAlias->aliasname),
+ (Node *) makeString(usingItemName));
+ ColumnRef *crUsingItemR =
+ makeColumnRef2((Node *) makeString(rargAlias->aliasname),
+ (Node *) makeString(usingItemName));
+ ResTarget *rtUsingItemR = makeResTarget((Node *) crUsingItemR,
+ NULL);
+
+ /*
+ * Skip temporal attributes, because temporal normalizer's USING
+ * list must contain only non-temporal attributes. We allow
+ * temporal attributes as input, such that we can copy colname lists
+ * to create temporal normalizers easier.
+ */
+ if(strcmp(usingItemName, colnameTs) == 0
+ || strcmp(usingItemName, colnameTe) == 0
+ || (colnameTr && strcmp(usingItemName, colnameTr) == 0))
+ continue;
+
+ expr = makeSimpleA_Expr(AEXPR_OP,
+ "=",
+ copyObject(crUsingItemL),
+ copyObject(crUsingItemR),
+ UNKNOWN_LOCATION);
+
+ qualList = lappend(qualList, expr);
+
+ ssTsP1->targetList = lappend(ssTsP1->targetList, rtUsingItemR);
+ ssTeP1->targetList = lappend(ssTeP1->targetList, rtUsingItemR);
+ }
+
+ j->quals = (Node *) makeBoolExpr(AND_EXPR, qualList, UNKNOWN_LOCATION);
+ }
+ else if (j->quals)
+ {
+ rtRargStar = makeResTarget((Node *) crRargStar, NULL);
+ ssTsP1->targetList = list_make1(rtRargStar);
+ ssTeP1->targetList = list_make1(rtRargStar);
+ }
+
+ ssTsP1->targetList = lappend(ssTsP1->targetList, rtTsP1);
+ ssTeP1->targetList = lappend(ssTeP1->targetList, rtTeP1);
+
+ /*
+ * Build sub-select for "( SELECT ... UNION ALL SELECT ... ) s", i.e.,
+ * build an union between two select-clauses, i.e. a select-clause with
+ * set-operation set to "union".
+ */
+ ssUnionAll = makeNode(SelectStmt);
+ ssUnionAll->op = SETOP_UNION;
+ ssUnionAll->all = j->usingClause == NIL; /* true, if ON-clause */
+ ssUnionAll->larg = ssTsP1;
+ ssUnionAll->rarg = ssTeP1;
+
+ /* Build range sub-select for "( ...UNION ALL... ) s" */
+ rssUnionAll = makeNode(RangeSubselect);
+ rssUnionAll->subquery = (Node *) ssUnionAll;
+ rssUnionAll->alias = rargAlias;
+ rssUnionAll->lateral = false;
+
+ /*
+ * Create a conjunction of all Boolean expressions
+ */
+ if (j->quals)
+ {
+ boolExpr = (Node *) makeBoolExpr(AND_EXPR,
+ lappend(boundariesExpr, j->quals),
+ UNKNOWN_LOCATION);
+ }
+ else /* empty USING() clause found, i.e. theta = true */
+ {
+ boolExpr = (Node *) makeBoolExpr(AND_EXPR,
+ boundariesExpr,
+ UNKNOWN_LOCATION);
+ ssUnionAll->all = false;
+
+ }
+
+ joinExpr = (JoinExpr *) linitial(ssResult->fromClause);
+ joinExpr->rarg = (Node *) rssUnionAll;
+ joinExpr->quals = boolExpr;
+
+ /* Build range sub-select */
+ rssResult = makeNode(RangeSubselect);
+ rssResult->subquery = (Node *) ssResult;
+ rssResult->alias = copyObject(j->alias);
+ rssResult->lateral = false;
+
+ return copyObject(rssResult);
+}
+
+/*
+ * typeGet -
+ * Return the type of a tuple from the system cache for a given OID.
+ */
+static Form_pg_type
+typeGet(Oid id)
+{
+ HeapTuple tp;
+ Form_pg_type typtup;
+
+ tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(id));
+ if (!HeapTupleIsValid(tp))
+ ereport(ERROR,
+ (errcode(ERROR),
+ errmsg("cache lookup failed for type %u", id)));
+
+ typtup = (Form_pg_type) GETSTRUCT(tp);
+ ReleaseSysCache(tp);
+ return typtup;
+}
+
+/*
+ * internalUseOnlyColumnNames -
+ * Creates a list of all internal-use-only column names, depending on the
+ * temporal primitive type (i.e., normalizer or aligner). These column
+ * names also differ depending on weither we have range types or scalars
+ * for temporal bounds. The list is then compared with the aliases from
+ * the current parser state, and renamed if necessary.
+ */
+static List *
+internalUseOnlyColumnNames(ParseState *pstate,
+ bool hasRangeTypes,
+ TemporalType tmpType)
+{
+ List *filter = NIL;
+ ListCell *lcFilter;
+ ListCell *lcAlias;
+
+ filter = list_make2(makeString("rn"), makeString("p1"));
+
+ if(tmpType == TEMPORAL_TYPE_ALIGNER)
+ filter = lappend(filter, makeString("p2"));
+
+ /* We split range types into upper and lower bounds, called ts and te */
+ if(hasRangeTypes)
+ {
+ filter = lappend(filter, makeString("ts"));
+ filter = lappend(filter, makeString("te"));
+ }
+
+ foreach(lcFilter, filter)
+ {
+ Value *filterValue = (Value *) lfirst(lcFilter);
+ char *filterName = strVal(filterValue);
+
+ foreach(lcAlias, pstate->p_temporal_aliases)
+ {
+ char *aliasKey = strVal(linitial((List *) lfirst(lcAlias)));
+ char *aliasValue = strVal(lsecond((List *) lfirst(lcAlias)));
+
+ if(strcmp(filterName, aliasKey) == 0 )
+ filterValue->val.str = pstrdup(aliasValue);
+ }
+ }
+
+ return filter;
+}
+
+/*
+ * temporalBoundCheckIntegrity -
+ * For each column name check if it is a temporal bound. If so, check
+ * also if it does not clash with an internal-use-only column name, and if
+ * the attribute types match with the range type predicate. This means, if
+ * we have only one item in boundary list, all bounds must be range types.
+ * Otherwise, all bounds must be scalars.
+ */
+static void
+temporalBoundCheckIntegrity(ParseState *pstate,
+ List *bounds,
+ List *colnames,
+ List *colvars,
+ TemporalType tmpType)
+{
+ ListCell *lcNames;
+ ListCell *lcVars;
+ ListCell *lcBound;
+ ListCell *lcFilter;
+ bool hasRangeTypes = list_length(bounds) == 1;
+ List *filter = internalUseOnlyColumnNames(pstate,
+ hasRangeTypes,
+ tmpType);
+
+ forboth(lcNames, colnames, lcVars, colvars)
+ {
+ char *name = strVal((Value *) lfirst(lcNames));
+ Var *var = (Var *) lfirst(lcVars);
+
+ foreach(lcBound, bounds)
+ {
+ ColumnRef *crb = (ColumnRef *) lfirst(lcBound);
+ char *nameb = strVal((Value *) llast(crb->fields));
+
+ if(strcmp(nameb, name) == 0)
+ {
+ char *msg = "";
+ Form_pg_type type;
+
+ foreach(lcFilter, filter)
+ {
+ char *n = strVal((Value *) lfirst(lcFilter));
+ if(strcmp(n, name) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" does not exist", n),
+ parser_errposition(pstate, crb->location)));
+ }
+
+ type = typeGet(var->vartype);
+
+ if(hasRangeTypes && type->typtype != TYPTYPE_RANGE)
+ msg = "Invalid column type \"%s\" for the temporal bound " \
+ "\"%s\". It must be a range type column.";
+
+ if(! hasRangeTypes && type->typtype == TYPTYPE_RANGE)
+ msg = "Invalid column type \"%s\" for the temporal bound " \
+ "\"%s\". It must be a scalar type column (i.e., " \
+ "not a range type).";
+
+ if (strlen(msg) > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg(msg,
+ NameStr(type->typname),
+ NameListToString(crb->fields)),
+ errhint("Specify four scalar columns for the " \
+ "temporal boundaries, or two range-typed "\
+ "columns."),
+ parser_errposition(pstate, crb->location)));
+
+ }
+ }
+ }
+
+}
+
+
+/*
+ * transformTemporalClauseAmbiguousColumns -
+ * Rename columns automatically to unique not-in-use column names, if
+ * column names clash with internal-use-only columns of temporal
+ * primitives.
+ */
+void
+transformTemporalClauseAmbiguousColumns(ParseState* pstate, JoinExpr* j,
+ List* l_colnames, List* r_colnames,
+ List *l_colvars, List *r_colvars,
+ RangeTblEntry* l_rte,
+ RangeTblEntry* r_rte)
+{
+ ListCell *l = NULL;
+ bool foundP1 = false;
+ bool foundP2 = false;
+ bool foundRN = false;
+ bool foundTS = false;
+ bool foundTE = false;
+ int counterP1 = -1;
+ int counterP2 = -1;
+ int counterRN = -1;
+ int counterTS = -1;
+ int counterTE = -1;
+
+ /* Nothing to do, if we have no temporal primitive */
+ if (j->inTmpPrimTempType == TEMPORAL_TYPE_NONE)
+ return;
+
+ /*
+ * Check ambiguity of column names, search for p1, p2, and rn
+ * columns and rename them accordingly to X_N, where X = {p1,p2,rn},
+ * and N is the highest number after X_ starting from 0. This is, if we do
+ * not find any X_N column pattern the new column is renamed to X_0.
+ */
+ foreach(l, l_colnames)
+ {
+ const char *colname = strVal((Value *) lfirst(l));
+
+ /*
+ * Skip the last entry of the left column names, i.e. row_id
+ * is only an internally added column by both temporal
+ * primitives.
+ */
+ if (l == list_tail(l_colnames))
+ continue;
+
+ getColumnCounter(colname, "p1", &foundP1, &counterP1);
+ getColumnCounter(colname, "rn", &foundRN, &counterRN);
+
+ /* Only temporal aligners have a p2 column */
+ if (j->inTmpPrimTempType == TEMPORAL_TYPE_ALIGNER)
+ getColumnCounter(colname, "p2", &foundP2, &counterP2);
+
+ if (j->inTmpPrimHasRangeT)
+ {
+ getColumnCounter(colname, "ts", &foundTS, &counterTS);
+ getColumnCounter(colname, "te", &foundTE, &counterTE);
+ }
+ }
+
+ foreach(l, r_colnames)
+ {
+ const char *colname = strVal((Value *) lfirst(l));
+
+ /*
+ * The temporal normalizer adds also a column called p1 which is
+ * the union of te and ts interval boundaries. We ignore it here
+ * since it does not belong to the user defined columns of the
+ * given input, iff it is the last entry of the column list.
+ */
+ if (j->inTmpPrimTempType == TEMPORAL_TYPE_NORMALIZER
+ && l == list_tail(r_colnames))
+ continue;
+
+ getColumnCounter(colname, "p1", &foundP1, &counterP1);
+ getColumnCounter(colname, "rn", &foundRN, &counterRN);
+
+ /* Only temporal aligners have a p2 column */
+ if (j->inTmpPrimTempType == TEMPORAL_TYPE_ALIGNER)
+ getColumnCounter(colname, "p2", &foundP2, &counterP2);
+
+ if (j->inTmpPrimHasRangeT)
+ {
+ getColumnCounter(colname, "ts", &foundTS, &counterTS);
+ getColumnCounter(colname, "te", &foundTE, &counterTE);
+ }
+ }
+
+ if (foundP1)
+ {
+ char *name = addTemporalAlias(pstate, "p1", counterP1);
+
+ /*
+ * The right subtree gets now a new name for the column p1.
+ * In addition, we rename both expressions used for temporal
+ * boundary checks. It is fixed that they are at the end of this
+ * join's qualifier list.
+ * Only temporal normalization needs these steps.
+ */
+ if (j->inTmpPrimTempType == TEMPORAL_TYPE_NORMALIZER)
+ {
+ A_Expr *e1;
+ A_Expr *e2;
+ List *qualArgs;
+ bool hasRangeTypes = list_length(j->temporalBounds) == 2;
+
+ llast(r_rte->eref->colnames) = makeString(name);
+ llast(r_colnames) = makeString(name);
+
+ qualArgs = ((BoolExpr *) j->quals)->args;
+ e1 = (A_Expr *) linitial(qualArgs);
+ linitial(((ColumnRef *)e1->lexpr)->fields) = makeString(name);
+
+ if(! hasRangeTypes)
+ {
+ e2 = (A_Expr *) lsecond(qualArgs);
+ linitial(((ColumnRef *)e2->lexpr)->fields) = makeString(name);
+ }
+ }
+ }
+
+ if (foundRN)
+ {
+ char *name = addTemporalAlias(pstate, "rn", counterRN);
+
+ /* The left subtree has now a new name for the column rn */
+ llast(l_rte->eref->colnames) = makeString(name);
+ llast(l_colnames) = makeString(name);
+ }
+
+ if (foundP2)
+ addTemporalAlias(pstate, "p2", counterP2);
+
+ if (foundTS)
+ addTemporalAlias(pstate, "ts", counterTS);
+
+ if (foundTE)
+ addTemporalAlias(pstate, "te", counterTE);
+
+ temporalBoundCheckIntegrity(pstate,
+ temporalBoundGetLeftBounds(j->temporalBounds),
+ l_colnames, l_colvars, j->inTmpPrimTempType);
+
+
+ temporalBoundCheckIntegrity(pstate,
+ temporalBoundGetRightBounds(j->temporalBounds),
+ r_colnames, r_colvars, j->inTmpPrimTempType);
+
+}
+
+/*
+ * makeTemporalNormalizer -
+ * Creates a temporal normalizer join expression.
+ * XXX PEMOSER Should we create a separate temporal primitive expression?
+ */
+JoinExpr *
+makeTemporalNormalizer(Node *larg, Node *rarg, List *bounds, Node *quals,
+ Alias *alias)
+{
+ JoinExpr *j = makeNode(JoinExpr);
+
+ if(! ((IsA(larg, RangeSubselect) || IsA(larg, RangeVar)) &&
+ (IsA(rarg, RangeSubselect) || IsA(rarg, RangeVar))))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("Normalizer arguments must be of type RangeVar or " \
+ "RangeSubselect.")));
+
+ j->jointype = TEMPORAL_NORMALIZE;
+
+ /*
+ * Qualifiers can be an boolean expression or an USING clause, i.e. a list
+ * of column names.
+ */
+ if(quals == (Node *) NIL || IsA(quals, List))
+ j->usingClause = (List *) quals;
+ else
+ j->quals = quals;
+
+ j->larg = larg;
+ j->rarg = rarg;
+ j->alias = alias;
+ j->temporalBounds = bounds;
+ j->inTmpPrimHasRangeT = list_length(bounds) == 2;
+
+ return j;
+}
+
+/*
+ * makeTemporalAligner -
+ * Creates a temporal aligner join expression.
+ * XXX PEMOSER Should we create a separate temporal primitive expression?
+ */
+JoinExpr *
+makeTemporalAligner(Node *larg, Node *rarg, List *bounds, Node *quals,
+ Alias *alias)
+{
+ JoinExpr *j = makeNode(JoinExpr);
+
+ if(! ((IsA(larg, RangeSubselect) || IsA(larg, RangeVar)) &&
+ (IsA(rarg, RangeSubselect) || IsA(rarg, RangeVar))))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("Aligner arguments must be of type RangeVar or " \
+ "RangeSubselect.")));
+
+ j->jointype = TEMPORAL_ALIGN;
+
+ /* Empty quals allowed (i.e., NULL), but no LISTS */
+ if(quals && IsA(quals, List))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("Aligner do not support an USING clause.")));
+ else
+ j->quals = quals;
+
+ j->larg = larg;
+ j->rarg = rarg;
+ j->alias = alias;
+ j->temporalBounds = bounds;
+ j->inTmpPrimHasRangeT = list_length(bounds) == 2;
+
+ return j;
+}
+
diff --git src/backend/utils/adt/windowfuncs.c src/backend/utils/adt/windowfuncs.c
index 3c1d3cf..e22814b 100644
--- src/backend/utils/adt/windowfuncs.c
+++ src/backend/utils/adt/windowfuncs.c
@@ -88,6 +88,19 @@ window_row_number(PG_FUNCTION_ARGS)
PG_RETURN_INT64(curpos + 1);
}
+/*
+ * row_id
+ * just increment up from 1 until current partition finishes.
+ */
+Datum
+window_row_id(PG_FUNCTION_ARGS)
+{
+ WindowObject winobj = PG_WINDOW_OBJECT();
+ int64 curpos = WinGetCurrentPosition(winobj);
+
+ WinSetMarkPosition(winobj, curpos);
+ PG_RETURN_INT64(curpos + 1);
+}
/*
* rank
diff --git src/backend/utils/errcodes.txt src/backend/utils/errcodes.txt
index e7bdb92..ee42cee 100644
--- src/backend/utils/errcodes.txt
+++ src/backend/utils/errcodes.txt
@@ -204,6 +204,7 @@ Section: Class 22 - Data Exception
2200N E ERRCODE_INVALID_XML_CONTENT invalid_xml_content
2200S E ERRCODE_INVALID_XML_COMMENT invalid_xml_comment
2200T E ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION invalid_xml_processing_instruction
+220T0 E ERRCODE_INVALID_ARGUMENT_FOR_TEMPORAL_ADJUSTMENT invalid_argument_for_temporal_adjustment
Section: Class 23 - Integrity Constraint Violation
diff --git src/include/catalog/pg_proc.h src/include/catalog/pg_proc.h
index cd7b909..735fa4f 100644
--- src/include/catalog/pg_proc.h
+++ src/include/catalog/pg_proc.h
@@ -4991,6 +4991,8 @@ DATA(insert OID = 3113 ( last_value PGNSP PGUID 12 1 0 0 0 f t f f t f i s 1 0
DESCR("fetch the last row value");
DATA(insert OID = 3114 ( nth_value PGNSP PGUID 12 1 0 0 0 f t f f t f i s 2 0 2283 "2283 23" _null_ _null_ _null_ _null_ _null_ window_nth_value _null_ _null_ _null_ ));
DESCR("fetch the Nth row value");
+DATA(insert OID = 3999 ( row_id PGNSP PGUID 12 1 0 0 0 f t f f f f i s 0 0 20 "" _null_ _null_ _null_ _null_ _null_ window_row_id _null_ _null_ _null_ ));
+DESCR("row id within partition");
/* functions for range types */
DATA(insert OID = 3832 ( anyrange_in PGNSP PGUID 12 1 0 0 0 f f f f t f s s 3 0 3831 "2275 26 23" _null_ _null_ _null_ _null_ _null_ anyrange_in _null_ _null_ _null_ ));
diff --git src/include/executor/nodeTemporalAdjustment.h src/include/executor/nodeTemporalAdjustment.h
new file mode 100644
index 0000000..7a4be3d
--- /dev/null
+++ src/include/executor/nodeTemporalAdjustment.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeTemporalAdjustment.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/nodeLimit.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODETEMPORALADJUSTMENT_H
+#define NODETEMPORALADJUSTMENT_H
+
+#include "nodes/execnodes.h"
+
+extern TemporalAdjustmentState *ExecInitTemporalAdjustment(TemporalAdjustment *node, EState *estate, int eflags);
+extern TupleTableSlot *ExecTemporalAdjustment(TemporalAdjustmentState *node);
+extern void ExecEndTemporalAdjustment(TemporalAdjustmentState *node);
+extern void ExecReScanTemporalAdjustment(TemporalAdjustmentState *node);
+
+#endif /* NODETEMPORALADJUSTMENT_H */
diff --git src/include/nodes/execnodes.h src/include/nodes/execnodes.h
index 1de5c81..16483bd 100644
--- src/include/nodes/execnodes.h
+++ src/include/nodes/execnodes.h
@@ -1273,6 +1273,30 @@ typedef struct ScanState
} ScanState;
/* ----------------
+ * TemporalAdjustmentState information
+ * ----------------
+ */
+typedef struct TemporalAdjustmentState
+{
+ ScanState ss;
+ bool firstCall; /* Setup on first call already done? */
+ bool alignment; /* true = align; false = normalize */
+ bool sameleft; /* Is the previous and current tuple
+ from the same group? */
+ Datum sweepline; /* Sweep line status */
+ int64 outrn; /* temporal aligner group-id */
+ TemporalClause *temporalCl;
+ bool *nullMask; /* See heap_modify_tuple */
+ bool *tsteMask; /* See heap_modify_tuple */
+ Datum *newValues; /* tuple values that get updated */
+ MemoryContext tempContext;
+ FunctionCallInfoData eqFuncCallInfo; /* calling equal */
+ FunctionCallInfoData ltFuncCallInfo; /* calling less-than */
+ FunctionCallInfoData rcFuncCallInfo; /* calling range_constructor2 */
+ Form_pg_attribute datumFormat; /* Datum format of sweepline, P1, P2 */
+} TemporalAdjustmentState;
+
+/* ----------------
* SeqScanState information
* ----------------
*/
diff --git src/include/nodes/makefuncs.h src/include/nodes/makefuncs.h
index 47500cb..05d8587 100644
--- src/include/nodes/makefuncs.h
+++ src/include/nodes/makefuncs.h
@@ -85,5 +85,9 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
DefElemAction defaction, int location);
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+extern ColumnRef *makeColumnRef1(Node *field1);
+extern ColumnRef *makeColumnRef2(Node *field1, Node *field2);
+extern ResTarget *makeResTarget(Node *val, char *name);
+extern Alias *makeAliasFromArgument(Node *arg);
#endif /* MAKEFUNC_H */
diff --git src/include/nodes/nodes.h src/include/nodes/nodes.h
index c514d3f..5eedb3b 100644
--- src/include/nodes/nodes.h
+++ src/include/nodes/nodes.h
@@ -79,6 +79,7 @@ typedef enum NodeTag
T_SetOp,
T_LockRows,
T_Limit,
+ T_TemporalAdjustment,
/* these aren't subclasses of Plan: */
T_NestLoopParam,
T_PlanRowMark,
@@ -127,6 +128,7 @@ typedef enum NodeTag
T_SetOpState,
T_LockRowsState,
T_LimitState,
+ T_TemporalAdjustmentState,
/*
* TAGS FOR PRIMITIVE NODES (primnodes.h)
@@ -257,6 +259,7 @@ typedef enum NodeTag
T_LockRowsPath,
T_ModifyTablePath,
T_LimitPath,
+ T_TemporalAdjustmentPath,
/* these aren't subclasses of Path: */
T_EquivalenceClass,
T_EquivalenceMember,
@@ -459,6 +462,7 @@ typedef enum NodeTag
T_PartitionSpec,
T_PartitionBoundSpec,
T_PartitionRangeDatum,
+ T_TemporalClause,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
@@ -661,7 +665,14 @@ typedef enum JoinType
* by the executor (nor, indeed, by most of the planner).
*/
JOIN_UNIQUE_OUTER, /* LHS path must be made unique */
- JOIN_UNIQUE_INNER /* RHS path must be made unique */
+ JOIN_UNIQUE_INNER, /* RHS path must be made unique */
+
+
+ /*
+ * Temporal adjustment primitives
+ */
+ TEMPORAL_ALIGN,
+ TEMPORAL_NORMALIZE
/*
* We might need additional join types someday.
diff --git src/include/nodes/parsenodes.h src/include/nodes/parsenodes.h
index fc532fb..dae2c82 100644
--- src/include/nodes/parsenodes.h
+++ src/include/nodes/parsenodes.h
@@ -162,6 +162,8 @@ typedef struct Query
* are only added during rewrite and
* therefore are not written out as
* part of Query. */
+
+ Node *temporalClause; /* temporal primitive node */
} Query;
@@ -1417,6 +1419,8 @@ typedef struct SelectStmt
List *lockingClause; /* FOR UPDATE (list of LockingClause's) */
WithClause *withClause; /* WITH clause */
+ TemporalClause *temporalClause; /* Temporal primitive node */
+
/*
* These fields are used only in upper-level SelectStmts.
*/
diff --git src/include/nodes/plannodes.h src/include/nodes/plannodes.h
index e2fbc7d..b30dd5f 100644
--- src/include/nodes/plannodes.h
+++ src/include/nodes/plannodes.h
@@ -200,6 +200,24 @@ typedef struct ModifyTable
} ModifyTable;
/* ----------------
+ * TemporalAdjustment node -
+ * Generate a temporal adjustment node as temporal aligner or normalizer.
+ * ----------------
+ */
+typedef struct TemporalAdjustment
+{
+ Plan plan;
+ int numCols; /* number of columns in total */
+ Oid eqOperatorID; /* equality operator to compare with */
+ Oid ltOperatorID; /* less-than operator to compare with */
+ Oid sortCollationID; /* sort operator collation id */
+ TemporalClause *temporalCl; /* Temporal type, attribute numbers,
+ and colnames */
+ Var *rangeVar; /* targetlist entry of the given range
+ type used to call range_constructor */
+} TemporalAdjustment;
+
+/* ----------------
* Append node -
* Generate the concatenation of the results of sub-plans.
* ----------------
diff --git src/include/nodes/primnodes.h src/include/nodes/primnodes.h
index 65510b0..e1e09c8 100644
--- src/include/nodes/primnodes.h
+++ src/include/nodes/primnodes.h
@@ -58,6 +58,35 @@ typedef enum OnCommitAction
ONCOMMIT_DROP /* ON COMMIT DROP */
} OnCommitAction;
+/* Options for temporal primitives used by queries with temporal alignment */
+typedef enum TemporalType
+{
+ TEMPORAL_TYPE_NONE,
+ TEMPORAL_TYPE_ALIGNER,
+ TEMPORAL_TYPE_NORMALIZER
+} TemporalType;
+
+typedef struct TemporalClause
+{
+ NodeTag type;
+ TemporalType temporalType; /* Type of temporal primitives */
+
+ /*
+ * Attribute number or column position for internal-use-only columns, and
+ * temporal boundaries
+ */
+ AttrNumber attNumTs;
+ AttrNumber attNumTe;
+ AttrNumber attNumTr;
+ AttrNumber attNumP1;
+ AttrNumber attNumP2;
+ AttrNumber attNumRN;
+
+ char *colnameTs;
+ char *colnameTe;
+ char *colnameTr; /* If range type used for bounds, or NULL */
+} TemporalClause;
+
/*
* RangeVar - range variable, used in FROM clauses
*
@@ -1422,6 +1451,10 @@ typedef struct JoinExpr
Node *quals; /* qualifiers on join, if any */
Alias *alias; /* user-written alias clause, if any */
int rtindex; /* RT index assigned for join, or 0 */
+ List *temporalBounds; /* columns that form bounds for both subtrees,
+ * used by temporal adjustment primitives */
+ TemporalType inTmpPrimTempType; /* inside a temporal primitive clause */
+ bool inTmpPrimHasRangeT; /* true, if bounds are range types */
} JoinExpr;
/*----------
diff --git src/include/nodes/print.h src/include/nodes/print.h
index 431d72d..2f875c0 100644
--- src/include/nodes/print.h
+++ src/include/nodes/print.h
@@ -30,5 +30,6 @@ extern void print_expr(const Node *expr, const List *rtable);
extern void print_pathkeys(const List *pathkeys, const List *rtable);
extern void print_tl(const List *tlist, const List *rtable);
extern void print_slot(TupleTableSlot *slot);
+extern void print_namespace(const List *namespace);
#endif /* PRINT_H */
diff --git src/include/nodes/relation.h src/include/nodes/relation.h
index 3a1255a..70d8fe7 100644
--- src/include/nodes/relation.h
+++ src/include/nodes/relation.h
@@ -1047,6 +1047,25 @@ typedef struct SubqueryScanPath
} SubqueryScanPath;
/*
+ * TemporalAdjustmentPath represents a scan of a rewritten temporal subquery.
+ *
+ * Depending, whether it is a temporal normalizer or a temporal aligner, we have
+ * different subqueries below the temporal adjustment node, but for sure there
+ * is a sort clause on top of the rewritten subquery for both temporal
+ * primitives. We remember this sort clause, because we need to fetch equality,
+ * sort operator, and collation Oids from it. Which will then re-used for the
+ * temporal primitive clause.
+ */
+typedef struct TemporalAdjustmentPath
+{
+ Path path;
+ Path *subpath; /* path representing subquery execution */
+ List *sortClause;
+ TemporalClause *temporalClause;
+} TemporalAdjustmentPath;
+
+
+/*
* ForeignPath represents a potential scan of a foreign table, foreign join
* or foreign upper-relation.
*
diff --git src/include/optimizer/pathnode.h src/include/optimizer/pathnode.h
index 71d9154..17094f9 100644
--- src/include/optimizer/pathnode.h
+++ src/include/optimizer/pathnode.h
@@ -149,6 +149,11 @@ extern SortPath *create_sort_path(PlannerInfo *root,
Path *subpath,
List *pathkeys,
double limit_tuples);
+extern TemporalAdjustmentPath *create_temporaladjustment_path(PlannerInfo *root,
+ RelOptInfo *rel,
+ Path *subpath,
+ List *sortClause,
+ TemporalClause *temporalClause);
extern GroupPath *create_group_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
diff --git src/include/parser/kwlist.h src/include/parser/kwlist.h
index 581ff6e..78b2275 100644
--- src/include/parser/kwlist.h
+++ src/include/parser/kwlist.h
@@ -34,6 +34,7 @@ PG_KEYWORD("add", ADD_P, UNRESERVED_KEYWORD)
PG_KEYWORD("admin", ADMIN, UNRESERVED_KEYWORD)
PG_KEYWORD("after", AFTER, UNRESERVED_KEYWORD)
PG_KEYWORD("aggregate", AGGREGATE, UNRESERVED_KEYWORD)
+PG_KEYWORD("align", ALIGN, RESERVED_KEYWORD)
PG_KEYWORD("all", ALL, RESERVED_KEYWORD)
PG_KEYWORD("also", ALSO, UNRESERVED_KEYWORD)
PG_KEYWORD("alter", ALTER, UNRESERVED_KEYWORD)
@@ -257,6 +258,7 @@ PG_KEYWORD("new", NEW, UNRESERVED_KEYWORD)
PG_KEYWORD("next", NEXT, UNRESERVED_KEYWORD)
PG_KEYWORD("no", NO, UNRESERVED_KEYWORD)
PG_KEYWORD("none", NONE, COL_NAME_KEYWORD)
+PG_KEYWORD("normalize", NORMALIZE, RESERVED_KEYWORD)
PG_KEYWORD("not", NOT, RESERVED_KEYWORD)
PG_KEYWORD("nothing", NOTHING, UNRESERVED_KEYWORD)
PG_KEYWORD("notify", NOTIFY, UNRESERVED_KEYWORD)
diff --git src/include/parser/parse_node.h src/include/parser/parse_node.h
index bd6dc02..a3be05e 100644
--- src/include/parser/parse_node.h
+++ src/include/parser/parse_node.h
@@ -160,6 +160,12 @@ struct ParseState
RangeTblEntry *p_target_rangetblentry;
/*
+ * Temporal aliases for internal-use-only columns (used by temporal
+ * primitives only.
+ */
+ List *p_temporal_aliases;
+
+ /*
* Optional hook functions for parser callbacks. These are null unless
* set up by the caller of make_parsestate.
*/
diff --git src/include/parser/parse_temporal.h src/include/parser/parse_temporal.h
new file mode 100644
index 0000000..235831e
--- /dev/null
+++ src/include/parser/parse_temporal.h
@@ -0,0 +1,62 @@
+/*-------------------------------------------------------------------------
+ *
+ * parse_temporal.h
+ * handle temporal operators in parser
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/parser/parse_temporal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARSE_TEMPORAL_H
+#define PARSE_TEMPORAL_H
+
+#include "parser/parse_node.h"
+
+extern Node *
+transformTemporalClauseResjunk(Query* qry);
+
+extern Node *
+transformTemporalClause(ParseState *pstate,
+ Query *qry,
+ SelectStmt *stmt);
+
+extern Node *
+transformTemporalAligner(ParseState *pstate,
+ JoinExpr *j);
+
+extern Node *
+transformTemporalNormalizer(ParseState *pstate,
+ JoinExpr *j);
+
+extern void
+transformTemporalClauseAmbiguousColumns(ParseState *pstate,
+ JoinExpr *j,
+ List *l_colnames,
+ List *r_colnames,
+ List *l_colvars,
+ List *r_colvars,
+ RangeTblEntry *l_rte,
+ RangeTblEntry *r_rte);
+
+extern JoinExpr *
+makeTemporalNormalizer(Node *larg,
+ Node *rarg,
+ List *bounds,
+ Node *quals,
+ Alias *alias);
+
+extern JoinExpr *
+makeTemporalAligner(Node *larg,
+ Node *rarg,
+ List *bounds,
+ Node *quals,
+ Alias *alias);
+
+extern void
+tpprint(const void *obj, const char *marker);
+
+#endif /* PARSE_TEMPORAL_H */
diff --git src/include/utils/builtins.h src/include/utils/builtins.h
index 7ed1623..743345c 100644
--- src/include/utils/builtins.h
+++ src/include/utils/builtins.h
@@ -1245,6 +1245,7 @@ extern Datum uuid_hash(PG_FUNCTION_ARGS);
/* windowfuncs.c */
extern Datum window_row_number(PG_FUNCTION_ARGS);
+extern Datum window_row_id(PG_FUNCTION_ARGS);
extern Datum window_rank(PG_FUNCTION_ARGS);
extern Datum window_dense_rank(PG_FUNCTION_ARGS);
extern Datum window_percent_rank(PG_FUNCTION_ARGS);
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers