Second attempt at organizing jsonpath operators and methods
authorPeter Eisentraut <peter@eisentraut.org>
Wed, 3 Jan 2024 20:56:41 +0000 (21:56 +0100)
committerPeter Eisentraut <peter@eisentraut.org>
Wed, 3 Jan 2024 20:56:41 +0000 (21:56 +0100)
Second attempt at 283a95da923.  Since we can't reorder the enum values
of JsonPathItemType, instead reorder the switch cases where they are
used to generally follow the order of the enum values, for better
maintainability.

src/backend/utils/adt/jsonpath.c
src/backend/utils/adt/jsonpath_exec.c
src/include/utils/jsonpath.h

index c5ba3b7f1d0525da8b681b2232caab8550563c29..0a16f93f39bc41433de316092baa05ab4271b87d 100644 (file)
@@ -519,18 +519,9 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
                case jpiNull:
                        appendStringInfoString(buf, "null");
                        break;
-               case jpiKey:
-                       if (inKey)
-                               appendStringInfoChar(buf, '.');
-                       escape_json(buf, jspGetString(v, NULL));
-                       break;
                case jpiString:
                        escape_json(buf, jspGetString(v, NULL));
                        break;
-               case jpiVariable:
-                       appendStringInfoChar(buf, '$');
-                       escape_json(buf, jspGetString(v, NULL));
-                       break;
                case jpiNumeric:
                        if (jspHasNext(v))
                                appendStringInfoChar(buf, '(');
@@ -576,58 +567,6 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
                        if (printBracketes)
                                appendStringInfoChar(buf, ')');
                        break;
-               case jpiLikeRegex:
-                       if (printBracketes)
-                               appendStringInfoChar(buf, '(');
-
-                       jspInitByBuffer(&elem, v->base, v->content.like_regex.expr);
-                       printJsonPathItem(buf, &elem, false,
-                                                         operationPriority(elem.type) <=
-                                                         operationPriority(v->type));
-
-                       appendStringInfoString(buf, " like_regex ");
-
-                       escape_json(buf, v->content.like_regex.pattern);
-
-                       if (v->content.like_regex.flags)
-                       {
-                               appendStringInfoString(buf, " flag \"");
-
-                               if (v->content.like_regex.flags & JSP_REGEX_ICASE)
-                                       appendStringInfoChar(buf, 'i');
-                               if (v->content.like_regex.flags & JSP_REGEX_DOTALL)
-                                       appendStringInfoChar(buf, 's');
-                               if (v->content.like_regex.flags & JSP_REGEX_MLINE)
-                                       appendStringInfoChar(buf, 'm');
-                               if (v->content.like_regex.flags & JSP_REGEX_WSPACE)
-                                       appendStringInfoChar(buf, 'x');
-                               if (v->content.like_regex.flags & JSP_REGEX_QUOTE)
-                                       appendStringInfoChar(buf, 'q');
-
-                               appendStringInfoChar(buf, '"');
-                       }
-
-                       if (printBracketes)
-                               appendStringInfoChar(buf, ')');
-                       break;
-               case jpiPlus:
-               case jpiMinus:
-                       if (printBracketes)
-                               appendStringInfoChar(buf, '(');
-                       appendStringInfoChar(buf, v->type == jpiPlus ? '+' : '-');
-                       jspGetArg(v, &elem);
-                       printJsonPathItem(buf, &elem, false,
-                                                         operationPriority(elem.type) <=
-                                                         operationPriority(v->type));
-                       if (printBracketes)
-                               appendStringInfoChar(buf, ')');
-                       break;
-               case jpiFilter:
-                       appendStringInfoString(buf, "?(");
-                       jspGetArg(v, &elem);
-                       printJsonPathItem(buf, &elem, false, false);
-                       appendStringInfoChar(buf, ')');
-                       break;
                case jpiNot:
                        appendStringInfoString(buf, "!(");
                        jspGetArg(v, &elem);
@@ -640,22 +579,17 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
                        printJsonPathItem(buf, &elem, false, false);
                        appendStringInfoString(buf, ") is unknown");
                        break;
-               case jpiExists:
-                       appendStringInfoString(buf, "exists (");
+               case jpiPlus:
+               case jpiMinus:
+                       if (printBracketes)
+                               appendStringInfoChar(buf, '(');
+                       appendStringInfoChar(buf, v->type == jpiPlus ? '+' : '-');
                        jspGetArg(v, &elem);
