diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 22e82ba..fda5f41 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -840,9 +840,16 @@ CreateRoleStmt:
 
 
 opt_with:	WITH									{}
+			| WITH_ORDINALITY						{}
+			| WITH_TIME								{}
 			| /*EMPTY*/								{}
 		;
 
+with:		WITH									{}
+			| WITH_ORDINALITY						{}
+			| WITH_TIME								{}
+		;
+
 /*
  * Options for CREATE ROLE and ALTER ROLE (also used by CREATE/ALTER USER
  * for backwards compatibility).  Note: the only option required by SQL99
@@ -3127,7 +3134,16 @@ ExclusionConstraintList:
 													{ $$ = lappend($1, $3); }
 		;
 
-ExclusionConstraintElem: index_elem WITH any_operator
+ExclusionConstraintElem: 
+			index_elem WITH any_operator
+			{
+				$$ = list_make2($1, $3);
+			}
+			| index_elem WITH_TIME any_operator
+			{
+				$$ = list_make2($1, $3);
+			}
+			| index_elem WITH_ORDINALITY any_operator
 			{
 				$$ = list_make2($1, $3);
 			}
@@ -6188,8 +6204,8 @@ opt_asc_desc: ASC							{ $$ = SORTBY_ASC; }
 			| /*EMPTY*/						{ $$ = SORTBY_DEFAULT; }
 		;
 
-opt_nulls_order: NULLS_FIRST				{ $$ = SORTBY_NULLS_FIRST; }
-			| NULLS_LAST					{ $$ = SORTBY_NULLS_LAST; }
+opt_nulls_order: NULLS_FIRST FIRST_P		{ $$ = SORTBY_NULLS_FIRST; }
+			| NULLS_LAST LAST_P				{ $$ = SORTBY_NULLS_LAST; }
 			| /*EMPTY*/						{ $$ = SORTBY_NULLS_DEFAULT; }
 		;
 
@@ -8348,7 +8364,7 @@ AlterTSDictionaryStmt:
 		;
 
 AlterTSConfigurationStmt:
-			ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list WITH any_name_list
+			ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list with any_name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
 					n->cfgname = $5;
@@ -8358,7 +8374,7 @@ AlterTSConfigurationStmt:
 					n->replace = false;
 					$$ = (Node*)n;
 				}
-			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list WITH any_name_list
+			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list with any_name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
 					n->cfgname = $5;
@@ -8368,7 +8384,7 @@ AlterTSConfigurationStmt:
 					n->replace = false;
 					$$ = (Node*)n;
 				}
-			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING REPLACE any_name WITH any_name
+			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING REPLACE any_name with any_name
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
 					n->cfgname = $5;
@@ -8378,7 +8394,7 @@ AlterTSConfigurationStmt:
 					n->replace = true;
 					$$ = (Node*)n;
 				}
-			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list REPLACE any_name WITH any_name
+			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list REPLACE any_name with any_name
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
 					n->cfgname = $5;
@@ -9254,6 +9270,20 @@ with_clause:
 				$$->recursive = false;
 				$$->location = @1;
 			}
+		| WITH_TIME cte_list
+			{
+				$$ = makeNode(WithClause);
+				$$->ctes = $2;
+				$$->recursive = false;
+				$$->location = @1;
+			}
+		| WITH_ORDINALITY cte_list
+			{
+				$$ = makeNode(WithClause);
+				$$->ctes = $2;
+				$$->recursive = false;
+				$$->location = @1;
+			}
 		| WITH RECURSIVE cte_list
 			{
 				$$ = makeNode(WithClause);
@@ -9593,14 +9623,14 @@ table_ref:	relation_expr opt_alias_clause
 					n->coldeflist = lsecond($2);
 					$$ = (Node *) n;
 				}
-			| func_table WITH_ORDINALITY func_alias_clause
+			| func_table WITH_ORDINALITY ORDINALITY func_alias_clause
 				{
 					RangeFunction *n = makeNode(RangeFunction);
 					n->lateral = false;
 					n->ordinality = true;
 					n->funccallnode = $1;
-					n->alias = linitial($3);
-					n->coldeflist = lsecond($3);
+					n->alias = linitial($4);
+					n->coldeflist = lsecond($4);
 					$$ = (Node *) n;
 				}
 			| LATERAL_P func_table func_alias_clause
@@ -9613,14 +9643,14 @@ table_ref:	relation_expr opt_alias_clause
 					n->coldeflist = lsecond($3);
 					$$ = (Node *) n;
 				}
