use std::cmp::Ordering;
use rupg_common::{Error, Result, SqlState};
use rupg_types::{JsonItem, JsonTree, Value, json_index, numeric_in, oid};
use crate::cast::{numeric_to_float, numeric_to_int};
use crate::{Call, Kernel, bad_value, type_error};
pub(crate) fn by_src(src: &str) -> Option<Kernel> {
Some(match src {
"json_object_field" => json_object_field,
"json_object_field_text" => json_object_field_text,
"json_array_element" => json_array_element,
"json_array_element_text" => json_array_element_text,
"json_extract_path" => json_extract_path,
"json_extract_path_text" => json_extract_path_text,
"json_typeof" => json_typeof,
"json_array_length" => json_array_length,
"json_strip_nulls" => json_strip_nulls,
"jsonb_object_field" => jsonb_object_field,
"jsonb_object_field_text" => jsonb_object_field_text,
"jsonb_array_element" => jsonb_array_element,
"jsonb_array_element_text" => jsonb_array_element_text,
"jsonb_extract_path" => jsonb_extract_path,
"jsonb_extract_path_text" => jsonb_extract_path_text,
"jsonb_typeof" => jsonb_typeof,
"jsonb_array_length" => jsonb_array_length,
"jsonb_strip_nulls" => jsonb_strip_nulls,
"jsonb_pretty" => jsonb_pretty,
"jsonb_bool" => jsonb_bool,
"jsonb_numeric" => jsonb_numeric,
"jsonb_int2" | "jsonb_int4" | "jsonb_int8" => jsonb_int,
"jsonb_float4" | "jsonb_float8" => jsonb_float,
"jsonb_exists" => jsonb_exists,
"jsonb_exists_any" => jsonb_exists_any,
"jsonb_exists_all" => jsonb_exists_all,
"jsonb_contains" => jsonb_contains,
"jsonb_contained" => jsonb_contained,
"jsonb_eq" => jsonb_eq,
"jsonb_ne" => jsonb_ne,
"jsonb_lt" => jsonb_lt,
"jsonb_gt" => jsonb_gt,
"jsonb_le" => jsonb_le,
"jsonb_ge" => jsonb_ge,
"jsonb_cmp" => jsonb_cmp,
_ => return None,
})
}
pub(crate) fn text(args: &[Value], at: usize) -> Result<&str> {
args.get(at).and_then(Value::as_str).ok_or_else(bad_value)
}
pub(crate) fn tree(args: &[Value], at: usize) -> Result<JsonTree<'_>> {
JsonTree::parse(text(args, at)?).map_err(type_error)
}
fn path(args: &[Value], at: usize) -> Result<Option<Vec<&str>>> {
let Some(Value::Array(array)) = args.get(at) else { return Err(bad_value()) };
let mut out = Vec::with_capacity(array.values.len());
for value in &array.values {
match value {
Some(v) => out.push(v.as_str().ok_or_else(bad_value)?),
None => return Ok(None),
}
}
Ok(Some(out))
}
fn keys(args: &[Value], at: usize) -> Result<Vec<&str>> {
let Some(Value::Array(array)) = args.get(at) else { return Err(bad_value()) };
array.values.iter().flatten().map(|v| v.as_str().ok_or_else(bad_value)).collect()
}
fn json_value(text: Option<&str>) -> Value {
text.map_or(Value::Null, Value::text)
}
fn text_value(text: Option<String>) -> Value {
text.map_or(Value::Null, Value::Text)
}
fn object_field(tree: &JsonTree<'_>, name: &str) -> Option<usize> {
tree.field(0, name)
}
fn array_element(tree: &JsonTree<'_>, index: i64) -> Option<usize> {
tree.element(0, index)
}
fn index(args: &[Value], at: usize) -> Result<i64> {
args.get(at).and_then(Value::as_i64).ok_or_else(bad_value)
}
fn json_object_field(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let tree = tree(args, 0)?;
Ok(json_value(object_field(&tree, text(args, 1)?).map(|at| tree.text(at))))
}
fn json_object_field_text(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let tree = tree(args, 0)?;
Ok(text_value(object_field(&tree, text(args, 1)?).and_then(|at| tree.as_text(at))))
}
fn json_array_element(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let tree = tree(args, 0)?;
Ok(json_value(array_element(&tree, index(args, 1)?).map(|at| tree.text(at))))
}
fn json_array_element_text(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let tree = tree(args, 0)?;
Ok(text_value(array_element(&tree, index(args, 1)?).and_then(|at| tree.as_text(at))))
}
fn json_path(tree: &JsonTree<'_>, path: &[&str]) -> Option<usize> {
let mut nodes = vec![0];
for step in path {
let index = json_index(step);
let mut next = Vec::new();
for &node in &nodes {
match tree.item(node) {
JsonItem::Object(fields) => {
next.extend(fields.iter().filter(|(k, _)| k == step).map(|f| f.1));
}
JsonItem::Array(items) => {
let Some(index) = index else { continue };
let n = items.len() as i64;
let mut index = i64::from(index);
if index < 0 && -index <= n {
index += n;
}
if let Some(&item) = usize::try_from(index).ok().and_then(|i| items.get(i)) {
next.push(item);
}
}
_ => {}
}
}
nodes = next;
}
nodes.last().copied()
}
fn json_extract(args: &[Value], as_text: bool) -> Result<Value> {
let Some(path) = path(args, 1)? else { return Ok(Value::Null) };
let tree = tree(args, 0)?;
if path.is_empty() {
let root = tree.node(0);
return Ok(match tree.item(0) {
JsonItem::Array(_) | JsonItem::Object(_) => Value::text(tree.text(0)),
JsonItem::String(s) if as_text => Value::text(s.as_str()),
JsonItem::Null if as_text => Value::Null,
_ => Value::text(&tree.source()[..root.end]),
});
}
let Some(at) = json_path(&tree, &path) else { return Ok(Value::Null) };
Ok(if as_text { text_value(tree.as_text(at)) } else { Value::text(tree.text(at)) })
}
fn json_extract_path(_: &Call<'_>, args: &[Value]) -> Result<Value> {
json_extract(args, false)
}
fn json_extract_path_text(_: &Call<'_>, args: &[Value]) -> Result<Value> {
json_extract(args, true)
}
fn json_typeof(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let s = text(args, 0)?.trim_start_matches([' ', '\t', '\n', '\r']);
Ok(Value::text(match s.bytes().next() {
Some(b'{') => "object",
Some(b'[') => "array",
Some(b'"') => "string",
Some(b't' | b'f') => "boolean",
Some(b'n') => "null",
_ => "number",
}))
}
pub(crate) fn invalid(message: &str) -> Error {
Error::new(SqlState::INVALID_PARAMETER_VALUE, message)
}
fn array_length(tree: &JsonTree<'_>) -> Result<Value> {
match tree.item(0) {
JsonItem::Array(items) => Ok(Value::Int4(i32::try_from(items.len()).unwrap_or(i32::MAX))),
JsonItem::Object(_) => Err(invalid("cannot get array length of a non-array")),
_ => Err(invalid("cannot get array length of a scalar")),
}
}
fn json_array_length(_: &Call<'_>, args: &[Value]) -> Result<Value> {
array_length(&JsonTree::parse_with(text(args, 0)?, false).map_err(type_error)?)
}
fn strip_arrays(args: &[Value]) -> bool {
args.get(1).and_then(Value::as_bool).unwrap_or(false)
}
fn json_strip_nulls(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Text(tree(args, 0)?.strip_nulls(strip_arrays(args), false)))
}
fn jsonb_object_field(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let tree = tree(args, 0)?;
Ok(json_value(object_field(&tree, text(args, 1)?).map(|at| tree.text(at))))
}
fn jsonb_object_field_text(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let tree = tree(args, 0)?;
Ok(text_value(object_field(&tree, text(args, 1)?).and_then(|at| tree.as_text(at))))
}
fn jsonb_element(tree: &JsonTree<'_>, index: i64) -> Option<usize> {
match tree.item(0) {
JsonItem::Array(_) => tree.element(0, index),
JsonItem::Object(_) => None,
_ => (index == 0 || index == -1).then_some(0),
}
}
fn jsonb_array_element(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let tree = tree(args, 0)?;
Ok(json_value(jsonb_element(&tree, index(args, 1)?).map(|at| tree.text(at))))
}
fn jsonb_array_element_text(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let tree = tree(args, 0)?;
Ok(text_value(jsonb_element(&tree, index(args, 1)?).and_then(|at| tree.as_text(at))))
}
fn jsonb_path(tree: &JsonTree<'_>, path: &[&str]) -> Option<usize> {
let mut node = 0;
for step in path {
node = match tree.item(node) {
JsonItem::Object(_) => tree.field(node, step)?,
JsonItem::Array(_) => tree.element(node, i64::from(json_index(step)?))?,
_ => return None,
};
}
Some(node)
}
fn jsonb_extract(args: &[Value], as_text: bool) -> Result<Value> {
let Some(path) = path(args, 1)? else { return Ok(Value::Null) };
let tree = tree(args, 0)?;
let Some(at) = jsonb_path(&tree, &path) else { return Ok(Value::Null) };
Ok(if as_text { text_value(tree.as_text(at)) } else { Value::text(tree.text(at)) })
}
fn jsonb_extract_path(_: &Call<'_>, args: &[Value]) -> Result<Value> {
jsonb_extract(args, false)
}
fn jsonb_extract_path_text(_: &Call<'_>, args: &[Value]) -> Result<Value> {
jsonb_extract(args, true)
}
fn jsonb_typeof(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::text(tree(args, 0)?.type_name(0)))
}
fn jsonb_array_length(_: &Call<'_>, args: &[Value]) -> Result<Value> {
array_length(&tree(args, 0)?)
}
fn jsonb_strip_nulls(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let tree = tree(args, 0)?;
if !matches!(tree.item(0), JsonItem::Array(_) | JsonItem::Object(_)) {
return Ok(Value::text(tree.source()));
}
Ok(Value::Text(tree.strip_nulls(strip_arrays(args), true)))
}
fn jsonb_pretty(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Text(tree(args, 0)?.pretty()))
}
fn cannot_cast(item: &JsonItem, sqltype: &str) -> Error {
let kind = match item {
JsonItem::Null => "null",
JsonItem::String(_) => "string",
JsonItem::Number => "numeric",
JsonItem::Bool(_) => "boolean",
JsonItem::Array(_) => "array",
JsonItem::Object(_) => "object",
};
invalid(&format!("cannot cast jsonb {kind} to type {sqltype}"))
}
fn jsonb_number(args: &[Value], sqltype: &str) -> Result<Option<Value>> {
let tree = tree(args, 0)?;
match tree.item(0) {
JsonItem::Null => Ok(None),
JsonItem::Number => {
Ok(Some(Value::Numeric(numeric_in(tree.text(0), -1).map_err(type_error)?)))
}
item => Err(cannot_cast(item, sqltype)),
}
}
fn jsonb_bool(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let tree = tree(args, 0)?;
match tree.item(0) {
JsonItem::Null => Ok(Value::Null),
JsonItem::Bool(b) => Ok(Value::Bool(*b)),
item => Err(cannot_cast(item, "boolean")),
}
}
fn jsonb_numeric(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(jsonb_number(args, "numeric")?.unwrap_or(Value::Null))
}
fn sqltype(call: &Call<'_>) -> &'static str {
match call.ret {
oid::INT2 => "smallint",
oid::INT4 => "integer",
oid::INT8 => "bigint",
oid::FLOAT4 => "real",
_ => "double precision",
}
}
fn jsonb_int(call: &Call<'_>, args: &[Value]) -> Result<Value> {
match jsonb_number(args, sqltype(call))? {
Some(n) => numeric_to_int(call, &[n]),
None => Ok(Value::Null),
}
}
fn jsonb_float(call: &Call<'_>, args: &[Value]) -> Result<Value> {
match jsonb_number(args, sqltype(call))? {
Some(n) => numeric_to_float(call, &[n]),
None => Ok(Value::Null),
}
}
fn jsonb_exists(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Bool(tree(args, 0)?.exists(text(args, 1)?)))
}
fn jsonb_exists_any(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let tree = tree(args, 0)?;
Ok(Value::Bool(keys(args, 1)?.iter().any(|k| tree.exists(k))))
}
fn jsonb_exists_all(_: &Call<'_>, args: &[Value]) -> Result<Value> {
let tree = tree(args, 0)?;
Ok(Value::Bool(keys(args, 1)?.iter().all(|k| tree.exists(k))))
}
fn jsonb_contains(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Bool(tree(args, 0)?.contains(&tree(args, 1)?).map_err(type_error)?))
}
fn jsonb_contained(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Bool(tree(args, 1)?.contains(&tree(args, 0)?).map_err(type_error)?))
}
fn order(args: &[Value]) -> Result<Ordering> {
Ok(tree(args, 0)?.compare(&tree(args, 1)?))
}
fn jsonb_eq(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Bool(order(args)?.is_eq()))
}
fn jsonb_ne(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Bool(order(args)?.is_ne()))
}
fn jsonb_lt(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Bool(order(args)?.is_lt()))
}
fn jsonb_gt(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Bool(order(args)?.is_gt()))
}
fn jsonb_le(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Bool(order(args)?.is_le()))
}
fn jsonb_ge(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Bool(order(args)?.is_ge()))
}
fn jsonb_cmp(_: &Call<'_>, args: &[Value]) -> Result<Value> {
Ok(Value::Int4(order(args)? as i32))
}
fn first_byte(s: &str) -> Option<u8> {
s.trim_start_matches([' ', '\t', '\n', '\r']).bytes().next()
}
fn json_root<'a>(
args: &'a [Value],
escapes: bool,
root_error: impl Fn(u8) -> Option<Error>,
) -> Result<JsonTree<'a>> {
let s = text(args, 0)?;
JsonTree::parse_with(s, escapes)
.map_err(|error| first_byte(s).and_then(&root_error).unwrap_or_else(|| type_error(error)))
}
fn each_rows(tree: &JsonTree<'_>, as_text: bool) -> Vec<Vec<Value>> {
let JsonItem::Object(fields) = tree.item(0) else { return Vec::new() };
fields
.iter()
.map(|(name, at)| {
let value =
if as_text { text_value(tree.as_text(*at)) } else { Value::text(tree.text(*at)) };
vec![Value::text(name), value]
})
.collect()
}
fn json_each_worker(args: &[Value], as_text: bool) -> Result<Vec<Vec<Value>>> {
let array = || invalid("cannot deconstruct an array as an object");
let tree = json_root(args, true, |b| (b == b'[').then(array))?;
match tree.item(0) {
JsonItem::Object(_) => Ok(each_rows(&tree, as_text)),
JsonItem::Array(_) => Err(array()),
_ => Err(invalid("cannot deconstruct a scalar")),
}
}
pub(crate) fn json_each(_: &Call<'_>, args: &[Value]) -> Result<Vec<Vec<Value>>> {
json_each_worker(args, false)
}
pub(crate) fn json_each_text(_: &Call<'_>, args: &[Value]) -> Result<Vec<Vec<Value>>> {
json_each_worker(args, true)
}
fn jsonb_each_worker(args: &[Value], name: &str, as_text: bool) -> Result<Vec<Vec<Value>>> {
let tree = tree(args, 0)?;
if !matches!(tree.item(0), JsonItem::Object(_)) {
return Err(invalid(&format!("cannot call {name} on a non-object")));
}
Ok(each_rows(&tree, as_text))
}
pub(crate) fn jsonb_each(_: &Call<'_>, args: &[Value]) -> Result<Vec<Vec<Value>>> {
jsonb_each_worker(args, "jsonb_each", false)
}
pub(crate) fn jsonb_each_text(_: &Call<'_>, args: &[Value]) -> Result<Vec<Vec<Value>>> {
jsonb_each_worker(args, "jsonb_each_text", true)
}
fn object_keys(tree: &JsonTree<'_>, name: &str) -> Result<Vec<Vec<Value>>> {
match tree.item(0) {
JsonItem::Object(fields) => Ok(fields.iter().map(|f| vec![Value::text(&f.0)]).collect()),
JsonItem::Array(_) => Err(invalid(&format!("cannot call {name} on an array"))),
_ => Err(invalid(&format!("cannot call {name} on a scalar"))),
}
}
pub(crate) fn json_object_keys(_: &Call<'_>, args: &[Value]) -> Result<Vec<Vec<Value>>> {
let array = || invalid("cannot call json_object_keys on an array");
object_keys(&json_root(args, true, |b| (b == b'[').then(array))?, "json_object_keys")
}
pub(crate) fn jsonb_object_keys(_: &Call<'_>, args: &[Value]) -> Result<Vec<Vec<Value>>> {
object_keys(&tree(args, 0)?, "jsonb_object_keys")
}
fn element_rows(tree: &JsonTree<'_>, as_text: bool) -> Vec<Vec<Value>> {
let JsonItem::Array(items) = tree.item(0) else { return Vec::new() };
items
.iter()
.map(|&at| {
vec![if as_text { text_value(tree.as_text(at)) } else { Value::text(tree.text(at)) }]
})
.collect()
}
fn json_elements_worker(args: &[Value], name: &str, as_text: bool) -> Result<Vec<Vec<Value>>> {
let object = || invalid(&format!("cannot call {name} on a non-array"));
let tree = json_root(args, as_text, |b| (b == b'{').then(object))?;
match tree.item(0) {
JsonItem::Array(_) => Ok(element_rows(&tree, as_text)),
JsonItem::Object(_) => Err(object()),
_ => Err(invalid(&format!("cannot call {name} on a scalar"))),
}
}
pub(crate) fn json_array_elements(_: &Call<'_>, args: &[Value]) -> Result<Vec<Vec<Value>>> {
json_elements_worker(args, "json_array_elements", false)
}
pub(crate) fn json_array_elements_text(_: &Call<'_>, args: &[Value]) -> Result<Vec<Vec<Value>>> {
json_elements_worker(args, "json_array_elements_text", true)
}
fn jsonb_elements_worker(args: &[Value], as_text: bool) -> Result<Vec<Vec<Value>>> {
let tree = tree(args, 0)?;
match tree.item(0) {
JsonItem::Array(_) => Ok(element_rows(&tree, as_text)),
JsonItem::Object(_) => Err(invalid("cannot extract elements from an object")),
_ => Err(invalid("cannot extract elements from a scalar")),
}
}
pub(crate) fn jsonb_array_elements(_: &Call<'_>, args: &[Value]) -> Result<Vec<Vec<Value>>> {
jsonb_elements_worker(args, false)
}
pub(crate) fn jsonb_array_elements_text(_: &Call<'_>, args: &[Value]) -> Result<Vec<Vec<Value>>> {
jsonb_elements_worker(args, true)
}