-                       printJsonPathItem(buf, &elem, false, false);
-                       appendStringInfoChar(buf, ')');
-                       break;
-               case jpiCurrent:
-                       Assert(!inKey);
-                       appendStringInfoChar(buf, '@');
-                       break;
-               case jpiRoot:
-                       Assert(!inKey);
-                       appendStringInfoChar(buf, '$');
-                       break;
-               case jpiLast:
-                       appendStringInfoString(buf, "last");
+                       printJsonPathItem(buf, &elem, false,
+                                                         operationPriority(elem.type) <=
+                                                         operationPriority(v->type));
+                       if (printBracketes)
+                               appendStringInfoChar(buf, ')');
                        break;
                case jpiAnyArray:
                        appendStringInfoString(buf, "[*]");
@@ -712,6 +646,35 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
                                                                 v->content.anybounds.first,
                                                                 v->content.anybounds.last);
                        break;
+               case jpiKey:
+                       if (inKey)
+                               appendStringInfoChar(buf, '.');
+                       escape_json(buf, jspGetString(v, NULL));
+                       break;
+               case jpiCurrent:
+                       Assert(!inKey);
+                       appendStringInfoChar(buf, '@');
+                       break;
+               case jpiRoot:
+                       Assert(!inKey);
+                       appendStringInfoChar(buf, '$');
+                       break;
+               case jpiVariable:
+                       appendStringInfoChar(buf, '$');
+                       escape_json(buf, jspGetString(v, NULL));
+                       break;
+               case jpiFilter:
+                       appendStringInfoString(buf, "?(");
+                       jspGetArg(v, &elem);
+                       printJsonPathItem(buf, &elem, false, false);
+                       appendStringInfoChar(buf, ')');
+                       break;
+               case jpiExists:
+                       appendStringInfoString(buf, "exists (");
+                       jspGetArg(v, &elem);
+                       printJsonPathItem(buf, &elem, false, false);
+                       appendStringInfoChar(buf, ')');
+                       break;
                case jpiType:
                        appendStringInfoString(buf, ".type()");
                        break;
@@ -742,6 +705,43 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
                case jpiKeyValue:
                        appendStringInfoString(buf, ".keyvalue()");
                        break;
+               case jpiLast:
+                       appendStringInfoString(buf, "last");
+                       break;
+               case jpiLikeRegex:
+                       if (printBracketes)
+                               appendStringInfoChar(buf, '(');
+
+                       jspInitByBuffer(&elem, v->base, v->content.like_regex.expr);
+                       printJsonPathItem(buf, &elem, false,
+                                                         operationPriority(elem.type) <=
+                                                         operationPriority(v->type));
+
+                       appendStringInfoString(buf, " like_regex ");
+
+                       escape_json(buf, v->content.like_regex.pattern);
+
+                       if (v->content.like_regex.flags)
+                       {
+                               appendStringInfoString(buf, " flag \"");
+
+                               if (v->content.like_regex.flags & JSP_REGEX_ICASE)
+                                       appendStringInfoChar(buf, 'i');
+                               if (v->content.like_regex.flags & JSP_REGEX_DOTALL)
+                                       appendStringInfoChar(buf, 's');
+                               if (v->content.like_regex.flags & JSP_REGEX_MLINE)
+                                       appendStringInfoChar(buf, 'm');
+                               if (v->content.like_regex.flags & JSP_REGEX_WSPACE)
+                                       appendStringInfoChar(buf, 'x');
+                               if (v->content.like_regex.flags & JSP_REGEX_QUOTE)
+                                       appendStringInfoChar(buf, 'q');
+
+                               appendStringInfoChar(buf, '"');
+                       }
+
+                       if (printBracketes)
+                               appendStringInfoChar(buf, ')');
+                       break;
                default:
                        elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
        }
@@ -771,11 +771,11 @@ jspOperationName(JsonPathItemType type)
                        return "<=";
                case jpiGreaterOrEqual:
                        return ">=";
-               case jpiPlus:
                case jpiAdd:
+               case jpiPlus:
                        return "+";
-               case jpiMinus:
                case jpiSub:
+               case jpiMinus:
                        return "-";
                case jpiMul:
                        return "*";
