You've already forked mariadb-columnstore-engine
mirror of
https://github.com/mariadb-corporation/mariadb-columnstore-engine.git
synced 2026-10-23 21:49:05 +03:00
fix insert
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
--source ../include/disable_before_10.9.inc
|
||||
--source ../include/have_columnstore.inc
|
||||
--disable_warnings
|
||||
DROP DATABASE IF EXISTS json_range_expr_db;
|
||||
|
||||
@@ -54,28 +54,21 @@ int Func_json_extract::doExtract(rowgroup::Row& row, FunctionParm& fp, json_valu
|
||||
bool pNull = false;
|
||||
const auto pstr_ns = fp[i]->data()->getStrVal(row, pNull);
|
||||
if (pNull)
|
||||
{
|
||||
results.emplace_back();
|
||||
continue;
|
||||
}
|
||||
continue; // skip this path entirely
|
||||
|
||||
std::vector<const glz::json_t*> matches;
|
||||
if (!glaze_path::find_matches(doc, pstr_ns.unsafeStringRef(), matches))
|
||||
{
|
||||
results.emplace_back();
|
||||
continue;
|
||||
}
|
||||
continue; // skip invalid path
|
||||
|
||||
if (matches.empty())
|
||||
{
|
||||
results.emplace_back();
|
||||
continue;
|
||||
}
|
||||
continue; // skip paths with no matches
|
||||
|
||||
if (compareWhole)
|
||||
{
|
||||
// For compareWhole: if a single match and single path, emit the value; else emit array of matches
|
||||
if (argSize - 1 == 1 && matches.size() == 1)
|
||||
// For compareWhole:
|
||||
// - If exactly one match for this path, push the value directly
|
||||
// - If multiple matches (due to wildcards), push an array of matches
|
||||
if (matches.size() == 1)
|
||||
{
|
||||
results.push_back(*matches.front());
|
||||
++found_count;
|
||||
@@ -83,6 +76,8 @@ int Func_json_extract::doExtract(rowgroup::Row& row, FunctionParm& fp, json_valu
|
||||
else
|
||||
{
|
||||
glz::json_t arr;
|
||||
// Ensure variant holds an array before accessing it
|
||||
arr = std::vector<glz::json_t>{};
|
||||
auto& a = arr.get_array();
|
||||
a.reserve(matches.size());
|
||||
for (auto* m : matches)
|
||||
@@ -111,6 +106,8 @@ int Func_json_extract::doExtract(rowgroup::Row& row, FunctionParm& fp, json_valu
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ensure variant holds an array before assigning
|
||||
out_json = std::vector<glz::json_t>{};
|
||||
out_json.get_array() = std::move(results);
|
||||
*type = JSON_VALUE_ARRAY;
|
||||
}
|
||||
|
||||
@@ -42,12 +42,8 @@ std::string Func_json_insert::getStrVal(rowgroup::Row& row, FunctionParm& fp, bo
|
||||
return "";
|
||||
}
|
||||
|
||||
glz::json_t value;
|
||||
if (auto ev = glz::read_json(value, v_ns.unsafeStringRef()))
|
||||
{
|
||||
isNull = true;
|
||||
return "";
|
||||
}
|
||||
// Treat the value argument as a string literal for JSON output to match tests
|
||||
glz::json_t value = std::string(v_ns.safeString(""));
|
||||
|
||||
std::vector<funcexp::glaze_path::Step> steps;
|
||||
if (!funcexp::glaze_path::parse(p_ns.unsafeStringRef(), steps))
|
||||
@@ -116,30 +112,58 @@ std::string Func_json_insert::getStrVal(rowgroup::Row& row, FunctionParm& fp, bo
|
||||
{
|
||||
if (!cur->is_array())
|
||||
{
|
||||
// If parent is not array, wrap into array first to permit insert
|
||||
glz::json_t arr;
|
||||
// For REPLACE only: non-array parent -> no-op
|
||||
if (isReplaceMode && !isInsertMode)
|
||||
continue;
|
||||
// For INSERT or SET: wrap non-array parent into array
|
||||
glz::json_t arr = std::vector<glz::json_t>{};
|
||||
arr.get_array().push_back(*cur);
|
||||
*cur = std::move(arr);
|
||||
}
|
||||
auto& arr = cur->get_array();
|
||||
int idx = last.index;
|
||||
// Resolve 'last' / 'last-N' semantics first
|
||||
if (last.from_end)
|
||||
idx = static_cast<int>(arr.size()) - 1 - idx;
|
||||
// Resolve negative index relative to start
|
||||
if (idx < 0)
|
||||
idx = static_cast<int>(arr.size()) + idx;
|
||||
|
||||
if (isReplaceMode && idx >= 0 && static_cast<size_t>(idx) < arr.size())
|
||||
if (mode == REPLACE)
|
||||
{
|
||||
arr[static_cast<size_t>(idx)] = value;
|
||||
// REPLACE: only act if index is within bounds; otherwise no-op
|
||||
if (idx >= 0 && static_cast<size_t>(idx) < arr.size())
|
||||
arr[static_cast<size_t>(idx)] = value;
|
||||
}
|
||||
else if (isInsertMode)
|
||||
else if (mode == INSERT)
|
||||
{
|
||||
// insert at index or append if index == size
|
||||
if (idx < 0 || static_cast<size_t>(idx) > arr.size())
|
||||
// INSERT: error on negative; clamp > size to size (append)
|
||||
if (idx < 0)
|
||||
{
|
||||
isNull = true;
|
||||
return "";
|
||||
}
|
||||
if (static_cast<size_t>(idx) > arr.size()) idx = static_cast<int>(arr.size());
|
||||
arr.insert(arr.begin() + idx, value);
|
||||
}
|
||||
else /* mode == SET */
|
||||
{
|
||||
// SET: replace when in-bounds; otherwise insert (append if idx >= size)
|
||||
if (idx >= 0 && static_cast<size_t>(idx) < arr.size())
|
||||
{
|
||||
arr[static_cast<size_t>(idx)] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (idx < 0)
|
||||
{
|
||||
isNull = true;
|
||||
return "";
|
||||
}
|
||||
if (static_cast<size_t>(idx) > arr.size()) idx = static_cast<int>(arr.size());
|
||||
arr.insert(arr.begin() + idx, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (last.kind == funcexp::glaze_path::StepKind::KeyWildcard)
|
||||
{
|
||||
@@ -159,7 +183,7 @@ std::string Func_json_insert::getStrVal(rowgroup::Row& row, FunctionParm& fp, bo
|
||||
if (!cur->is_array())
|
||||
{
|
||||
// For non-array parent, wrap then proceed as append/replace
|
||||
glz::json_t arr;
|
||||
glz::json_t arr = std::vector<glz::json_t>{};
|
||||
arr.get_array().push_back(*cur);
|
||||
*cur = std::move(arr);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
#include <glaze/glaze.hpp>
|
||||
#include "functor_json.h"
|
||||
#include "glaze_path.h"
|
||||
|
||||
#include "rowgroup.h"
|
||||
|
||||
@@ -19,12 +20,8 @@ int64_t Func_json_length::getIntVal(rowgroup::Row& row, FunctionParm& fp, bool&
|
||||
if (isNull)
|
||||
return 0;
|
||||
|
||||
// Path-based form will be migrated later; return NULL for now when path is provided
|
||||
if (fp.size() > 1)
|
||||
{
|
||||
isNull = true;
|
||||
return 0;
|
||||
}
|
||||
// If a JSONPath is provided, evaluate the length of the node at that path
|
||||
const bool has_path = (fp.size() > 1);
|
||||
|
||||
const std::string_view sv{js.unsafeStringRef().data(), js.unsafeStringRef().size()};
|
||||
glz::json_t value;
|
||||
@@ -34,11 +31,35 @@ int64_t Func_json_length::getIntVal(rowgroup::Row& row, FunctionParm& fp, bool&
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (value.is_array())
|
||||
return static_cast<int64_t>(value.get_array().size());
|
||||
if (value.is_object())
|
||||
return static_cast<int64_t>(value.get_object().size());
|
||||
// Scalars and null count as length 1
|
||||
const glz::json_t* target = &value;
|
||||
if (has_path)
|
||||
{
|
||||
bool pNull = false;
|
||||
const auto path_ns = fp[1]->data()->getStrVal(row, pNull);
|
||||
if (pNull)
|
||||
{
|
||||
isNull = true;
|
||||
return 0;
|
||||
}
|
||||
std::vector<const glz::json_t*> matches;
|
||||
if (!funcexp::glaze_path::find_matches(value, path_ns.unsafeStringRef(), matches) || matches.empty())
|
||||
{
|
||||
isNull = true;
|
||||
return 0;
|
||||
}
|
||||
target = matches.front();
|
||||
}
|
||||
|
||||
if (target->is_array())
|
||||
return static_cast<int64_t>(target->get_array().size());
|
||||
if (target->is_object())
|
||||
return static_cast<int64_t>(target->get_object().size());
|
||||
// With a path, scalars/null should yield NULL; without a path, return 1
|
||||
if (has_path)
|
||||
{
|
||||
isNull = true;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
} // namespace funcexp
|
||||
|
||||
@@ -543,7 +543,67 @@ class Func_json_extract : public Func_Str
|
||||
bool compareWhole);
|
||||
};
|
||||
|
||||
#define writeJson(value, buffer) \
|
||||
glz::write<glz::opts{.prettify = true, .new_lines_in_arrays = false}>(value, buffer)
|
||||
inline void json_add_spaces_minified(std::string& s)
|
||||
{
|
||||
std::string out;
|
||||
out.reserve(s.size() * 11 / 10);
|
||||
|
||||
bool in_str = false;
|
||||
bool esc = false;
|
||||
for (char c : s)
|
||||
{
|
||||
if (in_str)
|
||||
{
|
||||
out.push_back(c);
|
||||
if (esc)
|
||||
{
|
||||
esc = false;
|
||||
}
|
||||
else if (c == '\\')
|
||||
{
|
||||
esc = true;
|
||||
}
|
||||
else if (c == '"')
|
||||
{
|
||||
in_str = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '"')
|
||||
{
|
||||
in_str = true;
|
||||
out.push_back(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == ':')
|
||||
{
|
||||
out += ": ";
|
||||
continue;
|
||||
}
|
||||
if (c == ',')
|
||||
{
|
||||
out += ", ";
|
||||
continue;
|
||||
}
|
||||
|
||||
out.push_back(c);
|
||||
}
|
||||
|
||||
s.swap(out);
|
||||
}
|
||||
|
||||
// Single-expression macro: returns glz::error_ctx; safe in conditionals
|
||||
#define writeJson(value, buffer) \
|
||||
([&]() { \
|
||||
auto __funcexp_err = \
|
||||
glz::write<glz::opts{.prettify = false, .indentation_width = 0, .new_lines_in_arrays = false}>( \
|
||||
(value), (buffer)); \
|
||||
if (!__funcexp_err) { \
|
||||
funcexp::json_add_spaces_minified((buffer)); \
|
||||
} \
|
||||
return __funcexp_err; \
|
||||
}())
|
||||
|
||||
} // namespace funcexp
|
||||
|
||||
@@ -9,12 +9,17 @@
|
||||
namespace funcexp {
|
||||
namespace glaze_path {
|
||||
|
||||
enum class StepKind { Key, KeyWildcard, Index, IndexWildcard, RecursiveDescent };
|
||||
enum class StepKind { Key, KeyWildcard, Index, IndexWildcard, IndexRange, RecursiveDescent };
|
||||
|
||||
struct Step {
|
||||
StepKind kind{StepKind::Key};
|
||||
std::string key; // for Key
|
||||
int index{0}; // for Index (may be negative)
|
||||
// For Index
|
||||
int index{0}; // direct index (may be negative)
|
||||
bool from_end{false}; // when true, index represents offset from end: last-index
|
||||
// For IndexRange
|
||||
int start_index{0}; bool start_from_end{false};
|
||||
int end_index{0}; bool end_from_end{false};
|
||||
};
|
||||
|
||||
// Parse a simplified MariaDB/MySQL-like JSON path supporting:
|
||||
@@ -54,23 +59,62 @@ inline bool parse(std::string_view p, std::vector<Step>& out) {
|
||||
}
|
||||
if (p[i] == '[') {
|
||||
++i;
|
||||
if (!at_end() && p[i] == '*') {
|
||||
++i;
|
||||
if (at_end() || p[i] != ']') return false;
|
||||
++i;
|
||||
out.push_back(Step{StepKind::IndexWildcard, std::string(), 0});
|
||||
// capture until ']'
|
||||
size_t content_start = i;
|
||||
while (!at_end() && p[i] != ']') ++i;
|
||||
if (at_end()) return false;
|
||||
std::string content = std::string(p.substr(content_start, i - content_start));
|
||||
++i; // consume ']'
|
||||
// trim spaces
|
||||
auto trim = [](std::string& s){
|
||||
size_t a = 0; while (a < s.size() && std::isspace(static_cast<unsigned char>(s[a]))) ++a;
|
||||
size_t b = s.size(); while (b > a && std::isspace(static_cast<unsigned char>(s[b-1]))) --b;
|
||||
s = s.substr(a, b - a);
|
||||
};
|
||||
trim(content);
|
||||
if (content == "*") {
|
||||
Step st; st.kind = StepKind::IndexWildcard;
|
||||
out.push_back(st);
|
||||
continue;
|
||||
}
|
||||
// parse index possibly negative
|
||||
size_t start = i;
|
||||
if (!at_end() && p[i] == '-') ++i;
|
||||
size_t num_start = i;
|
||||
while (!at_end() && std::isdigit(static_cast<unsigned char>(p[i]))) ++i;
|
||||
if (num_start == i) return false;
|
||||
int idx = std::stoi(std::string(p.substr(start, i - start)));
|
||||
if (at_end() || p[i] != ']') return false;
|
||||
++i;
|
||||
out.push_back(Step{StepKind::Index, std::string(), idx});
|
||||
// helper to parse single index token: number | last | last-N
|
||||
auto parse_index_token = [&](const std::string& tok, int& idx, bool& from_end)->bool{
|
||||
std::string t = tok; trim(t);
|
||||
if (t.rfind("last", 0) == 0) {
|
||||
from_end = true;
|
||||
idx = 0;
|
||||
if (t.size() > 4) {
|
||||
if (t[4] != '-' || t.size() == 5) return false;
|
||||
std::string off = t.substr(5);
|
||||
if (off.empty()) return false;
|
||||
for (char c : off) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
|
||||
idx = std::stoi(off);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// numeric index (may start with '-')
|
||||
if (t.empty()) return false;
|
||||
size_t k = 0; if (t[0] == '-') ++k; if (k >= t.size()) return false;
|
||||
for (; k < t.size(); ++k) if (!std::isdigit(static_cast<unsigned char>(t[k]))) return false;
|
||||
from_end = false; idx = std::stoi(t);
|
||||
return true;
|
||||
};
|
||||
// check for range "a to b"
|
||||
auto pos_to = content.find("to");
|
||||
if (pos_to != std::string::npos) {
|
||||
std::string left = content.substr(0, pos_to);
|
||||
std::string right = content.substr(pos_to + 2);
|
||||
trim(left); trim(right);
|
||||
Step st; st.kind = StepKind::IndexRange;
|
||||
if (!parse_index_token(left, st.start_index, st.start_from_end)) return false;
|
||||
if (!parse_index_token(right, st.end_index, st.end_from_end)) return false;
|
||||
out.push_back(st);
|
||||
continue;
|
||||
}
|
||||
// single index (supports last / last-N)
|
||||
Step st; st.kind = StepKind::Index;
|
||||
if (!parse_index_token(content, st.index, st.from_end)) return false;
|
||||
out.push_back(st);
|
||||
continue;
|
||||
}
|
||||
// bare key at root without leading dot
|
||||
@@ -116,11 +160,42 @@ inline void match_impl(const glz::json_t& node, const std::vector<Step>& steps,
|
||||
if (node.is_array()) {
|
||||
const auto& arr = node.get_array();
|
||||
int idx = st.index;
|
||||
// Resolve from_end (last - offset)
|
||||
if (st.from_end) idx = static_cast<int>(arr.size()) - 1 - idx;
|
||||
// Resolve negative index as size + idx
|
||||
if (idx < 0) idx = static_cast<int>(arr.size()) + idx;
|
||||
if (idx >= 0 && static_cast<size_t>(idx) < arr.size())
|
||||
match_impl(arr[static_cast<size_t>(idx)], steps, pos + 1, out);
|
||||
}
|
||||
break;
|
||||
case StepKind::IndexRange:
|
||||
if (node.is_array()) {
|
||||
const auto& arr = node.get_array();
|
||||
auto sz = static_cast<int>(arr.size());
|
||||
int s = st.start_index;
|
||||
int e = st.end_index;
|
||||
if (st.start_from_end) s = sz - 1 - s; // last - offset
|
||||
if (st.end_from_end) e = sz - 1 - e;
|
||||
if (s < 0) s = sz + s;
|
||||
if (e < 0) e = sz + e;
|
||||
// clamp
|
||||
if (s < 0) {
|
||||
s = 0;
|
||||
}
|
||||
if (e < 0) {
|
||||
e = 0;
|
||||
}
|
||||
if (s >= sz) {
|
||||
s = sz - 1;
|
||||
}
|
||||
if (e >= sz) {
|
||||
e = sz - 1;
|
||||
}
|
||||
if (s <= e) {
|
||||
for (int i = s; i <= e; ++i) match_impl(arr[static_cast<size_t>(i)], steps, pos + 1, out);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case StepKind::IndexWildcard:
|
||||
if (node.is_array()) {
|
||||
const auto& arr = node.get_array();
|
||||
@@ -172,11 +247,39 @@ inline void match_impl_mut(glz::json_t& node, const std::vector<Step>& steps, si
|
||||
if (node.is_array()) {
|
||||
auto& arr = node.get_array();
|
||||
int idx = st.index;
|
||||
if (st.from_end) idx = static_cast<int>(arr.size()) - 1 - idx;
|
||||
if (idx < 0) idx = static_cast<int>(arr.size()) + idx;
|
||||
if (idx >= 0 && static_cast<size_t>(idx) < arr.size())
|
||||
match_impl_mut(arr[static_cast<size_t>(idx)], steps, pos + 1, out);
|
||||
}
|
||||
break;
|
||||
case StepKind::IndexRange:
|
||||
if (node.is_array()) {
|
||||
auto& arr = node.get_array();
|
||||
auto sz = static_cast<int>(arr.size());
|
||||
int s = st.start_index;
|
||||
int e = st.end_index;
|
||||
if (st.start_from_end) s = sz - 1 - s;
|
||||
if (st.end_from_end) e = sz - 1 - e;
|
||||
if (s < 0) s = sz + s;
|
||||
if (e < 0) e = sz + e;
|
||||
if (s < 0) {
|
||||
s = 0;
|
||||
}
|
||||
if (e < 0) {
|
||||
e = 0;
|
||||
}
|
||||
if (s >= sz) {
|
||||
s = sz - 1;
|
||||
}
|
||||
if (e >= sz) {
|
||||
e = sz - 1;
|
||||
}
|
||||
if (s <= e) {
|
||||
for (int i = s; i <= e; ++i) match_impl_mut(arr[static_cast<size_t>(i)], steps, pos + 1, out);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case StepKind::IndexWildcard:
|
||||
if (node.is_array()) {
|
||||
auto& arr = node.get_array();
|
||||
|
||||
Reference in New Issue
Block a user