On Tue, May 12, 2015 at 3:18 PM, Peter Geoghegan <p...@heroku.com> wrote:
> On Tue, May 12, 2015 at 3:16 PM, Andres Freund <and...@anarazel.de> wrote:
>> Could you rebase and adjust your patch? I'd rather have the inference
>> structure refactoring separate.
>
> Sure.

Rebased version of patch is attached.

-- 
Peter Geoghegan
From cc2fcd8862e01880b1559f315aa3da23017edfe6 Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <peter.geoghega...@gmail.com>
Date: Mon, 11 May 2015 15:37:54 -0700
Subject: [PATCH] Further fixes to minor ON CONFLICT issues

Deparsing with an inference clause is now correctly supported.  In
passing, re-factor InferenceElem representation of opclass, to defer
opfamily lookup for Relation index matching until index inference proper
(i.e., within the optimizer).  This is done for the convenience of the
new ruleutils.c code, but independently make senses.

Finally, fix a few typos, and rename a variable -- "candidates" seemed
like a misnomer for the return value of infer_arbiter_indexes().
---
 contrib/pg_stat_statements/pg_stat_statements.c |  3 +-
 src/backend/executor/nodeModifyTable.c          |  2 +-
 src/backend/nodes/copyfuncs.c                   |  3 +-
 src/backend/nodes/equalfuncs.c                  |  3 +-
 src/backend/nodes/outfuncs.c                    |  3 +-
 src/backend/nodes/readfuncs.c                   |  3 +-
 src/backend/optimizer/util/plancat.c            | 34 +++++++----
 src/backend/parser/parse_clause.c               | 16 ++----
 src/backend/utils/adt/ruleutils.c               | 75 ++++++++++++++++++++++++-
 src/include/nodes/primnodes.h                   |  3 +-
 src/test/regress/expected/insert_conflict.out   |  2 +-
 src/test/regress/expected/rules.out             | 38 +++++++------
 src/test/regress/sql/rules.sql                  |  7 ++-
 13 files changed, 133 insertions(+), 59 deletions(-)

diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 6abe3f0..f97cc2c 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -2637,8 +2637,7 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
 				InferenceElem *ie = (InferenceElem *) node;
 
 				APP_JUMB(ie->infercollid);
-				APP_JUMB(ie->inferopfamily);
-				APP_JUMB(ie->inferopcinputtype);
+				APP_JUMB(ie->inferopclass);
 				JumbleExpr(jstate, ie->expr);
 			}
 			break;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 89f1f57..72bbd62 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -414,7 +414,7 @@ ExecInsert(ModifyTableState *mtstate,
 												   estate, true, &specConflict,
 												   arbiterIndexes);
 
-			/* adjust the tuple's state accordingly */
+			/* adjust the tuple's state */
 			if (!specConflict)
 				heap_finish_speculative(resultRelationDesc, tuple);
 			else
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 76b63af..957c2bc 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1803,8 +1803,7 @@ _copyInferenceElem(const InferenceElem *from)
 
 	COPY_NODE_FIELD(expr);
 	COPY_SCALAR_FIELD(infercollid);
-	COPY_SCALAR_FIELD(inferopfamily);
-	COPY_SCALAR_FIELD(inferopcinputtype);
+	COPY_SCALAR_FIELD(inferopclass);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e032142..f26626e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -687,8 +687,7 @@ _equalInferenceElem(const InferenceElem *a, const InferenceElem *b)
 {
 	COMPARE_NODE_FIELD(expr);
 	COMPARE_SCALAR_FIELD(infercollid);
-	COMPARE_SCALAR_FIELD(inferopfamily);
-	COMPARE_SCALAR_FIELD(inferopcinputtype);
+	COMPARE_SCALAR_FIELD(inferopclass);
 
 	return true;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index fe868b8..454d9ec 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1450,8 +1450,7 @@ _outInferenceElem(StringInfo str, const InferenceElem *node)
 
 	WRITE_NODE_FIELD(expr);
 	WRITE_OID_FIELD(infercollid);
-	WRITE_OID_FIELD(inferopfamily);
-	WRITE_OID_FIELD(inferopcinputtype);
+	WRITE_OID_FIELD(inferopclass);
 }
 
 static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 8136306..81b6243 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1141,8 +1141,7 @@ _readInferenceElem(void)
 
 	READ_NODE_FIELD(expr);
 	READ_OID_FIELD(infercollid);