@@ -783,26 +783,26 @@ jspOperationName(JsonPathItemType type)
                        return "/";
                case jpiMod:
                        return "%";
-               case jpiStartsWith:
-                       return "starts with";
-               case jpiLikeRegex:
-                       return "like_regex";
                case jpiType:
                        return "type";
                case jpiSize:
                        return "size";
-               case jpiKeyValue:
-                       return "keyvalue";
-               case jpiDouble:
-                       return "double";
                case jpiAbs:
                        return "abs";
                case jpiFloor:
                        return "floor";
                case jpiCeiling:
                        return "ceiling";
+               case jpiDouble:
+                       return "double";
                case jpiDatetime:
                        return "datetime";
+               case jpiKeyValue:
+                       return "keyvalue";
+               case jpiStartsWith:
+                       return "starts with";
+               case jpiLikeRegex:
+                       return "like_regex";
                default:
                        elog(ERROR, "unrecognized jsonpath item type: %d", type);
                        return NULL;
@@ -900,8 +900,8 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
                case jpiKeyValue:
                case jpiLast:
                        break;
-               case jpiKey:
                case jpiString:
+               case jpiKey:
                case jpiVariable:
                        read_int32(v->content.value.datalen, base, pos);
                        /* FALLTHROUGH */
@@ -911,30 +911,24 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
                        break;
                case jpiAnd:
                case jpiOr:
-               case jpiAdd:
-               case jpiSub:
-               case jpiMul:
-               case jpiDiv:
-               case jpiMod:
                case jpiEqual:
                case jpiNotEqual:
                case jpiLess:
                case jpiGreater:
                case jpiLessOrEqual:
                case jpiGreaterOrEqual:
+               case jpiAdd:
+               case jpiSub:
+               case jpiMul:
+               case jpiDiv:
+               case jpiMod:
                case jpiStartsWith:
                        read_int32(v->content.args.left, base, pos);
                        read_int32(v->content.args.right, base, pos);
                        break;
-               case jpiLikeRegex:
-                       read_int32(v->content.like_regex.flags, base, pos);
-                       read_int32(v->content.like_regex.expr, base, pos);
-                       read_int32(v->content.like_regex.patternlen, base, pos);
-                       v->content.like_regex.pattern = base + pos;
-                       break;
                case jpiNot:
-               case jpiExists:
                case jpiIsUnknown:
+               case jpiExists:
                case jpiPlus:
                case jpiMinus:
                case jpiFilter:
@@ -950,6 +944,12 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
                        read_int32(v->content.anybounds.first, base, pos);
                        read_int32(v->content.anybounds.last, base, pos);
                        break;
+               case jpiLikeRegex:
+                       read_int32(v->content.like_regex.flags, base, pos);
+                       read_int32(v->content.like_regex.expr, base, pos);
+                       read_int32(v->content.like_regex.patternlen, base, pos);
+                       v->content.like_regex.pattern = base + pos;
+                       break;
                default:
                        elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
        }
