44
44
#define JB_PATH_INSERT_AFTER 0x0010
45
45
#define JB_PATH_CREATE_OR_INSERT \
46
46
(JB_PATH_INSERT_BEFORE | JB_PATH_INSERT_AFTER | JB_PATH_CREATE)
47
+ #define JB_PATH_FILL_GAPS 0x0020
48
+ #define JB_PATH_CONSISTENT_POSITION 0x0040
47
49
48
50
/* state for json_object_keys */
49
51
typedef struct OkeysState
@@ -1634,14 +1636,117 @@ jsonb_set_element(Jsonb *jb, Datum *path, int path_len,
1634
1636
1635
1637
it = JsonbIteratorInit (& jb -> root );
1636
1638
1637
- res = setPath (& it , path , path_nulls , path_len , & state , 0 ,
1638
- newval , JB_PATH_CREATE );
1639
+ res = setPath (& it , path , path_nulls , path_len , & state , 0 , newval ,
1640
+ JB_PATH_CREATE | JB_PATH_FILL_GAPS |
1641
+ JB_PATH_CONSISTENT_POSITION );
1639
1642
1640
1643
pfree (path_nulls );
1641
1644
1642
1645
PG_RETURN_JSONB_P (JsonbValueToJsonb (res ));
1643
1646
}
1644
1647
1648
+ static void
1649
+ push_null_elements (JsonbParseState * * ps , int num )
1650
+ {
1651
+ JsonbValue null ;
1652
+
1653
+ null .type = jbvNull ;
1654
+
1655
+ while (num -- > 0 )
1656
+ pushJsonbValue (ps , WJB_ELEM , & null );
1657
+ }
1658
+
1659
+ /*
1660
+ * Prepare a new structure containing nested empty objects and arrays
1661
+ * corresponding to the specified path, and assign a new value at the end of
1662
+ * this path. E.g. the path [a][0][b] with the new value 1 will produce the
1663
+ * structure {a: [{b: 1}]}.
1664
+ *
1665
+ * Called is responsible to make sure such path does not exist yet.
1666
+ */
1667
+ static void
1668
+ push_path (JsonbParseState * * st , int level , Datum * path_elems ,
1669
+ bool * path_nulls , int path_len , JsonbValue * newval )
1670
+ {
1671
+ /*
1672
+ * tpath contains expected type of an empty jsonb created at each level
1673
+ * higher or equal than the current one, either jbvObject or jbvArray.
1674
+ * Since it contains only information about path slice from level to the
1675
+ * end, the access index must be normalized by level.
1676
+ */
1677
+ enum jbvType * tpath = palloc0 ((path_len - level ) * sizeof (enum jbvType ));
1678
+ long lindex ;
1679
+ JsonbValue newkey ;
1680
+
1681
+ /*
1682
+ * Create first part of the chain with beginning tokens. For the current
1683
+ * level WJB_BEGIN_OBJECT/WJB_BEGIN_ARRAY was already created, so start
1684
+ * with the next one.
1685
+ */
1686
+ for (int i = level + 1 ; i < path_len ; i ++ )
1687
+ {
1688
+ char * c ,
1689
+ * badp ;
1690
+
1691
+ if (path_nulls [i ])
1692
+ break ;
1693
+
1694
+ /*
1695
+ * Try to convert to an integer to find out the expected type, object
1696
+ * or array.
1697
+ */
1698
+ c = TextDatumGetCString (path_elems [i ]);
1699
+ errno = 0 ;
1700
+ lindex = strtol (c , & badp , 10 );
1701
+ if (errno != 0 || badp == c || * badp != '\0' || lindex > INT_MAX ||
1702
+ lindex < INT_MIN )
1703
+ {
1704
+ /* text, an object is expected */
1705
+ newkey .type = jbvString ;
1706
+ newkey .val .string .len = VARSIZE_ANY_EXHDR (path_elems [i ]);
1707
+ newkey .val .string .val = VARDATA_ANY (path_elems [i ]);
1708
+
1709
+ (void ) pushJsonbValue (st , WJB_BEGIN_OBJECT , NULL );
1710
+ (void ) pushJsonbValue (st , WJB_KEY , & newkey );
1711
+
1712
+ tpath [i - level ] = jbvObject ;
1713
+ }
1714
+ else
1715
+ {
1716
+ /* integer, an array is expected */
1717
+ (void ) pushJsonbValue (st , WJB_BEGIN_ARRAY , NULL );
1718
+
1719
+ push_null_elements (st , lindex );
1720
+
1721
+ tpath [i - level ] = jbvArray ;
1722
+ }
1723
+
1724
+ }
1725
+
1726
+ /* Insert an actual value for either an object or array */
1727
+ if (tpath [(path_len - level ) - 1 ] == jbvArray )
1728
+ {
1729
+ (void ) pushJsonbValue (st , WJB_ELEM , newval );
1730
+ }
1731
+ else
1732
+ (void ) pushJsonbValue (st , WJB_VALUE , newval );
1733
+
1734
+ /*
1735
+ * Close everything up to the last but one level. The last one will be
1736
+ * closed outside of this function.
1737
+ */
1738
+ for (int i = path_len - 1 ; i > level ; i -- )
1739
+ {
1740
+ if (path_nulls [i ])
1741
+ break ;
1742
+
1743
+ if (tpath [i - level ] == jbvObject )
1744
+ (void ) pushJsonbValue (st , WJB_END_OBJECT , NULL );
1745
+ else
1746
+ (void ) pushJsonbValue (st , WJB_END_ARRAY , NULL );
1747
+ }
1748
+ }
1749
+
1645
1750
/*
1646
1751
* Return the text representation of the given JsonbValue.
1647
1752
*/
@@ -4786,6 +4891,21 @@ IteratorConcat(JsonbIterator **it1, JsonbIterator **it2,
4786
4891
* Bits JB_PATH_INSERT_BEFORE and JB_PATH_INSERT_AFTER in op_type
4787
4892
* behave as JB_PATH_CREATE if new value is inserted in JsonbObject.
4788
4893
*
4894
+ * If JB_PATH_FILL_GAPS bit is set, this will change an assignment logic in
4895
+ * case if target is an array. The assignment index will not be restricted by
4896
+ * number of elements in the array, and if there are any empty slots between
4897
+ * last element of the array and a new one they will be filled with nulls. If
4898
+ * the index is negative, it still will be considered an an index from the end
4899
+ * of the array. Of a part of the path is not present and this part is more
4900
+ * than just one last element, this flag will instruct to create the whole
4901
+ * chain of corresponding objects and insert the value.
4902
+ *
4903
+ * JB_PATH_CONSISTENT_POSITION for an array indicates that the called wants to
4904
+ * keep values with fixed indices. Indices for existing elements could be
4905
+ * changed (shifted forward) in case if the array is prepended with a new value
4906
+ * and a negative index out of the range, so this behavior will be prevented
4907
+ * and return an error.
4908
+ *
4789
4909
* All path elements before the last must already exist
4790
4910
* whatever bits in op_type are set, or nothing is done.
4791
4911
*/
@@ -4880,6 +5000,8 @@ setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls,
4880
5000
memcmp (k .val .string .val , VARDATA_ANY (path_elems [level ]),
4881
5001
k .val .string .len ) == 0 )
4882
5002
{
5003
+ done = true;
5004
+
4883
5005
if (level == path_len - 1 )
4884
5006
{
4885
5007
/*
@@ -4899,7 +5021,6 @@ setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls,
4899
5021
(void ) pushJsonbValue (st , WJB_KEY , & k );
4900
5022
(void ) pushJsonbValue (st , WJB_VALUE , newval );
4901
5023
}
4902
- done = true;
4903
5024
}
4904
5025
else
4905
5026
{
@@ -4944,6 +5065,31 @@ setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls,
4944
5065
}
4945
5066
}
4946
5067
}
5068
+
5069
+ /*--
5070
+ * If we got here there are only few possibilities:
5071
+ * - no target path was found, and an open object with some keys/values was
5072
+ * pushed into the state
5073
+ * - an object is empty, only WJB_BEGIN_OBJECT is pushed
5074
+ *
5075
+ * In both cases if instructed to create the path when not present,
5076
+ * generate the whole chain of empty objects and insert the new value
5077
+ * there.
5078
+ */
5079
+ if (!done && (op_type & JB_PATH_FILL_GAPS ) && (level < path_len - 1 ))
5080
+ {
5081
+ JsonbValue newkey ;
5082
+
5083
+ newkey .type = jbvString ;
5084
+ newkey .val .string .len = VARSIZE_ANY_EXHDR (path_elems [level ]);
5085
+ newkey .val .string .val = VARDATA_ANY (path_elems [level ]);
5086
+
5087
+ (void ) pushJsonbValue (st , WJB_KEY , & newkey );
5088
+ (void ) push_path (st , level , path_elems , path_nulls ,
5089
+ path_len , newval );
5090
+
5091
+ /* Result is closed with WJB_END_OBJECT outside of this function */
5092
+ }
4947
5093
}
4948
5094
4949
5095
/*
@@ -4982,25 +5128,48 @@ setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls,
4982
5128
if (idx < 0 )
4983
5129
{
4984
5130
if (- idx > nelems )
4985
- idx = INT_MIN ;
5131
+ {
5132
+ /*
5133
+ * If asked to keep elements position consistent, it's not allowed
5134
+ * to prepend the array.
5135
+ */
5136
+ if (op_type & JB_PATH_CONSISTENT_POSITION )
5137
+ ereport (ERROR ,
5138
+ (errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
5139
+ errmsg ("path element at position %d is out of range: %d" ,
5140
+ level + 1 , idx )));
5141
+ else
5142
+ idx = INT_MIN ;
5143
+ }
4986
5144
else
4987
5145
idx = nelems + idx ;
4988
5146
}
4989
5147
4990
- if (idx > 0 && idx > nelems )
4991
- idx = nelems ;
5148
+ /*
5149
+ * Filling the gaps means there are no limits on the positive index are
5150
+ * imposed, we can set any element. Otherwise limit the index by nelems.
5151
+ */
5152
+ if (!(op_type & JB_PATH_FILL_GAPS ))
5153
+ {
5154
+ if (idx > 0 && idx > nelems )
5155
+ idx = nelems ;
5156
+ }
4992
5157
4993
5158
/*
4994
5159
* if we're creating, and idx == INT_MIN, we prepend the new value to the
4995
5160
* array also if the array is empty - in which case we don't really care
4996
5161
* what the idx value is
4997
5162
*/
4998
-
4999
5163
if ((idx == INT_MIN || nelems == 0 ) && (level == path_len - 1 ) &&
5000
5164
(op_type & JB_PATH_CREATE_OR_INSERT ))
5001
5165
{
5002
5166
Assert (newval != NULL );
5167
+
5168
+ if (op_type & JB_PATH_FILL_GAPS && nelems == 0 && idx > 0 )
5169
+ push_null_elements (st , idx );
5170
+
5003
5171
(void ) pushJsonbValue (st , WJB_ELEM , newval );
5172
+
5004
5173
done = true;
5005
5174
}
5006
5175
@@ -5011,6 +5180,8 @@ setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls,
5011
5180
5012
5181
if (i == idx && level < path_len )
5013
5182
{
5183
+ done = true;
5184
+
5014
5185
if (level == path_len - 1 )
5015
5186
{
5016
5187
r = JsonbIteratorNext (it , & v , true); /* skip */
@@ -5028,8 +5199,6 @@ setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls,
5028
5199
5029
5200
if (op_type & (JB_PATH_INSERT_AFTER | JB_PATH_REPLACE ))
5030
5201
(void ) pushJsonbValue (st , WJB_ELEM , newval );
5031
-
5032
- done = true;
5033
5202
}
5034
5203
else
5035
5204
(void ) setPath (it , path_elems , path_nulls , path_len ,
@@ -5057,14 +5226,42 @@ setPathArray(JsonbIterator **it, Datum *path_elems, bool *path_nulls,
5057
5226
(void ) pushJsonbValue (st , r , r < WJB_BEGIN_ARRAY ? & v : NULL );
5058
5227
}
5059
5228
}
5060
-
5061
- if ((op_type & JB_PATH_CREATE_OR_INSERT ) && !done &&
5062
- level == path_len - 1 && i == nelems - 1 )
5063
- {
5064
- (void ) pushJsonbValue (st , WJB_ELEM , newval );
5065
- }
5066
5229
}
5067
5230
}
5231
+
5232
+ if ((op_type & JB_PATH_CREATE_OR_INSERT ) && !done && level == path_len - 1 )
5233
+ {
5234
+ /*
5235
+ * If asked to fill the gaps, idx could be bigger than nelems, so
5236
+ * prepend the new element with nulls if that's the case.
5237
+ */
5238
+ if (op_type & JB_PATH_FILL_GAPS && idx > nelems )
5239
+ push_null_elements (st , idx - nelems );
5240
+
5241
+ (void ) pushJsonbValue (st , WJB_ELEM , newval );
5242
+ done = true;
5243
+ }
5244
+
5245
+ /*--
5246
+ * If we got here there are only few possibilities:
5247
+ * - no target path was found, and an open array with some keys/values was
5248
+ * pushed into the state
5249
+ * - an array is empty, only WJB_BEGIN_ARRAY is pushed
5250
+ *
5251
+ * In both cases if instructed to create the path when not present,
5252
+ * generate the whole chain of empty objects and insert the new value
5253
+ * there.
5254
+ */
5255
+ if (!done && (op_type & JB_PATH_FILL_GAPS ) && (level < path_len - 1 ))
5256
+ {
5257
+ if (idx > 0 )
5258
+ push_null_elements (st , idx - nelems );
5259
+
5260
+ (void ) push_path (st , level , path_elems , path_nulls ,
5261
+ path_len , newval );
5262
+
5263
+ /* Result is closed with WJB_END_OBJECT outside of this function */
5264
+ }
5068
5265
}
5069
5266
5070
5267
/*
0 commit comments