-	READ_OID_FIELD(inferopfamily);
-	READ_OID_FIELD(inferopcinputtype);
+	READ_OID_FIELD(inferopclass);
 
 	READ_DONE();
 }
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index b425680..a857ba3 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -438,8 +438,8 @@ infer_arbiter_indexes(PlannerInfo *root)
 	Bitmapset  *inferAttrs = NULL;
 	List	   *inferElems = NIL;
 
-	/* Result */
-	List	   *candidates = NIL;
+	/* Results */
+	List	   *results = NIL;
 
 	/*
 	 * Quickly return NIL for ON CONFLICT DO NOTHING without an inference
@@ -565,11 +565,11 @@ infer_arbiter_indexes(PlannerInfo *root)
 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 						 errmsg("ON CONFLICT DO UPDATE not supported with exclusion constraints")));
 
-			candidates = lappend_oid(candidates, idxForm->indexrelid);
+			results = lappend_oid(results, idxForm->indexrelid);
 			list_free(indexList);
 			index_close(idxRel, NoLock);
 			heap_close(relation, NoLock);
-			return candidates;
+			return results;
 		}
 		else if (indexOidFromConstraint != InvalidOid)
 		{
@@ -633,7 +633,7 @@ infer_arbiter_indexes(PlannerInfo *root)
 			 * index definition.
 			 */
 			if (elem->infercollid != InvalidOid ||
-				elem->inferopfamily != InvalidOid ||
+				elem->inferopclass != InvalidOid ||
 				list_member(idxExprs, elem->expr))
 				continue;
 
@@ -660,7 +660,7 @@ infer_arbiter_indexes(PlannerInfo *root)
 		if (!predicate_implied_by(predExprs, whereExplicit))
 			goto next;
 
-		candidates = lappend_oid(candidates, idxForm->indexrelid);
+		results = lappend_oid(results, idxForm->indexrelid);
 next:
 		index_close(idxRel, NoLock);
 	}
@@ -668,12 +668,12 @@ next:
 	list_free(indexList);
 	heap_close(relation, NoLock);
 
-	if (candidates == NIL)
+	if (results == NIL)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
 				 errmsg("there is no unique or exclusion constraint matching the ON CONFLICT specification")));
 
-	return candidates;
+	return results;
 }
 
 /*
@@ -709,23 +709,33 @@ infer_collation_opclass_match(InferenceElem *elem, Relation idxRel,
 							  Bitmapset *inferAttrs, List *idxExprs)
 {
 	AttrNumber	natt;
+	Oid			inferopfamily = InvalidOid;		/* OID of att opfamily */
+	Oid			inferopcinputtype = InvalidOid;		/* OID of att opfamily */
 
 	/*
 	 * If inference specification element lacks collation/opclass, then no
 	 * need to check for exact match.
 	 */
-	if (elem->infercollid == InvalidOid && elem->inferopfamily == InvalidOid)
+	if (elem->infercollid == InvalidOid && elem->inferopclass == InvalidOid)
 		return true;
 