@@ -958,12 +958,12 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
 void
 jspGetArg(JsonPathItem *v, JsonPathItem *a)
 {
-       Assert(v->type == jpiFilter ||
-                  v->type == jpiNot ||
+       Assert(v->type == jpiNot ||
                   v->type == jpiIsUnknown ||
-                  v->type == jpiExists ||
                   v->type == jpiPlus ||
                   v->type == jpiMinus ||
+                  v->type == jpiFilter ||
+                  v->type == jpiExists ||
                   v->type == jpiDatetime);
 
        jspInitByBuffer(a, v->base, v->content.arg);
@@ -974,21 +974,20 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
 {
        if (jspHasNext(v))
        {
-               Assert(v->type == jpiString ||
+               Assert(v->type == jpiNull ||
+                          v->type == jpiString ||
                           v->type == jpiNumeric ||
                           v->type == jpiBool ||
-                          v->type == jpiNull ||
-                          v->type == jpiKey ||
-                          v->type == jpiAny ||
-                          v->type == jpiAnyArray ||
-                          v->type == jpiAnyKey ||
-                          v->type == jpiIndexArray ||
-                          v->type == jpiFilter ||
-                          v->type == jpiCurrent ||
-                          v->type == jpiExists ||
-                          v->type == jpiRoot ||
-                          v->type == jpiVariable ||
-                          v->type == jpiLast ||
+                          v->type == jpiAnd ||
+                          v->type == jpiOr ||
+                          v->type == jpiNot ||
+                          v->type == jpiIsUnknown ||
+                          v->type == jpiEqual ||
+                          v->type == jpiNotEqual ||
+                          v->type == jpiLess ||
+                          v->type == jpiGreater ||
+                          v->type == jpiLessOrEqual ||
+                          v->type == jpiGreaterOrEqual ||
                           v->type == jpiAdd ||
                           v->type == jpiSub ||
                           v->type == jpiMul ||
@@ -996,16 +995,16 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
                           v->type == jpiMod ||
                           v->type == jpiPlus ||
                           v->type == jpiMinus ||
-                          v->type == jpiEqual ||
-                          v->type == jpiNotEqual ||
-                          v->type == jpiGreater ||
-                          v->type == jpiGreaterOrEqual ||
-                          v->type == jpiLess ||
-                          v->type == jpiLessOrEqual ||
-                          v->type == jpiAnd ||
-                          v->type == jpiOr ||
-                          v->type == jpiNot ||
-                          v->type == jpiIsUnknown ||
+                          v->type == jpiAnyArray ||
+                          v->type == jpiAnyKey ||
+                          v->type == jpiIndexArray ||
+                          v->type == jpiAny ||
+                          v->type == jpiKey ||
+                          v->type == jpiCurrent ||
+                          v->type == jpiRoot ||
+                          v->type == jpiVariable ||
+                          v->type == jpiFilter ||
+                          v->type == jpiExists ||
                           v->type == jpiType ||
                           v->type == jpiSize ||
                           v->type == jpiAbs ||
@@ -1014,6 +1013,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
                           v->type == jpiDouble ||
                           v->type == jpiDatetime ||
                           v->type == jpiKeyValue ||
+                          v->type == jpiLast ||
                           v->type == jpiStartsWith ||
                           v->type == jpiLikeRegex);
 
index 9a09604f6423004900e49ea16376b6d5161dba10..163b75eb81519b6474e9d6918c800e93feb9d901 100644 (file)
@@ -621,6 +621,37 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
 
        switch (jsp->type)
        {
+               case jpiNull:
+               case jpiBool:
+               case jpiNumeric:
+               case jpiString:
+               case jpiVariable:
+                       {
+                               JsonbValue      vbuf;
+                               JsonbValue *v;
+                               bool            hasNext = jspGetNext(jsp, &elem);
+
+                               if (!hasNext && !found && jsp->type != jpiVariable)
+                               {
+                                       /*
+                                        * Skip evaluation, but not for variables.  We must
+                                        * trigger an error for the missing variable.
+                                        */
+                                       res = jperOk;
+                                       break;
+                               }
+
+                               v = hasNext ? &vbuf : palloc(sizeof(*v));
+
+                               baseObject = cxt->baseObject;
+                               getJsonPathItem(cxt, jsp, v);
+
+                               res = executeNextItem(cxt, jsp, &elem,
+                                                                         v, found, hasNext);
+                               cxt->baseObject = baseObject;
+                       }
+                       break;
+
                        /* all boolean item types: */
                case jpiAnd:
                case jpiOr:
@@ -642,63 +673,32 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
                                break;
                        }
 
-               case jpiKey:
-                       if (JsonbType(jb) == jbvObject)
-                       {
-                               JsonbValue *v;
-                               JsonbValue      key;
-
-                               key.type = jbvString;
-                               key.val.string.val = jspGetString(jsp, &key.val.string.len);
-
-                               v = findJsonbValueFromContainer(jb->val.binary.data,
-                                                                                               JB_FOBJECT, &key);
+               case jpiAdd:
+                       return executeBinaryArithmExpr(cxt, jsp, jb,
+                                                                                  numeric_add_opt_error, found);
 
-                               if (v != NULL)
-                               {
-                                       res = executeNextItem(cxt, jsp, NULL,
-                                                                                 v, found, false);
+               case jpiSub:
+                       return executeBinaryArithmExpr(cxt, jsp, jb,
+                                                                                  numeric_sub_opt_error, found);
 
-                                       /* free value if it was not added to found list */
-                                       if (jspHasNext(jsp) || !found)
-                                               pfree(v);
-                               }
-                               else if (!jspIgnoreStructuralErrors(cxt))
-                               {
-                                       Assert(found);
+               case jpiMul:
+                       return executeBinaryArithmExpr(cxt, jsp, jb,
+                                                                                  numeric_mul_opt_error, found);
 
-                                       if (!jspThrowErrors(cxt))
-                                               return jperError;
+               case jpiDiv:
+                       return executeBinaryArithmExpr(cxt, jsp, jb,
+                                                                                  numeric_div_opt_error, found);
 
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_SQL_JSON_MEMBER_NOT_FOUND), \
-                                                        errmsg("JSON object does not contain key \"%s\"",
-                                                                       pnstrdup(key.val.string.val,
-                                                                                        key.val.string.len))));
-                               }
-                       }
-                       else if (unwrap && JsonbType(jb) == jbvArray)
-                               return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
-                       else if (!jspIgnoreStructuralErrors(cxt))
-                       {
-                               Assert(found);
-                               RETURN_ERROR(ereport(ERROR,
-                                                                        (errcode(ERRCODE_SQL_JSON_MEMBER_NOT_FOUND),
-                                                                         errmsg("jsonpath member accessor can only be applied to an object"))));
-                       }
-                       break;
+               case jpiMod:
+                       return executeBinaryArithmExpr(cxt, jsp, jb,
+                                                                                  numeric_mod_opt_error, found);
 
-               case jpiRoot:
-                       jb = cxt->root;
-                       baseObject = setBaseObject(cxt, jb, 0);
-                       res = executeNextItem(cxt, jsp, NULL, jb, found, true);
-                       cxt->baseObject = baseObject;
-                       break;
+               case jpiPlus:
+                       return executeUnaryArithmExpr(cxt, jsp, jb, NULL, found);
 
-               case jpiCurrent:
-                       res = executeNextItem(cxt, jsp, NULL, cxt->current,
-                                                                 found, true);
-                       break;
+               case jpiMinus:
+                       return executeUnaryArithmExpr(cxt, jsp, jb, numeric_uminus,
+                                                                                 found);
 
                case jpiAnyArray:
                        if (JsonbType(jb) == jbvArray)
@@ -716,6 +716,30 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
                                                                          errmsg("jsonpath wildcard array accessor can only be applied to an array"))));
                        break;
 
