Remove undocumented IS [NOT] OF syntax.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 19 Nov 2020 22:39:39 +0000 (17:39 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 19 Nov 2020 22:39:39 +0000 (17:39 -0500)
This feature was added a long time ago, in 7c1e67bd5 and eb121ba2c,
but never documented in any user-facing way.  (Documentation added
in 6126d3e70 was commented out almost immediately, in 8272fc3f7.)
That's because, while this syntax is defined by SQL:99, our
implementation is only vaguely related to the standard's semantics.
The standard appears to intend a run-time not parse-time test, and
it definitely intends that the test should understand subtype
relationships.

No one has stepped up to fix that in the intervening years, but
people keep coming across the code and asking why it's not documented.
Let's just get rid of it: if anyone ever wants to make it work per
spec, they can easily recover whatever parts of this code are still
of value from our git history.

If there's anyone out there who's actually using this despite its
undocumented status, they can switch to using pg_typeof() instead,
eg. "pg_typeof(something) = 'mytype'::regtype".  That gives
essentially the same semantics as what our IS OF code did.
(We didn't have that function last time this was discussed, or
we would have ripped out IS OF then.)

Discussion: https://postgr.es/m/CAKFQuwZ2pTc-DSkOiTfjauqLYkNREeNZvWmeg12Q-_69D+sYZA@mail.gmail.com
Discussion: https://postgr.es/m/BAY20-F23E9F2B4DAB3E4E88D3623F99B0@phx.gbl
Discussion: https://postgr.es/m/3E7CF81D.1000203@joeconway.com

12 files changed:
doc/src/sgml/func.sgml
src/backend/catalog/sql_features.txt
src/backend/nodes/outfuncs.c
src/backend/parser/gram.y
src/backend/parser/parse_expr.c
src/include/nodes/parsenodes.h
src/test/regress/expected/arrays.out
src/test/regress/expected/domain.out
src/test/regress/expected/with.out
src/test/regress/sql/arrays.sql
src/test/regress/sql/domain.sql
src/test/regress/sql/with.sql

index df2c1c6f053bf20b356b1e84629cb3b9d20904fe..7d06b979ebf92f0981fb1be651fa1c139680a332 100644 (file)
@@ -762,25 +762,6 @@ repeat('Pg', 4) <returnvalue>PgPgPgPg</returnvalue>
     expression must be of Boolean type.
    </para>
 
-<!-- IS OF does not conform to the ISO SQL behavior, so it is undocumented here
-   <para>
-    <indexterm>
-     <primary>IS OF</primary>
-    </indexterm>
-    <indexterm>
-     <primary>IS NOT OF</primary>
-    </indexterm>
-    It is possible to check the data type of an expression using the
-    predicates
-<synopsis>
-<replaceable>expression</replaceable> IS OF (typename, ...)
-<replaceable>expression</replaceable> IS NOT OF (typename, ...)
-</synopsis>
-    They return a boolean value based on whether the expression's data
-    type is one of the listed data types.
-   </para>
--->
-
    <para>
     Some comparison-related functions are also available, as shown in <xref
     linkend="functions-comparison-func-table"/>.
index b6e58e84939c8760ce0adb2dd729024ec500e094..caa971c4356acd39e3903350cd30ae5cb6b8711a 100644 (file)
@@ -373,7 +373,7 @@ S096    Optional array bounds           YES
 S097   Array element assignment            NO  
 S098   ARRAY_AGG           YES 
 S111   ONLY in query expressions           YES 
-S151   Type predicate          NO  
+S151   Type predicate          NO  see pg_typeof()
 S161   Subtype treatment           NO  
 S162   Subtype treatment for references            NO  
 S201   SQL-invoked routines on arrays          YES 
index 4504b1503b928490062018af6c421f925d831b22..f26498cea2d15c6fdac33c74a4bf23a7bd185753 100644 (file)
@@ -3213,10 +3213,6 @@ _outAExpr(StringInfo str, const A_Expr *node)
            appendStringInfoString(str, " NULLIF ");
            WRITE_NODE_FIELD(name);
            break;
-       case AEXPR_OF:
-           appendStringInfoString(str, " OF ");
-           WRITE_NODE_FIELD(name);
-           break;
        case AEXPR_IN:
            appendStringInfoString(str, " IN ");
            WRITE_NODE_FIELD(name);
index 2cb377d03423f1a14455aef91a3a94bfe37c7809..efc9c997541b93d4876f56759aa124b574d700df 100644 (file)
@@ -13238,14 +13238,6 @@ a_expr:        c_expr                                  { $$ = $1; }
                {
                    $$ = (Node *) makeSimpleA_Expr(AEXPR_NOT_DISTINCT, "=", $1, $6, @2);
                }
-           | a_expr IS OF '(' type_list ')'            %prec IS
-               {
-                   $$ = (Node *) makeSimpleA_Expr(AEXPR_OF, "=", $1, (Node *) $5, @2);
-               }
-           | a_expr IS NOT OF '(' type_list ')'        %prec IS
-               {
-                   $$ = (Node *) makeSimpleA_Expr(AEXPR_OF, "<>", $1, (Node *) $6, @2);
-               }
            | a_expr BETWEEN opt_asymmetric b_expr AND a_expr       %prec BETWEEN
                {
                    $$ = (Node *) makeSimpleA_Expr(AEXPR_BETWEEN,
@@ -13464,14 +13456,6 @@ b_expr:        c_expr
                {
                    $$ = (Node *) makeSimpleA_Expr(AEXPR_NOT_DISTINCT, "=", $1, $6, @2);
                }
-           | b_expr IS OF '(' type_list ')'        %prec IS
-               {
-                   $$ = (Node *) makeSimpleA_Expr(AEXPR_OF, "=", $1, (Node *) $5, @2);
-               }
-           | b_expr IS NOT OF '(' type_list ')'    %prec IS
-               {
-                   $$ = (Node *) makeSimpleA_Expr(AEXPR_OF, "<>", $1, (Node *) $6, @2);
-               }
            | b_expr IS DOCUMENT_P                  %prec IS
                {
                    $$ = makeXmlExpr(IS_DOCUMENT, NULL, NIL,
index f5165863d7791f5a582ea79c24bc546a85da74b7..36002f059d1eddad7988c4e1289348330da30764 100644 (file)
@@ -94,7 +94,6 @@ static Node *transformAExprOpAny(ParseState *pstate, A_Expr *a);
 static Node *transformAExprOpAll(ParseState *pstate, A_Expr *a);
 static Node *transformAExprDistinct(ParseState *pstate, A_Expr *a);
 static Node *transformAExprNullIf(ParseState *pstate, A_Expr *a);
-static Node *transformAExprOf(ParseState *pstate, A_Expr *a);
 static Node *transformAExprIn(ParseState *pstate, A_Expr *a);
 static Node *transformAExprBetween(ParseState *pstate, A_Expr *a);
 static Node *transformBoolExpr(ParseState *pstate, BoolExpr *a);
@@ -228,9 +227,6 @@ transformExprRecurse(ParseState *pstate, Node *expr)
                    case AEXPR_NULLIF:
                        result = transformAExprNullIf(pstate, a);
                        break;
-                   case AEXPR_OF:
-                       result = transformAExprOf(pstate, a);
-                       break;
                    case AEXPR_IN:
                        result = transformAExprIn(pstate, a);
                        break;
@@ -1168,51 +1164,6 @@ transformAExprNullIf(ParseState *pstate, A_Expr *a)
    return (Node *) result;
 }
 
-/*
- * Checking an expression for match to a list of type names. Will result
- * in a boolean constant node.
- */
-static Node *
-transformAExprOf(ParseState *pstate, A_Expr *a)
-{
-   Node       *lexpr = a->lexpr;
-   Const      *result;
-   ListCell   *telem;
-   Oid         ltype,
-               rtype;
-   bool        matched = false;
-
-   if (operator_precedence_warning)
-       emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_IS, "IS",
-                                lexpr, NULL,
-                                a->location);
-
-   lexpr = transformExprRecurse(pstate, lexpr);
-
-   ltype = exprType(lexpr);
-   foreach(telem, (List *) a->rexpr)
-   {
-       rtype = typenameTypeId(pstate, lfirst(telem));
-       matched = (rtype == ltype);
-       if (matched)
-           break;
-   }
-
-   /*
-    * We have two forms: equals or not equals. Flip the sense of the result
-    * for not equals.
-    */
-   if (strcmp(strVal(linitial(a->name)), "<>") == 0)
-       matched = (!matched);
-
-   result = (Const *) makeBoolConst(matched, false);
-
-   /* Make the result have the original input's parse location */
-   result->location = exprLocation((Node *) a);
-
-   return (Node *) result;
-}
-
 static Node *
 transformAExprIn(ParseState *pstate, A_Expr *a)
 {
@@ -3257,11 +3208,6 @@ operator_precedence_group(Node *node, const char **nodename)
            *nodename = "IS";
            group = PREC_GROUP_INFIX_IS;
        }
-       else if (aexpr->kind == AEXPR_OF)
-       {
-           *nodename = "IS";
-           group = PREC_GROUP_POSTFIX_IS;
-       }
        else if (aexpr->kind == AEXPR_IN)
        {
            *nodename = "IN";
index a2dcdef3fada2bfc9be204a315ea3414e0bd0eac..d1f9ef29ca0d1705340f664a8ae1b423f2f75e22 100644 (file)
@@ -258,7 +258,6 @@ typedef enum A_Expr_Kind
    AEXPR_DISTINCT,             /* IS DISTINCT FROM - name must be "=" */
    AEXPR_NOT_DISTINCT,         /* IS NOT DISTINCT FROM - name must be "=" */
    AEXPR_NULLIF,               /* NULLIF - name must be "=" */
-   AEXPR_OF,                   /* IS [NOT] OF - name must be "=" or "<>" */
    AEXPR_IN,                   /* [NOT] IN - name must be "=" or "<>" */
    AEXPR_LIKE,                 /* [NOT] LIKE - name must be "~~" or "!~~" */
    AEXPR_ILIKE,                /* [NOT] ILIKE - name must be "~~*" or "!~~*" */
index 5abfeb6773bfebe235f54af98df5bd1aa851e448..e0f870e9d7b7f2a3eadf850d7006bd26c5618af3 100644 (file)
@@ -1149,10 +1149,10 @@ SELECT ARRAY[1,2,3]::text[]::int[]::float8[] AS "{1,2,3}";
  {1,2,3}
 (1 row)
 
-SELECT ARRAY[1,2,3]::text[]::int[]::float8[] is of (float8[]) as "TRUE";
- TRUE 
-------
- t
+SELECT pg_typeof(ARRAY[1,2,3]::text[]::int[]::float8[]) AS "double precision[]";
+ double precision[] 
+--------------------
+ double precision[]
 (1 row)
 
 SELECT ARRAY[['a','bc'],['def','hijk']]::text[]::varchar[] AS "{{a,bc},{def,hijk}}";
@@ -1161,10 +1161,10 @@ SELECT ARRAY[['a','bc'],['def','hijk']]::text[]::varchar[] AS "{{a,bc},{def,hijk
  {{a,bc},{def,hijk}}
 (1 row)
 
-SELECT ARRAY[['a','bc'],['def','hijk']]::text[]::varchar[] is of (varchar[]) as "TRUE";
- TRUE 
-------
- t
+SELECT pg_typeof(ARRAY[['a','bc'],['def','hijk']]::text[]::varchar[]) AS "character varying[]";
+ character varying[] 
+---------------------
+ character varying[]
 (1 row)
 
 SELECT CAST(ARRAY[[[[[['a','bb','ccc']]]]]] as text[]) as "{{{{{{a,bb,ccc}}}}}}";
index 2a033a6e11ed8bb7f0bb1a00a28997b99e9af0a3..411d5c003ef11685d5ef498b67ab752772d1690a 100644 (file)
@@ -70,22 +70,16 @@ from basictest;
 (3 rows)
 
 -- check that union/case/coalesce type resolution handles domains properly
-select coalesce(4::domainint4, 7) is of (int4) as t;
- t 
----
- t
-(1 row)
-
-select coalesce(4::domainint4, 7) is of (domainint4) as f;
- f 
----
- f
+select pg_typeof(coalesce(4::domainint4, 7));
+ pg_typeof 
+-----------
+ integer
 (1 row)
 
-select coalesce(4::domainint4, 7::domainint4) is of (domainint4) as t;
- t 
----
- t
+select pg_typeof(coalesce(4::domainint4, 7::domainint4));
+ pg_typeof  
+------------
+ domainint4
 (1 row)
 
 drop table basictest;
index 96835a517eec5ae3701e1a6d7749487ca2cf0b4f..9429e9fd28f08d7ece24f6c52aaf57495fa8fbbe 100644 (file)
@@ -142,10 +142,10 @@ SELECT * FROM t LIMIT 10;
 
 -- Test behavior with an unknown-type literal in the WITH
 WITH q AS (SELECT 'foo' AS x)
-SELECT x, x IS OF (text) AS is_text FROM q;
-  x  | is_text 
------+---------
- foo | t
+SELECT x, pg_typeof(x) FROM q;
+  x  | pg_typeof 
+-----+-----------
+ foo | text
 (1 row)
 
 WITH RECURSIVE t(n) AS (
@@ -153,15 +153,15 @@ WITH RECURSIVE t(n) AS (
 UNION ALL
     SELECT n || ' bar' FROM t WHERE length(n) < 20
 )
-SELECT n, n IS OF (text) AS is_text FROM t;
-            n            | is_text 
--------------------------+---------
- foo                     | t
- foo bar                 | t
- foo bar bar             | t
- foo bar bar bar         | t
- foo bar bar bar bar     | t
- foo bar bar bar bar bar | t
+SELECT n, pg_typeof(n) FROM t;
+            n            | pg_typeof 
+-------------------------+-----------
+ foo                     | text
+ foo bar                 | text
+ foo bar bar             | text
+ foo bar bar bar         | text
+ foo bar bar bar bar     | text
+ foo bar bar bar bar bar | text
 (6 rows)
 
 -- In a perfect world, this would work and resolve the literal as int ...
@@ -171,7 +171,7 @@ WITH RECURSIVE t(n) AS (
 UNION ALL
     SELECT n+1 FROM t WHERE n < 10
 )
-SELECT n, n IS OF (int) AS is_int FROM t;
+SELECT n, pg_typeof(n) FROM t;
 ERROR:  operator does not exist: text + integer
 LINE 4:     SELECT n+1 FROM t WHERE n < 10
                     ^
index 906fd712b718f9108410ae8f6fcbd850b7c746e2..199049b2a27ca46f49feef6f776fae4539229994 100644 (file)
@@ -343,9 +343,9 @@ SELECT * FROM array_op_test WHERE t <@ '{}' ORDER BY seqno;
 
 -- array casts
 SELECT ARRAY[1,2,3]::text[]::int[]::float8[] AS "{1,2,3}";
-SELECT ARRAY[1,2,3]::text[]::int[]::float8[] is of (float8[]) as "TRUE";
+SELECT pg_typeof(ARRAY[1,2,3]::text[]::int[]::float8[]) AS "double precision[]";
 SELECT ARRAY[['a','bc'],['def','hijk']]::text[]::varchar[] AS "{{a,bc},{def,hijk}}";
-SELECT ARRAY[['a','bc'],['def','hijk']]::text[]::varchar[] is of (varchar[]) as "TRUE";
+SELECT pg_typeof(ARRAY[['a','bc'],['def','hijk']]::text[]::varchar[]) AS "character varying[]";
 SELECT CAST(ARRAY[[[[[['a','bb','ccc']]]]]] as text[]) as "{{{{{{a,bb,ccc}}}}}}";
 SELECT NULL::text[]::int[] AS "NULL";
 
index 1291d550d687ef7d2c7b10196a95e31bd1fe65d6..549c0b5adfa26c42575933ba98ea14dc58772173 100644 (file)
@@ -59,9 +59,8 @@ select testtext || testvarchar as concat, testnumeric + 42 as sum
 from basictest;
 
 -- check that union/case/coalesce type resolution handles domains properly
-select coalesce(4::domainint4, 7) is of (int4) as t;
-select coalesce(4::domainint4, 7) is of (domainint4) as f;
-select coalesce(4::domainint4, 7::domainint4) is of (domainint4) as t;
+select pg_typeof(coalesce(4::domainint4, 7));
+select pg_typeof(coalesce(4::domainint4, 7::domainint4));
 
 drop table basictest;
 drop domain domainvarchar restrict;
index b1b79eb1722f34b12366099490d261acd5b8e2a0..ad976de531132c7c95276c8fe84ec4d527e0f251 100644 (file)
@@ -77,14 +77,14 @@ SELECT * FROM t LIMIT 10;
 
 -- Test behavior with an unknown-type literal in the WITH
 WITH q AS (SELECT 'foo' AS x)
-SELECT x, x IS OF (text) AS is_text FROM q;
+SELECT x, pg_typeof(x) FROM q;
 
 WITH RECURSIVE t(n) AS (
     SELECT 'foo'
 UNION ALL
     SELECT n || ' bar' FROM t WHERE length(n) < 20
 )
-SELECT n, n IS OF (text) AS is_text FROM t;
+SELECT n, pg_typeof(n) FROM t;
 
 -- In a perfect world, this would work and resolve the literal as int ...
 -- but for now, we have to be content with resolving to text too soon.
@@ -93,7 +93,7 @@ WITH RECURSIVE t(n) AS (
 UNION ALL
     SELECT n+1 FROM t WHERE n < 10
 )
-SELECT n, n IS OF (int) AS is_int FROM t;
+SELECT n, pg_typeof(n) FROM t;
 
 --
 -- Some examples with a tree