Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/generated/generated_temporal_udfs_12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1974,6 +1974,36 @@ static void Gen_jsonb_array_length(DataChunk &args, ExpressionState &, Vector &r
});
}

static void Gen_jsonb_extract_path(DataChunk &args, ExpressionState &, Vector &result) {
EnsureMeosThreadInitialized();
auto &lst = args.data[1]; lst.Flatten(args.size());
auto &child = ListVector::GetEntry(lst);
child.Flatten(ListVector::GetListSize(lst));
const idx_t rows = args.size();
UnifiedVectorFormat u[2];
for (idx_t c = 0; c < 2; c++) args.data[c].ToUnifiedFormat(rows, u[c]);
result.SetVectorType(VectorType::FLAT_VECTOR);
auto out = FlatVector::GetData<string_t>(result);
auto &mask = FlatVector::Validity(result);
for (idx_t idx = 0; idx < rows; idx++) {
idx_t ri[2];
bool valid = true;
for (idx_t c = 0; c < 2; c++) {
ri[c] = u[c].sel->get_index(idx);
valid = valid && u[c].validity.RowIsValid(ri[c]);
}
if (!valid) { mask.SetInvalid(idx); continue; }
out[idx] = [&]() -> string_t {
Jsonb *t = BlobToJsonb(UnifiedVectorFormat::GetData<string_t>(u[0])[ri[0]]);
int n = 0;
text **elems = ListToTextArr(child, UnifiedVectorFormat::GetData<list_entry_t>(u[1])[ri[1]], &n);
Jsonb *r = jsonb_extract_path(t, elems, n);
free(t); FreeTextArr(elems, n);
return JsonbToBlobN(result, r, mask, idx);
}();
}
}