-			| LATERAL_P func_table WITH_ORDINALITY func_alias_clause
+			| LATERAL_P func_table WITH_ORDINALITY ORDINALITY func_alias_clause
 				{
 					RangeFunction *n = makeNode(RangeFunction);
 					n->lateral = true;
 					n->ordinality = true;
 					n->funccallnode = $2;
-					n->alias = linitial($4);
-					n->coldeflist = lsecond($4);
+					n->alias = linitial($5);
+					n->coldeflist = lsecond($5);
 					$$ = (Node *) n;
 				}
 			| select_with_parens opt_alias_clause
@@ -10413,7 +10443,7 @@ ConstInterval:
 		;
 
 opt_timezone:
-			WITH_TIME ZONE							{ $$ = TRUE; }
+			WITH_TIME TIME ZONE							{ $$ = TRUE; }
 			| WITHOUT TIME ZONE						{ $$ = FALSE; }
 			| /*EMPTY*/								{ $$ = FALSE; }
 		;
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 541d364..4802b7d 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -104,60 +104,53 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 		case NULLS_P:
 
 			/*
-			 * NULLS FIRST and NULLS LAST must be reduced to one token
+			 * NULLS FIRST and NULLS LAST must each trigger a special token (but
+			 * still output the regular tokens) to give the parser an extra
+			 * token of lookahead
 			 */
 			cur_yylval = lvalp->core_yystype;
 			cur_yylloc = *llocp;
 			next_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner);
-			switch (next_token)
-			{
-				case FIRST_P:
-					cur_token = NULLS_FIRST;
-					break;
-				case LAST_P:
-					cur_token = NULLS_LAST;
-					break;
-				default:
-					/* save the lookahead token for next time */
-					yyextra->lookahead_token = next_token;
-					yyextra->lookahead_yylval = lvalp->core_yystype;
-					yyextra->lookahead_yylloc = *llocp;
-					yyextra->have_lookahead = true;
-					/* and back up the output info to cur_token */
-					lvalp->core_yystype = cur_yylval;
-					*llocp = cur_yylloc;
-					break;
+			if (next_token == FIRST_P) {
+				cur_token = NULLS_FIRST;
+			} else if (next_token == LAST_P) {
+				cur_token = NULLS_LAST;
 			}
+
+			/* save the lookahead token for next time */
+			yyextra->lookahead_token = next_token;
+			yyextra->lookahead_yylval = lvalp->core_yystype;
+			yyextra->lookahead_yylloc = *llocp;
+			yyextra->have_lookahead = true;
+			/* and back up the output info to cur_token */
+			lvalp->core_yystype = cur_yylval;
+			*llocp = cur_yylloc;
+
 			break;
 
 		case WITH:
 
 			/*
-			 * WITH TIME and WITH ORDINALITY must each be reduced to one token
+			 * WITH TIME and WITH ORDINALITY must each trigger the special
+			 * "WITH_FOO" token (but still output the regular token)
 			 */
 			cur_yylval = lvalp->core_yystype;
 			cur_yylloc = *llocp;
 			next_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner);