+               case jpiAnyKey:
+                       if (JsonbType(jb) == jbvObject)
+                       {
+                               bool            hasNext = jspGetNext(jsp, &elem);
+
+                               if (jb->type != jbvBinary)
+                                       elog(ERROR, "invalid jsonb object type: %d", jb->type);
+
+                               return executeAnyItem
+                                       (cxt, hasNext ? &elem : NULL,
+                                        jb->val.binary.data, found, 1, 1, 1,
+                                        false, jspAutoUnwrap(cxt));
+                       }
+                       else if (unwrap && JsonbType(jb) == jbvArray)
+                               return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
+                       else if (!jspIgnoreStructuralErrors(cxt))
+                       {
+                               Assert(found);
+                               RETURN_ERROR(ereport(ERROR,
+                                                                        (errcode(ERRCODE_SQL_JSON_OBJECT_NOT_FOUND),
+                                                                         errmsg("jsonpath wildcard member accessor can only be applied to an object"))));
+                       }
+                       break;
+
                case jpiIndexArray:
                        if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt))
                        {
@@ -822,46 +846,70 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
                        }
                        break;
 
-               case jpiLast:
+               case jpiAny:
                        {
-                               JsonbValue      tmpjbv;
-                               JsonbValue *lastjbv;
-                               int                     last;
                                bool            hasNext = jspGetNext(jsp, &elem);
 
-                               if (cxt->innermostArraySize < 0)
-                                       elog(ERROR, "evaluating jsonpath LAST outside of array subscript");
-
-                               if (!hasNext && !found)
+                               /* first try without any intermediate steps */
+                               if (jsp->content.anybounds.first == 0)
                                {
-                                       res = jperOk;
-                                       break;
-                               }
-
-                               last = cxt->innermostArraySize - 1;
+                                       bool            savedIgnoreStructuralErrors;
 
-                               lastjbv = hasNext ? &tmpjbv : palloc(sizeof(*lastjbv));
+                                       savedIgnoreStructuralErrors = cxt->ignoreStructuralErrors;
+                                       cxt->ignoreStructuralErrors = true;
+                                       res = executeNextItem(cxt, jsp, &elem,
+                                                                                 jb, found, true);
+                                       cxt->ignoreStructuralErrors = savedIgnoreStructuralErrors;
 
-                               lastjbv->type = jbvNumeric;
-                               lastjbv->val.numeric = int64_to_numeric(last);
+                                       if (res == jperOk && !found)
+                                               break;
+                               }
 
