1use 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
74const 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 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(§ion[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(§ion).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(§ion).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}