1use crate::SQLError;
10use uqa_core::{
11 memory::{Produced, ProductionControl, ProductionVec},
12 DatumValue, DecimalValue, Value,
13};
14
15mod arrays;
16mod compression;
17mod fixed;
18mod malformed;
19mod records;
20
21pub fn contains_datum(value: &Value) -> bool {
23 match value {
24 Value::Datum(_) => true,
25 Value::Array(array) => array.elements().iter().any(contains_datum),
26 Value::List(values) => values.iter().any(contains_datum),
27 Value::Row(values) => values.iter().any(contains_datum),
28 Value::Record(fields) => fields.iter().any(|(_, value)| contains_datum(value)),
29 Value::Map(values) => values.values().any(contains_datum),
30 _ => false,
31 }
32}
33
34pub(in crate::expr) fn record_type_oid(
36 datum: &DatumValue,
37 control: &ProductionControl<'_>,
38) -> Result<u32, SQLError> {
39 let payload = payload(datum, control)?;
40 word(payload.bytes().get(4..).unwrap_or_default())
41 .ok_or_else(|| corrupt("invalid record header"))
42}
43
44pub(in crate::expr) fn array_shape(
46 datum: &DatumValue,
47 control: &ProductionControl<'_>,
48) -> Result<arrays::ArrayShape, SQLError> {
49 control.check()?;
50 let payload = payload(datum, control)?;
51 arrays::ArrayShape::read(payload.bytes())
52}
53
54#[cfg(test)]
55mod tests;
56
57pub fn type_name(datum: &DatumValue) -> &'static str {
58 match datum.type_oid() {
59 18 => "\"char\"",
60 19 => "name",
61 700 => "real",
62 1042 => "character",
63 1043 => "character varying",
64 oid => crate::catalog::type_metadata::catalog_type_name(i64::from(oid)),
65 }
66}
67
68pub fn read(datum: &DatumValue) -> Result<Value, SQLError> {
69 read_with_catalog(datum, None)
70}
71
72pub fn read_with_catalog(
74 datum: &DatumValue,
75 engine: Option<&dyn super::EngineHook>,
76) -> Result<Value, SQLError> {
77 read_with_catalog_and_control(datum, engine, &ProductionControl::uncontrolled())
78 .map(|value| value.into_uncontrolled().expect("ordinary datum read"))
79}
80
81pub fn copy_constant_field(value: &Value, ty: &crate::ColumnType) -> Result<Value, SQLError> {
83 let Value::Datum(datum) = value else {
84 return Ok(value.clone());
85 };
86 if crate::catalog::type_metadata::pg_type_len(ty) != -1 {
87 return Ok(value.clone());
88 }
89 let control = ProductionControl::uncontrolled();
90 let payload = payload(datum, &control)?;
91 let size = payload
92 .bytes()
93 .len()
94 .checked_add(4)
95 .and_then(|size| u32::try_from(size).ok())
96 .and_then(|size| size.checked_mul(4))
97 .ok_or_else(|| corrupt("invalid datum length"))?;
98 let mut bytes = size.to_le_bytes().to_vec();
99 bytes.extend_from_slice(payload.bytes());
100 Ok(Value::Datum(DatumValue::new(datum.type_oid(), 0, bytes)))
101}
102
103pub fn read_with_control(
104 datum: &DatumValue,
105 control: &ProductionControl<'_>,
106) -> Result<Produced<Value>, SQLError> {
107 read_with_catalog_and_control(datum, None, control)
108}
109
110pub fn read_with_catalog_and_control(
112 datum: &DatumValue,
113 engine: Option<&dyn super::EngineHook>,
114 control: &ProductionControl<'_>,
115) -> Result<Produced<Value>, SQLError> {
116 let catalog = engine.map(super::value_catalog::EngineValueCatalog);
117 read_with_value_catalog_and_control(
118 datum,
119 catalog
120 .as_ref()
121 .map(|catalog| catalog as &dyn super::SQLValueCatalog),
122 control,
123 )
124}
125
126pub fn read_with_value_catalog_and_control(
128 datum: &DatumValue,
129 catalog: Option<&dyn super::SQLValueCatalog>,
130 control: &ProductionControl<'_>,
131) -> Result<Produced<Value>, SQLError> {
132 control.check()?;
133 if let Some(value) = fixed::read(datum, control) {
134 return value;
135 }
136 if crate::catalog::type_metadata::builtin_scalar_type(datum.type_oid()).is_none()
137 && crate::catalog::type_metadata::builtin_array_element(datum.type_oid()).is_none()
138 {
139 if let Some(enums) = catalog {
140 if enums.enum_type_labels(datum.type_oid())?.is_some() {
141 let oid = enum_label_oid(datum)?;
142 return Ok(
143 control.retain_external_value(super::enums::enum_value_from_oid(
144 Some(enums),
145 oid,
146 )?)?,
147 );
148 }
149 }
150 }
151 let payload = payload(datum, control)?;
152 read_payload(datum.type_oid(), payload.bytes(), catalog, control)
153}
154
155pub(crate) fn enum_label_oid(datum: &DatumValue) -> Result<u32, SQLError> {
158 word(
159 datum
160 .bytes()
161 .get(datum.offset() as usize..)
162 .unwrap_or_default(),
163 )
164 .ok_or_else(|| corrupt("invalid datum length"))
165}
166
167pub(super) fn compare_jsonb_with_control(
168 left: &Value,
169 right: &Value,
170 control: &ProductionControl<'_>,
171) -> Result<Option<std::cmp::Ordering>, SQLError> {
172 let physical = |value: &Value| matches!(value, Value::Datum(datum) if datum.type_oid() == 3802);
173 let jsonb = |value: &Value| physical(value) || matches!(value, Value::JsonB(_));
174 if !(jsonb(left) && jsonb(right) && (physical(left) || physical(right))) {
175 return Ok(None);
176 }
177 let left_bytes = match left {
178 Value::Datum(datum) => Some(payload(datum, control)?),
179 _ => None,
180 };
181 let right_bytes = match right {
182 Value::Datum(datum) => Some(payload(datum, control)?),
183 _ => None,
184 };
185 let left = match left {
186 Value::JsonB(text) => super::json::JsonbInput::Text(text),
187 _ => super::json::JsonbInput::Bytes(left_bytes.as_ref().expect("physical JSONB").bytes()),
188 };
189 let right = match right {
190 Value::JsonB(text) => super::json::JsonbInput::Text(text),
191 _ => super::json::JsonbInput::Bytes(right_bytes.as_ref().expect("physical JSONB").bytes()),
192 };
193 super::json::compare_jsonb_datums_with_control(left, right, control).map(Some)
194}
195
196fn read_payload(
197 oid: u32,
198 bytes: &[u8],
199 catalog: Option<&dyn super::SQLValueCatalog>,
200 control: &ProductionControl<'_>,
201) -> Result<Produced<Value>, SQLError> {
202 if crate::catalog::type_metadata::builtin_array_element(oid).is_some() {
203 return arrays::read(bytes, catalog, control);
204 }
205 if crate::catalog::type_metadata::builtin_scalar_type(oid).is_none() {
206 if let Some(ty) = catalog
207 .map(|catalog| catalog.value_type_by_oid(oid))
208 .transpose()?
209 .flatten()
210 {
211 match ty {
212 crate::ColumnType::Array(_) => return arrays::read(bytes, catalog, control),
213 crate::ColumnType::Composite(_) => return records::read(bytes, catalog, control),
214 crate::ColumnType::Domain { base, .. } => {
215 return read_payload(
216 crate::catalog::type_metadata::pg_type_oid(&base) as u32,
217 bytes,
218 catalog,
219 control,
220 )
221 }
222 _ => {}
223 }
224 }
225 }
226 match oid {
227 17 => {
228 let mut output = ProductionVec::new(*control);
229 output.reserve(bytes.len())?;
230 for byte in bytes {
231 output.push_copy(*byte)?;
232 }
233 let (bytes, memory) = output.finish()?.into_parts();
234 Ok(control.finish(Value::Bytes(bytes), memory)?)
235 }
236 25 | 114 | 194 | 1042 | 1043 | 1790 => {
237 let text = std::str::from_utf8(bytes).map_err(|error| SQLError::Routine {
238 sqlstate: "22021".into(),
239 message: format!(
240 "invalid byte sequence for encoding \"UTF8\": 0x{:02x}",
241 bytes[error.valid_up_to()]
242 ),
243 })?;
244 let (text, memory) = control.copy_text(text)?.into_parts();
245 let value = match oid {
246 114 => Value::Json(text),
247 1042 => Value::FixedChar(text),
248 _ => Value::Str(text),
249 };
250 Ok(control.finish(value, memory)?)
251 }
252 3802 => {
253 let (text, memory) =
254 super::json::decode_jsonb_datum_with_control(bytes, control)?.into_parts();
255 Ok(control.finish(Value::JsonB(text), memory)?)
256 }
257 1700 => {
258 let text =
259 crate::catalog::node_tree::decode_numeric_datum_with_control(bytes, control)?;
260 let (number, memory) = DecimalValue::parse_with_control(&text, control)?
261 .ok_or_else(|| corrupt("invalid numeric datum"))?
262 .into_parts();
263 Ok(control.finish(Value::Decimal(number), memory)?)
264 }
265 oid => Err(SQLError::Unsupported(format!(
266 "physical datum output for type OID {oid}"
267 ))),
268 }
269}
270
271pub(super) fn binary_payload<'a>(
273 value: &'a Value,
274 control: &ProductionControl<'_>,
275) -> Result<Option<Payload<'a>>, SQLError> {
276 control.check()?;
277 match value {
278 Value::Bytes(bytes) => Ok(Some(Payload::Borrowed(bytes))),
279 Value::Datum(datum) if datum.type_oid() == 17 => payload(datum, control).map(Some),
280 _ => Ok(None),
281 }
282}
283
284pub(super) fn binary_length(
286 value: &Value,
287 control: &ProductionControl<'_>,
288) -> Result<Option<i64>, SQLError> {
289 control.check()?;
290 let datum = match value {
291 Value::Bytes(bytes) => return Ok(Some(bytes.len() as i64)),
292 Value::Datum(datum) if datum.type_oid() == 17 => datum,
293 _ => return Ok(None),
294 };
295 let bytes = datum
296 .bytes()
297 .get(datum.offset() as usize..)
298 .ok_or_else(|| corrupt("invalid datum length"))?;
299 let first = *bytes
300 .first()
301 .ok_or_else(|| corrupt("invalid datum length"))?;
302 let size = if first == 1 && bytes.get(1) == Some(&18) {
303 i64::from(
304 word(bytes.get(2..).unwrap_or_default())
305 .ok_or_else(|| corrupt("invalid datum length"))?,
306 ) - 4
307 } else if first & 1 != 0 {
308 i64::from(first >> 1) - 1
309 } else if first & 3 == 2 {
310 i64::from(
311 word(bytes.get(4..).unwrap_or_default())
312 .ok_or_else(|| corrupt("invalid datum length"))?
313 & 0x3fff_ffff,
314 )
315 } else {
316 i64::from(word(bytes).ok_or_else(|| corrupt("invalid datum length"))? >> 2) - 4
317 };
318 Ok(Some(size))
319}
320
321pub(super) enum Payload<'a> {
322 Borrowed(&'a [u8]),
323 Owned(Produced<Vec<u8>>),
324}
325
326impl Payload<'_> {
327 pub(super) fn bytes(&self) -> &[u8] {
328 match self {
329 Self::Borrowed(bytes) => bytes,
330 Self::Owned(bytes) => bytes,
331 }
332 }
333}
334
335fn payload<'a>(
336 datum: &'a DatumValue,
337 control: &ProductionControl<'_>,
338) -> Result<Payload<'a>, SQLError> {
339 let offset = datum.offset() as usize;
340 let bytes = datum
341 .bytes()
342 .get(offset..)
343 .ok_or_else(|| corrupt("invalid datum length"))?;
344 let first = *bytes
345 .first()
346 .ok_or_else(|| corrupt("invalid datum length"))?;
347 if first == 1 {
348 return Err(malformed::external(datum));
349 }
350 if first & 1 != 0 {
351 let length = usize::from(first >> 1);
352 return bytes
353 .get(1..length)
354 .map(Payload::Borrowed)
355 .ok_or_else(|| corrupt("invalid datum length"));
356 }
357 let header = word(bytes).ok_or_else(|| corrupt("invalid datum length"))?;
358 let length = (header >> 2) as usize;
359 if header & 3 == 2 {
360 let information = word(bytes.get(4..).unwrap_or_default()).unwrap_or(0);
361 let method = information >> 30;
362 let compressed = bytes
363 .get(8..length)
364 .ok_or_else(|| compression::corrupt(method))?;
365 return compression::decompress(
366 compressed,
367 (information & 0x3fff_ffff) as usize,
368 method,
369 control,
370 )
371 .map(Payload::Owned);
372 }
373 bytes
374 .get(4..length)
375 .map(Payload::Borrowed)
376 .ok_or_else(|| corrupt("invalid datum length"))
377}
378
379fn word(bytes: &[u8]) -> Option<u32> {
380 Some(u32::from_le_bytes(bytes.get(..4)?.try_into().ok()?))
381}
382
383fn corrupt(message: &str) -> SQLError {
384 SQLError::Routine {
385 sqlstate: "XX001".into(),
386 message: message.into(),
387 }
388}