1
0
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:
Leonid Fedorov
2025-09-13 00:33:15 +00:00
parent aab7083a96
commit ddb13da0dc
6 changed files with 263 additions and 59 deletions

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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

View File

@@ -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();