use rupg_common::{Error, Result};
use rupg_types::{Array, ArrayDim, Inet, Interval, NetFamily, Range, Record, Recv, Value};
use crate::expr::{
BoolOp, BoolTest, Case, CastForm, Expr, ExprKind, FieldStore, Func, FuncForm, IsJson, JoinRow,
JsonBehavior, JsonBehaviorKind, JsonCtor, JsonCtorKind, JsonEncoding, JsonExpr, JsonExprOp,
JsonFormat, JsonFormatKind, JsonItemType, JsonReturning, JsonValue, JsonWrapper, NamedArg,
RowCompare, RowCompareKind, SqlValue, Subscript, Var, XmlExpr, XmlOp,
};
pub fn write(expr: &Expr) -> Result<String> {
let mut out = String::new();
write_expr(expr, &mut out)?;
Ok(out)
}
pub fn read(text: &str) -> Result<Expr> {
let mut tokens = Tokens { text, at: 0 };
let item = tokens.item()?;
if tokens.next()?.is_some() {
return Err(bad("text after the end"));
}
expr_of(&item)
}
pub fn write_list(exprs: &[Expr]) -> Result<String> {
let mut out = String::from("(");
for (i, expr) in exprs.iter().enumerate() {
if i > 0 {
out.push(' ');
}
write_expr(expr, &mut out)?;
}
out.push(')');
Ok(out)
}
pub fn read_list(text: &str) -> Result<Vec<Expr>> {
let mut tokens = Tokens { text, at: 0 };
let item = tokens.item()?;
if tokens.next()?.is_some() {
return Err(bad("text after the end"));
}
list(&item)?.iter().map(expr_of).collect()
}
#[derive(Debug)]
pub enum StoredDatum {
MinValue,
MaxValue,
Value(Expr),
}
#[derive(Debug)]
pub enum StoredBound {
Default,
Hash {
modulus: i32,
remainder: i32,
},
List(Vec<Option<Expr>>),
Range {
lower: Vec<StoredDatum>,
upper: Vec<StoredDatum>,
},
}
const BOUND: &str = "PARTITIONBOUNDSPEC";
pub fn is_bound(text: &str) -> bool {
text.trim_start().strip_prefix('{').is_some_and(|rest| rest.starts_with(BOUND))
}
pub fn write_bound(bound: &rupg_catalog::PartitionBound) -> String {
use rupg_catalog::{PartitionBound, RangeDatum};
let datums = |list: &[RangeDatum]| {
let items: Vec<String> = list
.iter()
.map(|d| match d {
RangeDatum::MinValue => "{PARTITIONRANGEDATUM :kind -1 :value <>}".to_string(),
RangeDatum::MaxValue => "{PARTITIONRANGEDATUM :kind 1 :value <>}".to_string(),
RangeDatum::Value(text) => format!("{{PARTITIONRANGEDATUM :kind 0 :value {text}}}"),
})
.collect();
format!("({})", items.join(" "))
};
match bound {
PartitionBound::Default => format!("{{{BOUND} :strategy <> :is_default true}}"),
PartitionBound::Hash { modulus, remainder } => format!(
"{{{BOUND} :strategy h :is_default false :modulus {modulus} :remainder {remainder}}}"
),
PartitionBound::List(values) => {
let items: Vec<&str> = values.iter().map(|v| v.as_deref().unwrap_or("<>")).collect();
format!("{{{BOUND} :strategy l :is_default false :listdatums ({})}}", items.join(" "))
}
PartitionBound::Range { lower, upper } => format!(
"{{{BOUND} :strategy r :is_default false :lowerdatums {} :upperdatums {}}}",
datums(lower),
datums(upper)
),
}
}
pub fn read_bound(text: &str) -> Result<StoredBound> {
let mut tokens = Tokens { text, at: 0 };
let item = tokens.item()?;
if tokens.next()?.is_some() {
return Err(bad("text after the end"));
}
let Item::Node(name, fields) = &item else { return Err(bad("no node")) };
if name != BOUND {
return Err(bad("no partition bound"));
}
let f = Fields { fields };
if f.flag("is_default")? {
return Ok(StoredBound::Default);
}
let datums = |name: &str| -> Result<Vec<StoredDatum>> {
list(f.get(name)?)?
.iter()
.map(|item| {
let Item::Node(_, fields) = item else { return Err(bad("no range datum")) };
let d = Fields { fields };
Ok(match d.number::<i32>("kind")? {
-1 => StoredDatum::MinValue,
1 => StoredDatum::MaxValue,
_ => StoredDatum::Value(expr_of(d.get("value")?)?),
})
})
.collect()
};
match f.atom("strategy")? {
"h" => Ok(StoredBound::Hash {
modulus: f.number("modulus")?,
remainder: f.number("remainder")?,
}),
"l" => {
let values = list(f.get("listdatums")?)?
.iter()
.map(|item| match item {
Item::Atom(empty) if empty == "<>" => Ok(None),
item => expr_of(item).map(Some),
})
.collect::<Result<Vec<_>>>()?;
Ok(StoredBound::List(values))
}
"r" => {
Ok(StoredBound::Range { lower: datums("lowerdatums")?, upper: datums("upperdatums")? })
}
other => Err(bad(&format!("partition strategy {other}"))),
}
}
fn bad(what: &str) -> Error {
Error::internal(format!("bad stored expression: {what}"))
}
fn write_expr(expr: &Expr, out: &mut String) -> Result<()> {
let name = match &expr.kind {
ExprKind::Const(_) => "CONST",
ExprKind::Func(_) => "FUNC",
ExprKind::Relabel(..) => "RELABEL",
ExprKind::CoerceViaIo(..) => "COERCEVIAIO",
ExprKind::ArrayCoerce { .. } => "ARRAYCOERCE",
ExprKind::CoerceToDomain(..) => "COERCETODOMAIN",
ExprKind::DomainValue => "COERCETODOMAINVALUE",
ExprKind::Subscript(_) => "SUBSCRIPT",
ExprKind::Bool(..) => "BOOL",
ExprKind::NullTest(..) => "NULLTEST",
ExprKind::BooleanTest(..) => "BOOLEANTEST",
ExprKind::Case(_) => "CASE",
ExprKind::CaseTest => "CASETEST",
ExprKind::Coalesce(_) => "COALESCE",
ExprKind::MinMax { .. } => "MINMAX",
ExprKind::NullIf { .. } => "NULLIF",
ExprKind::Distinct { .. } => "DISTINCT",
ExprKind::ScalarArrayOp { .. } => "SCALARARRAYOP",
ExprKind::Array { .. } => "ARRAY",
ExprKind::Row(..) => "ROW",
ExprKind::JoinRow(_) => "JOINROW",
ExprKind::RowCompare(_) => "ROWCOMPARE",
ExprKind::SqlValue(_) => "SQLVALUE",
ExprKind::Var(_) => "VAR",
ExprKind::FieldSelect(..) => "FIELDSELECT",
ExprKind::FieldStore(_) => "FIELDSTORE",
ExprKind::NamedArg(_) => "NAMEDARG",
ExprKind::JsonValue(_) => "JSONVALUEEXPR",
ExprKind::JsonCtor(_) => "JSONCONSTRUCTOREXPR",
ExprKind::IsJson(_) => "JSONISPREDICATE",
ExprKind::JsonExpr(_) => "JSONEXPR",
ExprKind::Xml(_) => "XMLEXPR",
ExprKind::Param(_)
| ExprKind::Agg(_)
| ExprKind::Grouping(_)
| ExprKind::PlaceHolder(_)
| ExprKind::Window(_)
| ExprKind::SubLink(_)
| ExprKind::SubColumn(_)
| ExprKind::CurrentOf(_)
| ExprKind::JsonTable(_) => {
return Err(Error::internal("a stored expression cannot have this node"));
}
};
let location = expr.location.map_or(-1, |l| i64::try_from(l).unwrap_or(-1));
out.push('{');
out.push_str(name);
out.push_str(&format!(" :type {} :typmod {} :location {location}", expr.ty, expr.typmod));
match &expr.kind {
ExprKind::Const(value) => {
out.push_str(" :value ");
write_value(value, out);
}
ExprKind::Func(f) => {
out.push_str(&format!(" :oid {}", f.oid));
match f.form {
FuncForm::Call => out.push_str(" :form call"),
FuncForm::Operator(op) => out.push_str(&format!(" :form (operator {op})")),
FuncForm::ExplicitCast => out.push_str(" :form explicit"),
FuncForm::ImplicitCast => out.push_str(" :form implicit"),
FuncForm::SqlSyntax => out.push_str(" :form sql"),
}
out.push_str(&format!(" :variadic {}", f.variadic));
write_args(&f.args, out)?;
}
ExprKind::Relabel(arg, form)
| ExprKind::CoerceViaIo(arg, form)
| ExprKind::CoerceToDomain(arg, form) => {
out.push_str(match form {
CastForm::Explicit => " :form explicit",
CastForm::Implicit => " :form implicit",
});
write_field("arg", arg, out)?;
}
ExprKind::ArrayCoerce { arg, element, form } => {
out.push_str(match form {
CastForm::Explicit => " :form explicit",
CastForm::Implicit => " :form implicit",
});
write_field("arg", arg, out)?;
write_field("element", element, out)?;
}
ExprKind::Subscript(sub) => {
write_field("container", &sub.container, out)?;
out.push_str(" :upper ");
write_bounds(&sub.upper, out)?;
out.push_str(" :lower ");
match &sub.lower {
Some(lower) => write_bounds(lower, out)?,
None => out.push_str("<>"),
}
if let Some(assign) = &sub.assign {
write_field("assign", assign, out)?;
}
}
ExprKind::Bool(op, args) => {
let op = match op {
BoolOp::And => "and",
BoolOp::Or => "or",
BoolOp::Not => "not",
};
out.push_str(&format!(" :op {op}"));
write_args(args, out)?;
}
ExprKind::NullTest(arg, is_null) => {
write_field("arg", arg, out)?;
out.push_str(&format!(" :isnull {is_null}"));
}
ExprKind::BooleanTest(arg, test) => {
write_field("arg", arg, out)?;
out.push_str(" :test ");
out.push_str(test_name(*test));
}
ExprKind::Case(case) => {
out.push_str(" :arg ");
match &case.arg {
Some(arg) => write_expr(arg, out)?,
None => out.push_str("<>"),
}
out.push_str(" :whens (");
for (i, (when, then)) in case.whens.iter().enumerate() {
if i > 0 {
out.push(' ');
}
out.push('(');
write_expr(when, out)?;
out.push(' ');
write_expr(then, out)?;
out.push(')');
}
out.push(')');
write_field("default", &case.default, out)?;
}
ExprKind::CaseTest | ExprKind::DomainValue => {}
ExprKind::Coalesce(args) => write_args(args, out)?,
ExprKind::Row(args, form) => {
out.push_str(match form {
CastForm::Explicit => " :form explicit",
CastForm::Implicit => " :form implicit",
});
write_args(args, out)?;
}
ExprKind::JoinRow(row) => {
out.push_str(" :alias ");
match &row.alias {
Some(alias) => hex(alias.as_bytes(), out),
None => out.push_str("<>"),
}
out.push_str(" :names (");
for (i, name) in row.names.iter().enumerate() {
if i > 0 {
out.push(' ');
}
hex(name.as_bytes(), out);
}
out.push(')');
write_args(&row.fields, out)?;
}
ExprKind::MinMax { greatest, less, args } => {
out.push_str(&format!(" :greatest {greatest} :less {less}"));
write_args(args, out)?;
}
ExprKind::NullIf { equal, args } => {
out.push_str(&format!(" :equal {equal}"));
write_args(args, out)?;
}
ExprKind::Distinct { equal, not, args } => {
out.push_str(&format!(" :equal {equal} :not {not}"));
write_args(args, out)?;
}
ExprKind::RowCompare(rc) => {
let compares: Vec<String> = rc.compares.iter().map(u32::to_string).collect();
out.push_str(&format!(" :kind {} :compares ({})", rc.kind.name(), compares.join(" ")));
write_args(&rc.ops, out)?;
}
ExprKind::ScalarArrayOp { func, any, args } => {
out.push_str(&format!(" :func {func} :any {any}"));
write_args(args, out)?;
}
ExprKind::Array { element, multidims, elements } => {
out.push_str(&format!(" :element {element} :multidims {multidims}"));
write_args(elements, out)?;
}
ExprKind::SqlValue(value) => {
let (name, precision) = sql_value_name(*value);
out.push_str(&format!(" :function {name} :precision "));
match precision {
Some(p) => out.push_str(&p.to_string()),
None => out.push_str("<>"),
}
}
ExprKind::Var(var) => {
out.push_str(&format!(
" :relation {} :attnum {} :levelsup {}",
var.relation, var.attnum, var.levels_up
));
}
ExprKind::FieldSelect(arg, field) => {
out.push_str(&format!(" :field {field}"));
write_field("arg", arg, out)?;
}
ExprKind::FieldStore(store) => {
let numbers = |n: &mut dyn Iterator<Item = String>| n.collect::<Vec<_>>().join(" ");
let fields = numbers(&mut store.fields.iter().map(|(i, _)| i.to_string()));
let types = numbers(&mut store.types.iter().map(u32::to_string));
out.push_str(&format!(" :fieldnums ({fields}) :types ({types})"));
write_field("arg", &store.arg, out)?;
let values: Vec<Expr> = store.fields.iter().map(|(_, v)| v.clone()).collect();
write_args(&values, out)?;
}
ExprKind::NamedArg(na) => {
out.push_str(" :name ");
hex(na.name.as_bytes(), out);
out.push_str(&format!(" :number {}", na.number));
write_field("arg", &na.arg, out)?;
}
ExprKind::JsonValue(v) => {
write_format(v.format, out);
write_field("raw", &v.raw, out)?;
write_field("formatted", &v.formatted, out)?;
}
ExprKind::JsonCtor(c) => {
let kind = CTOR_KINDS.iter().find(|(k, _)| *k == c.kind).map_or("object", |(_, n)| n);
out.push_str(&format!(
" :kind {kind} :unique {} :absent {} :rtype {} :rtypmod {}",
c.unique, c.absent_on_null, c.returning.ty, c.returning.typmod
));
write_format(c.returning.format, out);
write_args(&c.args, out)?;
if c.func.is_some() {
return Err(Error::internal("a stored expression cannot have this node"));
}
out.push_str(" :coercion ");
match &c.coercion {
Some(coercion) => write_expr(coercion, out)?,
None => out.push_str("<>"),
}
}
ExprKind::IsJson(p) => {
let item = ITEM_TYPES.iter().find(|(i, _)| *i == p.item).map_or("any", |(_, n)| n);
out.push_str(&format!(" :item {item} :unique {} :basetype {}", p.unique, p.base_type));
write_field("arg", &p.expr, out)?;
}
ExprKind::JsonExpr(j) => {
let op = JSON_OPS.iter().find(|(o, _)| *o == j.op).map_or("exists", |(_, n)| n);
let wrapper =
WRAPPERS.iter().find(|(w, _)| *w == j.wrapper).map_or("unspec", |(_, n)| n);
out.push_str(&format!(
" :op {op} :wrapper {wrapper} :omitquotes {} :jsoncoercion {} :iocoercion {} :rtype {} :rtypmod {}",
j.omit_quotes, j.json_coercion, j.io_coercion, j.returning.ty, j.returning.typmod
));
write_format(j.returning.format, out);
write_field("formatted", &j.formatted, out)?;
write_field("path", &j.path, out)?;
out.push_str(" :names (");
for (i, (name, _)) in j.passing.iter().enumerate() {
if i > 0 {
out.push(' ');
}
hex(name.as_bytes(), out);
}
out.push(')');
let values: Vec<Expr> = j.passing.iter().map(|(_, e)| e.clone()).collect();
write_args(&values, out)?;
out.push_str(" :onempty ");
match &j.on_empty {
Some(behavior) => write_behavior(behavior, out)?,
None => out.push_str("<>"),
}
out.push_str(" :onerror ");
write_behavior(&j.on_error, out)?;
}
ExprKind::Xml(x) => {
let op = XML_OPS.iter().find(|(o, _)| *o == x.op).map_or("concat", |(_, n)| n);
out.push_str(&format!(" :op {op} :name "));
match &x.name {
Some(name) => hex(name.as_bytes(), out),
None => out.push_str("<>"),
}
out.push_str(" :names (");
for (i, (name, _)) in x.named.iter().enumerate() {
if i > 0 {
out.push(' ');
}
hex(name.as_bytes(), out);
}
out.push(')');
let values: Vec<Expr> = x.named.iter().map(|(_, e)| e.clone()).collect();
out.push_str(" :named ");
out.push_str(&write_list(&values)?);
write_args(&x.args, out)?;
out.push_str(&format!(
" :document {} :indent {} :ttype {} :ttypmod {}",
x.document, x.indent, x.target.0, x.target.1
));
}
ExprKind::Param(_)
| ExprKind::Agg(_)
| ExprKind::Grouping(_)
| ExprKind::PlaceHolder(_)
| ExprKind::Window(_)
| ExprKind::SubLink(_)
| ExprKind::SubColumn(_)
| ExprKind::CurrentOf(_)
| ExprKind::JsonTable(_) => {}
}
out.push('}');
Ok(())
}
const FORMAT_KINDS: [(JsonFormatKind, &str); 3] = [
(JsonFormatKind::Default, "default"),
(JsonFormatKind::Json, "json"),
(JsonFormatKind::Jsonb, "jsonb"),
];
const ENCODINGS: [(JsonEncoding, &str); 4] = [
(JsonEncoding::Default, "default"),
(JsonEncoding::Utf8, "utf8"),
(JsonEncoding::Utf16, "utf16"),
(JsonEncoding::Utf32, "utf32"),
];
const CTOR_KINDS: [(JsonCtorKind, &str); 8] = [
(JsonCtorKind::Object, "object"),
(JsonCtorKind::Array, "array"),
(JsonCtorKind::Parse, "parse"),
(JsonCtorKind::Scalar, "scalar"),
(JsonCtorKind::Serialize, "serialize"),
(JsonCtorKind::ObjectAgg, "objectagg"),
(JsonCtorKind::ArrayAgg, "arrayagg"),
(JsonCtorKind::ArrayQuery, "arrayquery"),
];
const ITEM_TYPES: [(JsonItemType, &str); 4] = [
(JsonItemType::Any, "any"),
(JsonItemType::Object, "object"),
(JsonItemType::Array, "array"),
(JsonItemType::Scalar, "scalar"),
];
const JSON_OPS: [(JsonExprOp, &str); 3] =
[(JsonExprOp::Exists, "exists"), (JsonExprOp::Query, "query"), (JsonExprOp::Value, "value")];
const XML_OPS: [(XmlOp, &str); 8] = [
(XmlOp::Concat, "concat"),
(XmlOp::Element, "element"),
(XmlOp::Forest, "forest"),
(XmlOp::Parse, "parse"),
(XmlOp::Pi, "pi"),
(XmlOp::Root, "root"),
(XmlOp::Serialize, "serialize"),
(XmlOp::Document, "document"),
];
const WRAPPERS: [(JsonWrapper, &str); 4] = [
(JsonWrapper::Unspec, "unspec"),
(JsonWrapper::None, "none"),
(JsonWrapper::Conditional, "conditional"),
(JsonWrapper::Unconditional, "unconditional"),
];
const BEHAVIORS: [(JsonBehaviorKind, &str); 9] = [
(JsonBehaviorKind::Null, "null"),
(JsonBehaviorKind::Error, "error"),
(JsonBehaviorKind::Empty, "empty"),
(JsonBehaviorKind::True, "true"),
(JsonBehaviorKind::False, "false"),
(JsonBehaviorKind::Unknown, "unknown"),
(JsonBehaviorKind::EmptyArray, "emptyarray"),
(JsonBehaviorKind::EmptyObject, "emptyobject"),
(JsonBehaviorKind::Default, "default"),
];
fn write_behavior(behavior: &JsonBehavior, out: &mut String) -> Result<()> {
let kind = BEHAVIORS.iter().find(|(k, _)| *k == behavior.kind).map_or("null", |(_, n)| n);
out.push_str(&format!("({kind} {} ", behavior.coerce));
match &behavior.expr {
Some(expr) => write_expr(expr, out)?,
None => out.push_str("<>"),
}
out.push(')');
Ok(())
}
fn behavior_of(item: &Item) -> Result<JsonBehavior> {
let [kind, coerce, expr] = list(item)? else { return Err(bad("a SQL/JSON behavior")) };
let kind = atom(kind)?;
let kind =
BEHAVIORS.iter().find(|(_, n)| *n == kind).ok_or_else(|| bad("a SQL/JSON behavior"))?.0;
let coerce = atom(coerce)? == "true";
let expr = match expr {
Item::Atom(empty) if empty == "<>" => None,
item => Some(expr_of(item)?),
};
Ok(JsonBehavior { kind, expr, coerce })
}
fn write_format(format: JsonFormat, out: &mut String) {
let kind = FORMAT_KINDS.iter().find(|(k, _)| *k == format.kind).map_or("default", |(_, n)| n);
let encoding =
ENCODINGS.iter().find(|(e, _)| *e == format.encoding).map_or("default", |(_, n)| n);
out.push_str(&format!(" :format {kind} :encoding {encoding}"));
}
fn format_of(f: &Fields<'_>) -> Result<JsonFormat> {
let kind = f.atom("format")?;
let encoding = f.atom("encoding")?;
let kind = FORMAT_KINDS.iter().find(|(_, n)| *n == kind).ok_or_else(|| bad("a JSON format"))?;
let encoding =
ENCODINGS.iter().find(|(_, n)| *n == encoding).ok_or_else(|| bad("a JSON encoding"))?;
Ok(JsonFormat { kind: kind.0, encoding: encoding.0 })
}
fn write_field(name: &str, expr: &Expr, out: &mut String) -> Result<()> {
out.push_str(&format!(" :{name} "));
write_expr(expr, out)
}
fn write_args(args: &[Expr], out: &mut String) -> Result<()> {
out.push_str(" :args (");
for (i, arg) in args.iter().enumerate() {
if i > 0 {
out.push(' ');
}
write_expr(arg, out)?;
}
out.push(')');
Ok(())
}
fn write_bounds(bounds: &[Option<Expr>], out: &mut String) -> Result<()> {
out.push('(');
for (i, bound) in bounds.iter().enumerate() {
if i > 0 {
out.push(' ');
}
match bound {
Some(bound) => write_expr(bound, out)?,
None => out.push_str("<>"),
}
}
out.push(')');
Ok(())
}
fn bounds_of(item: &Item) -> Result<Vec<Option<Expr>>> {
list(item)?
.iter()
.map(|item| match item {
Item::Atom(empty) if empty == "<>" => Ok(None),
item => expr_of(item).map(Some),
})
.collect()
}
const TESTS: [(BoolTest, &str); 6] = [
(BoolTest::IsTrue, "is_true"),
(BoolTest::IsNotTrue, "is_not_true"),
(BoolTest::IsFalse, "is_false"),
(BoolTest::IsNotFalse, "is_not_false"),
(BoolTest::IsUnknown, "is_unknown"),
(BoolTest::IsNotUnknown, "is_not_unknown"),
];
fn test_name(test: BoolTest) -> &'static str {
TESTS.iter().find(|(t, _)| *t == test).map_or("is_true", |(_, name)| name)
}
fn sql_value_name(value: SqlValue) -> (&'static str, Option<i32>) {
match value {
SqlValue::CurrentDate => ("current_date", None),
SqlValue::CurrentTime(p) => ("current_time", p),
SqlValue::CurrentTimestamp(p) => ("current_timestamp", p),
SqlValue::LocalTime(p) => ("localtime", p),
SqlValue::LocalTimestamp(p) => ("localtimestamp", p),
SqlValue::CurrentRole => ("current_role", None),
SqlValue::CurrentUser => ("current_user", None),
SqlValue::User => ("user", None),
SqlValue::SessionUser => ("session_user", None),
SqlValue::CurrentCatalog => ("current_catalog", None),
SqlValue::CurrentSchema => ("current_schema", None),
}
}
fn sql_value_of(name: &str, precision: Option<i32>) -> Option<SqlValue> {
Some(match name {
"current_date" => SqlValue::CurrentDate,
"current_time" => SqlValue::CurrentTime(precision),
"current_timestamp" => SqlValue::CurrentTimestamp(precision),
"localtime" => SqlValue::LocalTime(precision),
"localtimestamp" => SqlValue::LocalTimestamp(precision),
"current_role" => SqlValue::CurrentRole,
"current_user" => SqlValue::CurrentUser,
"user" => SqlValue::User,
"session_user" => SqlValue::SessionUser,
"current_catalog" => SqlValue::CurrentCatalog,
"current_schema" => SqlValue::CurrentSchema,
_ => return None,
})
}
fn hex(bytes: &[u8], out: &mut String) {
out.push('x');
for b in bytes {
out.push_str(&format!("{b:02x}"));
}
}
fn write_value(value: &Value, out: &mut String) {
match value {
Value::Null => out.push_str("null"),
Value::Bool(v) => out.push_str(&format!("(bool {v})")),
Value::Int2(v) => out.push_str(&format!("(int2 {v})")),
Value::Int4(v) => out.push_str(&format!("(int4 {v})")),
Value::Int8(v) => out.push_str(&format!("(int8 {v})")),
Value::Float4(v) => out.push_str(&format!("(float4 {})", v.to_bits())),
Value::Float8(v) => out.push_str(&format!("(float8 {})", v.to_bits())),
Value::Numeric(v) => {
let mut bytes = Vec::new();
rupg_types::numeric_send(v, &mut bytes);
out.push_str("(numeric ");
hex(&bytes, out);
out.push(')');
}
Value::Oid(v) => out.push_str(&format!("(oid {v})")),
Value::Char(v) => out.push_str(&format!("(char {v})")),
Value::Text(v) => {
out.push_str("(text \"");
for c in v.chars() {
if c == '"' || c == '\\' {
out.push('\\');
}
out.push(c);
}
out.push_str("\")");
}
Value::Bytea(v) => {
out.push_str("(bytea ");
hex(v, out);
out.push(')');
}
Value::Uuid(v) => {
out.push_str("(uuid ");
hex(v, out);
out.push(')');
}
Value::Date(v) => out.push_str(&format!("(date {v})")),
Value::Time(v) => out.push_str(&format!("(time {v})")),
Value::TimeTz(time, zone) => out.push_str(&format!("(timetz {time} {zone})")),
Value::Timestamp(v) => out.push_str(&format!("(timestamp {v})")),
Value::TimestampTz(v) => out.push_str(&format!("(timestamptz {v})")),
Value::Interval(v) => {
out.push_str(&format!("(interval {} {} {})", v.time, v.day, v.month));
}
Value::Inet(v) => {
let family = if v.family == NetFamily::V4 { 4 } else { 6 };
out.push_str(&format!("(inet {family} {} ", v.bits));
hex(v.bytes(), out);
out.push(')');
}
Value::Array(array) => {
out.push_str("(array (");
for (i, dim) in array.dims.iter().enumerate() {
if i > 0 {
out.push(' ');
}
out.push_str(&format!("({} {})", dim.len, dim.lower));
}
out.push_str(") (");
for (i, element) in array.values.iter().enumerate() {
if i > 0 {
out.push(' ');
}
match element {
Some(element) => write_value(element, out),
None => out.push_str("null"),
}
}
out.push_str("))");
}
Value::Record(record) => {
out.push_str("(record (");
for (i, ty) in record.types.iter().enumerate() {
if i > 0 {
out.push(' ');
}
out.push_str(&ty.to_string());
}
out.push_str(") (");
for (i, field) in record.values.iter().enumerate() {
if i > 0 {
out.push(' ');
}
write_value(field, out);
}
out.push_str("))");
}
Value::TsVector(lexemes) => {
let mut bytes = Vec::new();
rupg_types::tsvector_send(lexemes, &mut bytes);
out.push_str("(tsvector ");
hex(&bytes, out);
out.push(')');
}
Value::TsQuery(items) => {
let mut bytes = Vec::new();
rupg_types::tsquery_send(items, &mut bytes);
out.push_str("(tsquery ");
hex(&bytes, out);
out.push(')');
}
Value::JsonPath(path) => {
let mut bytes = Vec::new();
rupg_types::jsonpath_send(path, &mut bytes);
out.push_str("(jsonpath ");
hex(&bytes, out);
out.push(')');
}
Value::Range(range) => write_range(range, out),
Value::Multirange(ranges) => {
out.push_str("(multirange");
for range in ranges {
out.push(' ');
write_range(range, out);
}
out.push(')');
}
}
}
fn write_range(range: &Range, out: &mut String) {
out.push_str(&format!("(range {} ", range.flags));
write_value(&range.lower, out);
out.push(' ');
write_value(&range.upper, out);
out.push(')');
}
#[derive(Debug)]
enum Item {
Atom(String),
Str(String),
List(Vec<Item>),
Node(String, Vec<(String, Item)>),
}
struct Tokens<'a> {
text: &'a str,
at: usize,
}
enum Token {
Open(char),
Close(char),
Str(String),
Atom(String),
}
impl Tokens<'_> {
fn next(&mut self) -> Result<Option<Token>> {
let rest = &self.text[self.at..];
let trimmed = rest.trim_start();
self.at += rest.len() - trimmed.len();
let Some(c) = trimmed.chars().next() else { return Ok(None) };
match c {
'{' | '(' => {
self.at += 1;
Ok(Some(Token::Open(c)))
}
'}' | ')' => {
self.at += 1;
Ok(Some(Token::Close(c)))
}
'"' => {
let mut value = String::new();
let mut chars = trimmed[1..].char_indices();
loop {
let Some((i, c)) = chars.next() else { return Err(bad("an open string")) };
match c {
'"' => {
self.at += i + 2;
return Ok(Some(Token::Str(value)));
}
'\\' => match chars.next() {
Some((_, c)) => value.push(c),
None => return Err(bad("an open string")),
},
_ => value.push(c),
}
}
}
_ => {
let end = trimmed
.find(|c: char| c.is_whitespace() || "{}()\"".contains(c))
.unwrap_or(trimmed.len());
self.at += end;
Ok(Some(Token::Atom(trimmed[..end].to_string())))
}
}
}
fn item(&mut self) -> Result<Item> {
match self.next()? {
Some(token) => self.item_from(token),
None => Err(bad("an early end")),
}
}
fn item_from(&mut self, token: Token) -> Result<Item> {
match token {
Token::Atom(atom) => Ok(Item::Atom(atom)),
Token::Str(text) => Ok(Item::Str(text)),
Token::Close(_) => Err(bad("a close bracket with no open bracket")),
Token::Open('(') => {
let mut items = Vec::new();
loop {
match self.next()? {
Some(Token::Close(')')) => return Ok(Item::List(items)),
Some(token) => items.push(self.item_from(token)?),
None => return Err(bad("an open list")),
}
}
}
Token::Open(_) => {
let Some(Token::Atom(name)) = self.next()? else {
return Err(bad("a node with no name"));
};
let mut fields = Vec::new();
loop {
match self.next()? {
Some(Token::Close('}')) => return Ok(Item::Node(name, fields)),
Some(Token::Atom(field)) if field.starts_with(':') => {
fields.push((field[1..].to_string(), self.item()?));
}
_ => return Err(bad("a node field")),
}
}
}
}
}
}
struct Fields<'a> {
fields: &'a [(String, Item)],
}
impl<'a> Fields<'a> {
fn get(&self, name: &str) -> Result<&'a Item> {
self.fields
.iter()
.find(|(field, _)| field == name)
.map(|(_, item)| item)
.ok_or_else(|| bad(&format!("no field {name}")))
}
fn atom(&self, name: &str) -> Result<&'a str> {
atom(self.get(name)?)
}
fn number<T: std::str::FromStr>(&self, name: &str) -> Result<T> {
number(self.atom(name)?)
}
fn flag(&self, name: &str) -> Result<bool> {
match self.atom(name)? {
"true" => Ok(true),
"false" => Ok(false),
_ => Err(bad(&format!("field {name} is not true or false"))),
}
}
fn expr(&self, name: &str) -> Result<Box<Expr>> {
Ok(Box::new(expr_of(self.get(name)?)?))
}
fn exprs(&self, name: &str) -> Result<Vec<Expr>> {
list(self.get(name)?)?.iter().map(expr_of).collect()
}
}
fn atom(item: &Item) -> Result<&str> {
match item {
Item::Atom(atom) => Ok(atom),
_ => Err(bad("a list where a word must be")),
}
}
fn number<T: std::str::FromStr>(text: &str) -> Result<T> {
text.parse().map_err(|_| bad(&format!("{text} is not a number")))
}
fn list(item: &Item) -> Result<&[Item]> {
match item {
Item::List(items) => Ok(items),
_ => Err(bad("a word where a list must be")),
}
}
fn expr_of(item: &Item) -> Result<Expr> {
let Item::Node(name, fields) = item else { return Err(bad("no node")) };
let f = Fields { fields };
let ty = f.number("type")?;
let typmod = f.number("typmod")?;
let location: i64 = f.number("location")?;
let location = usize::try_from(location).ok();
let kind = match name.as_str() {
"CONST" => ExprKind::Const(value_of(f.get("value")?)?),
"FUNC" => {
let form = match f.get("form")? {
Item::Atom(form) if form == "call" => FuncForm::Call,
Item::Atom(form) if form == "explicit" => FuncForm::ExplicitCast,
Item::Atom(form) if form == "implicit" => FuncForm::ImplicitCast,
Item::Atom(form) if form == "sql" => FuncForm::SqlSyntax,
Item::List(parts) if parts.len() == 2 && atom(&parts[0])? == "operator" => {
FuncForm::Operator(number(atom(&parts[1])?)?)
}
_ => return Err(bad("a function form")),
};
ExprKind::Func(Func {
oid: f.number("oid")?,
args: f.exprs("args")?,
form,
variadic: f.flag("variadic")?,
retset: false,
})
}
"RELABEL" => ExprKind::Relabel(f.expr("arg")?, cast_form(&f)?),
"COERCEVIAIO" => ExprKind::CoerceViaIo(f.expr("arg")?, cast_form(&f)?),
"COERCETODOMAIN" => ExprKind::CoerceToDomain(f.expr("arg")?, cast_form(&f)?),
"COERCETODOMAINVALUE" => ExprKind::DomainValue,
"ARRAYCOERCE" => ExprKind::ArrayCoerce {
arg: f.expr("arg")?,
element: f.expr("element")?,
form: cast_form(&f)?,
},
"SUBSCRIPT" => ExprKind::Subscript(Box::new(Subscript {
container: *f.expr("container")?,
upper: bounds_of(f.get("upper")?)?,
lower: match f.get("lower")? {
Item::Atom(empty) if empty == "<>" => None,
item => Some(bounds_of(item)?),
},
assign: f.get("assign").ok().map(expr_of).transpose()?,
})),
"BOOL" => {
let op = match f.atom("op")? {
"and" => BoolOp::And,
"or" => BoolOp::Or,
"not" => BoolOp::Not,
_ => return Err(bad("a boolean operator")),
};
ExprKind::Bool(op, f.exprs("args")?)
}
"NULLTEST" => ExprKind::NullTest(f.expr("arg")?, f.flag("isnull")?),
"BOOLEANTEST" => {
let test = f.atom("test")?;
let Some((test, _)) = TESTS.iter().find(|(_, name)| *name == test) else {
return Err(bad("a boolean test"));
};
ExprKind::BooleanTest(f.expr("arg")?, *test)
}
"CASE" => {
let arg = match f.get("arg")? {
Item::Atom(empty) if empty == "<>" => None,
item => Some(Box::new(expr_of(item)?)),
};
let mut whens = Vec::new();
for when in list(f.get("whens")?)? {
let [when, then] = list(when)? else { return Err(bad("a WHEN of a CASE")) };
whens.push((expr_of(when)?, expr_of(then)?));
}
ExprKind::Case(Case { arg, whens, default: f.expr("default")? })
}
"CASETEST" => ExprKind::CaseTest,
"COALESCE" => ExprKind::Coalesce(f.exprs("args")?),
"ROW" => ExprKind::Row(f.exprs("args")?, cast_form(&f).unwrap_or(CastForm::Implicit)),
"JOINROW" => {
let text = |b: &str| {
String::from_utf8(bytes_of(b)?).map_err(|_| bad("a name that is not UTF-8"))
};
let alias = match f.atom("alias")? {
"<>" => None,
alias => Some(text(alias)?),
};
let names =
list(f.get("names")?)?.iter().map(|n| text(atom(n)?)).collect::<Result<_>>()?;
ExprKind::JoinRow(Box::new(JoinRow { alias, names, fields: f.exprs("args")? }))
}
"FIELDSTORE" => {
let numbers = |name: &str| -> Result<Vec<String>> {
list(f.get(name)?)?.iter().map(|n| atom(n).map(str::to_owned)).collect()
};
let fields =
numbers("fieldnums")?.iter().map(|n| number(n)).collect::<Result<Vec<usize>>>()?;
let types = numbers("types")?.iter().map(|n| number(n)).collect::<Result<_>>()?;
let values = f.exprs("args")?;
if values.len() != fields.len() {
return Err(bad("a FIELDSTORE with more fields than values"));
}
ExprKind::FieldStore(Box::new(FieldStore {
arg: *f.expr("arg")?,
fields: fields.into_iter().zip(values).collect(),
types,
}))
}
"ROWCOMPARE" => {
let kind = RowCompareKind::of_name(f.atom("kind")?)
.ok_or_else(|| bad("a kind of row comparison"))?;
let compares = list(f.get("compares")?)?
.iter()
.map(|c| number(atom(c)?))
.collect::<Result<_>>()?;
ExprKind::RowCompare(Box::new(RowCompare { kind, ops: f.exprs("args")?, compares }))
}
"MINMAX" => ExprKind::MinMax {
greatest: f.flag("greatest")?,
less: f.number("less")?,
args: f.exprs("args")?,
},
"NULLIF" => ExprKind::NullIf { equal: f.number("equal")?, args: f.exprs("args")? },
"DISTINCT" => ExprKind::Distinct {
equal: f.number("equal")?,
not: f.flag("not")?,
args: f.exprs("args")?,
},
"SCALARARRAYOP" => ExprKind::ScalarArrayOp {
func: f.number("func")?,
any: f.flag("any")?,
args: f.exprs("args")?,
},
"ARRAY" => ExprKind::Array {
element: f.number("element")?,
multidims: f.flag("multidims")?,
elements: f.exprs("args")?,
},
"SQLVALUE" => {
let precision = match f.atom("precision")? {
"<>" => None,
p => Some(number(p)?),
};
let value = sql_value_of(f.atom("function")?, precision)
.ok_or_else(|| bad("a SQL value function"))?;
ExprKind::SqlValue(value)
}
"VAR" => ExprKind::Var(Var {
relation: f.number("relation")?,
attnum: f.number("attnum")?,
levels_up: f.number("levelsup")?,
}),
"FIELDSELECT" => ExprKind::FieldSelect(f.expr("arg")?, f.number("field")?),
"NAMEDARG" => {
let name = String::from_utf8(bytes_of(f.atom("name")?)?)
.map_err(|_| bad("a name that is not UTF-8"))?;
let number = f.number("number")?;
ExprKind::NamedArg(Box::new(NamedArg { arg: *f.expr("arg")?, name, number }))
}
"JSONVALUEEXPR" => ExprKind::JsonValue(Box::new(JsonValue {
raw: *f.expr("raw")?,
formatted: *f.expr("formatted")?,
format: format_of(&f)?,
})),
"JSONCONSTRUCTOREXPR" => {
let kind = f.atom("kind")?;
let kind = CTOR_KINDS
.iter()
.find(|(_, n)| *n == kind)
.ok_or_else(|| bad("a SQL/JSON constructor"))?;
let returning = JsonReturning {
format: format_of(&f)?,
ty: f.number("rtype")?,
typmod: f.number("rtypmod")?,
};
let coercion = match f.get("coercion")? {
Item::Atom(empty) if empty == "<>" => None,
item => Some(expr_of(item)?),
};
ExprKind::JsonCtor(Box::new(JsonCtor {
kind: kind.0,
args: f.exprs("args")?,
func: None,
format: JsonFormat::default(),
returning,
unique: f.flag("unique")?,
absent_on_null: f.flag("absent")?,
coercion,
}))
}
"JSONISPREDICATE" => {
let item = f.atom("item")?;
let item = ITEM_TYPES
.iter()
.find(|(_, n)| *n == item)
.ok_or_else(|| bad("an IS JSON kind"))?;
ExprKind::IsJson(Box::new(IsJson {
expr: *f.expr("arg")?,
item: item.0,
unique: f.flag("unique")?,
base_type: f.number("basetype")?,
}))
}
"JSONEXPR" => {
let op = f.atom("op")?;
let op = JSON_OPS
.iter()
.find(|(_, n)| *n == op)
.ok_or_else(|| bad("a SQL/JSON function"))?;
let wrapper = f.atom("wrapper")?;
let wrapper = WRAPPERS
.iter()
.find(|(_, n)| *n == wrapper)
.ok_or_else(|| bad("a SQL/JSON wrapper"))?;
let returning = JsonReturning {
format: format_of(&f)?,
ty: f.number("rtype")?,
typmod: f.number("rtypmod")?,
};
let text = |b: &str| {
String::from_utf8(bytes_of(b)?).map_err(|_| bad("a name that is not UTF-8"))
};
let names: Vec<String> =
list(f.get("names")?)?.iter().map(|n| text(atom(n)?)).collect::<Result<_>>()?;
let values = f.exprs("args")?;
if names.len() != values.len() {
return Err(bad("a PASSING clause"));
}
let on_empty = match f.get("onempty")? {
Item::Atom(empty) if empty == "<>" => None,
item => Some(behavior_of(item)?),
};
ExprKind::JsonExpr(Box::new(JsonExpr {
op: op.0,
formatted: *f.expr("formatted")?,
path: *f.expr("path")?,
passing: names.into_iter().zip(values).collect(),
returning,
on_empty,
on_error: behavior_of(f.get("onerror")?)?,
wrapper: wrapper.0,
omit_quotes: f.flag("omitquotes")?,
json_coercion: f.flag("jsoncoercion")?,
io_coercion: f.flag("iocoercion")?,
column: None,
}))
}
"XMLEXPR" => {
let op = f.atom("op")?;
let op =
XML_OPS.iter().find(|(_, n)| *n == op).ok_or_else(|| bad("a SQL/XML function"))?;
let text = |b: &str| {
String::from_utf8(bytes_of(b)?).map_err(|_| bad("a name that is not UTF-8"))
};
let name = match f.atom("name")? {
"<>" => None,
name => Some(text(name)?),
};
let names: Vec<String> =
list(f.get("names")?)?.iter().map(|n| text(atom(n)?)).collect::<Result<_>>()?;
let values = f.exprs("named")?;
if names.len() != values.len() {
return Err(bad("the named arguments of a SQL/XML function"));
}
ExprKind::Xml(Box::new(XmlExpr {
op: op.0,
name,
named: names.into_iter().zip(values).collect(),
args: f.exprs("args")?,
document: f.flag("document")?,
indent: f.flag("indent")?,
target: (f.number("ttype")?, f.number("ttypmod")?),
}))
}
_ => return Err(bad(&format!("the node {name}"))),
};
Ok(Expr { kind, ty, typmod, location })
}
fn cast_form(f: &Fields<'_>) -> Result<CastForm> {
match f.atom("form")? {
"explicit" => Ok(CastForm::Explicit),
"implicit" => Ok(CastForm::Implicit),
_ => Err(bad("a cast form")),
}
}
fn bytes_of(text: &str) -> Result<Vec<u8>> {
let digits = text.strip_prefix('x').ok_or_else(|| bad("bytes with no x"))?;
if digits.len() % 2 != 0 {
return Err(bad("an odd number of hex digits"));
}
(0..digits.len())
.step_by(2)
.map(|i| u8::from_str_radix(&digits[i..i + 2], 16).map_err(|_| bad("a hex digit")))
.collect()
}
fn value_of(item: &Item) -> Result<Value> {
let parts = match item {
Item::Atom(null) if null == "null" => return Ok(Value::Null),
item => list(item)?,
};
let Some((kind, parts)) = parts.split_first() else { return Err(bad("an empty value")) };
let part = |i: usize| -> Result<&str> {
parts.get(i).ok_or_else(|| bad("a value with too few parts")).and_then(atom)
};
let value = match atom(kind)? {
"bool" => Value::Bool(part(0)? == "true"),
"int2" => Value::Int2(number(part(0)?)?),
"int4" => Value::Int4(number(part(0)?)?),
"int8" => Value::Int8(number(part(0)?)?),
"float4" => Value::Float4(f32::from_bits(number(part(0)?)?)),
"float8" => Value::Float8(f64::from_bits(number(part(0)?)?)),
"numeric" => {
let bytes = bytes_of(part(0)?)?;
let mut recv = Recv::new(&bytes);
let value = rupg_types::numeric_recv(&mut recv, -1)
.map_err(|_| bad("the bytes of a numeric"))?;
Value::Numeric(value)
}
"tsvector" => {
let bytes = bytes_of(part(0)?)?;
let value = rupg_types::tsvector_recv(&mut Recv::new(&bytes))
.map_err(|_| bad("the bytes of a tsvector"))?;
Value::TsVector(value)
}
"tsquery" => {
let bytes = bytes_of(part(0)?)?;
if bytes == [0; 4] {
Value::TsQuery(Vec::new())
} else {
let value = rupg_types::tsquery_recv(&mut Recv::new(&bytes))
.map_err(|_| bad("the bytes of a tsquery"))?;
Value::TsQuery(value)
}
}
"jsonpath" => {
let bytes = bytes_of(part(0)?)?;
let value = rupg_types::jsonpath_recv(&mut Recv::new(&bytes), &|_, _| Ok(()))
.map_err(|_| bad("the bytes of a jsonpath"))?;
Value::JsonPath(Box::new(value))
}
"oid" => Value::Oid(number(part(0)?)?),
"char" => Value::Char(number(part(0)?)?),
"text" => match parts.first() {
Some(Item::Str(text)) => Value::Text(text.clone()),
_ => return Err(bad("a text value with no string")),
},
"bytea" => Value::Bytea(bytes_of(part(0)?)?),
"uuid" => {
let bytes: [u8; 16] =
bytes_of(part(0)?)?.try_into().map_err(|_| bad("a uuid that is not 16 bytes"))?;
Value::Uuid(bytes)
}
"date" => Value::Date(number(part(0)?)?),
"time" => Value::Time(number(part(0)?)?),
"timetz" => Value::TimeTz(number(part(0)?)?, number(part(1)?)?),
"timestamp" => Value::Timestamp(number(part(0)?)?),
"timestamptz" => Value::TimestampTz(number(part(0)?)?),
"inet" => {
let family = match part(0)? {
"4" => NetFamily::V4,
"6" => NetFamily::V6,
_ => return Err(bad("an inet family that is not 4 or 6")),
};
let bytes = bytes_of(part(2)?)?;
if bytes.len() != family.size() {
return Err(bad("an inet address of the wrong size"));
}
let mut addr = [0u8; 16];
addr[..bytes.len()].copy_from_slice(&bytes);
Value::Inet(Inet { family, bits: number(part(1)?)?, addr })
}
"interval" => Value::Interval(Interval {
time: number(part(0)?)?,
day: number(part(1)?)?,
month: number(part(2)?)?,
}),
"array" => {
let [dims, values] = parts else { return Err(bad("an array value")) };
let mut array = Array::empty();
for dim in list(dims)? {
let [len, lower] = list(dim)? else { return Err(bad("an array dimension")) };
array
.dims
.push(ArrayDim { len: number(atom(len)?)?, lower: number(atom(lower)?)? });
}
for value in list(values)? {
array.values.push(match value_of(value)? {
Value::Null => None,
value => Some(value),
});
}
Value::Array(Box::new(array))
}
"record" => {
let [types, values] = parts else { return Err(bad("a record value")) };
let types = list(types)?.iter().map(|t| number(atom(t)?)).collect::<Result<_>>()?;
let values = list(values)?.iter().map(value_of).collect::<Result<_>>()?;
Value::Record(Box::new(Record::new(types, values)))
}
"range" => {
let [flags, lower, upper] = parts else { return Err(bad("a range value")) };
Value::Range(Box::new(Range {
flags: number(atom(flags)?)?,
lower: value_of(lower)?,
upper: value_of(upper)?,
}))
}
"multirange" => Value::Multirange(
parts
.iter()
.map(|part| match value_of(part)? {
Value::Range(range) => Ok(*range),
_ => Err(bad("a multirange value")),
})
.collect::<Result<_>>()?,
),
kind => return Err(bad(&format!("the value kind {kind}"))),
};
Ok(value)
}
#[cfg(test)]
mod tests {
use rupg_types::oid;
use super::*;
fn round_trip(expr: &Expr) -> String {
let text = write(expr).expect("write");
assert_eq!(&read(&text).expect("read"), expr, "{text}");
text
}
fn constant(value: Value, ty: u32) -> Expr {
Expr::constant(value, ty, -1, Some(3))
}
#[test]
fn values() {
let numeric = rupg_types::numeric_in("-12.3400", -1).expect("numeric");
let values = [
(Value::Null, oid::INT4),
(Value::Bool(true), oid::BOOL),
(Value::Int2(-7), oid::INT2),
(Value::Int4(i32::MIN), oid::INT4),
(Value::Int8(i64::MAX), oid::INT8),
(Value::Float4(-0.0), oid::FLOAT4),
(Value::Float8(0.1), oid::FLOAT8),
(Value::Float8(f64::INFINITY), oid::FLOAT8),
(Value::Numeric(numeric), oid::NUMERIC),
(Value::Oid(u32::MAX), oid::OID),
(Value::Char(0), oid::CHAR),
(Value::text("a \"b\" \\c\n{d} (e) é"), oid::TEXT),
(Value::text(""), oid::TEXT),
(Value::Bytea(vec![]), oid::BYTEA),
(Value::Bytea(vec![0, 255, 16]), oid::BYTEA),
(Value::Uuid([7; 16]), oid::UUID),
(Value::Date(-1), oid::DATE),
(Value::Time(86_400_000_000), oid::TIME),
(Value::TimeTz(1, -3600), oid::TIMETZ),
(Value::Timestamp(i64::MIN), oid::TIMESTAMP),
(Value::TimestampTz(5), oid::TIMESTAMPTZ),
(Value::Interval(Interval { time: -1, day: 2, month: -3 }), oid::INTERVAL),
(Value::Inet(rupg_types::inet_in("10.1.2.3/8", false).expect("inet")), oid::INET),
(Value::Inet(rupg_types::inet_in("2001:db8::/32", true).expect("cidr")), oid::CIDR),
(
Value::Range(Box::new(Range {
flags: rupg_types::RANGE_LB_INC,
lower: Value::Int4(1),
upper: Value::Int4(10),
})),
oid::INT4RANGE,
),
(Value::Range(Box::new(Range::empty())), oid::DATERANGE),
(Value::Multirange(Vec::new()), oid::INT4MULTIRANGE),
(Value::TsVector(Vec::new()), oid::TSVECTOR),
(
Value::TsVector(
rupg_types::tsvector_parse("'it''s':3A a b:1,2C").expect("tsvector"),
),
oid::TSVECTOR,
),
(Value::TsQuery(Vec::new()), oid::TSQUERY),
(
Value::TsQuery(
rupg_types::tsquery_parse("'it''s':A* & !(b | c <2> d)").expect("tsquery"),
),
oid::TSQUERY,
),
(
Value::JsonPath(Box::new(
rupg_types::jsonpath_parse(
"strict $.a[1 to last] ? (@ like_regex \"^b\" flag \"i\").**{2}",
false,
&|_, _| Ok(()),
)
.expect("jsonpath"),
)),
oid::JSONPATH,
),
(
Value::Multirange(vec![
Range {
flags: rupg_types::RANGE_LB_INF,
lower: Value::Null,
upper: Value::Int8(-4),
},
Range {
flags: rupg_types::RANGE_LB_INC,
lower: Value::Int8(7),
upper: Value::Int8(9),
},
]),
oid::INT8MULTIRANGE,
),
];
for (value, ty) in values {
round_trip(&constant(value, ty));
}
let mut array = Array::one(vec![Some(Value::Int4(1)), None, Some(Value::Int4(3))]);
array.dims[0].lower = -2;
round_trip(&constant(Value::Array(Box::new(array)), oid::INT4_ARRAY));
round_trip(&constant(Value::Array(Box::new(Array::empty())), oid::TEXT_ARRAY));
}
#[test]
fn nodes() {
let var =
|attnum| Expr::new(ExprKind::Var(Var { relation: 0, attnum, levels_up: 0 }), oid::INT4);
let call = Expr {
kind: ExprKind::Func(Func {
oid: 177,
args: vec![var(1), constant(Value::Int4(1), oid::INT4)],
form: FuncForm::Operator(551),
variadic: false,
retset: false,
}),
ty: oid::INT4,
typmod: -1,
location: Some(12),
};
let text = round_trip(&call);
assert_eq!(
text,
"{FUNC :type 23 :typmod -1 :location 12 :oid 177 :form (operator 551) :variadic false :args ({VAR :type 23 :typmod -1 :location -1 :relation 0 :attnum 1 :levelsup 0} {CONST :type 23 :typmod -1 :location 3 :value (int4 1)})}"
);
let test = Expr::new(ExprKind::NullTest(Box::new(var(2)), false), oid::BOOL);
let case = Expr::new(
ExprKind::Case(Case {
arg: Some(Box::new(var(1))),
whens: vec![(Expr::new(ExprKind::CaseTest, oid::INT4), var(2))],
default: Box::new(constant(Value::Null, oid::INT4)),
}),
oid::INT4,
);
let nodes = [
Expr::new(ExprKind::Bool(BoolOp::Not, vec![test.clone()]), oid::BOOL),
Expr::new(ExprKind::BooleanTest(Box::new(test), BoolTest::IsNotUnknown), oid::BOOL),
case,
Expr::new(
ExprKind::Case(Case { arg: None, whens: vec![], default: Box::new(var(1)) }),
oid::INT4,
),
Expr::new(ExprKind::Coalesce(vec![var(1), var(2)]), oid::INT4),
Expr::new(
ExprKind::NamedArg(Box::new(NamedArg {
arg: var(1),
name: "a b".to_owned(),
number: 2,
})),
oid::INT4,
),
Expr::new(
ExprKind::JoinRow(Box::new(JoinRow {
alias: Some("j k".to_owned()),
names: vec!["a".to_owned(), String::new()],
fields: vec![var(1), var(2)],
})),
oid::RECORD,
),
Expr::new(
ExprKind::JoinRow(Box::new(JoinRow { alias: None, names: vec![], fields: vec![] })),
oid::RECORD,
),
Expr::new(ExprKind::MinMax { greatest: true, less: 66, args: vec![var(1)] }, oid::INT4),
Expr::new(ExprKind::NullIf { equal: 65, args: vec![var(1), var(2)] }, oid::INT4),
Expr::new(
ExprKind::RowCompare(Box::new(RowCompare {
kind: RowCompareKind::LessEqual,
ops: vec![var(1), var(2)],
compares: vec![351, 2188],
})),
oid::BOOL,
),
Expr::new(
ExprKind::Distinct { equal: 65, not: true, args: vec![var(1), var(2)] },
oid::BOOL,
),
Expr::new(
ExprKind::ScalarArrayOp { func: 65, any: false, args: vec![var(1)] },
oid::BOOL,
),
Expr::new(
ExprKind::Array { element: oid::INT4, multidims: false, elements: vec![var(1)] },
oid::INT4_ARRAY,
),
Expr::new(ExprKind::SqlValue(SqlValue::CurrentTimestamp(Some(3))), oid::TIMESTAMPTZ),
Expr::new(ExprKind::SqlValue(SqlValue::CurrentUser), oid::NAME),
Expr::new(ExprKind::Relabel(Box::new(var(1)), CastForm::Explicit), oid::OID)
.at(Some(0)),
Expr::new(ExprKind::CoerceViaIo(Box::new(var(1)), CastForm::Implicit), oid::TEXT),
Expr {
kind: ExprKind::Func(Func {
oid: 1,
args: vec![],
form: FuncForm::ImplicitCast,
variadic: true,
retset: false,
}),
ty: oid::VARCHAR,
typmod: 14,
location: None,
},
];
for node in &nodes {
round_trip(node);
}
let text = write_list(&nodes[..2]).expect("write");
assert!(text.starts_with("({BOOL") && text.ends_with("})"), "{text}");
assert_eq!(read_list(&text).expect("read"), nodes[..2]);
assert_eq!(read_list("()").expect("read"), []);
}
#[test]
fn bad_forms() {
for text in [
"",
"{",
"{CONST}",
"{CONST :type 23 :typmod -1 :location -1 :value (int4 x)}",
"{VAR :type 23} x",
"(\"a)",
] {
assert!(read(text).is_err(), "{text}");
}
let param = Expr::new(ExprKind::Param(1), oid::INT4);
assert!(write(¶m).is_err());
}
}