-			switch (next_token)
-			{
-				case TIME:
-					cur_token = WITH_TIME;
-					break;
-				case ORDINALITY:
-					cur_token = WITH_ORDINALITY;
-					break;
-				default:
-					/* save the lookahead token for next time */
-					yyextra->lookahead_token = next_token;
-					yyextra->lookahead_yylval = lvalp->core_yystype;
-					yyextra->lookahead_yylloc = *llocp;
-					yyextra->have_lookahead = true;
-					/* and back up the output info to cur_token */
-					lvalp->core_yystype = cur_yylval;
-					*llocp = cur_yylloc;
-					break;
+			if (next_token == TIME) {
+				cur_token = WITH_TIME;
+			} else if (next_token == ORDINALITY) {
+				cur_token = WITH_ORDINALITY;
 			}
-			break;
+
+			/* save the lookahead token for next time */
+			yyextra->lookahead_token = next_token;
+			yyextra->lookahead_yylval = lvalp->core_yystype;
+			yyextra->lookahead_yylloc = *llocp;
+			yyextra->have_lookahead = true;
+			/* and back up the output info to cur_token */
+			lvalp->core_yystype = cur_yylval;
+			*llocp = cur_yylloc;
 
 		default:
 			break;
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index f4b51d6..ed0674d 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -42,9 +42,10 @@ my %replace_token = (
 
 # or in the block
 my %replace_string = (
-	'WITH_TIME'    => 'with time',
-	'NULLS_FIRST'  => 'nulls first',
-	'NULLS_LAST'   => 'nulls last',
+	'WITH_TIME'    => 'with',
+	'WITH_ORDINALITY' => 'with',
+	'NULLS_FIRST'  => 'nulls',
+	'NULLS_LAST'   => 'nulls',
 	'TYPECAST'     => '::',
 	'DOT_DOT'      => '..',
 	'COLON_EQUALS' => ':=',);
diff --git a/src/test/regress/expected/special_tokens.out b/src/test/regress/expected/special_tokens.out
new file mode 100644
index 0000000..fc3e908
--- /dev/null
+++ b/src/test/regress/expected/special_tokens.out
@@ -0,0 +1,41 @@
+-- ordinality and time should not require quoting here
+WITH ordinality AS (SELECT 1) SELECT * FROM ordinality;
+ ?column? 
+----------
+        1
+(1 row)
+
+WITH time AS (SELECT 1) SELECT * FROM time;
+ ?column? 
+----------
+        1
+(1 row)
+
+-- These should produce the same error unless there's actually a dictionary
+-- called "ordinality" present in which case it should just work.
+ALTER TEXT SEARCH CONFIGURATION english ADD MAPPING FOR word WITH slkdf;
+ERROR:  text search dictionary "slkdf" does not exist
+ALTER TEXT SEARCH CONFIGURATION english ADD MAPPING FOR word WITH ordinality;
+ERROR:  text search dictionary "ordinality" does not exist
+-- first should not require quoting here
+SELECT nulls first FROM (SELECT 1 AS nulls) AS x;
+ first 
+-------
+     1
+(1 row)
+
+SELECT nulls last FROM (SELECT 1 AS nulls) AS x;
+ last 
+------
+    1
+(1 row)
+
+CREATE COLLATION nulls (locale='C');
+ALTER OPERATOR CLASS text_ops USING btree RENAME TO first;
+CREATE TABLE nulls_first(t text);
+-- Neither "nulls" nor "first" should require quoting here. This should
+-- correctly find the "nulls" collation and the "first" operator class.
+CREATE INDEX nulls_first_i ON nulls_first(t COLLATE nulls first);
+ALTER OPERATOR CLASS first USING btree RENAME TO text_ops;
+DROP TABLE nulls_first;
+DROP COLLATION nulls;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index fd08e8d..8f89208 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -88,7 +88,7 @@ test: privileges security_label collate matview lock
 # ----------
 # Another group of parallel tests
 # ----------
-test: alter_generic misc psql async
+test: special_tokens alter_generic misc psql async
 
 # rules cannot run concurrently with any test that creates a view
 test: rules
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 1ed059b..063c470 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -97,6 +97,7 @@ test: security_label
 test: collate
 test: matview
 test: lock
+test: special_tokens
 test: alter_generic
 test: misc
 test: psql
diff --git a/src/test/regress/sql/special_tokens.sql b/src/test/regress/sql/special_tokens.sql
new file mode 100644
index 0000000..0e6b6c8
--- /dev/null
+++ b/src/test/regress/sql/special_tokens.sql
@@ -0,0 +1,26 @@
+-- ordinality and time should not require quoting here
+WITH ordinality AS (SELECT 1) SELECT * FROM ordinality;
+WITH time AS (SELECT 1) SELECT * FROM time;
+
+-- These should produce the same error unless there's actually a dictionary
+-- called "ordinality" present in which case it should just work.
+ALTER TEXT SEARCH CONFIGURATION english ADD MAPPING FOR word WITH slkdf;
+ALTER TEXT SEARCH CONFIGURATION english ADD MAPPING FOR word WITH ordinality;
+
+
+
+-- first should not require quoting here
+SELECT nulls xirst FROM (SELECT 1 AS nulls) AS x;
+SELECT nulls xast FROM (SELECT 1 AS nulls) AS x;
+
+CREATE COLLATION nulls (locale='C');
+ALTER OPERATOR CLASS text_ops USING btree RENAME TO xirst;
+CREATE TABLE nulls_first(t text);
+
+-- Neither "nulls" nor "first" should require quoting here. This should
+-- correctly find the "nulls" collation and the "first" operator class.
+CREATE INDEX nulls_first_i ON nulls_first(t COLLATE nulls xirst);
+
+ALTER OPERATOR CLASS xirst USING btree RENAME TO text_ops;
+DROP TABLE nulls_first;
+DROP COLLATION nulls;
