uqa_sql/catalog/node_tree/
values.rs1pub(super) mod numeric;
10pub(super) mod temporal;
11
12use super::{invalid, Field, Node};
13use crate::catalog::type_metadata::{
14 pg_type_by_value, pg_type_collation_oid, pg_type_len, pg_type_modifier, pg_type_oid,
15};
16use crate::expr::composites::{
17 retained::{capture_type, Descriptors},
18 CompositeConstantSource, CompositeTypeCatalog,
19};
20use crate::{ColumnType, SQLError};
21use uqa_core::Value;
22
23pub(super) fn unknown_constant(value: &Value) -> Result<Node, SQLError> {
25 let bytes = match value {
26 Value::Null => Field::Null,
27 Value::Str(text) => {
28 let mut bytes = text.as_bytes().to_vec();
29 bytes.push(0);
30 Field::Datum {
31 length: bytes.len(),
32 bytes,
33 }
34 }
35 _ => return Err(invalid("unknown constant is not a string or NULL")),
36 };
37 Ok(Node::new(
38 "CONST",
39 [
40 ("consttype", 705.into()),
41 ("consttypmod", (-1).into()),
42 ("constcollid", 0.into()),
43 ("constlen", (-2).into()),
44 ("constbyval", false.into()),
45 ("constisnull", matches!(value, Value::Null).into()),
46 ("location", (-1).into()),
47 ("constvalue", bytes),
48 ],
49 ))
50}
51
52pub(super) fn unknown_text(node: &Node) -> Result<&str, SQLError> {
53 let Field::Datum { length, bytes } = node.field("constvalue")? else {
54 return Err(invalid("unknown constant has no Datum"));
55 };
56 if node.integer("constlen")? != -2
57 || node.boolean("constbyval")?
58 || *length != bytes.len()
59 || bytes.last() != Some(&0)
60 || bytes[..bytes.len() - 1].contains(&0)
61 {
62 return Err(invalid("invalid unknown constant Datum"));
63 }
64 std::str::from_utf8(&bytes[..bytes.len() - 1])
65 .map_err(|_| invalid("invalid UTF-8 in unknown Datum"))
66}
67
68pub(super) fn constant(value: &Value, ty: &ColumnType) -> Result<Node, SQLError> {
69 constant_with_source(value, ty, None, None)
70}
71
72pub fn constant_with_source(
74 value: &Value,
75 ty: &ColumnType,
76 source: Option<&CompositeConstantSource>,
77 catalog: Option<&dyn CompositeTypeCatalog>,
78) -> Result<Node, SQLError> {
79 let value = source.map_or(value, |source| &source.value);
80 let bytes = if matches!(value, Value::Null) {
81 Field::Null
82 } else {
83 let descriptors = if let Some(source) = source {
84 source
85 .descriptors
86 .iter()
87 .map(|descriptor| (descriptor.type_oid, std::sync::Arc::new(descriptor.clone())))
88 .collect()
89 } else if matches!(value, Value::Datum(_)) {
90 Descriptors::new()
91 } else {
92 let mut descriptors = Descriptors::new();
93 capture_type(ty, catalog, &mut descriptors)?;
94 descriptors
95 };
96 datum(value, ty, &descriptors)?
97 };
98 Ok(Node::new(
99 "CONST",
100 [
101 ("consttype", pg_type_oid(ty).into()),
102 ("consttypmod", pg_type_modifier(ty).into()),
103 ("constcollid", pg_type_collation_oid(ty).into()),
104 ("constlen", pg_type_len(ty).into()),
105 ("constbyval", pg_type_by_value(ty).into()),
106 ("constisnull", matches!(value, Value::Null).into()),
107 ("location", (-1).into()),
108 ("constvalue", bytes),
109 ],
110 ))
111}
112
113fn datum(value: &Value, ty: &ColumnType, descriptors: &Descriptors) -> Result<Field, SQLError> {
114 let mut representation = ty;
115 while let ColumnType::Domain { base, .. } = representation {
116 representation = base;
117 }
118 let mut bytes = match (value, representation) {
119 (Value::Bool(value), ColumnType::Boolean) => vec![u8::from(*value)],
120 (
121 Value::Int(value),
122 ColumnType::SmallInteger
123 | ColumnType::Integer
124 | ColumnType::BigInteger
125 | ColumnType::Oid
126 | ColumnType::Xid
127 | ColumnType::Regclass
128 | ColumnType::Regtype
129 | ColumnType::Regcollation
130 | ColumnType::Regnamespace
131 | ColumnType::Regrole
132 | ColumnType::Regproc
133 | ColumnType::Regprocedure,
134 ) => value.to_le_bytes().to_vec(),
135 (Value::Float(value), ColumnType::Real) => (*value as f32).to_le_bytes().to_vec(),
136 (Value::Float(value), ColumnType::DoublePrecision) => value.to_le_bytes().to_vec(),
137 (Value::Enum(value), ColumnType::Enum(_)) => value
138 .label_oid()
139 .ok_or_else(|| invalid("enum constant has no retained label identity"))?
140 .to_le_bytes()
141 .to_vec(),
142 (Value::Str(_), ColumnType::InternalChar) => {
143 vec![crate::expr::internal_char::value_byte(value)
144 .ok_or_else(|| SQLError::Internal("invalid internal char carrier".into()))?]
145 }
146 (Value::Str(value) | Value::FixedChar(value), ColumnType::Name) => {
147 let mut bytes = value.as_bytes().to_vec();
148 if bytes.len() >= 64 {
149 return Err(SQLError::Internal("unclipped name Datum".into()));
150 }
151 bytes.resize(64, 0);
152 bytes
153 }
154 (
155 Value::Str(value) | Value::FixedChar(value),
156 ColumnType::Text
157 | ColumnType::Varchar(_)
158 | ColumnType::Bpchar
159 | ColumnType::Character(_)
160 | ColumnType::RefCursor,
161 ) => varlena(value.as_bytes())?,
162 (Value::Bytes(value), ColumnType::Bytea) => varlena(value)?,
163 (Value::Str(value), ColumnType::Uuid) => crate::expr::parse_uuid_bytes(value)?.to_vec(),
164 (Value::Json(value), ColumnType::Json) => varlena(value.as_bytes())?,
165 (Value::JsonB(value), ColumnType::JsonB) => {
166 varlena(&crate::expr::encode_jsonb_datum(value)?)?
167 }
168 (Value::Array(array), ColumnType::Array(element)) => {
169 let payload =
170 crate::expr::composites::retained::array_payload(array, element, descriptors)
171 .ok_or_else(|| {
172 SQLError::Unsupported(format!(
173 "catalog array Datum encoding for {}",
174 ty.sql_name()
175 ))
176 })?;
177 varlena(&payload)?
178 }
179 (Value::Record(fields), ColumnType::Composite(reference)) => {
180 crate::expr::composites::retained::record_bytes(fields, reference.oid, descriptors)
181 .ok_or_else(|| invalid("composite constant has no retained tuple layout"))?
182 }
183 (Value::Datum(_), ColumnType::Array(_) | ColumnType::Composite(_)) => {
184 let Value::Datum(owned) = crate::expr::datums::copy_constant_field(value, ty)? else {
185 return Err(SQLError::Internal(
186 "physical catalog constant changed carrier".into(),
187 ));
188 };
189 owned.bytes().to_vec()
190 }
191 (Value::Decimal(value), ColumnType::Numeric { .. }) => varlena(&numeric::encode(value)?)?,
192 (Value::Temporal(value), _) => temporal::encode(value, pg_type_oid(representation))?,
193 _ => {
194 return Err(SQLError::Unsupported(format!(
195 "catalog Datum encoding for {}",
196 ty.sql_name()
197 )))
198 }
199 };
200 let length = if pg_type_by_value(ty) {
201 bytes.resize(8, 0);
202 usize::try_from(pg_type_len(ty))
203 .map_err(|_| SQLError::Internal("invalid by-value Datum length".into()))?
204 } else {
205 bytes.len()
206 };
207 Ok(Field::Datum { length, bytes })
208}
209
210pub(super) fn scalar_text(node: &Node) -> Result<String, SQLError> {
211 let oid = u32::try_from(node.integer("consttype")?)
212 .map_err(|_| invalid("invalid scalar constant type"))?;
213 let ty = crate::catalog::type_metadata::builtin_scalar_type(oid)
214 .ok_or_else(|| invalid("unknown scalar constant type"))?;
215 if node.boolean("constbyval")? || node.integer("constlen")? != pg_type_len(ty) {
216 return Err(invalid("invalid scalar constant layout"));
217 }
218 let Field::Datum { length, bytes } = node.field("constvalue")? else {
219 return Err(invalid("constant has no Datum"));
220 };
221 if *ty == ColumnType::Uuid {
222 if *length != 16 {
223 return Err(invalid("invalid UUID Datum length"));
224 }
225 let bytes = bytes
226 .as_slice()
227 .try_into()
228 .map_err(|_| invalid("invalid UUID Datum length"))?;
229 return Ok(crate::expr::format_uuid(bytes));
230 }
231 let payload = varlena_payload(*length, bytes)?;
232 let control = uqa_core::memory::ProductionControl::uncontrolled();
233 match ty {
234 ColumnType::Bytea => crate::expr::bytea_to_string_with_control(payload, &control)
235 .map(|text| text.into_uncontrolled().expect("ordinary bytea text")),
236 ColumnType::Json => std::str::from_utf8(payload)
237 .map(str::to_owned)
238 .map_err(|_| invalid("invalid JSON Datum encoding")),
239 ColumnType::JsonB => crate::expr::decode_jsonb_datum_with_control(payload, &control)
240 .map(|text| text.into_uncontrolled().expect("ordinary JSONB text")),
241 _ => Err(invalid("unsupported scalar constant type")),
242 }
243}
244
245pub(super) fn varlena_payload(length: usize, bytes: &[u8]) -> Result<&[u8], SQLError> {
246 let header = bytes
247 .get(..4)
248 .and_then(|bytes| bytes.try_into().ok())
249 .map(u32::from_le_bytes)
250 .ok_or_else(|| invalid("truncated varlena Datum"))?;
251 if bytes.len() != length || header & 3 != 0 || usize::try_from(header >> 2).ok() != Some(length)
252 {
253 return Err(invalid("invalid varlena Datum length"));
254 }
255 Ok(&bytes[4..])
256}
257
258pub(super) fn catalog_text(
259 node: &Node,
260 catalog: Option<&dyn crate::expr::SQLValueCatalog>,
261) -> Result<Option<String>, SQLError> {
262 let oid = u32::try_from(node.integer("consttype")?)
263 .map_err(|_| invalid("invalid catalog constant type"))?;
264 let ty = if let Some(element) = crate::catalog::type_metadata::builtin_array_element(oid) {
265 ColumnType::Array(Box::new(element.clone()))
266 } else if let Some(ty) = catalog
267 .map(|catalog| catalog.value_type_by_oid(oid))
268 .transpose()?
269 .flatten()
270 {
271 ty
272 } else {
273 return Ok(None);
274 };
275 if node.boolean("constbyval")? != pg_type_by_value(&ty)
276 || node.integer("constlen")? != pg_type_len(&ty)
277 {
278 return Err(invalid("invalid catalog constant layout"));
279 }
280 let Field::Datum { length, bytes } = node.field("constvalue")? else {
281 return Err(invalid("constant has no Datum"));
282 };
283 if pg_type_len(&ty) == -1 {
284 varlena_payload(*length, bytes)?;
285 } else if i64::try_from(*length).ok() != Some(pg_type_len(&ty))
286 || (!pg_type_by_value(&ty) && bytes.len() != *length)
287 {
288 return Err(invalid("invalid catalog constant length"));
289 }
290 let value = Value::Datum(uqa_core::DatumValue::new(oid, 0, bytes.clone()));
291 crate::result::format_postgres_text_with_catalog(&value, &ty, catalog).map(Some)
292}
293
294fn varlena(payload: &[u8]) -> Result<Vec<u8>, SQLError> {
295 let length = payload
296 .len()
297 .checked_add(4)
298 .and_then(|length| u32::try_from(length).ok())
299 .filter(|length| *length <= u32::MAX >> 2)
300 .ok_or_else(|| SQLError::Routine {
301 sqlstate: "54000".into(),
302 message: "catalog Datum is too large".into(),
303 })?;
304 Ok((length << 2)
305 .to_le_bytes()
306 .into_iter()
307 .chain(payload.iter().copied())
308 .collect())
309}