-                               res = executeNextItem(cxt, jsp, &elem,
-                                                                         lastjbv, found, hasNext);
+                               if (jb->type == jbvBinary)
+                                       res = executeAnyItem
+                                               (cxt, hasNext ? &elem : NULL,
+                                                jb->val.binary.data, found,
+                                                1,
+                                                jsp->content.anybounds.first,
+                                                jsp->content.anybounds.last,
+                                                true, jspAutoUnwrap(cxt));
+                               break;
                        }
-                       break;
 
-               case jpiAnyKey:
+               case jpiKey:
                        if (JsonbType(jb) == jbvObject)
                        {
-                               bool            hasNext = jspGetNext(jsp, &elem);
+                               JsonbValue *v;
+                               JsonbValue      key;
 
-                               if (jb->type != jbvBinary)
-                                       elog(ERROR, "invalid jsonb object type: %d", jb->type);
+                               key.type = jbvString;
+                               key.val.string.val = jspGetString(jsp, &key.val.string.len);
 
-                               return executeAnyItem
-                                       (cxt, hasNext ? &elem : NULL,
-                                        jb->val.binary.data, found, 1, 1, 1,
-                                        false, jspAutoUnwrap(cxt));
+                               v = findJsonbValueFromContainer(jb->val.binary.data,
+                                                                                               JB_FOBJECT, &key);
+
+                               if (v != NULL)
+                               {
+                                       res = executeNextItem(cxt, jsp, NULL,
+                                                                                 v, found, false);
+
+                                       /* free value if it was not added to found list */
+                                       if (jspHasNext(jsp) || !found)
+                                               pfree(v);
+                               }
+                               else if (!jspIgnoreStructuralErrors(cxt))
+                               {
+                                       Assert(found);
+
+                                       if (!jspThrowErrors(cxt))
+                                               return jperError;
+
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_SQL_JSON_MEMBER_NOT_FOUND), \
+                                                        errmsg("JSON object does not contain key \"%s\"",
+                                                                       pnstrdup(key.val.string.val,
+                                                                                        key.val.string.len))));
+                               }
                        }
                        else if (unwrap && JsonbType(jb) == jbvArray)
                                return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
@@ -869,37 +917,22 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
                        {
                                Assert(found);
                                RETURN_ERROR(ereport(ERROR,
-                                                                        (errcode(ERRCODE_SQL_JSON_OBJECT_NOT_FOUND),
-                                                                         errmsg("jsonpath wildcard member accessor can only be applied to an object"))));
+                                                                        (errcode(ERRCODE_SQL_JSON_MEMBER_NOT_FOUND),
+                                                                         errmsg("jsonpath member accessor can only be applied to an object"))));
                        }
                        break;
 
-               case jpiAdd:
-                       return executeBinaryArithmExpr(cxt, jsp, jb,
-                                                                                  numeric_add_opt_error, found);
-
-               case jpiSub:
-                       return executeBinaryArithmExpr(cxt, jsp, jb,
-                                                                                  numeric_sub_opt_error, found);
-
-               case jpiMul:
-                       return executeBinaryArithmExpr(cxt, jsp, jb,
-                                                                                  numeric_mul_opt_error, found);
-
-               case jpiDiv:
-                       return executeBinaryArithmExpr(cxt, jsp, jb,
-                                                                                  numeric_div_opt_error, found);
-
-               case jpiMod:
-                       return executeBinaryArithmExpr(cxt, jsp, jb,
-                                                                                  numeric_mod_opt_error, found);
-
-               case jpiPlus:
-                       return executeUnaryArithmExpr(cxt, jsp, jb, NULL, found);
+               case jpiCurrent:
+                       res = executeNextItem(cxt, jsp, NULL, cxt->current,
+                                                                 found, true);
+                       break;
 
