use inillucent_value::Collation;
use super::{Binder, BoundExpr, SubqueryKind};
use crate::ast::ExprId;
use crate::diagnostic::ParseError;
use crate::function;
impl Binder<'_> {
pub(super) fn bind_subtype(&mut self, argument: ExprId) -> Result<BoundExpr, ParseError> {
let bound = self.bind_expr(argument)?;
let bound = self.walk_value(&bound).unwrap_or(bound);
if let BoundExpr::Aggregate { slot, .. } = &bound {
let carries = matches!(
self.aggregates.get(*slot).map(|held| held.func),
Some(
function::AggregateFunc::JsonGroupArray
| function::AggregateFunc::JsonGroupObject
)
);
return Ok(BoundExpr::Integer(if carries { 74 } else { 0 }));
}
Ok(match json_subtype(&bound) {
Subtyped::Always => BoundExpr::Integer(74),
Subtyped::Never => BoundExpr::Integer(0),
Subtyped::WhenShaped => BoundExpr::Function {
func: function::ScalarFunc::Subtype,
arguments: vec![bound],
collation: Collation::Binary,
},
})
}
pub(super) fn marked_as_json(&self, argument: BoundExpr) -> BoundExpr {
if let Some(walked) = self.walk_value(&argument) {
return walked;
}
let wrapper = match &argument {
BoundExpr::Aggregate { slot, .. } => {
json_aggregate_wrapper(self.aggregates.get(*slot).map(|held| held.func))
}
BoundExpr::Subquery {
kind: SubqueryKind::Scalar,
block,
..
} => match block.columns.as_slice() {
[only] => match &only.expr {
BoundExpr::Aggregate { slot, .. } => {
json_aggregate_wrapper(block.aggregates.get(*slot).map(|held| held.func))
}
other if json_subtype(other) == Subtyped::Always => {
Some(function::JsonFunc::Json)
}
_ => None,
},
_ => None,
},
_ => None,
};
match wrapper {
Some(func) => BoundExpr::Json {
func,
arguments: vec![argument],
},
None => argument,
}
}
}
impl Binder<'_> {
pub(super) fn walk_value(&self, argument: &BoundExpr) -> Option<BoundExpr> {
const VALUE: u16 = 1;
const TYPE: u16 = 2;
let BoundExpr::Column {
source,
column: VALUE,
..
} = argument
else {
return None;
};
let term = self.sources.iter().find(|term| term.id == *source)?;
let walks = term.table.kind == crate::catalog_view::TableKind::Virtual
&& matches!(term.table.folded.as_slice(), b"json_each" | b"json_tree");
if !walks {
return None;
}
let kind = BoundExpr::Column {
source: *source,
column: TYPE,
slot: TYPE,
affinity: inillucent_value::affinity::Affinity::Blob,
collation: Collation::Binary,
};
Some(BoundExpr::Json {
func: function::JsonFunc::WalkValue,
arguments: vec![argument.clone(), kind],
})
}
}
fn json_aggregate_wrapper(func: Option<function::AggregateFunc>) -> Option<function::JsonFunc> {
match func? {
function::AggregateFunc::JsonGroupArray | function::AggregateFunc::JsonGroupObject => {
Some(function::JsonFunc::Json)
}
function::AggregateFunc::JsonbGroupArray | function::AggregateFunc::JsonbGroupObject => {
Some(function::JsonFunc::Jsonb)
}
_ => None,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Subtyped {
Always,
Never,
WhenShaped,
}
fn json_subtype(bound: &BoundExpr) -> Subtyped {
let BoundExpr::Json { func, .. } = bound else {
return Subtyped::Never;
};
use function::JsonFunc;
match func {
JsonFunc::Extract | JsonFunc::Arrow | JsonFunc::WalkValue => Subtyped::WhenShaped,
JsonFunc::Jsonb
| JsonFunc::ArrayB
| JsonFunc::ExtractB
| JsonFunc::InsertB
| JsonFunc::ObjectB
| JsonFunc::PatchB
| JsonFunc::RemoveB
| JsonFunc::ReplaceB
| JsonFunc::SetB
| JsonFunc::ArrayInsertB
| JsonFunc::ArrowShift
| JsonFunc::ArrayLength
| JsonFunc::ErrorPosition
| JsonFunc::Type
| JsonFunc::Valid
| JsonFunc::Pretty => Subtyped::Never,
_ => Subtyped::Always,
}
}
pub fn always_json(bound: &BoundExpr) -> bool {
json_subtype(bound) == Subtyped::Always
}