static void Gen_jsonb_hash(DataChunk &args, ExpressionState &, Vector &result) {
EnsureMeosThreadInitialized();
UnaryExecutor::Execute<string_t, uint32_t>(args.data[0], result, args.size(),
Expand Down Expand Up @@ -4882,6 +4912,7 @@ void RegisterGenerated_meos_h3_vertex(ExtensionLoader &loader) {

void RegisterGenerated_meos_json_base_accessor(ExtensionLoader &loader) {
RegisterSerializedScalarFunction(loader, ScalarFunction("jsonbArrayLength", {TJsonbTypes::jsonb()}, LogicalType::INTEGER, Gen_jsonb_array_length));
RegisterSerializedScalarFunction(loader, ScalarFunction("jsonbExtractPath", {TJsonbTypes::jsonb(), LogicalType::LIST(LogicalType::VARCHAR)}, TJsonbTypes::jsonb(), Gen_jsonb_extract_path));
RegisterSerializedScalarFunction(loader, ScalarFunction("hash", {TJsonbTypes::jsonb()}, LogicalType::UINTEGER, Gen_jsonb_hash));
RegisterSerializedScalarFunction(loader, ScalarFunction("jsonbContained", {TJsonbTypes::jsonb(), TJsonbTypes::jsonb()}, LogicalType::BOOLEAN, Gen_jsonb_contained));
RegisterSerializedScalarFunction(loader, ScalarFunction("jsonbContains", {TJsonbTypes::jsonb(), TJsonbTypes::jsonb()}, LogicalType::BOOLEAN, Gen_jsonb_contains));
Expand Down
128 changes: 128 additions & 0 deletions src/generated/generated_temporal_udfs_15.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1390,6 +1390,130 @@ static void Gen_h3index_get_resolution(DataChunk &args, ExpressionState &, Vecto


// ===== @ingroup meos_json_base_transf =====
static void Gen_jsonb_delete_array(DataChunk &args, ExpressionState &, Vector &result) {
EnsureMeosThreadInitialized();
auto &lst = args.data[1]; lst.Flatten(args.size());
auto &child = ListVector::GetEntry(lst);
child.Flatten(ListVector::GetListSize(lst));
const idx_t rows = args.size();
UnifiedVectorFormat u[2];
for (idx_t c = 0; c < 2; c++) args.data[c].ToUnifiedFormat(rows, u[c]);
result.SetVectorType(VectorType::FLAT_VECTOR);
auto out = FlatVector::GetData<string_t>(result);
auto &mask = FlatVector::Validity(result);
for (idx_t idx = 0; idx < rows; idx++) {
idx_t ri[2];
bool valid = true;
for (idx_t c = 0; c < 2; c++) {
ri[c] = u[c].sel->get_index(idx);
valid = valid && u[c].validity.RowIsValid(ri[c]);
}
if (!valid) { mask.SetInvalid(idx); continue; }
out[idx] = [&]() -> string_t {
Jsonb *t = BlobToJsonb(UnifiedVectorFormat::GetData<string_t>(u[0])[ri[0]]);
int n = 0;
text **elems = ListToTextArr(child, UnifiedVectorFormat::GetData<list_entry_t>(u[1])[ri[1]], &n);
Jsonb *r = jsonb_delete_array(t, elems, n);
free(t); FreeTextArr(elems, n);
return JsonbToBlobN(result, r, mask, idx);
}();
}
}

static void Gen_jsonb_delete_path(DataChunk &args, ExpressionState &, Vector &result) {
EnsureMeosThreadInitialized();
auto &lst = args.data[1]; lst.Flatten(args.size());
auto &child = ListVector::GetEntry(lst);
child.Flatten(ListVector::GetListSize(lst));
const idx_t rows = args.size();
UnifiedVectorFormat u[2];
for (idx_t c = 0; c < 2; c++) args.data[c].ToUnifiedFormat(rows, u[c]);
result.SetVectorType(VectorType::FLAT_VECTOR);
auto out = FlatVector::GetData<string_t>(result);
auto &mask = FlatVector::Validity(result);
for (idx_t idx = 0; idx < rows; idx++) {
idx_t ri[2];
bool valid = true;
for (idx_t c = 0; c < 2; c++) {
ri[c] = u[c].sel->get_index(idx);
valid = valid && u[c].validity.RowIsValid(ri[c]);
}
if (!valid) { mask.SetInvalid(idx); continue; }
out[idx] = [&]() -> string_t {
Jsonb *t = BlobToJsonb(UnifiedVectorFormat::GetData<string_t>(u[0])[ri[0]]);
int n = 0;
text **elems = ListToTextArr(child, UnifiedVectorFormat::GetData<list_entry_t>(u[1])[ri[1]], &n);
Jsonb *r = jsonb_delete_path(t, elems, n);
free(t); FreeTextArr(elems, n);
return JsonbToBlobN(result, r, mask, idx);
}();
}
}

static void Gen_jsonb_insert(DataChunk &args, ExpressionState &, Vector &result) {
EnsureMeosThreadInitialized();
auto &lst = args.data[1]; lst.Flatten(args.size());
auto &child = ListVector::GetEntry(lst);
child.Flatten(ListVector::GetListSize(lst));
const idx_t rows = args.size();
UnifiedVectorFormat u[4];
for (idx_t c = 0; c < 4; c++) args.data[c].ToUnifiedFormat(rows, u[c]);
result.SetVectorType(VectorType::FLAT_VECTOR);
auto out = FlatVector::GetData<string_t>(result);
auto &mask = FlatVector::Validity(result);
for (idx_t idx = 0; idx < rows; idx++) {
idx_t ri[4];
bool valid = true;
for (idx_t c = 0; c < 4; c++) {
ri[c] = u[c].sel->get_index(idx);
valid = valid && u[c].validity.RowIsValid(ri[c]);
}
if (!valid) { mask.SetInvalid(idx); continue; }
out[idx] = [&]() -> string_t {
Jsonb *t = BlobToJsonb(UnifiedVectorFormat::GetData<string_t>(u[0])[ri[0]]);
int n = 0;
text **elems = ListToTextArr(child, UnifiedVectorFormat::GetData<list_entry_t>(u[1])[ri[1]], &n);
Jsonb *c2 = BlobToJsonb(UnifiedVectorFormat::GetData<string_t>(u[2])[ri[2]]);
bool c3 = UnifiedVectorFormat::GetData<bool>(u[3])[ri[3]];
Jsonb *r = jsonb_insert(t, elems, n, c2, c3);
free(t); FreeTextArr(elems, n); free(c2);
return JsonbToBlobN(result, r, mask, idx);
}();
}
}

static void Gen_jsonb_set(DataChunk &args, ExpressionState &, Vector &result) {
EnsureMeosThreadInitialized();
auto &lst = args.data[1]; lst.Flatten(args.size());
auto &child = ListVector::GetEntry(lst);
child.Flatten(ListVector::GetListSize(lst));
const idx_t rows = args.size();
UnifiedVectorFormat u[4];
for (idx_t c = 0; c < 4; c++) args.data[c].ToUnifiedFormat(rows, u[c]);
result.SetVectorType(VectorType::FLAT_VECTOR);
auto out = FlatVector::GetData<string_t>(result);
auto &mask = FlatVector::Validity(result);
for (idx_t idx = 0; idx < rows; idx++) {
idx_t ri[4];
bool valid = true;
for (idx_t c = 0; c < 4; c++) {
ri[c] = u[c].sel->get_index(idx);
valid = valid && u[c].validity.RowIsValid(ri[c]);
}
if (!valid) { mask.SetInvalid(idx); continue; }
out[idx] = [&]() -> string_t {
Jsonb *t = BlobToJsonb(UnifiedVectorFormat::GetData<string_t>(u[0])[ri[0]]);
int n = 0;
text **elems = ListToTextArr(child, UnifiedVectorFormat::GetData<list_entry_t>(u[1])[ri[1]], &n);
Jsonb *c2 = BlobToJsonb(UnifiedVectorFormat::GetData<string_t>(u[2])[ri[2]]);
bool c3 = UnifiedVectorFormat::GetData<bool>(u[3])[ri[3]];
Jsonb *r = jsonb_set(t, elems, n, c2, c3);
free(t); FreeTextArr(elems, n); free(c2);
return JsonbToBlobN(result, r, mask, idx);
}();
}
}

static void Gen_jsonb_concat(DataChunk &args, ExpressionState &, Vector &result) {
EnsureMeosThreadInitialized();
BinaryExecutor::ExecuteWithNulls<string_t, string_t, string_t>(args.data[0], args.data[1], result, args.size(),
Expand Down Expand Up @@ -4775,6 +4899,10 @@ void RegisterGenerated_meos_h3_base_inspection(ExtensionLoader &loader) {
}

void RegisterGenerated_meos_json_base_transf(ExtensionLoader &loader) {
RegisterSerializedScalarFunction(loader, ScalarFunction("jsonbDeleteArray", {TJsonbTypes::jsonb(), LogicalType::LIST(LogicalType::VARCHAR)}, TJsonbTypes::jsonb(), Gen_jsonb_delete_array));
RegisterSerializedScalarFunction(loader, ScalarFunction("jsonbDeletePath", {TJsonbTypes::jsonb(), LogicalType::LIST(LogicalType::VARCHAR)}, TJsonbTypes::jsonb(), Gen_jsonb_delete_path));
RegisterSerializedScalarFunction(loader, ScalarFunction("jsonbInsert", {TJsonbTypes::jsonb(), LogicalType::LIST(LogicalType::VARCHAR), TJsonbTypes::jsonb(), LogicalType::BOOLEAN}, TJsonbTypes::jsonb(), Gen_jsonb_insert));
RegisterSerializedScalarFunction(loader, ScalarFunction("jsonbSet", {TJsonbTypes::jsonb(), LogicalType::LIST(LogicalType::VARCHAR), TJsonbTypes::jsonb(), LogicalType::BOOLEAN}, TJsonbTypes::jsonb(), Gen_jsonb_set));
RegisterSerializedScalarFunction(loader, ScalarFunction("jsonbConcat", {TJsonbTypes::jsonb(), TJsonbTypes::jsonb()}, TJsonbTypes::jsonb(), Gen_jsonb_concat));
}

Expand Down
26 changes: 26 additions & 0 deletions test/sql/parity/027_tjsonb.test
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,32 @@ FROM (VALUES (1, '7', false), (2, '8', true), (3, NULL, false)) t(i, v, after) O
2 "{\"a\": [1, 2, 8]}"@2000-01-01 00:00:00+01
3 NULL

# The path functions of a plain jsonb take the path as a list of text, as their temporal
# twins do, and answer a jsonb: set and insert into an array, delete a key by its path and a
# list of keys, and extract the value at a path, NULL where the path names nothing.
query IIIII
SELECT jsonbSet(jsonb '{"a":[1,2]}', ['a','0'], jsonb '9', true)::VARCHAR,
jsonbInsert(jsonb '{"a":[1,2]}', ['a','1'], jsonb '9', false)::VARCHAR,
jsonbDeletePath(jsonb '{"a":{"b":1,"c":2}}', ['a','b'])::VARCHAR,
jsonbDeleteArray(jsonb '{"a":1,"b":2,"c":3}', ['a','c'])::VARCHAR,
jsonbExtractPath(jsonb '{"a":{"b":[1,2]}}', ['a','b'])::VARCHAR;
----
{"a": [9, 2]} {"a": [1, 9, 2]} {"a": {"c": 2}} {"b": 2} [1, 2]

query I
SELECT jsonbExtractPath(jsonb '{"a":1}', ['b']) IS NULL;
----
true

# Each row sets its own value, and a row whose value is NULL answers NULL.
query II
SELECT i, jsonbSet(jsonb '{"a":1}', ['a'], v::jsonb, true)::VARCHAR
FROM (VALUES (1, NULL), (2, '7'), (3, '8')) t(i, v) ORDER BY i;
----
1 NULL
2 {"a": 7}
3 {"a": 8}

# A NULL value in the first row answers NULL there and leaves the next row its own value.
query II
SELECT i, tjsonbInsert(TJSONB('"{\"a\":[1,2]}"@2000-01-01'), ['a','1'], v::jsonb, false)::VARCHAR
Expand Down
40 changes: 30 additions & 10 deletions tools/codegen_duck_udfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2366,21 +2366,26 @@ def emit_body_bound_tail(f, kind, vary, tail, sig):
return _close_body(L)

def shape_text_array(f):
"""(Temporal *, text **elems, int count, <bound literals and settings...>) -> Temporal *.
"""(Temporal *, text **elems, int count, <bound literals and settings...>) -> Temporal *,
and the same over a set or a base value (Set *, Jsonb *).

A `text **` and the `int` that follows it are ONE SQL argument — MobilityDB declares
`text[]` (`sqlSignatures.args`), so DuckDB registers LIST(VARCHAR) and the count is
supplied by the list's own length rather than exposed."""
ins, out = classify(f)
if out is not None or len(ins) < 3: return None
# The path array reads the same over a SET as over a temporal — MobilityDB declares
# `jsonbsetExtractPath(jsonbset, text[], text)` beside `tjsonbExtractPath(tjsonb, ...)` —
# The path array reads the same over a SET and a base value as over a temporal —
# MobilityDB declares `jsonbsetExtractPath(jsonbset, text[], text)` and
# `jsonbSet(jsonb, text[], jsonb, boolean)` beside `tjsonbExtractPath(tjsonb, ...)` —
# so the operand family is what varies, not the shape. A set scopes through
# set_reg_scope (`jsonbset_*` -> SetTypes::jsonbset()), which is a different map from the
# temporal reg_scope: reg_scope answers None for EVERY `jsonbset_*` name, including the
# ones the set shapes already emit, so reading it for a set operand rejects the family.
# A base value scopes to its own type (PTR_IN), as #shape_baseval_scalar registers it.
ob = base(ins[0]["canonical"])
if ob not in ("Temporal", "Set") or not norm(ins[0]["canonical"]).endswith("*"):
if ob not in ("Temporal", "Set") and ob not in BASEVAL_PTR_IN:
return None
if not norm(ins[0]["canonical"]).endswith("*"):
return None
# `base()` answers the sentinel for a double pointer, so the test reads the normalised
# spelling: a `text **` is the path/keys array MobilityDB declares as `text[]`.
Expand All @@ -2401,13 +2406,19 @@ def shape_text_array(f):
bound = ((f.get("shape") or {}).get("boundArgs") or {})
tail = _build_tail(ins[3:], bound)
if tail is None: return None
if (set_reg_scope if ob == "Set" else reg_scope)(f["name"]) is None: return None
if ob in ("Temporal", "Set") and \
(set_reg_scope if ob == "Set" else reg_scope)(f["name"]) is None:
return None
rb = base(f["returnType"]["canonical"]); rn = norm(f["returnType"]["canonical"])
# The operand family and the result family are the same one: a set path operation answers a
# set (`jsonbset_extract_path` -> `Set *`), a temporal one a temporal. Accepting a crossed
# pair would emit a body marshalling the result through the wrong `*ToBlobN`.
# set (`jsonbset_extract_path` -> `Set *`), a temporal one a temporal, a base value its own
# type (`jsonb_set` -> `Jsonb *`). Accepting a crossed pair would emit a body marshalling the
# result through the wrong `*ToBlobN`.
if not (rb == ob and rn.endswith("*")): return None
if ob in BASEVAL_PTR_IN and ob not in PTR_RET: return None
sig = ["LogicalType::LIST(LogicalType::VARCHAR)"] + _tail_sig(tail)
if ob in BASEVAL_PTR_IN:
return ("base:" + ob, PTR_RET[ob][0], tail, sig)
# `MD_SET` is the set counterpart of the `MD_TEMPORAL` sentinel: the registration resolves it
# per registered accessor, so the result reads as `jsonbset` rather than the raw BLOB the
# value travels in. Registering the storage type instead builds green and passes the suite
Expand All @@ -2422,9 +2433,15 @@ def emit_body_text_array(f, kind, tail, sig):
" auto &child = ListVector::GetEntry(lst);",
" child.Flatten(ListVector::GetListSize(lst));"]
# The operand and result marshal through the family the shape resolved: a set reads with
# BlobToSet and answers through SetToBlobN, a temporal with BlobToTemporal/TemporalToBlobN.
ctype, blob_in, blob_out = (("Set", "BlobToSet", "SetToBlobN") if kind == "set"
else ("Temporal", "BlobToTemporal", "TemporalToBlobN"))
# BlobToSet and answers through SetToBlobN, a temporal with BlobToTemporal/TemporalToBlobN,
# a base value with its own PTR_IN reader and {Base}ToBlobN (BlobToJsonb/JsonbToBlobN).
if kind.startswith("base:"):
ctype = kind.split(":", 1)[1]
blob_in = PTR_IN[ctype][1].split("(", 1)[0]
blob_out = PTR_RET[ctype][1].split("(", 1)[0] + "N"
else:
ctype, blob_in, blob_out = (("Set", "BlobToSet", "SetToBlobN") if kind == "set"
else ("Temporal", "BlobToTemporal", "TemporalToBlobN"))
_row_frame_open(L, len(sig) + 1, "string_t")
L += [f" {ctype} *t = {blob_in}({_row_get('string_t', 0)});",
" int n = 0;",
Expand Down Expand Up @@ -7774,8 +7791,11 @@ def gen_cpp(fns, out_path, declared=None, aliases=None):
# `scope, accs` above reads reg_scope, which resolves TEMPORAL scope and answers
# None for every set name; the set operand takes the set map the shape already
# cleared it on, the way the set emit paths below read set_reg_scope.
# A base value registers over its own type alone.
if kind == "set":
scope, accs = set_reg_scope(fn)
elif kind.startswith("base:"):
scope, accs = "types", [PTR_IN[kind.split(":", 1)[1]][0]]
bodies.append(emit_body_text_array(f, kind, tail, sig))
_rest = "".join(", %s" % x for x in sig[1:])
argsig = "{type, %s%s}" % (sig[0], _rest)
Expand Down
Loading