-               case jpiMinus:
-                       return executeUnaryArithmExpr(cxt, jsp, jb, numeric_uminus,
-                                                                                 found);
+               case jpiRoot:
+                       jb = cxt->root;
+                       baseObject = setBaseObject(cxt, jb, 0);
+                       res = executeNextItem(cxt, jsp, NULL, jb, found, true);
+                       cxt->baseObject = baseObject;
+                       break;
 
                case jpiFilter:
                        {
@@ -919,67 +952,6 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
                                break;
                        }
 
-               case jpiAny:
-                       {
-                               bool            hasNext = jspGetNext(jsp, &elem);
-
-                               /* first try without any intermediate steps */
-                               if (jsp->content.anybounds.first == 0)
-                               {
-                                       bool            savedIgnoreStructuralErrors;
-
-                                       savedIgnoreStructuralErrors = cxt->ignoreStructuralErrors;
-                                       cxt->ignoreStructuralErrors = true;
-                                       res = executeNextItem(cxt, jsp, &elem,
-                                                                                 jb, found, true);
-                                       cxt->ignoreStructuralErrors = savedIgnoreStructuralErrors;
-
-                                       if (res == jperOk && !found)
-                                               break;
-                               }
-
-                               if (jb->type == jbvBinary)
-                                       res = executeAnyItem
-                                               (cxt, hasNext ? &elem : NULL,
-                                                jb->val.binary.data, found,
-                                                1,
-                                                jsp->content.anybounds.first,
-                                                jsp->content.anybounds.last,
-                                                true, jspAutoUnwrap(cxt));
-                               break;
-                       }
-
-               case jpiNull:
-               case jpiBool:
-               case jpiNumeric:
-               case jpiString:
-               case jpiVariable:
-                       {
-                               JsonbValue      vbuf;
-                               JsonbValue *v;
-                               bool            hasNext = jspGetNext(jsp, &elem);
-
-                               if (!hasNext && !found && jsp->type != jpiVariable)
-                               {
-                                       /*
-                                        * Skip evaluation, but not for variables.  We must
-                                        * trigger an error for the missing variable.
-                                        */
-                                       res = jperOk;
-                                       break;
-                               }
-
-                               v = hasNext ? &vbuf : palloc(sizeof(*v));
-
-                               baseObject = cxt->baseObject;
-                               getJsonPathItem(cxt, jsp, v);
-
-                               res = executeNextItem(cxt, jsp, &elem,
-                                                                         v, found, hasNext);
-                               cxt->baseObject = baseObject;
-                       }
-                       break;
-
                case jpiType:
                        {
                                JsonbValue *jbv = palloc(sizeof(*jbv));
@@ -1110,6 +1082,34 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
 
                        return executeKeyValueMethod(cxt, jsp, jb, found);
 
+               case jpiLast:
+                       {
+                               JsonbValue      tmpjbv;
+                               JsonbValue *lastjbv;
+                               int                     last;
+                               bool            hasNext = jspGetNext(jsp, &elem);
+
+                               if (cxt->innermostArraySize < 0)
+                                       elog(ERROR, "evaluating jsonpath LAST outside of array subscript");
+
+                               if (!hasNext && !found)
+                               {
+                                       res = jperOk;
+                                       break;
+                               }
+
+                               last = cxt->innermostArraySize - 1;
+
+                               lastjbv = hasNext ? &tmpjbv : palloc(sizeof(*lastjbv));
+
+                               lastjbv->type = jbvNumeric;
+                               lastjbv->val.numeric = int64_to_numeric(last);
+
+                               res = executeNextItem(cxt, jsp, &elem,
+                                                                         lastjbv, found, hasNext);
+                       }
+                       break;
+
                default:
                        elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
        }
index f0181e045f795207f866b5b10069bbca207a3756..0a3a6330170c25c4134cbde63cb2601e5a05b65a 100644 (file)
@@ -49,6 +49,13 @@ DatumGetJsonPathPCopy(Datum d)
 
 /*
  * All node's type of jsonpath expression
+ *
+ * These become part of the on-disk representation of the jsonpath type.
+ * Therefore, to preserve pg_upgradability, the order must not be changed, and
+ * new values must be added at the end.
+ *
+ * It is recommended that switch cases etc. in other parts of the code also
+ * use this order, to maintain some consistency.
  */
 typedef enum JsonPathItemType
 {