diff --git src/backend/commands/explain.c src/backend/commands/explain.c index dbd27e5..5e15658 100644 --- src/backend/commands/explain.c +++ src/backend/commands/explain.c @@ -868,6 +868,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..c9e4029 --- /dev/null +++ src/backend/executor/nodeTemporalAdjustment.c @@ -0,0 +1,529 @@ +#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 */ + + +/* + * #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); +} +#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); + +#ifdef TEMPORAL_DEBUG + printf("Storing tuple:\n"); + print_slot(slotToStoreIn); +#endif +} + +/* + * 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; +} + +/* + * 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) +{ + TupleTableSlot *out; + TupleTableSlot *curr; + TupleTableSlot *prev; + PlanState *outerPlan; + bool produced; + bool isNull; + Datum currP1; + Datum currP2; + Datum currRN; + Datum prevRN; + Datum prevTe; + + outerPlan = outerPlanState(node); + out = node->ss.ps.ps_ResultTupleSlot; + prev = node->ss.ss_ScanTupleSlot; + curr = outerPlan->ps_ResultTupleSlot; + + if(node->firstCall) + { + curr = ExecProcNode(outerPlan); + if(TupIsNull(curr)) + return NULL; + ExecCopySlot(prev, curr); + node->sameleft = true; + node->firstCall = false; + node->outrn = 0; + node->sweepline = slotGetAttrNotNull(curr, node->temporalCl->attNumTs); + } + +#ifdef TEMPORAL_DEBUG + printf("CURR tuple:\n"); + print_slot(curr); + printf("\tsweepline = %s\n", + datumToString(curr->tts_tupleDescriptor->attrs[node->temporalCl->attNumTs - 1]->atttypid, + node->sweepline)); + printf("\tsameleft = %s\n", node->sameleft ? "t" : "f"); + fflush(stdout); +#endif + + produced = false; + while(!produced && !TupIsNull(prev)) + { + if(node->sameleft) + { + currRN = slotGetAttrNotNull(curr, node->temporalCl->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, node->temporalCl->attNumP1, &isNull); + if (isNull) + node->sameleft = false; + + if(!isNull && isLessThan(node->sweepline, currP1, node)) + { + temporalAdjustmentStoreTuple(node, curr, out, + node->sweepline, currP1); + produced = true; + node->sweepline = 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, + node->temporalCl->attNumP2); + + /* If alignment check to not produce the same tuple again */ + if(TupIsNull(out) + || !isEqual(heapGetAttrNotNull(out, node->temporalCl->attNumTs), + currP1, + node) + || !isEqual(heapGetAttrNotNull(out, node->temporalCl->attNumTe), + currP2, + node) + || node->outrn != DatumGetInt64(currRN)) + { + temporalAdjustmentStoreTuple(node, curr, out, + currP1, currP2); + + /* sweepline = max(sweepline, curr.P2) */ + if (isLessThan(node->sweepline, currP2, node)) + node->sweepline = currP2; + + node->outrn = DatumGetInt64(currRN); + produced = true; + } + } + + ExecCopySlot(prev, curr); + curr = ExecProcNode(outerPlan); + + if(TupIsNull(curr)) + node->sameleft = false; + else + { + currRN = slotGetAttrNotNull(curr, + node->temporalCl->attNumRN); + prevRN = slotGetAttrNotNull(prev, + node->temporalCl->attNumRN); + node->sameleft = + DatumGetInt64(currRN) == DatumGetInt64(prevRN); + } + } + } + else + { + prevTe = heapGetAttrNotNull(prev, node->temporalCl->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, node->temporalCl->attNumRN); + node->outrn = DatumGetInt64(currRN); + produced = true; + } + + if(TupIsNull(curr)) + prev = ExecClearTuple(prev); + else + { + ExecCopySlot(prev, curr); + node->sweepline = slotGetAttrNotNull(curr, + node->temporalCl->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; + + /* + * 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 3244c76..5b42f48 100644 --- src/backend/nodes/copyfuncs.c +++ src/backend/nodes/copyfuncs.c @@ -1932,6 +1932,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; } @@ -2292,6 +2295,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) { @@ -2737,6 +2779,7 @@ _copyQuery(const Query *from) COPY_NODE_FIELD(setOperations); COPY_NODE_FIELD(constraintDeps); COPY_NODE_FIELD(withCheckOptions); + COPY_NODE_FIELD(temporalClause); return newnode; } @@ -2804,6 +2847,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); @@ -5065,6 +5109,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; /* * MISCELLANEOUS NODES diff --git src/backend/nodes/equalfuncs.c src/backend/nodes/equalfuncs.c index 1eb6799..3bf6a41 100644 --- src/backend/nodes/equalfuncs.c +++ src/backend/nodes/equalfuncs.c @@ -744,6 +744,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 d72a85e..cbe88a2 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 - @@ -608,3 +691,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 e196622..85d4d8d 100644 --- src/backend/nodes/outfuncs.c +++ src/backend/nodes/outfuncs.c @@ -1550,6 +1550,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 @@ -1818,6 +1821,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"); @@ -2497,6 +2512,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); @@ -2562,6 +2578,38 @@ _outXmlSerialize(StringInfo str, const XmlSerialize *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"); @@ -2692,6 +2740,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 @@ -3608,6 +3657,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; @@ -3705,6 +3757,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); @@ -3846,7 +3904,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..5d73c41 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,18 @@ print_slot(TupleTableSlot *slot) debugtup(slot, NULL); } + +/* + * print_namespace + * print out all name space items' RTEs. + */ +void +print_namespace(const List *namespace) +{ + ListCell *lc; + 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 94954dc..3bc0b45 100644 --- src/backend/nodes/readfuncs.c +++ src/backend/nodes/readfuncs.c @@ -261,6 +261,7 @@ _readQuery(void) READ_NODE_FIELD(rowMarks); READ_NODE_FIELD(setOperations); READ_NODE_FIELD(constraintDeps); + READ_NODE_FIELD(temporalClause); READ_DONE(); } @@ -422,6 +423,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. @@ -456,6 +479,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) { @@ -1221,6 +1255,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(); } @@ -2386,6 +2423,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 88d833a..e226d33 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 */ @@ -127,6 +128,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); /* @@ -2323,7 +2325,8 @@ subquery_is_pushdown_safe(Query *subquery, Query *topquery, return false; /* Check points 3 and 4 */ - if (subquery->distinctClause || subquery->hasWindowFuncs) + if (subquery->distinctClause || subquery->hasWindowFuncs + || subquery->temporalClause) safetyInfo->unsafeVolatile = true; /* @@ -2433,6 +2436,7 @@ static void check_output_expressions(Query *subquery, pushdown_safety_info *safetyInfo) { ListCell *lc; + bool wfsafe = allWindowFuncsHaveRowId(subquery->targetList); foreach(lc, subquery->targetList) { @@ -2470,12 +2474,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; + } + } } } @@ -2545,6 +2572,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 @@ -2828,6 +2881,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 54d601f..a6608ac 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; +} + /***************************************************************************** * @@ -4819,6 +4857,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 b265628..945fac2 100644 --- src/backend/optimizer/plan/planner.c +++ src/backend/optimizer/plan/planner.c @@ -1969,6 +1969,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 @@ -4334,7 +4348,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 6ea46c4..2a847f6 100644 --- src/backend/optimizer/plan/setrefs.c +++ src/backend/optimizer/plan/setrefs.c @@ -613,6 +613,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 a46cc10..f9b1ca5 100644 --- src/backend/optimizer/plan/subselect.c +++ src/backend/optimizer/plan/subselect.c @@ -2667,6 +2667,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 ce7ad54..f408f12 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 29c8c4e..2ea10ce 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" @@ -1113,6 +1114,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"); @@ -1186,6 +1190,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 edf4516..14c417c 100644 --- src/backend/parser/gram.y +++ src/backend/parser/gram.y @@ -400,6 +400,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type all_or_distinct %type join_outer join_qual +%type normalizer_qual %type join_type %type extract_list overlay_list position_list @@ -452,11 +453,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type NumericOnly %type NumericOnly_list %type alias_clause opt_alias_clause +%type temporal_bounds %type func_alias_clause %type sortby %type index_elem %type table_ref %type joined_table +%type aligned_table +%type normalized_table %type relation_expr %type relation_expr_opt_alias %type tablesample_clause opt_repeatable_clause @@ -539,6 +543,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); opt_frame_clause frame_extent frame_bound %type opt_existing_window_name %type opt_if_not_exists +%type temporal_bounds_list /* * Non-keyword token types. These are hard-wired into the "flex" lexer. @@ -563,7 +568,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); /* ordinary key words in alphabetical order */ %token 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 ATTRIBUTE AUTHORIZATION BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT @@ -608,7 +613,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 NEXT NO NONE + NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NONE NORMALIZE NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC @@ -10450,6 +10455,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. @@ -10701,6 +10719,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; + } ; @@ -14106,7 +14212,8 @@ type_func_name_keyword: * forced to. */ reserved_keyword: - ALL + ALIGN + | ALL | ANALYSE | ANALYZE | AND @@ -14154,6 +14261,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..aa5a78c --- /dev/null +++ src/backend/parser/parse_temporal.c @@ -0,0 +1,1546 @@ +/*------------------------------------------------------------------------- + * + * 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 alternative is _N, where N is 0 if no + * other column with that pattern has been found, or N + 1 if + * the highest number for a _N column is N. N stand for the . + */ + 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 + * + * ON + * 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); + +} + + 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 be924d5..5d755a2 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 5d233e3..92d3d14 100644 --- src/include/catalog/pg_proc.h +++ src/include/catalog/pg_proc.h @@ -4965,6 +4965,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 e7fd7bd..4e7845b 100644 --- src/include/nodes/execnodes.h +++ src/include/nodes/execnodes.h @@ -1252,6 +1252,29 @@ 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 */ +} TemporalAdjustmentState; + +/* ---------------- * SeqScanState information * ---------------- */ diff --git src/include/nodes/makefuncs.h src/include/nodes/makefuncs.h index 01c5cb4..ac5e514 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); 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 6b850e4..d376658 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) @@ -256,6 +258,7 @@ typedef enum NodeTag T_LockRowsPath, T_ModifyTablePath, T_LimitPath, + T_TemporalAdjustmentPath, /* these aren't subclasses of Path: */ T_EquivalenceClass, T_EquivalenceMember, @@ -452,6 +455,7 @@ typedef enum NodeTag T_OnConflictClause, T_CommonTableExpr, T_RoleSpec, + T_TemporalClause, /* * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h) @@ -653,11 +657,19 @@ 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. */ + } JoinType; /* diff --git src/include/nodes/parsenodes.h src/include/nodes/parsenodes.h index 3773dd9..71eed0e 100644 --- src/include/nodes/parsenodes.h +++ src/include/nodes/parsenodes.h @@ -161,6 +161,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; @@ -1312,6 +1314,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 369179f..c638303 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 057cc2c..d19f9fb 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 * @@ -1375,6 +1404,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 2be8908..efbd03e 100644 --- src/include/nodes/relation.h +++ src/include/nodes/relation.h @@ -1045,6 +1045,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 17ffef5..89b6022 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) @@ -254,6 +255,7 @@ PG_KEYWORD("nchar", NCHAR, COL_NAME_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 e3e359c..8817923 100644 --- src/include/parser/parse_node.h +++ src/include/parser/parse_node.h @@ -158,6 +158,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..3faffa7 --- /dev/null +++ src/include/parser/parse_temporal.h @@ -0,0 +1,48 @@ +/*------------------------------------------------------------------------- + * + * 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 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 8cebc86..2f99558 100644 --- src/include/utils/builtins.h +++ src/include/utils/builtins.h @@ -1237,6 +1237,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);