+	/*
+	 * Lookup opfamily and input type, for matching indexes
+	 */
+	if (elem->inferopclass)
+	{
+		inferopfamily = get_opclass_family(elem->inferopclass);
+		inferopcinputtype = get_opclass_input_type(elem->inferopclass);
+	}
+
 	for (natt = 1; natt <= idxRel->rd_att->natts; natt++)
 	{
 		Oid		opfamily = idxRel->rd_opfamily[natt - 1];
 		Oid		opcinputtype = idxRel->rd_opcintype[natt - 1];
 		Oid		collation = idxRel->rd_indcollation[natt - 1];
 
-		if (elem->inferopfamily != InvalidOid &&
-			(elem->inferopfamily != opfamily ||
-			 elem->inferopcinputtype != opcinputtype))
+		if (elem->inferopclass != InvalidOid &&
+			(inferopfamily != opfamily || inferopcinputtype != opcinputtype))
 		{
 			/* Attribute needed to match opclass, but didn't */
 			continue;
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 73c505e..77a7a43 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -2258,18 +2258,10 @@ resolve_unique_index_expr(ParseState *pstate, InferClause *infer,
 												  exprLocation(pInfer->expr));
 
 		if (!ielem->opclass)
-		{
-			pInfer->inferopfamily = InvalidOid;
-			pInfer->inferopcinputtype = InvalidOid;
-		}
+			pInfer->inferopclass = InvalidOid;
 		else
-		{
-			Oid		opclass = get_opclass_oid(BTREE_AM_OID, ielem->opclass,
-											  false);
-
-			pInfer->inferopfamily = get_opclass_family(opclass);
-			pInfer->inferopcinputtype = get_opclass_input_type(opclass);
-		}
+			pInfer->inferopclass = get_opclass_oid(BTREE_AM_OID,
+												   ielem->opclass, false);
 
 		result = lappend(result, pInfer);
 	}
@@ -2301,7 +2293,7 @@ transformOnConflictArbiter(ParseState *pstate,
 		ereport(ERROR,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("ON CONFLICT DO UPDATE requires inference specification or constraint name"),
-				 errhint("For example, ON CONFLICT ON CONFLICT (<column>)."),
+				 errhint("For example, ON CONFLICT (<column>)."),
 				 parser_errposition(pstate,
 									exprLocation((Node *) onConflictClause))));
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 903e80a..87fe580 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5319,13 +5319,38 @@ get_insert_query_def(Query *query, deparse_context *context)
 	{
 		OnConflictExpr *confl = query->onConflict;
 
+		appendStringInfo(buf, " ON CONFLICT");
+
+		if (confl->arbiterElems)
+		{
+			/* Add the single-VALUES expression list */
+			appendStringInfoChar(buf, '(');
+			get_rule_expr((Node *) confl->arbiterElems, context, false);
+			appendStringInfoChar(buf, ')');
+
+			/* Add a WHERE clause (for partial indexes) if given */
+			if (confl->arbiterWhere != NULL)
+			{
+				appendContextKeyword(context, " WHERE ",
+									 -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
+				get_rule_expr(confl->arbiterWhere, context, false);
+			}
+		}
+		else
+		{
+			char   *constraint = get_constraint_name(confl->constraint);
+
+			appendStringInfo(buf, " ON CONSTRAINT %s",
+							 quote_qualified_identifier(NULL, constraint));
+		}
+
 		if (confl->action == ONCONFLICT_NOTHING)
 		{
-			appendStringInfoString(buf, " ON CONFLICT DO NOTHING");
+			appendStringInfoString(buf, " DO NOTHING");
 		}
 		else
 		{
-			appendStringInfoString(buf, " ON CONFLICT DO UPDATE SET ");
+			appendStringInfoString(buf, " DO UPDATE SET ");
 			/* Deparse targetlist */
 			get_update_query_targetlist_def(query, confl->onConflictSet,
 											context, rte);
@@ -7716,6 +7741,52 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_InferenceElem:
+			{
+				InferenceElem  *iexpr = (InferenceElem *) node;
+				bool			varprefix = context->varprefix;
+				bool			need_parens;
+
+				/*
+				 * InfereneElem can only refer to target relation, so prefix is
+				 * never useful
+				 */
+				context->varprefix = false;
+
+				/*
+				 * Parenthesize the element unless it's a simple Var or a bare
+				 * function call.  Follows pg_get_indexdef_worker().
+				 */
+				need_parens = !IsA(iexpr->expr, Var);
+				if (IsA(iexpr->expr, FuncExpr) &&
+					((FuncExpr *) iexpr->expr)->funcformat ==
+					COERCE_EXPLICIT_CALL)
+					need_parens = false;
+
+				if (need_parens)
+					appendStringInfoChar(buf, '(');
+				get_rule_expr((Node *) iexpr->expr,
+							  context, false);
+				if (need_parens)
+					appendStringInfoChar(buf, ')');
+
+				context->varprefix = varprefix;
+
+				if (iexpr->infercollid)
+					appendStringInfo(buf, " COLLATE %s",
+									 generate_collation_name(iexpr->infercollid));
+
+				/* Add the operator class name, if not default */
+				if (iexpr->inferopclass)
+				{
+					Oid 	inferopclass = iexpr->inferopclass;
+					Oid		inferopcinputtype = get_opclass_input_type(iexpr->inferopclass);
+
+					get_opclass_name(inferopclass, inferopcinputtype, buf);
+				}
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index f10ae4e..51a65bd 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1159,8 +1159,7 @@ typedef struct InferenceElem
 	Expr		xpr;
 	Node	   *expr;				/* expression to infer from, or NULL */
 	Oid			infercollid;		/* OID of collation, or InvalidOid */
-	Oid			inferopfamily;		/* OID of att opfamily, or InvalidOid */
-	Oid			inferopcinputtype;	/* OID of att input type, or InvalidOid */
+	Oid			inferopclass;		/* OID of att opclass, or InvalidOid */
 } InferenceElem;
 
 /*--------------------
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index 3273d98..a6abd8b 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -192,7 +192,7 @@ insert into insertconflicttest values (1, 'Apple') on conflict do update set fru
 ERROR:  ON CONFLICT DO UPDATE requires inference specification or constraint name
 LINE 1: ...nsert into insertconflicttest values (1, 'Apple') on conflic...
                                                              ^
-HINT:  For example, ON CONFLICT ON CONFLICT (<column>).
+HINT:  For example, ON CONFLICT (<column>).
 -- inference succeeds:
 insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit;
 insert into insertconflicttest values (2, 'Orange') on conflict (key, key, key) do update set fruit = excluded.fruit;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index cb18bb9..056dd8e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2777,16 +2777,19 @@ CREATE TABLE hats (
 	hat_color   char(10)      -- hat color
 );
 CREATE TABLE hat_data (
-	hat_name    char(10) primary key,
+	hat_name    char(10),
 	hat_color   char(10)      -- hat color
 );
+create unique index hat_data_unique_idx
+  on hat_data (hat_name COLLATE "C" bpchar_pattern_ops);
 -- okay
 CREATE RULE hat_nosert AS ON INSERT TO hats
     DO INSTEAD
     INSERT INTO hat_data VALUES (
            NEW.hat_name,
            NEW.hat_color)
-        ON CONFLICT (hat_name) DO NOTHING RETURNING *;
+        ON CONFLICT (hat_name COLLATE "C" bpchar_pattern_ops) WHERE hat_color = 'green'
+        DO NOTHING RETURNING *;
 -- Works (projects row)
 INSERT INTO hats VALUES ('h7', 'black') RETURNING *;
   hat_name  | hat_color  
@@ -2802,12 +2805,13 @@ INSERT INTO hats VALUES ('h7', 'black') RETURNING *;
 
 SELECT tablename, rulename, definition FROM pg_rules
 	WHERE tablename = 'hats';
- tablename |  rulename  |                                  definition                                  
------------+------------+------------------------------------------------------------------------------
- hats      | hat_nosert | CREATE RULE hat_nosert AS                                                   +
-           |            |     ON INSERT TO hats DO INSTEAD  INSERT INTO hat_data (hat_name, hat_color)+
-           |            |   VALUES (new.hat_name, new.hat_color) ON CONFLICT DO NOTHING               +
-           |            |   RETURNING hat_data.hat_name,                                              +
+ tablename |  rulename  |                                         definition                                          
+-----------+------------+---------------------------------------------------------------------------------------------
+ hats      | hat_nosert | CREATE RULE hat_nosert AS                                                                  +
+           |            |     ON INSERT TO hats DO INSTEAD  INSERT INTO hat_data (hat_name, hat_color)               +
+           |            |   VALUES (new.hat_name, new.hat_color) ON CONFLICT(hat_name COLLATE "C" bpchar_pattern_ops)+
+           |            |   WHERE (hat_data.hat_color = 'green'::bpchar) DO NOTHING                                  +
+           |            |   RETURNING hat_data.hat_name,                                                             +
            |            |     hat_data.hat_color;
 (1 row)
 
@@ -2860,13 +2864,13 @@ SELECT * FROM hat_data WHERE hat_name = 'h8';
 
 SELECT tablename, rulename, definition FROM pg_rules
 	WHERE tablename = 'hats';
- tablename |  rulename  |                                                          definition                                                           
------------+------------+-------------------------------------------------------------------------------------------------------------------------------
- hats      | hat_upsert | CREATE RULE hat_upsert AS                                                                                                    +
-           |            |     ON INSERT TO hats DO INSTEAD  INSERT INTO hat_data (hat_name, hat_color)                                                 +
-           |            |   VALUES (new.hat_name, new.hat_color) ON CONFLICT DO UPDATE SET hat_name = hat_data.hat_name, hat_color = excluded.hat_color+
-           |            |   WHERE (excluded.hat_color <> 'forbidden'::bpchar)                                                                          +
-           |            |   RETURNING hat_data.hat_name,                                                                                               +
+ tablename |  rulename  |                                                               definition                                                                
+-----------+------------+-----------------------------------------------------------------------------------------------------------------------------------------
+ hats      | hat_upsert | CREATE RULE hat_upsert AS                                                                                                              +
+           |            |     ON INSERT TO hats DO INSTEAD  INSERT INTO hat_data (hat_name, hat_color)                                                           +
+           |            |   VALUES (new.hat_name, new.hat_color) ON CONFLICT(hat_name) DO UPDATE SET hat_name = hat_data.hat_name, hat_color = excluded.hat_color+
+           |            |   WHERE (excluded.hat_color <> 'forbidden'::bpchar)                                                                                    +
+           |            |   RETURNING hat_data.hat_name,                                                                                                         +
            |            |     hat_data.hat_color;
 (1 row)
 
@@ -2876,7 +2880,7 @@ explain (costs off) INSERT INTO hats VALUES ('h8', 'forbidden') RETURNING *;
 ----------------------------------------------------------------
  Insert on hat_data
    Conflict Resolution: UPDATE
-   Conflict Arbiter Indexes: hat_data_pkey
+   Conflict Arbiter Indexes: hat_data_unique_idx
    Conflict Filter: (excluded.hat_color <> 'forbidden'::bpchar)
    ->  Result
 (5 rows)
@@ -2908,7 +2912,7 @@ RETURNING *;
 ----------------------------------------------------------------
  Insert on hat_data
    Conflict Resolution: UPDATE
-   Conflict Arbiter Indexes: hat_data_pkey
+   Conflict Arbiter Indexes: hat_data_unique_idx
    Conflict Filter: (excluded.hat_color <> 'forbidden'::bpchar)
    CTE data
      ->  Values Scan on "*VALUES*"
diff --git a/src/test/regress/sql/rules.sql b/src/test/regress/sql/rules.sql
index 1a81155..621adc0 100644
--- a/src/test/regress/sql/rules.sql
+++ b/src/test/regress/sql/rules.sql
@@ -1049,9 +1049,11 @@ CREATE TABLE hats (
 );
 
 CREATE TABLE hat_data (
-	hat_name    char(10) primary key,
+	hat_name    char(10),
 	hat_color   char(10)      -- hat color
 );
+create unique index hat_data_unique_idx
+  on hat_data (hat_name COLLATE "C" bpchar_pattern_ops);
 
 -- okay
 CREATE RULE hat_nosert AS ON INSERT TO hats
@@ -1059,7 +1061,8 @@ CREATE RULE hat_nosert AS ON INSERT TO hats
     INSERT INTO hat_data VALUES (
            NEW.hat_name,
            NEW.hat_color)
-        ON CONFLICT (hat_name) DO NOTHING RETURNING *;
+        ON CONFLICT (hat_name COLLATE "C" bpchar_pattern_ops) WHERE hat_color = 'green'
+        DO NOTHING RETURNING *;
 
 -- Works (projects row)
 INSERT INTO hats VALUES ('h7', 'black') RETURNING *;
-- 
1.9.1

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to