Skip to main content

pgrx_sql_entity_graph/
section.rs

1//LICENSE Portions Copyright 2019-2021 ZomboDB, LLC.
2//LICENSE
3//LICENSE Portions Copyright 2021-2023 Technology Concepts & Design, Inc.
4//LICENSE
5//LICENSE Portions Copyright 2023-2023 PgCentral Foundation, Inc. <contact@pgcentral.org>
6//LICENSE
7//LICENSE All rights reserved.
8//LICENSE
9//LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file.
10
11/*!
12Wire format support for the embedded pgrx schema linker section.
13
14This module owns the bytes we embed into the extension shared object and later
15decode from `cargo-pgrx schema`. That format is intentionally compact and
16const-friendly because the producer side runs through macro expansion into
17`static` linker-section entries, not through a normal runtime serializer.
18
19Why this is a bespoke binary format instead of JSON:
20
21- the producer must be able to size each entry at compile time
22- the producer must emit raw bytes into a `static` item placed in a custom
23  linker section
24- the metadata we serialize is exposed to macro expansion through associated
25  consts such as `SqlTranslatable::{ARGUMENT_SQL, RETURN_SQL}`
26
27JSON would make the decode side simpler, but it would not remove the hard part.
28We would still need a handwritten const-time encoder, plus escaping logic for
29strings, SQL snippets, paths, and error payloads. That is not a clear win over
30the current length-prefixed binary layout.
31
32So the current design choice is:
33
34- keep the section format binary while the producer is const-time linker-section
35  emission
36- keep the wire format logic centralized in this module instead of spreading it
37  across macro emitters
38
39If we ever move away from linker-section bytes and toward a normal build-time
40manifest artifact, that would be the right time to re-evaluate a serde-based
41format such as JSON.
42*/
43
44use crate::aggregate::entity::{AggregateTypeEntity, PgAggregateEntity};
45use crate::aggregate::{FinalizeModify, ParallelOption};
46use crate::extension_sql::entity::{
47    ExtensionSqlEntity, SqlDeclaredEntity, SqlDeclaredFunctionEntityData, SqlDeclaredTypeEntityData,
48};
49use crate::extern_args::ExternArgs;
50use crate::metadata::{
51    ArgumentError, FunctionMetadataTypeEntity, Returns, ReturnsError, ReturnsRef, SqlArrayMapping,
52    SqlArrayMappingRef, SqlMapping, SqlMappingRef, TypeOrigin,
53};
54use crate::pg_extern::entity::{
55    PgCastEntity, PgExternArgumentEntity, PgExternEntity, PgExternReturnEntity,
56    PgExternReturnEntityIteratedItem, PgOperatorEntity,
57};
58use crate::pg_trigger::entity::PgTriggerEntity;
59use crate::positioning_ref::PositioningRef;
60use crate::postgres_enum::entity::PostgresEnumEntity;
61use crate::postgres_hash::entity::PostgresHashEntity;
62use crate::postgres_ord::entity::PostgresOrdEntity;
63use crate::postgres_type::entity::PostgresTypeEntity;
64use crate::schema::entity::SchemaEntity;
65use crate::to_sql::entity::ToSqlConfigEntity;
66use crate::{SqlGraphEntity, UsedTypeEntity};
67use eyre::{Result, bail, eyre};
68
69pub const ELF_SECTION_NAME: &str = ".pgrxsc";
70pub const MACHO_SEGMENT_NAME: &str = "__DATA";
71pub const MACHO_SECTION_NAME: &str = "__pgrxsc";
72pub const MACHO_SECTION_PATH: &str = "__DATA,__pgrxsc";
73
74// PE/COFF section names are capped at 8 bytes, which is why the cross-platform
75// names here are the shortened `pgrxsc` forms instead of `.pgrx_schema`.
76const LEGACY_ELF_SECTION_NAME: &str = ".pgrx_schema";
77const LEGACY_MACHO_SECTION_NAME: &str = "__pgrx_schema";
78const LEGACY_MACHO_SECTION_PATH: &str = "__DATA,__pgrx_schema";
79
80pub const ENTITY_SENTINEL: u8 = 0;
81pub const ENTITY_SCHEMA: u8 = 1;
82pub const ENTITY_CUSTOM_SQL: u8 = 2;
83pub const ENTITY_FUNCTION: u8 = 3;
84pub const ENTITY_TYPE: u8 = 4;
85pub const ENTITY_ENUM: u8 = 5;
86pub const ENTITY_ORD: u8 = 6;
87pub const ENTITY_HASH: u8 = 7;
88pub const ENTITY_AGGREGATE: u8 = 8;
89pub const ENTITY_TRIGGER: u8 = 9;
90
91pub const POSITIONING_REF_FULL_PATH: u8 = 1;
92pub const POSITIONING_REF_NAME: u8 = 2;
93
94pub const SQL_DECLARED_TYPE: u8 = 1;
95pub const SQL_DECLARED_ENUM: u8 = 2;
96pub const SQL_DECLARED_FUNCTION: u8 = 3;
97
98pub const SQL_MAPPING_AS: u8 = 1;
99pub const SQL_MAPPING_ARRAY: u8 = 2;
100pub const SQL_MAPPING_NUMERIC: u8 = 3;
101pub const SQL_MAPPING_COMPOSITE: u8 = 4;
102pub const SQL_MAPPING_SKIP: u8 = 5;
103
104pub const RETURNS_ONE: u8 = 1;
105pub const RETURNS_SET_OF: u8 = 2;
106pub const RETURNS_TABLE: u8 = 3;
107
108pub const ARG_ERROR_SET_OF: u8 = 1;
109pub const ARG_ERROR_TABLE: u8 = 2;
110pub const ARG_ERROR_NESTED_ARRAY: u8 = 3;
111pub const ARG_ERROR_BARE_U8: u8 = 4;
112pub const ARG_ERROR_SKIP_IN_ARRAY: u8 = 5;
113pub const ARG_ERROR_DATUM: u8 = 6;
114pub const ARG_ERROR_NOT_VALID: u8 = 7;
115pub const RESULT_OK: u8 = 1;
116pub const RESULT_ERR: u8 = 2;
117
118pub const TYPE_ORIGIN_THIS_EXTENSION: u8 = 1;
119pub const TYPE_ORIGIN_EXTERNAL: u8 = 2;
120
121pub const RETURNS_ERROR_NESTED_SET_OF: u8 = 1;
122pub const RETURNS_ERROR_NESTED_TABLE: u8 = 2;
123pub const RETURNS_ERROR_NESTED_ARRAY: u8 = 3;
124pub const RETURNS_ERROR_SET_OF_CONTAINING_TABLE: u8 = 4;
125pub const RETURNS_ERROR_TABLE_CONTAINING_SET_OF: u8 = 5;
126pub const RETURNS_ERROR_SET_OF_IN_ARRAY: u8 = 6;
127pub const RETURNS_ERROR_TABLE_IN_ARRAY: u8 = 7;
128pub const RETURNS_ERROR_BARE_U8: u8 = 8;
129pub const RETURNS_ERROR_SKIP_IN_ARRAY: u8 = 9;
130pub const RETURNS_ERROR_DATUM: u8 = 10;
131
132pub const EXTERN_ARG_CREATE_OR_REPLACE: u8 = 1;
133pub const EXTERN_ARG_IMMUTABLE: u8 = 2;
134pub const EXTERN_ARG_STRICT: u8 = 3;
135pub const EXTERN_ARG_STABLE: u8 = 4;
136pub const EXTERN_ARG_VOLATILE: u8 = 5;
137pub const EXTERN_ARG_RAW: u8 = 6;
138pub const EXTERN_ARG_NO_GUARD: u8 = 7;
139pub const EXTERN_ARG_SECURITY_DEFINER: u8 = 8;
140pub const EXTERN_ARG_SECURITY_INVOKER: u8 = 9;
141pub const EXTERN_ARG_PARALLEL_SAFE: u8 = 10;
142pub const EXTERN_ARG_PARALLEL_UNSAFE: u8 = 11;
143pub const EXTERN_ARG_PARALLEL_RESTRICTED: u8 = 12;
144pub const EXTERN_ARG_SHOULD_PANIC: u8 = 13;
145pub const EXTERN_ARG_SCHEMA: u8 = 14;
146pub const EXTERN_ARG_SUPPORT: u8 = 15;
147pub const EXTERN_ARG_NAME: u8 = 16;
148pub const EXTERN_ARG_COST: u8 = 17;
149pub const EXTERN_ARG_REQUIRES: u8 = 18;
150
151pub const OPERATOR_CAST_DEFAULT: u8 = 1;
152pub const OPERATOR_CAST_ASSIGNMENT: u8 = 2;
153pub const OPERATOR_CAST_IMPLICIT: u8 = 3;
154
155pub const EXTERN_RET_NONE: u8 = 1;
156pub const EXTERN_RET_TYPE: u8 = 2;
157pub const EXTERN_RET_SET_OF: u8 = 3;
158pub const EXTERN_RET_ITERATED: u8 = 4;
159pub const EXTERN_RET_TRIGGER: u8 = 5;
160
161pub const AGGREGATE_FINALIZE_READ_ONLY: u8 = 1;
162pub const AGGREGATE_FINALIZE_SHAREABLE: u8 = 2;
163pub const AGGREGATE_FINALIZE_READ_WRITE: u8 = 3;
164
165pub const AGGREGATE_PARALLEL_SAFE: u8 = 1;
166pub const AGGREGATE_PARALLEL_RESTRICTED: u8 = 2;
167pub const AGGREGATE_PARALLEL_UNSAFE: u8 = 3;
168
169pub const SECTION_SENTINEL_MAGIC: &str = "pgrx";
170pub const SECTION_SENTINEL_PAYLOAD_LEN: usize = u8_len() + str_len(SECTION_SENTINEL_MAGIC);
171pub const SECTION_SENTINEL_ENTRY_LEN: usize = u32_len() + SECTION_SENTINEL_PAYLOAD_LEN;
172pub const SECTION_SENTINEL_PAYLOAD: [u8; SECTION_SENTINEL_PAYLOAD_LEN] =
173    EntryWriter::<SECTION_SENTINEL_PAYLOAD_LEN>::new()
174        .u8(ENTITY_SENTINEL)
175        .str(SECTION_SENTINEL_MAGIC)
176        .finish();
177
178pub fn is_schema_section_name(name: &str) -> bool {
179    name == ELF_SECTION_NAME
180        || name == MACHO_SECTION_NAME
181        || name == MACHO_SECTION_PATH
182        || name == LEGACY_ELF_SECTION_NAME
183        || name == LEGACY_MACHO_SECTION_NAME
184        || name == LEGACY_MACHO_SECTION_PATH
185}
186
187#[macro_export]
188macro_rules! __pgrx_schema_entry {
189    ($name:ident, $len:expr, $payload:expr) => {
190        #[doc(hidden)]
191        #[used]
192        #[allow(non_upper_case_globals)]
193        #[cfg_attr(target_os = "macos", unsafe(link_section = "__DATA,__pgrxsc"))]
194        #[cfg_attr(not(target_os = "macos"), unsafe(link_section = ".pgrxsc"))]
195        static $name: [u8; $len] = $payload;
196    };
197}
198
199pub const fn bytes_len(len: usize) -> usize {
200    4 + len
201}
202
203pub const fn str_len(value: &str) -> usize {
204    bytes_len(value.len())
205}
206
207pub const fn bool_len() -> usize {
208    1
209}
210
211pub const fn u8_len() -> usize {
212    1
213}
214
215pub const fn u32_len() -> usize {
216    4
217}
218
219pub const fn opt_len(inner: Option<usize>) -> usize {
220    1 + match inner {
221        Some(inner) => inner,
222        None => 0,
223    }
224}
225
226pub const fn list_len(items: &[usize]) -> usize {
227    let mut total = 4;
228    let mut i = 0;
229    while i < items.len() {
230        total += items[i];
231        i += 1;
232    }
233    total
234}
235
236pub const fn sql_mapping_len(value: SqlMappingRef) -> usize {
237    match value {
238        SqlMappingRef::As(sql) => u8_len() + str_len(sql),
239        SqlMappingRef::Numeric { .. } => u8_len() + bool_len() + u32_len() + bool_len() + u32_len(),
240        SqlMappingRef::Composite => u8_len(),
241        SqlMappingRef::Array(value) => u8_len() + sql_array_mapping_len(value),
242        SqlMappingRef::Skip => u8_len(),
243    }
244}
245
246pub const fn sql_array_mapping_len(value: SqlArrayMappingRef) -> usize {
247    match value {
248        SqlArrayMappingRef::As(sql) => u8_len() + str_len(sql),
249        SqlArrayMappingRef::Numeric { .. } => {
250            u8_len() + bool_len() + u32_len() + bool_len() + u32_len()
251        }
252        SqlArrayMappingRef::Composite => u8_len(),
253    }
254}
255
256pub const fn returns_len(value: ReturnsRef) -> usize {
257    match value {
258        ReturnsRef::One(mapping) | ReturnsRef::SetOf(mapping) => {
259            u8_len() + sql_mapping_len(mapping)
260        }
261        ReturnsRef::Table(items) => {
262            let mut total = u8_len() + u32_len();
263            let mut i = 0;
264            while i < items.len() {
265                total += sql_mapping_len(items[i]);
266                i += 1;
267            }
268            total
269        }
270    }
271}
272
273pub const fn argument_error_len(value: ArgumentError) -> usize {
274    match value {
275        ArgumentError::NotValidAsArgument(name) => u8_len() + str_len(name),
276        _ => u8_len(),
277    }
278}
279
280pub const fn argument_sql_len(value: Result<SqlMappingRef, ArgumentError>) -> usize {
281    u8_len()
282        + match value {
283            Ok(mapping) => sql_mapping_len(mapping),
284            Err(err) => argument_error_len(err),
285        }
286}
287
288pub const fn returns_error_len(_value: ReturnsError) -> usize {
289    u8_len()
290}
291
292pub const fn return_sql_len(value: Result<ReturnsRef, ReturnsError>) -> usize {
293    u8_len()
294        + match value {
295            Ok(returns) => returns_len(returns),
296            Err(err) => returns_error_len(err),
297        }
298}
299
300pub const fn function_metadata_type_len(
301    resolution: Option<&str>,
302    argument_sql: Result<SqlMappingRef, ArgumentError>,
303    return_sql: Result<ReturnsRef, ReturnsError>,
304) -> usize {
305    bool_len()
306        + match resolution {
307            Some(type_ident) => str_len(type_ident) + u8_len(),
308            None => 0,
309        }
310        + argument_sql_len(argument_sql)
311        + return_sql_len(return_sql)
312}
313
314#[derive(Clone, Copy)]
315pub struct EntryWriter<const N: usize> {
316    buf: [u8; N],
317    pos: usize,
318}
319
320impl<const N: usize> EntryWriter<N> {
321    pub const fn new() -> Self {
322        Self { buf: [0; N], pos: 0 }
323    }
324
325    pub const fn u8(mut self, value: u8) -> Self {
326        self.buf[self.pos] = value;
327        self.pos += 1;
328        self
329    }
330
331    pub const fn bool(self, value: bool) -> Self {
332        self.u8(if value { 1 } else { 0 })
333    }
334
335    pub const fn u32(self, value: u32) -> Self {
336        let [b0, b1, b2, b3] = value.to_le_bytes();
337        self.u8(b0).u8(b1).u8(b2).u8(b3)
338    }
339
340    pub const fn type_origin(self, value: TypeOrigin) -> Self {
341        self.u8(match value {
342            TypeOrigin::ThisExtension => TYPE_ORIGIN_THIS_EXTENSION,
343            TypeOrigin::External => TYPE_ORIGIN_EXTERNAL,
344        })
345    }
346
347    pub const fn bytes(mut self, value: &[u8]) -> Self {
348        let mut i = 0;
349        while i < value.len() {
350            self.buf[self.pos] = value[i];
351            self.pos += 1;
352            i += 1;
353        }
354        self
355    }
356
357    pub const fn str(self, value: &str) -> Self {
358        self.u32(value.len() as u32).bytes(value.as_bytes())
359    }
360
361    pub const fn sql_mapping(self, value: SqlMappingRef) -> Self {
362        match value {
363            SqlMappingRef::As(sql) => self.u8(SQL_MAPPING_AS).str(sql),
364            SqlMappingRef::Numeric { precision, scale } => self
365                .u8(SQL_MAPPING_NUMERIC)
366                .bool(precision.is_some())
367                .u32(match precision {
368                    Some(value) => value,
369                    None => 0,
370                })
371                .bool(scale.is_some())
372                .u32(match scale {
373                    Some(value) => value,
374                    None => 0,
375                }),
376            SqlMappingRef::Composite => self.u8(SQL_MAPPING_COMPOSITE),
377            SqlMappingRef::Array(value) => self.u8(SQL_MAPPING_ARRAY).sql_array_mapping(value),
378            SqlMappingRef::Skip => self.u8(SQL_MAPPING_SKIP),
379        }
380    }
381
382    pub const fn sql_array_mapping(self, value: SqlArrayMappingRef) -> Self {
383        match value {
384            SqlArrayMappingRef::As(sql) => self.u8(SQL_MAPPING_AS).str(sql),
385            SqlArrayMappingRef::Numeric { precision, scale } => self
386                .u8(SQL_MAPPING_NUMERIC)
387                .bool(precision.is_some())
388                .u32(match precision {
389                    Some(value) => value,
390                    None => 0,
391                })
392                .bool(scale.is_some())
393                .u32(match scale {
394                    Some(value) => value,
395                    None => 0,
396                }),
397            SqlArrayMappingRef::Composite => self.u8(SQL_MAPPING_COMPOSITE),
398        }
399    }
400
401    pub const fn returns(self, value: ReturnsRef) -> Self {
402        match value {
403            ReturnsRef::One(mapping) => self.u8(RETURNS_ONE).sql_mapping(mapping),
404            ReturnsRef::SetOf(mapping) => self.u8(RETURNS_SET_OF).sql_mapping(mapping),
405            ReturnsRef::Table(items) => {
406                let mut writer = self.u8(RETURNS_TABLE).u32(items.len() as u32);
407                let mut i = 0;
408                while i < items.len() {
409                    writer = writer.sql_mapping(items[i]);
410                    i += 1;
411                }
412                writer
413            }
414        }
415    }
416
417    pub const fn argument_error(self, value: ArgumentError) -> Self {
418        match value {
419            ArgumentError::SetOf => self.u8(ARG_ERROR_SET_OF),
420            ArgumentError::Table => self.u8(ARG_ERROR_TABLE),
421            ArgumentError::NestedArray => self.u8(ARG_ERROR_NESTED_ARRAY),
422            ArgumentError::BareU8 => self.u8(ARG_ERROR_BARE_U8),
423            ArgumentError::SkipInArray => self.u8(ARG_ERROR_SKIP_IN_ARRAY),
424            ArgumentError::Datum => self.u8(ARG_ERROR_DATUM),
425            ArgumentError::NotValidAsArgument(name) => self.u8(ARG_ERROR_NOT_VALID).str(name),
426        }
427    }
428
429    pub const fn argument_sql(self, value: Result<SqlMappingRef, ArgumentError>) -> Self {
430        match value {
431            Ok(mapping) => self.u8(RESULT_OK).sql_mapping(mapping),
432            Err(err) => self.u8(RESULT_ERR).argument_error(err),
433        }
434    }
435
436    pub const fn returns_error(self, value: ReturnsError) -> Self {
437        match value {
438            ReturnsError::NestedSetOf => self.u8(RETURNS_ERROR_NESTED_SET_OF),
439            ReturnsError::NestedTable => self.u8(RETURNS_ERROR_NESTED_TABLE),
440            ReturnsError::NestedArray => self.u8(RETURNS_ERROR_NESTED_ARRAY),
441            ReturnsError::SetOfContainingTable => self.u8(RETURNS_ERROR_SET_OF_CONTAINING_TABLE),
442            ReturnsError::TableContainingSetOf => self.u8(RETURNS_ERROR_TABLE_CONTAINING_SET_OF),
443            ReturnsError::SetOfInArray => self.u8(RETURNS_ERROR_SET_OF_IN_ARRAY),
444            ReturnsError::TableInArray => self.u8(RETURNS_ERROR_TABLE_IN_ARRAY),
445            ReturnsError::BareU8 => self.u8(RETURNS_ERROR_BARE_U8),
446            ReturnsError::SkipInArray => self.u8(RETURNS_ERROR_SKIP_IN_ARRAY),
447            ReturnsError::Datum => self.u8(RETURNS_ERROR_DATUM),
448        }
449    }
450
451    pub const fn return_sql(self, value: Result<ReturnsRef, ReturnsError>) -> Self {
452        match value {
453            Ok(returns) => self.u8(RESULT_OK).returns(returns),
454            Err(err) => self.u8(RESULT_ERR).returns_error(err),
455        }
456    }
457
458    pub const fn function_metadata_type(
459        self,
460        resolution: Option<(&str, TypeOrigin)>,
461        argument_sql: Result<SqlMappingRef, ArgumentError>,
462        return_sql: Result<ReturnsRef, ReturnsError>,
463    ) -> Self {
464        let writer = match resolution {
465            Some((type_ident, type_origin)) => {
466                self.bool(true).str(type_ident).type_origin(type_origin)
467            }
468            None => self.bool(false),
469        };
470        writer.argument_sql(argument_sql).return_sql(return_sql)
471    }
472
473    pub const fn finish(self) -> [u8; N] {
474        if self.pos != N {
475            panic!("pgrx schema entry length mismatch");
476        }
477        self.buf
478    }
479}
480
481impl<const N: usize> Default for EntryWriter<N> {
482    fn default() -> Self {
483        Self::new()
484    }
485}
486
487pub const fn schema_section_sentinel_entry() -> [u8; SECTION_SENTINEL_ENTRY_LEN] {
488    EntryWriter::<SECTION_SENTINEL_ENTRY_LEN>::new()
489        .u32(SECTION_SENTINEL_PAYLOAD_LEN as u32)
490        .bytes(&SECTION_SENTINEL_PAYLOAD)
491        .finish()
492}
493
494pub struct EntryReader<'a> {
495    bytes: &'a [u8],
496    pos: usize,
497}
498
499impl<'a> EntryReader<'a> {
500    pub fn new(bytes: &'a [u8]) -> Self {
501        Self { bytes, pos: 0 }
502    }
503
504    pub fn is_empty(&self) -> bool {
505        self.pos >= self.bytes.len()
506    }
507
508    pub fn remaining(&self) -> usize {
509        self.bytes.len().saturating_sub(self.pos)
510    }
511
512    pub fn read_u8(&mut self) -> Result<u8> {
513        if self.remaining() < 1 {
514            bail!("unexpected end of schema entry");
515        }
516        let value = self.bytes[self.pos];
517        self.pos += 1;
518        Ok(value)
519    }
520
521    pub fn read_bool(&mut self) -> Result<bool> {
522        match self.read_u8()? {
523            0 => Ok(false),
524            1 => Ok(true),
525            other => Err(eyre!("invalid bool tag in schema entry: {other}")),
526        }
527    }
528
529    pub fn read_u32(&mut self) -> Result<u32> {
530        if self.remaining() < 4 {
531            bail!("unexpected end of schema entry");
532        }
533        let start = self.pos;
534        self.pos += 4;
535        Ok(u32::from_le_bytes(
536            self.bytes[start..self.pos].try_into().expect("checked slice length"),
537        ))
538    }
539
540    pub fn read_bytes(&mut self) -> Result<&'a [u8]> {
541        let len = self.read_u32()? as usize;
542        if self.remaining() < len {
543            bail!("unexpected end of schema entry");
544        }
545        let start = self.pos;
546        self.pos += len;
547        Ok(&self.bytes[start..self.pos])
548    }
549
550    pub fn read_string(&mut self) -> Result<String> {
551        let bytes = self.read_bytes()?;
552        Ok(std::str::from_utf8(bytes)
553            .map_err(|err| eyre!("schema entry contained invalid utf8: {err}"))?
554            .to_owned())
555    }
556
557    pub fn read_str(&mut self) -> Result<&'a str> {
558        let bytes = self.read_bytes()?;
559        std::str::from_utf8(bytes)
560            .map_err(|err| eyre!("schema entry contained invalid utf8: {err}"))
561    }
562
563    pub fn read_option_str(&mut self) -> Result<Option<&'a str>> {
564        if self.read_bool()? { Ok(Some(self.read_str()?)) } else { Ok(None) }
565    }
566
567    pub fn read_option_string(&mut self) -> Result<Option<String>> {
568        if self.read_bool()? { Ok(Some(self.read_string()?)) } else { Ok(None) }
569    }
570
571    pub fn read_sql_mapping_owned(&mut self) -> Result<SqlMapping> {
572        match self.read_u8()? {
573            SQL_MAPPING_AS => Ok(SqlMapping::As(self.read_string()?)),
574            SQL_MAPPING_ARRAY => Ok(SqlMapping::Array(self.read_sql_array_mapping_owned()?)),
575            SQL_MAPPING_NUMERIC => {
576                let has_precision = self.read_bool()?;
577                let precision = self.read_u32()?;
578                let has_scale = self.read_bool()?;
579                let scale = self.read_u32()?;
580                Ok(SqlMapping::As(crate::metadata::numeric_sql_string(
581                    has_precision.then_some(precision),
582                    has_scale.then_some(scale),
583                )))
584            }
585            SQL_MAPPING_COMPOSITE => Ok(SqlMapping::Composite),
586            SQL_MAPPING_SKIP => Ok(SqlMapping::Skip),
587            other => Err(eyre!("invalid sql mapping tag in schema entry: {other}")),
588        }
589    }
590
591    pub fn read_sql_array_mapping_owned(&mut self) -> Result<SqlArrayMapping> {
592        match self.read_u8()? {
593            SQL_MAPPING_AS => Ok(SqlArrayMapping::As(self.read_string()?)),
594            SQL_MAPPING_NUMERIC => {
595                let has_precision = self.read_bool()?;
596                let precision = self.read_u32()?;
597                let has_scale = self.read_bool()?;
598                let scale = self.read_u32()?;
599                Ok(SqlArrayMapping::As(crate::metadata::numeric_sql_string(
600                    has_precision.then_some(precision),
601                    has_scale.then_some(scale),
602                )))
603            }
604            SQL_MAPPING_COMPOSITE => Ok(SqlArrayMapping::Composite),
605            other => Err(eyre!("invalid sql array mapping tag in schema entry: {other}")),
606        }
607    }
608
609    pub fn read_returns_owned(&mut self) -> Result<Returns> {
610        match self.read_u8()? {
611            RETURNS_ONE => Ok(Returns::One(self.read_sql_mapping_owned()?)),
612            RETURNS_SET_OF => Ok(Returns::SetOf(self.read_sql_mapping_owned()?)),
613            RETURNS_TABLE => {
614                let count = self.read_u32()? as usize;
615                let mut items = Vec::with_capacity(count);
616                for _ in 0..count {
617                    items.push(self.read_sql_mapping_owned()?);
618                }
619                Ok(Returns::Table(items))
620            }
621            other => Err(eyre!("invalid returns tag in schema entry: {other}")),
622        }
623    }
624
625    pub fn read_argument_sql_owned(&mut self) -> Result<Result<SqlMapping, ArgumentError>> {
626        match self.read_u8()? {
627            RESULT_OK => Ok(Ok(self.read_sql_mapping_owned()?)),
628            RESULT_ERR => Ok(Err(self.read_argument_error()?)),
629            other => Err(eyre!("invalid argument sql tag in schema entry: {other}")),
630        }
631    }
632
633    pub fn read_return_sql_owned(&mut self) -> Result<Result<Returns, ReturnsError>> {
634        match self.read_u8()? {
635            RESULT_OK => Ok(Ok(self.read_returns_owned()?)),
636            RESULT_ERR => Ok(Err(self.read_returns_error()?)),
637            other => Err(eyre!("invalid return sql tag in schema entry: {other}")),
638        }
639    }
640
641    pub fn read_argument_error(&mut self) -> Result<ArgumentError> {
642        match self.read_u8()? {
643            ARG_ERROR_SET_OF => Ok(ArgumentError::SetOf),
644            ARG_ERROR_TABLE => Ok(ArgumentError::Table),
645            ARG_ERROR_NESTED_ARRAY => Ok(ArgumentError::NestedArray),
646            ARG_ERROR_BARE_U8 => Ok(ArgumentError::BareU8),
647            ARG_ERROR_SKIP_IN_ARRAY => Ok(ArgumentError::SkipInArray),
648            ARG_ERROR_DATUM => Ok(ArgumentError::Datum),
649            ARG_ERROR_NOT_VALID => {
650                // ArgumentError::NotValidAsArgument requires &'static str for const compatibility.
651                // This is the one remaining leak — a tiny string for a rare error variant.
652                Ok(ArgumentError::NotValidAsArgument(Box::leak(
653                    self.read_string()?.into_boxed_str(),
654                )))
655            }
656            other => Err(eyre!("invalid argument error tag in schema entry: {other}")),
657        }
658    }
659
660    pub fn read_returns_error(&mut self) -> Result<ReturnsError> {
661        match self.read_u8()? {
662            RETURNS_ERROR_NESTED_SET_OF => Ok(ReturnsError::NestedSetOf),
663            RETURNS_ERROR_NESTED_TABLE => Ok(ReturnsError::NestedTable),
664            RETURNS_ERROR_NESTED_ARRAY => Ok(ReturnsError::NestedArray),
665            RETURNS_ERROR_SET_OF_CONTAINING_TABLE => Ok(ReturnsError::SetOfContainingTable),
666            RETURNS_ERROR_TABLE_CONTAINING_SET_OF => Ok(ReturnsError::TableContainingSetOf),
667            RETURNS_ERROR_SET_OF_IN_ARRAY => Ok(ReturnsError::SetOfInArray),
668            RETURNS_ERROR_TABLE_IN_ARRAY => Ok(ReturnsError::TableInArray),
669            RETURNS_ERROR_BARE_U8 => Ok(ReturnsError::BareU8),
670            RETURNS_ERROR_SKIP_IN_ARRAY => Ok(ReturnsError::SkipInArray),
671            RETURNS_ERROR_DATUM => Ok(ReturnsError::Datum),
672            other => Err(eyre!("invalid returns error tag in schema entry: {other}")),
673        }
674    }
675
676    pub fn read_positioning_ref(&mut self) -> Result<PositioningRef> {
677        match self.read_u8()? {
678            POSITIONING_REF_FULL_PATH => Ok(PositioningRef::FullPath(self.read_string()?)),
679            POSITIONING_REF_NAME => Ok(PositioningRef::Name(self.read_string()?)),
680            other => Err(eyre!("invalid positioning ref tag in schema entry: {other}")),
681        }
682    }
683
684    pub fn read_to_sql_config(&mut self) -> Result<ToSqlConfigEntity<'a>> {
685        let enabled = self.read_bool()?;
686        let content = self.read_option_str()?;
687        Ok(ToSqlConfigEntity { enabled, content })
688    }
689
690    pub fn read_function_metadata_type(&mut self) -> Result<FunctionMetadataTypeEntity<'a>> {
691        let resolution = if self.read_bool()? {
692            Some(crate::metadata::FunctionMetadataTypeResolutionEntity {
693                type_ident: self.read_str()?,
694                type_origin: self.read_type_origin()?,
695            })
696        } else {
697            None
698        };
699        Ok(FunctionMetadataTypeEntity {
700            resolution,
701            argument_sql: self.read_argument_sql_owned()?,
702            return_sql: self.read_return_sql_owned()?,
703        })
704    }
705
706    pub fn read_type_origin(&mut self) -> Result<TypeOrigin> {
707        match self.read_u8()? {
708            TYPE_ORIGIN_THIS_EXTENSION => Ok(TypeOrigin::ThisExtension),
709            TYPE_ORIGIN_EXTERNAL => Ok(TypeOrigin::External),
710            value => Err(eyre!("Unknown type origin discriminator `{value}`")),
711        }
712    }
713
714    pub fn read_used_type(&mut self) -> Result<UsedTypeEntity<'a>> {
715        Ok(UsedTypeEntity {
716            ty_source: self.read_str()?,
717            full_path: self.read_str()?,
718            composite_type: self.read_option_str()?,
719            variadic: self.read_bool()?,
720            default: self.read_option_str()?,
721            optional: self.read_bool()?,
722            metadata: self.read_function_metadata_type()?,
723        })
724    }
725
726    pub fn read_pg_extern_argument(&mut self) -> Result<PgExternArgumentEntity<'a>> {
727        Ok(PgExternArgumentEntity { pattern: self.read_str()?, used_ty: self.read_used_type()? })
728    }
729
730    pub fn read_pg_extern_return(&mut self) -> Result<PgExternReturnEntity<'a>> {
731        match self.read_u8()? {
732            EXTERN_RET_NONE => Ok(PgExternReturnEntity::None),
733            EXTERN_RET_TYPE => Ok(PgExternReturnEntity::Type { ty: self.read_used_type()? }),
734            EXTERN_RET_SET_OF => Ok(PgExternReturnEntity::SetOf { ty: self.read_used_type()? }),
735            EXTERN_RET_ITERATED => {
736                let count = self.read_u32()? as usize;
737                let mut tys = Vec::with_capacity(count);
738                for _ in 0..count {
739                    tys.push(PgExternReturnEntityIteratedItem {
740                        name: self.read_option_str()?,
741                        ty: self.read_used_type()?,
742                    });
743                }
744                Ok(PgExternReturnEntity::Iterated { tys })
745            }
746            EXTERN_RET_TRIGGER => Ok(PgExternReturnEntity::Trigger),
747            other => Err(eyre!("invalid extern return tag in schema entry: {other}")),
748        }
749    }
750
751    pub fn read_extern_arg(&mut self) -> Result<ExternArgs> {
752        match self.read_u8()? {
753            EXTERN_ARG_CREATE_OR_REPLACE => Ok(ExternArgs::CreateOrReplace),
754            EXTERN_ARG_IMMUTABLE => Ok(ExternArgs::Immutable),
755            EXTERN_ARG_STRICT => Ok(ExternArgs::Strict),
756            EXTERN_ARG_STABLE => Ok(ExternArgs::Stable),
757            EXTERN_ARG_VOLATILE => Ok(ExternArgs::Volatile),
758            EXTERN_ARG_RAW => Ok(ExternArgs::Raw),
759            EXTERN_ARG_NO_GUARD => Ok(ExternArgs::NoGuard),
760            EXTERN_ARG_SECURITY_DEFINER => Ok(ExternArgs::SecurityDefiner),
761            EXTERN_ARG_SECURITY_INVOKER => Ok(ExternArgs::SecurityInvoker),
762            EXTERN_ARG_PARALLEL_SAFE => Ok(ExternArgs::ParallelSafe),
763            EXTERN_ARG_PARALLEL_UNSAFE => Ok(ExternArgs::ParallelUnsafe),
764            EXTERN_ARG_PARALLEL_RESTRICTED => Ok(ExternArgs::ParallelRestricted),
765            EXTERN_ARG_SHOULD_PANIC => Ok(ExternArgs::ShouldPanic(self.read_string()?)),
766            EXTERN_ARG_SCHEMA => Ok(ExternArgs::Schema(self.read_string()?)),
767            EXTERN_ARG_SUPPORT => Ok(ExternArgs::Support(self.read_positioning_ref()?)),
768            EXTERN_ARG_NAME => Ok(ExternArgs::Name(self.read_string()?)),
769            EXTERN_ARG_COST => Ok(ExternArgs::Cost(self.read_string()?)),
770            EXTERN_ARG_REQUIRES => {
771                let count = self.read_u32()? as usize;
772                let mut items = Vec::with_capacity(count);
773                for _ in 0..count {
774                    items.push(self.read_positioning_ref()?);
775                }
776                Ok(ExternArgs::Requires(items))
777            }
778            other => Err(eyre!("invalid extern arg tag in schema entry: {other}")),
779        }
780    }
781
782    pub fn read_search_path(&mut self) -> Result<Option<Vec<&'a str>>> {
783        if !self.read_bool()? {
784            return Ok(None);
785        }
786        let count = self.read_u32()? as usize;
787        let mut values = Vec::with_capacity(count);
788        for _ in 0..count {
789            values.push(self.read_str()?);
790        }
791        Ok(Some(values))
792    }
793
794    pub fn read_operator(&mut self) -> Result<PgOperatorEntity<'a>> {
795        Ok(PgOperatorEntity {
796            opname: self.read_option_str()?,
797            commutator: self.read_option_str()?,
798            negator: self.read_option_str()?,
799            restrict: self.read_option_str()?,
800            join: self.read_option_str()?,
801            hashes: self.read_bool()?,
802            merges: self.read_bool()?,
803        })
804    }
805
806    pub fn read_cast(&mut self) -> Result<PgCastEntity> {
807        match self.read_u8()? {
808            OPERATOR_CAST_DEFAULT => Ok(PgCastEntity::Default),
809            OPERATOR_CAST_ASSIGNMENT => Ok(PgCastEntity::Assignment),
810            OPERATOR_CAST_IMPLICIT => Ok(PgCastEntity::Implicit),
811            other => Err(eyre!("invalid cast tag in schema entry: {other}")),
812        }
813    }
814
815    pub fn read_sql_declared(&mut self) -> Result<SqlDeclaredEntity> {
816        let kind = self.read_u8()?;
817        let name = self.read_string()?;
818
819        match kind {
820            SQL_DECLARED_TYPE | SQL_DECLARED_ENUM => {
821                let type_ident = self.read_string()?;
822                let sql = match self.read_argument_sql_owned()? {
823                    Ok(crate::metadata::SqlMapping::As(sql)) => sql,
824                    Ok(other) => {
825                        bail!("invalid SQL declaration mapping in schema entry: {other:?}")
826                    }
827                    Err(err) => return Err(err.into()),
828                };
829                let data = SqlDeclaredTypeEntityData { sql, name, type_ident };
830                Ok(match kind {
831                    SQL_DECLARED_TYPE => SqlDeclaredEntity::Type(data),
832                    SQL_DECLARED_ENUM => SqlDeclaredEntity::Enum(data),
833                    _ => unreachable!(),
834                })
835            }
836            SQL_DECLARED_FUNCTION => {
837                let sql = name
838                    .split("::")
839                    .last()
840                    .ok_or_else(|| eyre!("function declaration was missing a name"))?
841                    .to_owned();
842                Ok(SqlDeclaredEntity::Function(SqlDeclaredFunctionEntityData { sql, name }))
843            }
844            other => Err(eyre!("invalid SQL declared tag in schema entry: {other}")),
845        }
846    }
847
848    pub fn read_aggregate_type(&mut self) -> Result<AggregateTypeEntity<'a>> {
849        Ok(AggregateTypeEntity { name: self.read_option_str()?, used_ty: self.read_used_type()? })
850    }
851
852    pub fn read_aggregate_type_list(&mut self) -> Result<Vec<AggregateTypeEntity<'a>>> {
853        let count = self.read_u32()? as usize;
854        let mut items = Vec::with_capacity(count);
855        for _ in 0..count {
856            items.push(self.read_aggregate_type()?);
857        }
858        Ok(items)
859    }
860
861    pub fn read_finalize_modify(&mut self) -> Result<FinalizeModify> {
862        match self.read_u8()? {
863            AGGREGATE_FINALIZE_READ_ONLY => Ok(FinalizeModify::ReadOnly),
864            AGGREGATE_FINALIZE_SHAREABLE => Ok(FinalizeModify::Shareable),
865            AGGREGATE_FINALIZE_READ_WRITE => Ok(FinalizeModify::ReadWrite),
866            other => Err(eyre!("invalid finalize modify tag in schema entry: {other}")),
867        }
868    }
869
870    pub fn read_parallel_option(&mut self) -> Result<ParallelOption> {
871        match self.read_u8()? {
872            AGGREGATE_PARALLEL_SAFE => Ok(ParallelOption::Safe),
873            AGGREGATE_PARALLEL_RESTRICTED => Ok(ParallelOption::Restricted),
874            AGGREGATE_PARALLEL_UNSAFE => Ok(ParallelOption::Unsafe),
875            other => Err(eyre!("invalid parallel option tag in schema entry: {other}")),
876        }
877    }
878
879    pub fn finish(&self) -> Result<()> {
880        if self.is_empty() {
881            Ok(())
882        } else {
883            Err(eyre!("schema entry had {} trailing bytes", self.remaining()))
884        }
885    }
886}
887
888pub fn entry_payloads(section: &[u8]) -> Result<Vec<&[u8]>> {
889    let mut out = Vec::new();
890    let mut reader = EntryReader::new(section);
891    while !reader.is_empty() {
892        if reader.remaining() < 4 {
893            if section[reader.pos..].iter().all(|byte| *byte == 0) {
894                break;
895            }
896            bail!("invalid trailing bytes in pgrx schema section");
897        }
898
899        let len = reader.read_u32()? as usize;
900        if len == 0 {
901            if section[reader.pos..].iter().all(|byte| *byte == 0) {
902                break;
903            }
904            bail!("invalid zero-length pgrx schema entry");
905        }
906        if reader.remaining() < len {
907            bail!("invalid pgrx schema section payload length");
908        }
909        let start = reader.pos;
910        reader.pos += len;
911        out.push(&section[start..reader.pos]);
912    }
913    Ok(out)
914}
915
916pub fn decode_entity<'a>(payload: &'a [u8]) -> Result<SqlGraphEntity<'a>> {
917    let mut reader = EntryReader::new(payload);
918    let entity = match reader.read_u8()? {
919        ENTITY_SCHEMA => SqlGraphEntity::Schema(SchemaEntity {
920            module_path: reader.read_str()?,
921            name: reader.read_str()?,
922            file: reader.read_str()?,
923            line: reader.read_u32()?,
924        }),
925        ENTITY_CUSTOM_SQL => {
926            let sql = reader.read_str()?;
927            let module_path = reader.read_str()?;
928            let full_path = reader.read_str()?;
929            let file = reader.read_str()?;
930            let line = reader.read_u32()?;
931            let name = reader.read_str()?;
932            let bootstrap = reader.read_bool()?;
933            let finalize = reader.read_bool()?;
934
935            let require_count = reader.read_u32()? as usize;
936            let mut requires = Vec::with_capacity(require_count);
937            for _ in 0..require_count {
938                requires.push(reader.read_positioning_ref()?);
939            }
940
941            let create_count = reader.read_u32()? as usize;
942            let mut creates = Vec::with_capacity(create_count);
943            for _ in 0..create_count {
944                creates.push(reader.read_sql_declared()?);
945            }
946
947            SqlGraphEntity::CustomSql(ExtensionSqlEntity {
948                module_path,
949                full_path,
950                sql,
951                file,
952                line,
953                name,
954                bootstrap,
955                finalize,
956                requires,
957                creates,
958            })
959        }
960        ENTITY_FUNCTION => {
961            let name = reader.read_str()?;
962            let unaliased_name = reader.read_str()?;
963            let module_path = reader.read_str()?;
964            let full_path = reader.read_str()?;
965
966            let arg_count = reader.read_u32()? as usize;
967            let mut fn_args = Vec::with_capacity(arg_count);
968            for _ in 0..arg_count {
969                fn_args.push(reader.read_pg_extern_argument()?);
970            }
971
972            let fn_return = reader.read_pg_extern_return()?;
973            let schema = reader.read_option_str()?;
974            let file = reader.read_str()?;
975            let line = reader.read_u32()?;
976
977            let extern_attr_count = reader.read_u32()? as usize;
978            let mut extern_attrs = Vec::with_capacity(extern_attr_count);
979            for _ in 0..extern_attr_count {
980                extern_attrs.push(reader.read_extern_arg()?);
981            }
982
983            let search_path = reader.read_search_path()?;
984            let operator = if reader.read_bool()? { Some(reader.read_operator()?) } else { None };
985            let cast = if reader.read_bool()? { Some(reader.read_cast()?) } else { None };
986            let to_sql_config = reader.read_to_sql_config()?;
987
988            SqlGraphEntity::Function(PgExternEntity {
989                name,
990                unaliased_name,
991                module_path,
992                full_path,
993                fn_args,
994                fn_return,
995                schema,
996                file,
997                line,
998                extern_attrs,
999                search_path,
1000                operator,
1001                cast,
1002                to_sql_config,
1003            })
1004        }
1005        ENTITY_TYPE => {
1006            let name = reader.read_str()?;
1007            let file = reader.read_str()?;
1008            let line = reader.read_u32()?;
1009            let module_path = reader.read_str()?;
1010            let full_path = reader.read_str()?;
1011            let type_ident = reader.read_str()?;
1012            let in_fn_path = reader.read_str()?;
1013            let out_fn_path = reader.read_str()?;
1014            let receive_fn_path = reader.read_option_str()?;
1015            let send_fn_path = reader.read_option_str()?;
1016            let to_sql_config = reader.read_to_sql_config()?;
1017            let alignment =
1018                if reader.read_bool()? { Some(reader.read_u32()? as usize) } else { None };
1019
1020            SqlGraphEntity::Type(PostgresTypeEntity {
1021                name,
1022                file,
1023                line,
1024                full_path,
1025                module_path,
1026                type_ident,
1027                in_fn_path,
1028                out_fn_path,
1029                receive_fn_path,
1030                send_fn_path,
1031                to_sql_config,
1032                alignment,
1033            })
1034        }
1035        ENTITY_ENUM => {
1036            let name = reader.read_str()?;
1037            let file = reader.read_str()?;
1038            let line = reader.read_u32()?;
1039            let module_path = reader.read_str()?;
1040            let full_path = reader.read_str()?;
1041            let type_ident = reader.read_str()?;
1042
1043            let variant_count = reader.read_u32()? as usize;
1044            let mut variants = Vec::with_capacity(variant_count);
1045            for _ in 0..variant_count {
1046                variants.push(reader.read_str()?);
1047            }
1048
1049            let to_sql_config = reader.read_to_sql_config()?;
1050
1051            SqlGraphEntity::Enum(PostgresEnumEntity {
1052                name,
1053                file,
1054                line,
1055                full_path,
1056                module_path,
1057                type_ident,
1058                variants,
1059                to_sql_config,
1060            })
1061        }
1062        ENTITY_ORD => SqlGraphEntity::Ord(PostgresOrdEntity {
1063            name: reader.read_str()?,
1064            file: reader.read_str()?,
1065            line: reader.read_u32()?,
1066            full_path: reader.read_str()?,
1067            module_path: reader.read_str()?,
1068            type_ident: reader.read_str()?,
1069            to_sql_config: reader.read_to_sql_config()?,
1070        }),
1071        ENTITY_HASH => SqlGraphEntity::Hash(PostgresHashEntity {
1072            name: reader.read_str()?,
1073            file: reader.read_str()?,
1074            line: reader.read_u32()?,
1075            full_path: reader.read_str()?,
1076            module_path: reader.read_str()?,
1077            type_ident: reader.read_str()?,
1078            to_sql_config: reader.read_to_sql_config()?,
1079        }),
1080        ENTITY_AGGREGATE => {
1081            let full_path = reader.read_str()?;
1082            let module_path = reader.read_str()?;
1083            let file = reader.read_str()?;
1084            let line = reader.read_u32()?;
1085            let name = reader.read_str()?;
1086            let ordered_set = reader.read_bool()?;
1087            let args = reader.read_aggregate_type_list()?;
1088            let direct_args =
1089                if reader.read_bool()? { Some(reader.read_aggregate_type_list()?) } else { None };
1090            let stype = reader.read_aggregate_type()?;
1091            let sfunc = reader.read_str()?;
1092            let finalfunc = reader.read_option_str()?;
1093            let finalfunc_modify =
1094                if reader.read_bool()? { Some(reader.read_finalize_modify()?) } else { None };
1095            let combinefunc = reader.read_option_str()?;
1096            let serialfunc = reader.read_option_str()?;
1097            let deserialfunc = reader.read_option_str()?;
1098            let initcond = reader.read_option_str()?;
1099            let msfunc = reader.read_option_str()?;
1100            let minvfunc = reader.read_option_str()?;
1101            let mstype = if reader.read_bool()? { Some(reader.read_used_type()?) } else { None };
1102            let mfinalfunc = reader.read_option_str()?;
1103            let mfinalfunc_modify =
1104                if reader.read_bool()? { Some(reader.read_finalize_modify()?) } else { None };
1105            let minitcond = reader.read_option_str()?;
1106            let sortop = reader.read_option_str()?;
1107            let parallel =
1108                if reader.read_bool()? { Some(reader.read_parallel_option()?) } else { None };
1109            let hypothetical = reader.read_bool()?;
1110            let to_sql_config = reader.read_to_sql_config()?;
1111
1112            SqlGraphEntity::Aggregate(PgAggregateEntity {
1113                full_path,
1114                module_path,
1115                file,
1116                line,
1117                name,
1118                ordered_set,
1119                args,
1120                direct_args,
1121                stype,
1122                sfunc,
1123                finalfunc,
1124                finalfunc_modify,
1125                combinefunc,
1126                serialfunc,
1127                deserialfunc,
1128                initcond,
1129                msfunc,
1130                minvfunc,
1131                mstype,
1132                mfinalfunc,
1133                mfinalfunc_modify,
1134                minitcond,
1135                sortop,
1136                parallel,
1137                hypothetical,
1138                to_sql_config,
1139            })
1140        }
1141        ENTITY_TRIGGER => SqlGraphEntity::Trigger(PgTriggerEntity {
1142            function_name: reader.read_str()?,
1143            file: reader.read_str()?,
1144            line: reader.read_u32()?,
1145            module_path: reader.read_str()?,
1146            full_path: reader.read_str()?,
1147            to_sql_config: reader.read_to_sql_config()?,
1148        }),
1149        other => return Err(eyre!("invalid entity tag in schema entry: {other}")),
1150    };
1151
1152    reader.finish()?;
1153    Ok(entity)
1154}
1155
1156pub fn decode_entities<'a>(section: &'a [u8]) -> Result<Vec<SqlGraphEntity<'a>>> {
1157    entry_payloads(section)?
1158        .into_iter()
1159        .filter(|payload| *payload != SECTION_SENTINEL_PAYLOAD.as_slice())
1160        .map(decode_entity)
1161        .collect()
1162}
1163
1164#[cfg(test)]
1165mod tests {
1166    use super::*;
1167
1168    #[test]
1169    fn round_trip_basic_entry() {
1170        const PAYLOAD_LEN: usize = u8_len() + str_len("hello") + bool_len() + u32_len();
1171        const TOTAL_LEN: usize = u32_len() + PAYLOAD_LEN;
1172        const ENTRY: [u8; TOTAL_LEN] = EntryWriter::<TOTAL_LEN>::new()
1173            .u32(PAYLOAD_LEN as u32)
1174            .u8(7)
1175            .str("hello")
1176            .bool(true)
1177            .u32(42)
1178            .finish();
1179
1180        let payloads = entry_payloads(&ENTRY).unwrap();
1181        assert_eq!(payloads.len(), 1);
1182        let mut reader = EntryReader::new(payloads[0]);
1183        assert_eq!(reader.read_u8().unwrap(), 7);
1184        assert_eq!(reader.read_string().unwrap(), "hello");
1185        assert!(reader.read_bool().unwrap());
1186        assert_eq!(reader.read_u32().unwrap(), 42);
1187        assert!(reader.is_empty());
1188    }
1189
1190    #[test]
1191    fn ignores_trailing_zero_padding() {
1192        const PAYLOAD_LEN: usize = u8_len();
1193        const TOTAL_LEN: usize = u32_len() + PAYLOAD_LEN;
1194        const ENTRY: [u8; TOTAL_LEN] =
1195            EntryWriter::<TOTAL_LEN>::new().u32(PAYLOAD_LEN as u32).u8(ENTITY_SCHEMA).finish();
1196        let mut section = ENTRY.to_vec();
1197        section.extend_from_slice(&[0, 0, 0, 0]);
1198
1199        let payloads = entry_payloads(&section).unwrap();
1200        assert_eq!(payloads.len(), 1);
1201        assert_eq!(payloads[0], &[ENTITY_SCHEMA]);
1202    }
1203
1204    #[test]
1205    fn zero_filled_section_decodes_as_empty() {
1206        assert!(decode_entities(&[0]).unwrap().is_empty());
1207    }
1208
1209    #[test]
1210    fn sentinel_entry_decodes_as_empty() {
1211        assert!(decode_entities(&schema_section_sentinel_entry()).unwrap().is_empty());
1212    }
1213
1214    #[test]
1215    fn sentinel_entry_is_ignored_mid_section() {
1216        const PAYLOAD_LEN: usize =
1217            u8_len() + str_len("module") + str_len("tests") + str_len("file.rs") + u32_len();
1218        const TOTAL_LEN: usize = u32_len() + PAYLOAD_LEN;
1219        const ENTRY: [u8; TOTAL_LEN] = EntryWriter::<TOTAL_LEN>::new()
1220            .u32(PAYLOAD_LEN as u32)
1221            .u8(ENTITY_SCHEMA)
1222            .str("module")
1223            .str("tests")
1224            .str("file.rs")
1225            .u32(42)
1226            .finish();
1227
1228        let mut section = Vec::new();
1229        section.extend_from_slice(&ENTRY);
1230        section.extend_from_slice(&schema_section_sentinel_entry());
1231        section.extend_from_slice(&ENTRY);
1232        let entities = decode_entities(&section).unwrap();
1233        assert_eq!(entities.len(), 2);
1234        assert!(entities.iter().all(|entity| matches!(entity, SqlGraphEntity::Schema(_))));
1235    }
1236
1237    #[test]
1238    fn recognizes_macho_qualified_section_name() {
1239        assert!(is_schema_section_name(MACHO_SECTION_NAME));
1240        assert!(is_schema_section_name(MACHO_SECTION_PATH));
1241        assert!(is_schema_section_name(ELF_SECTION_NAME));
1242        assert!(is_schema_section_name(".pgrx_schema"));
1243        assert!(is_schema_section_name("__pgrx_schema"));
1244        assert!(is_schema_section_name("__DATA,__pgrx_schema"));
1245        assert!(!is_schema_section_name("__TEXT,__text"));
1246    }
1247
1248    #[test]
1249    fn schema_section_names_fit_windows_image_limits() {
1250        assert!(ELF_SECTION_NAME.len() <= 8);
1251        assert!(MACHO_SECTION_NAME.len() <= 8);
1252    }
1253
1254    #[test]
1255    fn round_trip_function_metadata_type_preserves_type_origin() {
1256        const TYPE_IDENT: &str = "tests::FancyText";
1257        const SQL: &str = "TEXT";
1258        const PAYLOAD_LEN: usize = function_metadata_type_len(
1259            Some(TYPE_IDENT),
1260            Ok(SqlMappingRef::literal(SQL)),
1261            Ok(ReturnsRef::One(SqlMappingRef::literal(SQL))),
1262        );
1263        const TOTAL_LEN: usize = u32_len() + PAYLOAD_LEN;
1264        const ENTRY: [u8; TOTAL_LEN] = EntryWriter::<TOTAL_LEN>::new()
1265            .u32(PAYLOAD_LEN as u32)
1266            .function_metadata_type(
1267                Some((TYPE_IDENT, TypeOrigin::External)),
1268                Ok(SqlMappingRef::literal(SQL)),
1269                Ok(ReturnsRef::One(SqlMappingRef::literal(SQL))),
1270            )
1271            .finish();
1272
1273        let payloads = entry_payloads(&ENTRY).unwrap();
1274        let mut reader = EntryReader::new(payloads[0]);
1275        let metadata = reader.read_function_metadata_type().unwrap();
1276
1277        assert_eq!(metadata.type_ident(), Some(TYPE_IDENT));
1278        assert_eq!(metadata.type_origin(), Some(TypeOrigin::External));
1279        assert_eq!(metadata.argument_sql, Ok(SqlMapping::literal(SQL)));
1280        assert_eq!(metadata.return_sql, Ok(Returns::One(SqlMapping::literal(SQL))));
1281        assert!(reader.is_empty());
1282    }
1283
1284    #[test]
1285    fn round_trip_function_metadata_type_preserves_array_mappings() {
1286        const TYPE_IDENT: &str = "tests::FancyNumeric";
1287        const PAYLOAD_LEN: usize = function_metadata_type_len(
1288            Some(TYPE_IDENT),
1289            Ok(SqlMappingRef::Array(SqlArrayMappingRef::As("INT"))),
1290            Ok(ReturnsRef::One(SqlMappingRef::Array(SqlArrayMappingRef::Numeric {
1291                precision: Some(10),
1292                scale: Some(2),
1293            }))),
1294        );
1295        const TOTAL_LEN: usize = u32_len() + PAYLOAD_LEN;
1296        const ENTRY: [u8; TOTAL_LEN] = EntryWriter::<TOTAL_LEN>::new()
1297            .u32(PAYLOAD_LEN as u32)
1298            .function_metadata_type(
1299                Some((TYPE_IDENT, TypeOrigin::External)),
1300                Ok(SqlMappingRef::Array(SqlArrayMappingRef::As("INT"))),
1301                Ok(ReturnsRef::One(SqlMappingRef::Array(SqlArrayMappingRef::Numeric {
1302                    precision: Some(10),
1303                    scale: Some(2),
1304                }))),
1305            )
1306            .finish();
1307
1308        let payloads = entry_payloads(&ENTRY).unwrap();
1309        let mut reader = EntryReader::new(payloads[0]);
1310        let metadata = reader.read_function_metadata_type().unwrap();
1311
1312        assert_eq!(
1313            metadata.argument_sql,
1314            Ok(SqlMapping::Array(SqlArrayMapping::As("INT".to_string())))
1315        );
1316        assert_eq!(
1317            metadata.return_sql,
1318            Ok(Returns::One(SqlMapping::Array(SqlArrayMapping::As("NUMERIC(10, 2)".to_string(),))))
1319        );
1320        assert!(reader.is_empty());
1321    }
1322
1323    #[test]
1324    fn round_trip_function_metadata_type_preserves_composite_array_mappings() {
1325        const PAYLOAD_LEN: usize = function_metadata_type_len(
1326            None,
1327            Ok(SqlMappingRef::Array(SqlArrayMappingRef::Composite)),
1328            Ok(ReturnsRef::One(SqlMappingRef::Array(SqlArrayMappingRef::Composite))),
1329        );
1330        const TOTAL_LEN: usize = u32_len() + PAYLOAD_LEN;
1331        const ENTRY: [u8; TOTAL_LEN] = EntryWriter::<TOTAL_LEN>::new()
1332            .u32(PAYLOAD_LEN as u32)
1333            .function_metadata_type(
1334                None,
1335                Ok(SqlMappingRef::Array(SqlArrayMappingRef::Composite)),
1336                Ok(ReturnsRef::One(SqlMappingRef::Array(SqlArrayMappingRef::Composite))),
1337            )
1338            .finish();
1339
1340        let payloads = entry_payloads(&ENTRY).unwrap();
1341        let mut reader = EntryReader::new(payloads[0]);
1342        let metadata = reader.read_function_metadata_type().unwrap();
1343
1344        assert_eq!(metadata.argument_sql, Ok(SqlMapping::Array(SqlArrayMapping::Composite)));
1345        assert_eq!(
1346            metadata.return_sql,
1347            Ok(Returns::One(SqlMapping::Array(SqlArrayMapping::Composite)))
1348        );
1349        assert_eq!(metadata.type_ident(), None);
1350        assert_eq!(metadata.type_origin(), None);
1351        assert!(reader.is_empty());
1352    }
1353
1354    #[test]
1355    fn round_trip_function_metadata_type_preserves_nested_array_errors() {
1356        const TYPE_IDENT: &str = "tests::NestedArrayError";
1357        const PAYLOAD_LEN: usize = function_metadata_type_len(
1358            Some(TYPE_IDENT),
1359            Err(ArgumentError::NestedArray),
1360            Err(ReturnsError::NestedArray),
1361        );
1362        const TOTAL_LEN: usize = u32_len() + PAYLOAD_LEN;
1363        const ENTRY: [u8; TOTAL_LEN] = EntryWriter::<TOTAL_LEN>::new()
1364            .u32(PAYLOAD_LEN as u32)
1365            .function_metadata_type(
1366                Some((TYPE_IDENT, TypeOrigin::External)),
1367                Err(ArgumentError::NestedArray),
1368                Err(ReturnsError::NestedArray),
1369            )
1370            .finish();
1371
1372        let payloads = entry_payloads(&ENTRY).unwrap();
1373        let mut reader = EntryReader::new(payloads[0]);
1374        let metadata = reader.read_function_metadata_type().unwrap();
1375
1376        assert_eq!(metadata.argument_sql, Err(ArgumentError::NestedArray));
1377        assert_eq!(metadata.return_sql, Err(ReturnsError::NestedArray));
1378        assert!(reader.is_empty());
1379    }
1380
1381    #[test]
1382    fn round_trip_sql_declared_type_preserves_type_ident_and_sql() {
1383        const NAME: &str = "tests::FancyText";
1384        const TYPE_IDENT: &str = "tests::FancyText";
1385        const SQL: &str = "fancy_text";
1386        const PAYLOAD_LEN: usize = u8_len()
1387            + str_len(NAME)
1388            + str_len(TYPE_IDENT)
1389            + argument_sql_len(Ok(SqlMappingRef::literal(SQL)));
1390        const TOTAL_LEN: usize = u32_len() + PAYLOAD_LEN;
1391        const ENTRY: [u8; TOTAL_LEN] = EntryWriter::<TOTAL_LEN>::new()
1392            .u32(PAYLOAD_LEN as u32)
1393            .u8(SQL_DECLARED_TYPE)
1394            .str(NAME)
1395            .str(TYPE_IDENT)
1396            .argument_sql(Ok(SqlMappingRef::literal(SQL)))
1397            .finish();
1398
1399        let payloads = entry_payloads(&ENTRY).unwrap();
1400        let mut reader = EntryReader::new(payloads[0]);
1401        let declared = reader.read_sql_declared().unwrap();
1402
1403        assert_eq!(declared.type_ident(), Some(TYPE_IDENT));
1404        assert_eq!(declared.sql(), SQL);
1405        assert!(reader.is_empty());
1406    }
1407
1408    #[test]
1409    fn round_trip_sql_declared_function_skips_type_ident() {
1410        const NAME: &str = "tests::helper_fn";
1411        const PAYLOAD_LEN: usize = u8_len() + str_len(NAME);
1412        const TOTAL_LEN: usize = u32_len() + PAYLOAD_LEN;
1413        const ENTRY: [u8; TOTAL_LEN] = EntryWriter::<TOTAL_LEN>::new()
1414            .u32(PAYLOAD_LEN as u32)
1415            .u8(SQL_DECLARED_FUNCTION)
1416            .str(NAME)
1417            .finish();
1418
1419        let payloads = entry_payloads(&ENTRY).unwrap();
1420        let mut reader = EntryReader::new(payloads[0]);
1421        let declared = reader.read_sql_declared().unwrap();
1422
1423        assert_eq!(declared.type_ident(), None);
1424        assert_eq!(declared.sql(), "helper_fn");
1425        assert!(reader.is_empty());
1426    }
1427}