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 <boolean>	all_or_distinct
 
 %type <node>	join_outer join_qual
+%type <node>	normalizer_qual
 %type <jtype>	join_type
 
 %type <list>	extract_list overlay_list position_list
@@ -452,11 +453,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <value>	NumericOnly
 %type <list>	NumericOnly_list
 %type <alias>	alias_clause opt_alias_clause
+%type <list>	temporal_bounds
 %type <list>	func_alias_clause
 %type <sortby>	sortby
 %type <ielem>	index_elem
 %type <node>	table_ref
 %type <jexpr>	joined_table
+%type <jexpr>   aligned_table
+%type <jexpr>   normalized_table
 %type <range>	relation_expr
 %type <range>	relation_expr_opt_alias
 %type <node>	tablesample_clause opt_repeatable_clause
@@ -539,6 +543,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_frame_clause frame_extent frame_bound
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
+%type <list>    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 <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
-	AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
+	AGGREGATE ALIGN ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
 	ASSERTION ASSIGNMENT ASYMMETRIC AT 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 <name> alternative is <name>_N, where N is 0 if no
+	 * other column with that pattern has been found, or N + 1 if
+	 * the highest number for a <name>_N column is N. N stand for the <counter>.
+	 */
+	counter++;
+	sprintf(newName, "%s_%d", name, counter);
+
+	/*
+	 * Changed aliases must be remembered by the parser state in
+	 * order to use them on nodes above, i.e. if they are used in targetlists,
+	 * group-by or order-by clauses outside.
+	 */
+	pstate->p_temporal_aliases =
+			lappend(pstate->p_temporal_aliases,
+					list_make2(makeString(name),
+							   makeString(newName)));
+
+	return newName;
+}
+
+/*
+ * getColumnCounter -
+ * 		Check if a column name starts with a certain prefix. If it ends after
+ * 		the prefix, return found (we ignore the counter in this case). However,
+ * 		if it continuous with an underscore check if it has a tail after it that
+ * 		is a string representation of an integer. If so, return this number as
+ * 		integer (keep the parameter "found" as is).
+ * 		We use this function to rename "internal-use-only" columns on an
+ * 		ambiguity error with user-specified columns.
+ */
+static void
+getColumnCounter(const char *colname, const char *prefix,
+				 bool *found, int *counter)
+{
+	if(memcmp(colname, prefix, strlen(prefix)) == 0)
+	{
+		colname += strlen(prefix);
+		if(*colname == '\0')
+			*found = true;
+		else if (*colname++ == '_')
+		{
+			char 	*pos;
+			int 	 n = -1;
+
+			errno = 0;
+			n = strtol(colname, &pos, 10);
+
+			/*
+			 * No error and fully parsed (i.e., string contained
+			 * only an integer) => save it if it is bigger than
+			 * the last.
+			 */
+			if(errno == 0 && *pos == 0 && n > *counter)
+				*counter = n;
+		}
+	}
+}
+
+/*
+ * Creates a skeleton query that can be filled with needed fields from both
+ * temporal primitives. This is the common part of both generated to re-use
+ * the same code. It also returns palloc'd names for p1, p2, and rn, where p2
+ * is optional (omit it by passing NULL).
+ *
+ * OUTPUT:
+ * 		(
+ * 		SELECT r.*
+ *      FROM
+ *      (
+ *      	SELECT *, row_id() OVER () rn FROM r
+ *      ) r
+ *      LEFT OUTER JOIN
+ *      <not set yet>
+ *      ON <not set yet>
+ *      ORDER BY rn, p1
+ *      ) x
+ */
+static SelectStmt *
+makeTemporalQuerySkeleton(JoinExpr *j, char **nameRN, char **nameP1,
+						  char **nameP2, bool *hasRangeTypes, Alias **largAlias,
+						  Alias **rargAlias)
+{
+	const int 	 		 UNKNOWN_LOCATION = -1;
+
+	SelectStmt			*ssJoinLarg;
+	SelectStmt 			*ssRowNumber;
+	SelectStmt			*ssResult;
+	RangeSubselect 		*rssJoinLarg;
+	RangeSubselect 		*rssRowNumber;
+	ResTarget			*rtRowNumber;
+	ResTarget			*rtAStar;
+	ResTarget			*rtAStarWithR;
+	ColumnRef 			*crAStarWithR;
+	ColumnRef 			*crAStar;
+	WindowDef			*wdRowNumber;
+	FuncCall			*fcRowNumber;
+	JoinExpr			*joinExpr;
+	SortBy				*sb1;
+	SortBy				*sb2;
+
+	/*
+	 * We can have 2 or 4 column references, i.e. if we have 4, the first two
+	 * form the left argument period [largTs, largTe), and the last two the
+	 * right argument period respectively. Otherwise, we have two range typed
+	 * values of the form '[)' where the first argument contains the boundaries
+	 * of the left-hand-side, and the second argument contains the boundaries
+	 * of the RHS respectively. The parser checked already if there was another
+	 * number of arguments (not equal to 2 or 4) given.
+	 */
+	*hasRangeTypes = list_length(j->temporalBounds) == 2;
+
+	/*
+	 * These attribute names could cause conflicts, if the left or right
+	 * relation has column names like these. We solve this later by renaming
+	 * column names when we know which columns are in use, in order to create
+	 * unique column names.
+	 */
+	*nameRN = pstrdup("rn");
+	*nameP1 = pstrdup("p1");
+	if(nameP2) *nameP2 = pstrdup("p2");
+
+	/* Find aliases of arguments */
+	*largAlias = makeAliasFromArgument(j->larg);
+	*rargAlias = makeAliasFromArgument(j->rarg);
+
+	/*
+	 * Build "(SELECT row_id() OVER (), * FROM r) r".
+	 * We start with building the resource target for "*".
+	 */
+	crAStar = makeColumnRef1((Node *) makeNode(A_Star));
+	rtAStar = makeResTarget((Node *) crAStar, NULL);
+
+	/* Build an empty window definition clause, i.e. "OVER ()" */
+	wdRowNumber = makeNode(WindowDef);
+	wdRowNumber->frameOptions = FRAMEOPTION_DEFAULTS;
+	wdRowNumber->startOffset = NULL;
+	wdRowNumber->endOffset = NULL;
+
+	/*
+	 * Build a target for "row_id() OVER ()", row_id() enumerates each tuple
+	 * similar to row_number().
+	 * The rowid-function is push-down-safe, because we need only unique ids for
+	 * each tuple, and do not care about gaps between numbers.
+	 */
+	fcRowNumber = makeFuncCall(SystemFuncName("row_id"),
+							   NIL,
+							   UNKNOWN_LOCATION);
+	fcRowNumber->over = wdRowNumber;
+	rtRowNumber = makeResTarget((Node *) fcRowNumber, NULL);
+	rtRowNumber->name = *nameRN;
+
+	/*
+	 * Build sub-select clause with from- and where-clause from the
+	 * outer query. Add "row_id() OVER ()" to the target list.
+	 */
+	ssRowNumber = makeNode(SelectStmt);
+	ssRowNumber->fromClause = list_make1(j->larg);
+	ssRowNumber->groupClause = NIL;
+	ssRowNumber->whereClause = NULL;
+	ssRowNumber->targetList = list_make2(rtAStar, rtRowNumber);
+
+	/* Build range sub-select */
+	rssRowNumber = makeNode(RangeSubselect);
+	rssRowNumber->subquery = (Node *) ssRowNumber;
+	rssRowNumber->alias = *largAlias;
+	rssRowNumber->lateral = false;
+
+	/* Build resource target for "r.*" */
+	crAStarWithR = makeColumnRef2((Node *) makeString((*largAlias)->aliasname),
+								  (Node *) makeNode(A_Star));
+	rtAStarWithR = makeResTarget((Node *) crAStarWithR, NULL);
+
+	/* Build the outer range sub-select */
+	ssJoinLarg = makeNode(SelectStmt);
+	ssJoinLarg->fromClause = list_make1(rssRowNumber);
+	ssJoinLarg->groupClause = NIL;
+	ssJoinLarg->whereClause = NULL;
+
+	/* Build range sub-select */
+	rssJoinLarg = makeNode(RangeSubselect);
+	rssJoinLarg->subquery = (Node *) ssJoinLarg;
+	rssJoinLarg->lateral = false;
+
+	/* Build a join expression */
+	joinExpr = makeNode(JoinExpr);
+	joinExpr->isNatural = false;
+	joinExpr->larg = (Node *) rssRowNumber;
+	joinExpr->jointype = JOIN_LEFT; /* left outer join */
+
+	/*
+	 * Copy temporal bounds into temporal primitive subquery join in order to
+	 * compare temporal bound var types with actual target list var types. We
+	 * do this to trigger an error on type mismatch, before a subquery function
+	 * fails and triggers an non-meaningful error (as for example, "operator
+	 * does not exists, or similar").
+	 */
+	joinExpr->temporalBounds = copyObject(j->temporalBounds);
+
+	sb1 = makeNode(SortBy);
+	sb1->location = UNKNOWN_LOCATION;
+	sb1->node = (Node *) makeColumnRef1((Node *) makeString(*nameRN));
+
+	sb2 = makeNode(SortBy);
+	sb2->location = UNKNOWN_LOCATION;
+	sb2->node = (Node *) makeColumnRef1((Node *) makeString(*nameP1));
+
+	ssResult = makeNode(SelectStmt);
+	ssResult->withClause = NULL;
+	ssResult->fromClause = list_make1(joinExpr);
+	ssResult->targetList = list_make1(rtAStarWithR);
+	ssResult->sortClause = list_make2(sb1, sb2);
+
+	ssResult->temporalClause = makeNode(TemporalClause);
+	if(*hasRangeTypes)
+	{
+		/*
+		 * Hardcoded column names for ts and te. We handle ambiguous column
+		 * names during the transformation of temporal primitive clauses.
+		 */
+		ssResult->temporalClause->colnameTs = "ts";
+		ssResult->temporalClause->colnameTe = "te";
+		ssResult->temporalClause->colnameTr =
+				temporalBoundGetName(j->temporalBounds, TPB_LARGTST);
+	}
+	else
+	{
+		ssResult->temporalClause->colnameTs =
+				temporalBoundGetName(j->temporalBounds, TPB_LARGTST);
+		ssResult->temporalClause->colnameTe =
+				temporalBoundGetName(j->temporalBounds, TPB_LARGTE);
+		ssResult->temporalClause->colnameTr = NULL;
+	}
+
+	/*
+	 * We mark the outer sub-query with the current temporal adjustment type,
+	 * s.t. the optimizer understands that we need the corresponding temporal
+	 * adjustment node above.
+	 */
+	ssResult->temporalClause->temporalType =
+			j->jointype == TEMPORAL_ALIGN ? TEMPORAL_TYPE_ALIGNER
+										  : TEMPORAL_TYPE_NORMALIZER;
+
+	/* Let the join inside a temporal primitive know which type its parent has */
+	joinExpr->inTmpPrimTempType = ssResult->temporalClause->temporalType;
+	joinExpr->inTmpPrimHasRangeT = *hasRangeTypes;
+
+	return ssResult;
+}
+
+/*
+ * transformTemporalAligner -
+ * 		transform a TEMPORAL ALIGN clause into standard SQL
+ *
+ * INPUT:
+ * 		(r ALIGN s ON q WITH (r.ts, r.te, s.ts, s.te)) c
+ * 		where q can be any join qualifier, and r.ts, r.te, s.ts, and s.te
+ * 		can be any column name.
+ *
+ * OUTPUT:
+ * 		(
+ * 		SELECT r.*, GREATEST(r.ts, s.ts) P1, LEAST(r.te, s.te) P2
+ *      FROM
+ *      (
+ *      	SELECT *, row_id() OVER () rn FROM r
+ *      ) r
+ *      LEFT OUTER JOIN
+ *      s
+ *      ON q AND r.ts < s.te AND r.te > s.ts
+ *      ORDER BY rn, P1, P2
+ *      ) c
+ */
+Node *
+transformTemporalAligner(ParseState *pstate, JoinExpr *j)
+{
+	const int 	 		 UNKNOWN_LOCATION = -1;
+
+	bool				 hasRangeTypes;
+	SelectStmt			*ssResult;
+	RangeSubselect 		*rssResult;
+	ResTarget			*rtGreatest;
+	ResTarget			*rtLeast;
+	ResTarget			*rtLowerLarg;
+	ResTarget			*rtUpperLarg;
+	ColumnRef 			*crLargTs;
+	ColumnRef 			*crRargTs;
+	ColumnRef 			*crLargTe;
+	ColumnRef 			*crRargTe;
+	MinMaxExpr			*mmeGreatest;
+	MinMaxExpr			*mmeLeast;
+	FuncCall			*fcLowerLarg;
+	FuncCall			*fcLowerRarg;
+	FuncCall			*fcUpperLarg;
+	FuncCall			*fcUpperRarg;
+	List				*mmeGreatestArgs;
+	List				*mmeLeastArgs;
+	List				*boundariesExpr;
+	JoinExpr			*joinExpr;
+	A_Expr				*lowerBoundExpr;
+	A_Expr				*upperBoundExpr;
+	A_Expr				*overlapExpr;
+	Node				*boolExpr;
+	SortBy				*sb3;
+	Alias				*largAlias = NULL;
+	Alias				*rargAlias = NULL;
+	char 				*colnameRN;
+	char 				*colnameP1;
+	char 				*colnameP2;
+
+	/* Create a select statement skeleton to be filled here */
+	ssResult = makeTemporalQuerySkeleton(j, &colnameRN, &colnameP1,
+										 &colnameP2, &hasRangeTypes,
+										 &largAlias, &rargAlias);
+
+	/* Temporal aligners do not support the USING-clause */
+	Assert(j->usingClause == NIL);
+
+	/*
+	 * Build column references, for use later. If we need only two range types
+	 * only Ts columnrefs are used.
+	 */
+	if (hasRangeTypes)
+	{
+		crLargTs = temporalBoundGetCopyFQN(j->temporalBounds, TPB_LARGTST,
+										   largAlias->aliasname);
+		crRargTs = temporalBoundGetCopyFQN(j->temporalBounds, TPB_RARGTST,
+										   rargAlias->aliasname);
+
+		/* Create argument list for function call to "greatest" and "least" */
+		fcLowerLarg = makeFuncCall(SystemFuncName("lower"),
+								   list_make1(crLargTs),
+								   UNKNOWN_LOCATION);
+		fcLowerRarg = makeFuncCall(SystemFuncName("lower"),
+								   list_make1(crRargTs),
+								   UNKNOWN_LOCATION);
+		fcUpperLarg = makeFuncCall(SystemFuncName("upper"),
+								   list_make1(crLargTs),
+								   UNKNOWN_LOCATION);
+		fcUpperRarg = makeFuncCall(SystemFuncName("upper"),
+								   list_make1(crRargTs),
+								   UNKNOWN_LOCATION);
+		mmeGreatestArgs = list_make2(fcLowerLarg, fcLowerRarg);
+		mmeLeastArgs = list_make2(fcUpperLarg, fcUpperRarg);
+
+		overlapExpr = makeSimpleA_Expr(AEXPR_OP,
+									   "&&",
+									   copyObject(crLargTs),
+									   copyObject(crRargTs),
+									   UNKNOWN_LOCATION);
+
+		boundariesExpr = list_make1(overlapExpr);
+
+		rtLowerLarg = makeResTarget((Node *) fcLowerLarg,
+									ssResult->temporalClause->colnameTs);
+		rtUpperLarg = makeResTarget((Node *) fcUpperLarg,
+									ssResult->temporalClause->colnameTe);
+
+		ssResult->targetList = list_concat(ssResult->targetList,
+										   list_make2(rtLowerLarg, rtUpperLarg));
+	}
+	else
+	{
+		crLargTs = temporalBoundGetCopyFQN(j->temporalBounds, TPB_LARGTST,
+										   largAlias->aliasname);
+		crLargTe = temporalBoundGetCopyFQN(j->temporalBounds, TPB_LARGTE,
+										   largAlias->aliasname);
+		crRargTs = temporalBoundGetCopyFQN(j->temporalBounds, TPB_RARGTST,
+										   rargAlias->aliasname);
+		crRargTe = temporalBoundGetCopyFQN(j->temporalBounds, TPB_RARGTE,
+										   rargAlias->aliasname);
+
+		/* Create argument list for function call to "greatest" and "least" */
+		mmeGreatestArgs = list_make2(crLargTs, crRargTs);
+		mmeLeastArgs = list_make2(crLargTe, crRargTe);
+
+		/*
+		 * Build Boolean expressions, i.e. "r.ts < s.te AND r.te > s.ts"
+		 * and concatenate it with q (=theta)
+		 */
+		lowerBoundExpr = makeSimpleA_Expr(AEXPR_OP,
+										  "<",
+										  copyObject(crLargTs),
+										  copyObject(crRargTe),
+										  UNKNOWN_LOCATION);
+		upperBoundExpr = makeSimpleA_Expr(AEXPR_OP,
+										  ">",
+										  copyObject(crLargTe),
+										  copyObject(crRargTs),
+										  UNKNOWN_LOCATION);
+
+		boundariesExpr = list_make2(lowerBoundExpr, upperBoundExpr);
+	}
+
+	/* Concatenate all Boolean expressions by AND */
+	boolExpr = (Node *) makeBoolExpr(AND_EXPR,
+									 lappend(boundariesExpr, j->quals),
+									 UNKNOWN_LOCATION);
+
+	/* Build the function call "greatest(r.ts, s.ts) P1" */
+	mmeGreatest = makeNode(MinMaxExpr);
+	mmeGreatest->args = mmeGreatestArgs;
+	mmeGreatest->location = UNKNOWN_LOCATION;
+	mmeGreatest->op = IS_GREATEST;
+	rtGreatest = makeResTarget((Node *) mmeGreatest, NULL);
+	rtGreatest->name = colnameP1;
+
+	/* Build the function call "least(r.te, s.te) P2" */
+	mmeLeast = makeNode(MinMaxExpr);
+	mmeLeast->args = mmeLeastArgs;
+	mmeLeast->location = UNKNOWN_LOCATION;
+	mmeLeast->op = IS_LEAST;
+	rtLeast = makeResTarget((Node *) mmeLeast, NULL);
+	rtLeast->name = colnameP2;
+
+	sb3 = makeNode(SortBy);
+	sb3->location = UNKNOWN_LOCATION;
+	sb3->node = (Node *) makeColumnRef1((Node *) makeString(colnameP2));
+
+	ssResult->targetList = list_concat(ssResult->targetList,
+									   list_make2(rtGreatest, rtLeast));
+	ssResult->sortClause = lappend(ssResult->sortClause, sb3);
+
+	joinExpr = (JoinExpr *) linitial(ssResult->fromClause);
+	joinExpr->rarg = copyObject(j->rarg);
+	joinExpr->quals = boolExpr;
+
+	/* Build range sub-select */
+	rssResult = makeNode(RangeSubselect);
+	rssResult->subquery = (Node *) ssResult;
+	rssResult->alias = copyObject(j->alias);
+	rssResult->lateral = false;
+
+	return copyObject(rssResult);
+}
+
+/*
+ * transformTemporalNormalizer -
+ * 		transform a TEMPORAL NORMALIZE clause into standard SQL
+ *
+ * INPUT:
+ * 		(r NORMALIZE s ON q WITH (r.ts, r.te, s.ts, s.te)) c
+ *
+ * 		-- or --
+ *
+ * 		(r NORMALIZE s USING(atts) WITH (r.ts, r.te, s.ts, s.te)) c
+ * 		where q can be any join qualifier and r.ts, r.te, s.ts, and s.te
+ * 		can be any column name.
+ *
+ * OUTPUT:
+ * 		(
+ * 			SELECT r.*,
+ *      	FROM
+ *      	(
+ *      		SELECT *, row_id() OVER () rn FROM r
+ *      	) r
+ *      	LEFT OUTER JOIN
+ *      	(
+ *      		SELECT s.*, ts P1 FROM s
+ *      		UNION ALL
+ *      		SELECT s.*, te P1 FROM s
+ *      	) s
+ *      	ON q AND P1 >= r.ts AND P1 < r.te
+ *      	ORDER BY rn, P1
+ *      ) c
+ *
+ *      -- or --
+ *
+ * 		(
+ * 			SELECT r.*,
+ *      	FROM
+ *      	(
+ *      		SELECT *, row_id() OVER () rn FROM r
+ *      	) r
+ *      	LEFT OUTER JOIN
+ *      	(
+ *      		SELECT atts, ts P1 FROM s
+ *      		UNION
+ *      		SELECT atts, te P1 FROM s
+ *      	) s
+ *      	ON r.atts = s.atts AND P1 >= r.ts AND P1 < r.te
+ *      	ORDER BY rn, P1
+ *      ) c
+ */
+Node *
+transformTemporalNormalizer(ParseState *pstate, JoinExpr *j)
+{
+	const int 	 		 UNKNOWN_LOCATION = -1;
+
+	SelectStmt			*ssTsP1;
+	SelectStmt			*ssTeP1;
+	SelectStmt			*ssUnionAll;
+	SelectStmt			*ssResult;
+	RangeSubselect 		*rssUnionAll;
+	RangeSubselect 		*rssResult;
+	ResTarget			*rtRargStar;
+	ResTarget			*rtTsP1;
+	ResTarget			*rtTeP1;
+	ResTarget			*rtLowerLarg;
+	ResTarget			*rtUpperLarg;
+	ColumnRef 			*crRargStar;
+	ColumnRef 			*crLargTsT = NULL;
+	ColumnRef 			*crRargTsT = NULL;
+	ColumnRef 			*crLargTe = NULL;
+	ColumnRef 			*crRargTe = NULL;
+	ColumnRef 			*crP1;
+	JoinExpr			*joinExpr;
+	A_Expr				*lowerBoundExpr;
+	A_Expr				*upperBoundExpr;
+	A_Expr				*containsExpr;
+	Node				*boolExpr;
+	Alias				*largAlias;
+	Alias				*rargAlias;
+	char 				*colnameRN;
+	char 				*colnameP1;
+	bool				 hasRangeTypes;
+	FuncCall			*fcLowerLarg = NULL;
+	FuncCall			*fcUpperLarg = NULL;
+	FuncCall			*fcLowerRarg = NULL;
+	FuncCall			*fcUpperRarg = NULL;
+	List				*boundariesExpr;
+
+	/* Create a select statement skeleton to be filled here */
+	ssResult = makeTemporalQuerySkeleton(j, &colnameRN, &colnameP1,
+										 NULL, &hasRangeTypes,
+										 &largAlias, &rargAlias);
+
+	/* Build resource target for "s.*" to use it later. */
+	crRargStar = makeColumnRef2((Node *) makeString(rargAlias->aliasname),
+								(Node *) makeNode(A_Star));
+
+	crP1 = makeColumnRef1((Node *) makeString(colnameP1));
+
+	/*
+	 * Build column references, for use later. If we need only two range types
+	 * only Ts columnrefs are used.
+	 */
+	if (hasRangeTypes)
+	{
+		crLargTsT = temporalBoundGetCopyFQN(j->temporalBounds, TPB_LARGTST,
+										   largAlias->aliasname);
+		crRargTsT = temporalBoundGetCopyFQN(j->temporalBounds, TPB_RARGTST,
+										   rargAlias->aliasname);
+
+		/* Create argument list for function call to "greatest" and "least" */
+		fcLowerLarg = makeFuncCall(SystemFuncName("lower"),
+								   list_make1(crLargTsT),
+								   UNKNOWN_LOCATION);
+		fcLowerRarg = makeFuncCall(SystemFuncName("lower"),
+								   list_make1(crRargTsT),
+								   UNKNOWN_LOCATION);
+		fcUpperLarg = makeFuncCall(SystemFuncName("upper"),
+								   list_make1(crLargTsT),
+								   UNKNOWN_LOCATION);
+		fcUpperRarg = makeFuncCall(SystemFuncName("upper"),
+								   list_make1(crRargTsT),
+								   UNKNOWN_LOCATION);
+
+		/* Build resource target "lower(s.t) P1" and "upper(s.t) P1" */
+		rtTsP1 = makeResTarget((Node *) fcLowerRarg, colnameP1);
+		rtTeP1 = makeResTarget((Node *) fcUpperRarg, colnameP1);
+
+		rtLowerLarg = makeResTarget((Node *) fcLowerLarg,
+									ssResult->temporalClause->colnameTs);
+		rtUpperLarg = makeResTarget((Node *) fcUpperLarg,
+									ssResult->temporalClause->colnameTe);
+
+		ssResult->targetList = list_concat(ssResult->targetList,
+										   list_make2(rtLowerLarg, rtUpperLarg));
+		/*
+		 * Build "contains" expression for range types, i.e. "P1 <@ t"
+		 * and concatenate it with q (=theta)
+		 */
+		containsExpr = makeSimpleA_Expr(AEXPR_OP,
+										"<@",
+										copyObject(crP1),
+										copyObject(crLargTsT),
+										UNKNOWN_LOCATION);
+
+		boundariesExpr = list_make1(containsExpr);
+	}
+	else
+	{
+		/*
+		 * Build column references, for use later.
+		 */
+		crLargTsT = temporalBoundGetCopyFQN(j->temporalBounds, TPB_LARGTST,
+										   largAlias->aliasname);
+		crLargTe = temporalBoundGetCopyFQN(j->temporalBounds, TPB_LARGTE,
+										   largAlias->aliasname);
+		crRargTsT = temporalBoundGetCopyFQN(j->temporalBounds, TPB_RARGTST,
+										   rargAlias->aliasname);
+		crRargTe = temporalBoundGetCopyFQN(j->temporalBounds, TPB_RARGTE,
+										   rargAlias->aliasname);
+
+		/* Build resource target "ts P1" and "te P1" */
+		rtTsP1 = makeResTarget((Node *) crRargTsT, colnameP1);
+		rtTeP1 = makeResTarget((Node *) crRargTe, colnameP1);
+		/*
+		 * Build "contains" expressions, i.e. "P1 >= ts AND P1 < te"
+		 * and concatenate it with q (=theta)
+		 */
+		lowerBoundExpr = makeSimpleA_Expr(AEXPR_OP,
+										  ">=",
+										  copyObject(crP1),
+										  copyObject(crLargTsT),
+										  UNKNOWN_LOCATION);
+		upperBoundExpr = makeSimpleA_Expr(AEXPR_OP,
+										  "<",
+										  copyObject(crP1),
+										  copyObject(crLargTe),
+										  UNKNOWN_LOCATION);
+
+		boundariesExpr = list_make2(lowerBoundExpr, upperBoundExpr);
+	}
+
+	/*
+	 * Build "SELECT s.*, ts P1 FROM s" and "SELECT s.*, te P1 FROM s", iff we
+	 * have a ON-clause.
+	 * If we have an USING-clause with a name-list 'atts' build "SELECT atts,
+	 * ts P1 FROM s" and "SELECT atts, te P1 FROM s"
+	 */
+
+	ssTsP1 = makeNode(SelectStmt);
+	ssTsP1->fromClause = list_make1(j->rarg);
+	ssTsP1->groupClause = NIL;
+	ssTsP1->whereClause = NULL;
+
+	ssTeP1 = copyObject(ssTsP1);
+
+	if (j->usingClause)
+	{
+		ListCell   *usingItem;
+		A_Expr     *expr;
+		List	   *qualList = NIL;
+		char	   *colnameTs = ssResult->temporalClause->colnameTs;
+		char	   *colnameTe = ssResult->temporalClause->colnameTe;
+		char	   *colnameTr = ssResult->temporalClause->colnameTr;
+
+		Assert(j->quals == NULL); 	/* shouldn't have ON() too */
+
+		foreach(usingItem, j->usingClause)
+		{
+			char		*usingItemName = strVal(lfirst(usingItem));
+			ColumnRef   *crUsingItemL =
+					makeColumnRef2((Node *) makeString(largAlias->aliasname),
+								   (Node *) makeString(usingItemName));
+			ColumnRef   *crUsingItemR =
+					makeColumnRef2((Node *) makeString(rargAlias->aliasname),
+								   (Node *) makeString(usingItemName));
+			ResTarget	*rtUsingItemR = makeResTarget((Node *) crUsingItemR,
+													  NULL);
+
+			/*
+			 * Skip temporal attributes, because temporal normalizer's USING
+			 * list must contain only non-temporal attributes. We allow
+			 * temporal attributes as input, such that we can copy colname lists
+			 * to create temporal normalizers easier.
+			 */
+			if(strcmp(usingItemName, colnameTs) == 0
+					|| strcmp(usingItemName, colnameTe) == 0
+					|| (colnameTr && strcmp(usingItemName, colnameTr) == 0))
+				continue;
+
+			expr = makeSimpleA_Expr(AEXPR_OP,
+									 "=",
+									 copyObject(crUsingItemL),
+									 copyObject(crUsingItemR),
+									 UNKNOWN_LOCATION);
+
+			qualList = lappend(qualList, expr);
+
+			ssTsP1->targetList = lappend(ssTsP1->targetList, rtUsingItemR);
+			ssTeP1->targetList = lappend(ssTeP1->targetList, rtUsingItemR);
+		}
+
+		j->quals = (Node *) makeBoolExpr(AND_EXPR, qualList, UNKNOWN_LOCATION);
+	}
+	else if (j->quals)
+	{
+		rtRargStar = makeResTarget((Node *) crRargStar, NULL);
+		ssTsP1->targetList = list_make1(rtRargStar);
+		ssTeP1->targetList = list_make1(rtRargStar);
+	}
+
+	ssTsP1->targetList = lappend(ssTsP1->targetList, rtTsP1);
+	ssTeP1->targetList = lappend(ssTeP1->targetList, rtTeP1);
+
+	/*
+	 * Build sub-select for "( SELECT ... UNION ALL SELECT ... ) s", i.e.,
+	 * build an union between two select-clauses, i.e. a select-clause with
+	 * set-operation set to "union".
+	 */
+	ssUnionAll = makeNode(SelectStmt);
+	ssUnionAll->op = SETOP_UNION;
+	ssUnionAll->all = j->usingClause == NIL;	/* true, if ON-clause */
+	ssUnionAll->larg = ssTsP1;
+	ssUnionAll->rarg = ssTeP1;
+
+	/* Build range sub-select for "( ...UNION ALL... ) s" */
+	rssUnionAll = makeNode(RangeSubselect);
+	rssUnionAll->subquery = (Node *) ssUnionAll;
+	rssUnionAll->alias = rargAlias;
+	rssUnionAll->lateral = false;
+
+	/*
+	 * Create a conjunction of all Boolean expressions
+	 */
+	if (j->quals)
+	{
+		boolExpr = (Node *) makeBoolExpr(AND_EXPR,
+										 lappend(boundariesExpr, j->quals),
+										 UNKNOWN_LOCATION);
+	}
+	else	/* empty USING() clause found, i.e. theta = true */
+	{
+		boolExpr = (Node *) makeBoolExpr(AND_EXPR,
+										 boundariesExpr,
+										 UNKNOWN_LOCATION);
+		ssUnionAll->all = false;
+
+	}
+
+	joinExpr = (JoinExpr *) linitial(ssResult->fromClause);
+	joinExpr->rarg = (Node *) rssUnionAll;
+	joinExpr->quals = boolExpr;
+
+	/* Build range sub-select */
+	rssResult = makeNode(RangeSubselect);
+	rssResult->subquery = (Node *) ssResult;
+	rssResult->alias = copyObject(j->alias);
+	rssResult->lateral = false;
+
+	return copyObject(rssResult);
+}
+
+/*
+ * typeGet -
+ * 		Return the type of a tuple from the system cache for a given OID.
+ */
+static Form_pg_type
+typeGet(Oid id)
+{
+	HeapTuple	tp;
+	Form_pg_type typtup;
+
+	tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(id));
+	if (!HeapTupleIsValid(tp))
+		ereport(ERROR,
+				(errcode(ERROR),
+				 errmsg("cache lookup failed for type %u", id)));
+
+	typtup = (Form_pg_type) GETSTRUCT(tp);
+	ReleaseSysCache(tp);
+	return typtup;
+}
+
+/*
+ * internalUseOnlyColumnNames -
+ * 		Creates a list of all internal-use-only column names, depending on the
+ * 		temporal primitive type (i.e., normalizer or aligner). These column
+ * 		names also differ depending on weither we have range types or scalars
+ * 		for temporal bounds. The list is then compared with the aliases from
+ * 		the current parser state, and renamed if necessary.
+ */
+static List *
+internalUseOnlyColumnNames(ParseState *pstate,
+						   bool hasRangeTypes,
+						   TemporalType tmpType)
+{
+	List		*filter = NIL;
+	ListCell	*lcFilter;
+	ListCell	*lcAlias;
+
+	filter = list_make2(makeString("rn"), makeString("p1"));
+
+	if(tmpType == TEMPORAL_TYPE_ALIGNER)
+		filter = lappend(filter, makeString("p2"));
+
+	/* We split range types into upper and lower bounds, called ts and te */
+	if(hasRangeTypes)
+	{
+		filter = lappend(filter, makeString("ts"));
+		filter = lappend(filter, makeString("te"));
+	}
+
+	foreach(lcFilter, filter)
+	{
+		Value	*filterValue = (Value *) lfirst(lcFilter);
+		char	*filterName = strVal(filterValue);
+
+		foreach(lcAlias, pstate->p_temporal_aliases)
+		{
+			char 	*aliasKey 	= strVal(linitial((List *) lfirst(lcAlias)));
+			char 	*aliasValue = strVal(lsecond((List *) lfirst(lcAlias)));
+
+			if(strcmp(filterName, aliasKey) == 0 )
+				filterValue->val.str = pstrdup(aliasValue);
+		}
+	}
+
+	return filter;
+}
+
+/*
+ * temporalBoundCheckIntegrity -
+ * 		For each column name check if it is a temporal bound. If so, check
+ * 		also if it does not clash with an internal-use-only column name, and if
+ * 		the attribute types match with the range type predicate. This means, if
+ * 		we have only one item in boundary list, all bounds must be range types.
+ * 		Otherwise, all bounds must be scalars.
+ */
+static void
+temporalBoundCheckIntegrity(ParseState *pstate,
+							 List *bounds,
+							 List *colnames,
+							 List *colvars,
+							 TemporalType tmpType)
+{
+	ListCell 	*lcNames;
+	ListCell 	*lcVars;
+	ListCell 	*lcBound;
+	ListCell 	*lcFilter;
+	bool 		 hasRangeTypes = list_length(bounds) == 1;
+	List		*filter = internalUseOnlyColumnNames(pstate,
+													 hasRangeTypes,
+													 tmpType);
+
+	forboth(lcNames, colnames, lcVars, colvars)
+	{
+		char *name = strVal((Value *) lfirst(lcNames));
+		Var	 *var  = (Var *) lfirst(lcVars);
+
+		foreach(lcBound, bounds)
+		{
+			ColumnRef 	*crb = (ColumnRef *) lfirst(lcBound);
+			char 		*nameb = strVal((Value *) llast(crb->fields));
+
+			if(strcmp(nameb, name) == 0)
+			{
+				char 				*msg = "";
+				Form_pg_type		 type;
+
+				foreach(lcFilter, filter)
+				{
+					char	*n = strVal((Value *) lfirst(lcFilter));
+					if(strcmp(n, name) == 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_UNDEFINED_COLUMN),
+								 errmsg("column \"%s\" does not exist", n),
+								 parser_errposition(pstate, crb->location)));
+				}
+
+				type = typeGet(var->vartype);
+
+				if(hasRangeTypes && type->typtype != TYPTYPE_RANGE)
+					msg = "Invalid column type \"%s\" for the temporal bound " \
+						  "\"%s\". It must be a range type column.";
+
+				if(! hasRangeTypes && type->typtype == TYPTYPE_RANGE)
+					msg = "Invalid column type \"%s\" for the temporal bound " \
+						  "\"%s\". It must be a scalar type column (i.e., " \
+						  "not a range type).";
+
+				if (strlen(msg) > 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+							 errmsg(msg,
+									NameStr(type->typname),
+									NameListToString(crb->fields)),
+							 errhint("Specify four scalar columns for the " \
+									 "temporal boundaries, or two range-typed "\
+									 "columns."),
+							 parser_errposition(pstate, crb->location)));
+
+			}
+		}
+	}
+
+}
+
+
+/*
+ * transformTemporalClauseAmbiguousColumns -
+ * 		Rename columns automatically to unique not-in-use column names, if
+ * 		column names clash with internal-use-only columns of temporal
+ * 		primitives.
+ */
+void
+transformTemporalClauseAmbiguousColumns(ParseState* pstate, JoinExpr* j,
+										List* l_colnames, List* r_colnames,
+										List *l_colvars, List *r_colvars,
+										RangeTblEntry* l_rte,
+										RangeTblEntry* r_rte)
+{
+	ListCell   *l = NULL;
+	bool 		foundP1 = false;
+	bool 		foundP2 = false;
+	bool 		foundRN = false;
+	bool 		foundTS = false;
+	bool 		foundTE = false;
+	int 		counterP1 = -1;
+	int 		counterP2 = -1;
+	int 		counterRN = -1;
+	int 		counterTS = -1;
+	int 		counterTE = -1;
+
+	/* Nothing to do, if we have no temporal primitive */
+	if (j->inTmpPrimTempType == TEMPORAL_TYPE_NONE)
+		return;
+
+	/*
+	 * Check ambiguity of column names, search for p1, p2, and rn
+	 * columns and rename them accordingly to X_N, where X = {p1,p2,rn},
+	 * and N is the highest number after X_ starting from 0. This is, if we do
+	 * not find any X_N column pattern the new column is renamed to X_0.
+	 */
+	foreach(l, l_colnames)
+	{
+		const char *colname = strVal((Value *) lfirst(l));
+
+		/*
+		 * Skip the last entry of the left column names, i.e. row_id
+		 * is only an internally added column by both temporal
+		 * primitives.
+		 */
+		if (l == list_tail(l_colnames))
+			continue;
+
+		getColumnCounter(colname, "p1", &foundP1, &counterP1);
+		getColumnCounter(colname, "rn", &foundRN, &counterRN);
+
+		/* Only temporal aligners have a p2 column */
+		if (j->inTmpPrimTempType == TEMPORAL_TYPE_ALIGNER)
+			getColumnCounter(colname, "p2", &foundP2, &counterP2);
+
+		if (j->inTmpPrimHasRangeT)
+		{
+			getColumnCounter(colname, "ts", &foundTS, &counterTS);
+			getColumnCounter(colname, "te", &foundTE, &counterTE);
+		}
+	}
+
+	foreach(l, r_colnames)
+	{
+		const char *colname = strVal((Value *) lfirst(l));
+
+		/*
+		 * The temporal normalizer adds also a column called p1 which is
+		 * the union of te and ts interval boundaries. We ignore it here
+		 * since it does not belong to the user defined columns of the
+		 * given input, iff it is the last entry of the column list.
+		 */
+		if (j->inTmpPrimTempType == TEMPORAL_TYPE_NORMALIZER
+				&& l == list_tail(r_colnames))
+			continue;
+
+		getColumnCounter(colname, "p1", &foundP1, &counterP1);
+		getColumnCounter(colname, "rn", &foundRN, &counterRN);
+
+		/* Only temporal aligners have a p2 column */
+		if (j->inTmpPrimTempType == TEMPORAL_TYPE_ALIGNER)
+			getColumnCounter(colname, "p2", &foundP2, &counterP2);
+
+		if (j->inTmpPrimHasRangeT)
+		{
+			getColumnCounter(colname, "ts", &foundTS, &counterTS);
+			getColumnCounter(colname, "te", &foundTE, &counterTE);
+		}
+	}
+
+	if (foundP1)
+	{
+		char *name = addTemporalAlias(pstate, "p1", counterP1);
+
+		/*
+		 * The right subtree gets now a new name for the column p1.
+		 * In addition, we rename both expressions used for temporal
+		 * boundary checks. It is fixed that they are at the end of this
+		 * join's qualifier list.
+		 * Only temporal normalization needs these steps.
+		 */
+		if (j->inTmpPrimTempType == TEMPORAL_TYPE_NORMALIZER)
+		{
+			A_Expr *e1;
+			A_Expr *e2;
+			List *qualArgs;
+			bool hasRangeTypes = list_length(j->temporalBounds) == 2;
+
+			llast(r_rte->eref->colnames) = makeString(name);
+			llast(r_colnames) = makeString(name);
+
+			qualArgs = ((BoolExpr *) j->quals)->args;
+			e1 = (A_Expr *) linitial(qualArgs);
+			linitial(((ColumnRef *)e1->lexpr)->fields) = makeString(name);
+
+			if(! hasRangeTypes)
+			{
+				e2 = (A_Expr *) lsecond(qualArgs);
+				linitial(((ColumnRef *)e2->lexpr)->fields) = makeString(name);
+			}
+		}
+	}
+
+	if (foundRN)
+	{
+		char *name = addTemporalAlias(pstate, "rn", counterRN);
+
+		/* The left subtree has now a new name for the column rn */
+		llast(l_rte->eref->colnames) = makeString(name);
+		llast(l_colnames) = makeString(name);
+	}
+
+	if (foundP2)
+		addTemporalAlias(pstate, "p2", counterP2);
+
+	if (foundTS)
+		addTemporalAlias(pstate, "ts", counterTS);
+
+	if (foundTE)
+		addTemporalAlias(pstate, "te", counterTE);
+
+	temporalBoundCheckIntegrity(pstate,
+								temporalBoundGetLeftBounds(j->temporalBounds),
+								l_colnames, l_colvars, j->inTmpPrimTempType);
+
+
+	temporalBoundCheckIntegrity(pstate,
+								temporalBoundGetRightBounds(j->temporalBounds),
+								r_colnames, r_colvars, j->inTmpPrimTempType);
+
+}
+
+
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);
