use crate::aggregate::entity::{AggregateTypeEntity, PgAggregateEntity};
use crate::aggregate::{FinalizeModify, ParallelOption};
use crate::extension_sql::entity::{
ExtensionSqlEntity, SqlDeclaredEntity, SqlDeclaredFunctionEntityData, SqlDeclaredTypeEntityData,
};
use crate::extern_args::ExternArgs;
use crate::metadata::{
ArgumentError, FunctionMetadataTypeEntity, Returns, ReturnsError, ReturnsRef, SqlArrayMapping,
SqlArrayMappingRef, SqlMapping, SqlMappingRef, TypeOrigin,
};
use crate::pg_extern::entity::{
PgCastEntity, PgExternArgumentEntity, PgExternEntity, PgExternReturnEntity,
PgExternReturnEntityIteratedItem, PgOperatorEntity,
};
use crate::pg_trigger::entity::PgTriggerEntity;
use crate::positioning_ref::PositioningRef;
use crate::postgres_enum::entity::PostgresEnumEntity;
use crate::postgres_hash::entity::PostgresHashEntity;
use crate::postgres_ord::entity::PostgresOrdEntity;
use crate::postgres_type::entity::PostgresTypeEntity;
use crate::schema::entity::SchemaEntity;
use crate::to_sql::entity::ToSqlConfigEntity;
use crate::{SqlGraphEntity, UsedTypeEntity};
use eyre::{Result, bail, eyre};
pub const ELF_SECTION_NAME: &str = ".pgrxsc";
pub const MACHO_SEGMENT_NAME: &str = "__DATA";
pub const MACHO_SECTION_NAME: &str = "__pgrxsc";
pub const MACHO_SECTION_PATH: &str = "__DATA,__pgrxsc";
const LEGACY_ELF_SECTION_NAME: &str = ".pgrx_schema";
const LEGACY_MACHO_SECTION_NAME: &str = "__pgrx_schema";
const LEGACY_MACHO_SECTION_PATH: &str = "__DATA,__pgrx_schema";
pub const ENTITY_SENTINEL: u8 = 0;
pub const ENTITY_SCHEMA: u8 = 1;
pub const ENTITY_CUSTOM_SQL: u8 = 2;
pub const ENTITY_FUNCTION: u8 = 3;
pub const ENTITY_TYPE: u8 = 4;
pub const ENTITY_ENUM: u8 = 5;
pub const ENTITY_ORD: u8 = 6;
pub const ENTITY_HASH: u8 = 7;
pub const ENTITY_AGGREGATE: u8 = 8;
pub const ENTITY_TRIGGER: u8 = 9;
pub const POSITIONING_REF_FULL_PATH: u8 = 1;
pub const POSITIONING_REF_NAME: u8 = 2;
pub const SQL_DECLARED_TYPE: u8 = 1;
pub const SQL_DECLARED_ENUM: u8 = 2;
pub const SQL_DECLARED_FUNCTION: u8 = 3;
pub const SQL_MAPPING_AS: u8 = 1;
pub const SQL_MAPPING_ARRAY: u8 = 2;
pub const SQL_MAPPING_NUMERIC: u8 = 3;
pub const SQL_MAPPING_COMPOSITE: u8 = 4;
pub const SQL_MAPPING_SKIP: u8 = 5;
pub const RETURNS_ONE: u8 = 1;
pub const RETURNS_SET_OF: u8 = 2;
pub const RETURNS_TABLE: u8 = 3;
pub const ARG_ERROR_SET_OF: u8 = 1;
pub const ARG_ERROR_TABLE: u8 = 2;
pub const ARG_ERROR_NESTED_ARRAY: u8 = 3;
pub const ARG_ERROR_BARE_U8: u8 = 4;
pub const ARG_ERROR_SKIP_IN_ARRAY: u8 = 5;
pub const ARG_ERROR_DATUM: u8 = 6;
pub const ARG_ERROR_NOT_VALID: u8 = 7;
pub const RESULT_OK: u8 = 1;
pub const RESULT_ERR: u8 = 2;
pub const TYPE_ORIGIN_THIS_EXTENSION: u8 = 1;
pub const TYPE_ORIGIN_EXTERNAL: u8 = 2;
pub const RETURNS_ERROR_NESTED_SET_OF: u8 = 1;
pub const RETURNS_ERROR_NESTED_TABLE: u8 = 2;
pub const RETURNS_ERROR_NESTED_ARRAY: u8 = 3;
pub const RETURNS_ERROR_SET_OF_CONTAINING_TABLE: u8 = 4;
pub const RETURNS_ERROR_TABLE_CONTAINING_SET_OF: u8 = 5;
pub const RETURNS_ERROR_SET_OF_IN_ARRAY: u8 = 6;
pub const RETURNS_ERROR_TABLE_IN_ARRAY: u8 = 7;
pub const RETURNS_ERROR_BARE_U8: u8 = 8;
pub const RETURNS_ERROR_SKIP_IN_ARRAY: u8 = 9;
pub const RETURNS_ERROR_DATUM: u8 = 10;
pub const EXTERN_ARG_CREATE_OR_REPLACE: u8 = 1;
pub const EXTERN_ARG_IMMUTABLE: u8 = 2;
pub const EXTERN_ARG_STRICT: u8 = 3;
pub const EXTERN_ARG_STABLE: u8 = 4;
pub const EXTERN_ARG_VOLATILE: u8 = 5;
pub const EXTERN_ARG_RAW: u8 = 6;
pub const EXTERN_ARG_NO_GUARD: u8 = 7;
pub const EXTERN_ARG_SECURITY_DEFINER: u8 = 8;
pub const EXTERN_ARG_SECURITY_INVOKER: u8 = 9;
pub const EXTERN_ARG_PARALLEL_SAFE: u8 = 10;
pub const EXTERN_ARG_PARALLEL_UNSAFE: u8 = 11;
pub const EXTERN_ARG_PARALLEL_RESTRICTED: u8 = 12;
pub const EXTERN_ARG_SHOULD_PANIC: u8 = 13;
pub const EXTERN_ARG_SCHEMA: u8 = 14;
pub const EXTERN_ARG_SUPPORT: u8 = 15;
pub const EXTERN_ARG_NAME: u8 = 16;
pub const EXTERN_ARG_COST: u8 = 17;
pub const EXTERN_ARG_REQUIRES: u8 = 18;
pub const OPERATOR_CAST_DEFAULT: u8 = 1;
pub const OPERATOR_CAST_ASSIGNMENT: u8 = 2;
pub const OPERATOR_CAST_IMPLICIT: u8 = 3;
pub const EXTERN_RET_NONE: u8 = 1;
pub const EXTERN_RET_TYPE: u8 = 2;
pub const EXTERN_RET_SET_OF: u8 = 3;
pub const EXTERN_RET_ITERATED: u8 = 4;
pub const EXTERN_RET_TRIGGER: u8 = 5;
pub const AGGREGATE_FINALIZE_READ_ONLY: u8 = 1;
pub const AGGREGATE_FINALIZE_SHAREABLE: u8 = 2;
pub const AGGREGATE_FINALIZE_READ_WRITE: u8 = 3;
pub const AGGREGATE_PARALLEL_SAFE: u8 = 1;
pub const AGGREGATE_PARALLEL_RESTRICTED: u8 = 2;
pub const AGGREGATE_PARALLEL_UNSAFE: u8 = 3;
pub const SECTION_SENTINEL_MAGIC: &str = "pgrx";
pub const SECTION_SENTINEL_PAYLOAD_LEN: usize = u8_len() + str_len(SECTION_SENTINEL_MAGIC);
pub const SECTION_SENTINEL_ENTRY_LEN: usize = u32_len() + SECTION_SENTINEL_PAYLOAD_LEN;
pub const SECTION_SENTINEL_PAYLOAD: [u8; SECTION_SENTINEL_PAYLOAD_LEN] =
EntryWriter::<SECTION_SENTINEL_PAYLOAD_LEN>::new()
.u8(ENTITY_SENTINEL)
.str(SECTION_SENTINEL_MAGIC)
.finish();
pub fn is_schema_section_name(name: &str) -> bool {
name == ELF_SECTION_NAME
|| name == MACHO_SECTION_NAME
|| name == MACHO_SECTION_PATH
|| name == LEGACY_ELF_SECTION_NAME
|| name == LEGACY_MACHO_SECTION_NAME
|| name == LEGACY_MACHO_SECTION_PATH
}
#[macro_export]
macro_rules! __pgrx_schema_entry {
($name:ident, $len:expr, $payload:expr) => {
#[doc(hidden)]
#[used]
#[allow(non_upper_case_globals)]
#[cfg_attr(target_os = "macos", unsafe(link_section = "__DATA,__pgrxsc"))]
#[cfg_attr(not(target_os = "macos"), unsafe(link_section = ".pgrxsc"))]
static $name: [u8; $len] = $payload;
};
}
pub const fn bytes_len(len: usize) -> usize {
4 + len
}
pub const fn str_len(value: &str) -> usize {
bytes_len(value.len())
}
pub const fn bool_len() -> usize {
1
}
pub const fn u8_len() -> usize {
1
}
pub const fn u32_len() -> usize {
4
}
pub const fn opt_len(inner: Option<usize>) -> usize {
1 + match inner {
Some(inner) => inner,
None => 0,
}
}
pub const fn list_len(items: &[usize]) -> usize {
let mut total = 4;
let mut i = 0;
while i < items.len() {
total += items[i];
i += 1;
}
total
}
pub const fn sql_mapping_len(value: SqlMappingRef) -> usize {
match value {
SqlMappingRef::As(sql) => u8_len() + str_len(sql),
SqlMappingRef::Numeric { .. } => u8_len() + bool_len() + u32_len() + bool_len() + u32_len(),
SqlMappingRef::Composite => u8_len(),
SqlMappingRef::Array(value) => u8_len() + sql_array_mapping_len(value),
SqlMappingRef::Skip => u8_len(),
}
}
pub const fn sql_array_mapping_len(value: SqlArrayMappingRef) -> usize {
match value {
SqlArrayMappingRef::As(sql) => u8_len() + str_len(sql),
SqlArrayMappingRef::Numeric { .. } => {
u8_len() + bool_len() + u32_len() + bool_len() + u32_len()
}
SqlArrayMappingRef::Composite => u8_len(),
}
}
pub const fn returns_len(value: ReturnsRef) -> usize {
match value {
ReturnsRef::One(mapping) | ReturnsRef::SetOf(mapping) => {
u8_len() + sql_mapping_len(mapping)
}
ReturnsRef::Table(items) => {
let mut total = u8_len() + u32_len();
let mut i = 0;
while i < items.len() {
total += sql_mapping_len(items[i]);
i += 1;
}
total
}
}
}
pub const fn argument_error_len(value: ArgumentError) -> usize {
match value {
ArgumentError::NotValidAsArgument(name) => u8_len() + str_len(name),
_ => u8_len(),
}
}
pub const fn argument_sql_len(value: Result<SqlMappingRef, ArgumentError>) -> usize {
u8_len()
+ match value {
Ok(mapping) => sql_mapping_len(mapping),
Err(err) => argument_error_len(err),
}
}
pub const fn returns_error_len(_value: ReturnsError) -> usize {
u8_len()
}
pub const fn return_sql_len(value: Result<ReturnsRef, ReturnsError>) -> usize {
u8_len()
+ match value {
Ok(returns) => returns_len(returns),
Err(err) => returns_error_len(err),
}
}
pub const fn function_metadata_type_len(
resolution: Option<&str>,
argument_sql: Result<SqlMappingRef, ArgumentError>,
return_sql: Result<ReturnsRef, ReturnsError>,
) -> usize {
bool_len()
+ match resolution {
Some(type_ident) => str_len(type_ident) + u8_len(),
None => 0,
}
+ argument_sql_len(argument_sql)
+ return_sql_len(return_sql)
}
#[derive(Clone, Copy)]
pub struct EntryWriter<const N: usize> {
buf: [u8; N],
pos: usize,
}
impl<const N: usize> EntryWriter<N> {
pub const fn new() -> Self {
Self { buf: [0; N], pos: 0 }
}
pub const fn u8(mut self, value: u8) -> Self {
self.buf[self.pos] = value;
self.pos += 1;
self
}
pub const fn bool(self, value: bool) -> Self {
self.u8(if value { 1 } else { 0 })
}
pub const fn u32(self, value: u32) -> Self {
let [b0, b1, b2, b3] = value.to_le_bytes();
self.u8(b0).u8(b1).u8(b2).u8(b3)
}
pub const fn type_origin(self, value: TypeOrigin) -> Self {
self.u8(match value {
TypeOrigin::ThisExtension => TYPE_ORIGIN_THIS_EXTENSION,
TypeOrigin::External => TYPE_ORIGIN_EXTERNAL,
})
}
pub const fn bytes(mut self, value: &[u8]) -> Self {
let mut i = 0;
while i < value.len() {
self.buf[self.pos] = value[i];
self.pos += 1;
i += 1;
}
self
}
pub const fn str(self, value: &str) -> Self {
self.u32(value.len() as u32).bytes(value.as_bytes())
}
pub const fn sql_mapping(self, value: SqlMappingRef) -> Self {
match value {
SqlMappingRef::As(sql) => self.u8(SQL_MAPPING_AS).str(sql),
SqlMappingRef::Numeric { precision, scale } => self
.u8(SQL_MAPPING_NUMERIC)
.bool(precision.is_some())
.u32(match precision {
Some(value) => value,
None => 0,
})
.bool(scale.is_some())
.u32(match scale {
Some(value) => value,
None => 0,
}),
SqlMappingRef::Composite => self.u8(SQL_MAPPING_COMPOSITE),
SqlMappingRef::Array(value) => self.u8(SQL_MAPPING_ARRAY).sql_array_mapping(value),
SqlMappingRef::Skip => self.u8(SQL_MAPPING_SKIP),
}
}
pub const fn sql_array_mapping(self, value: SqlArrayMappingRef) -> Self {
match value {
SqlArrayMappingRef::As(sql) => self.u8(SQL_MAPPING_AS).str(sql),
SqlArrayMappingRef::Numeric { precision, scale } => self
.u8(SQL_MAPPING_NUMERIC)
.bool(precision.is_some())
.u32(match precision {
Some(value) => value,
None => 0,
})
.bool(scale.is_some())
.u32(match scale {
Some(value) => value,
None => 0,
}),
SqlArrayMappingRef::Composite => self.u8(SQL_MAPPING_COMPOSITE),
}
}
pub const fn returns(self, value: ReturnsRef) -> Self {
match value {
ReturnsRef::One(mapping) => self.u8(RETURNS_ONE).sql_mapping(mapping),
ReturnsRef::SetOf(mapping) => self.u8(RETURNS_SET_OF).sql_mapping(mapping),
ReturnsRef::Table(items) => {
let mut writer = self.u8(RETURNS_TABLE).u32(items.len() as u32);
let mut i = 0;
while i < items.len() {
writer = writer.sql_mapping(items[i]);
i += 1;
}
writer
}
}
}
pub const fn argument_error(self, value: ArgumentError) -> Self {
match value {
ArgumentError::SetOf => self.u8(ARG_ERROR_SET_OF),
ArgumentError::Table => self.u8(ARG_ERROR_TABLE),
ArgumentError::NestedArray => self.u8(ARG_ERROR_NESTED_ARRAY),
ArgumentError::BareU8 => self.u8(ARG_ERROR_BARE_U8),
ArgumentError::SkipInArray => self.u8(ARG_ERROR_SKIP_IN_ARRAY),
ArgumentError::Datum => self.u8(ARG_ERROR_DATUM),
ArgumentError::NotValidAsArgument(name) => self.u8(ARG_ERROR_NOT_VALID).str(name),
}
}
pub const fn argument_sql(self, value: Result<SqlMappingRef, ArgumentError>) -> Self {
match value {
Ok(mapping) => self.u8(RESULT_OK).sql_mapping(mapping),
Err(err) => self.u8(RESULT_ERR).argument_error(err),
}
}
pub const fn returns_error(self, value: ReturnsError) -> Self {
match value {
ReturnsError::NestedSetOf => self.u8(RETURNS_ERROR_NESTED_SET_OF),
ReturnsError::NestedTable => self.u8(RETURNS_ERROR_NESTED_TABLE),
ReturnsError::NestedArray => self.u8(RETURNS_ERROR_NESTED_ARRAY),
ReturnsError::SetOfContainingTable => self.u8(RETURNS_ERROR_SET_OF_CONTAINING_TABLE),
ReturnsError::TableContainingSetOf => self.u8(RETURNS_ERROR_TABLE_CONTAINING_SET_OF),
ReturnsError::SetOfInArray => self.u8(RETURNS_ERROR_SET_OF_IN_ARRAY),
ReturnsError::TableInArray => self.u8(RETURNS_ERROR_TABLE_IN_ARRAY),
ReturnsError::BareU8 => self.u8(RETURNS_ERROR_BARE_U8),
ReturnsError::SkipInArray => self.u8(RETURNS_ERROR_SKIP_IN_ARRAY),
ReturnsError::Datum => self.u8(RETURNS_ERROR_DATUM),
}
}
pub const fn return_sql(self, value: Result<ReturnsRef, ReturnsError>) -> Self {
match value {
Ok(returns) => self.u8(RESULT_OK).returns(returns),
Err(err) => self.u8(RESULT_ERR).returns_error(err),
}
}
pub const fn function_metadata_type(
self,
resolution: Option<(&str, TypeOrigin)>,
argument_sql: Result<SqlMappingRef, ArgumentError>,
return_sql: Result<ReturnsRef, ReturnsError>,
) -> Self {
let writer = match resolution {
Some((type_ident, type_origin)) => {
self.bool(true).str(type_ident).type_origin(type_origin)
}
None => self.bool(false),
};
writer.argument_sql(argument_sql).return_sql(return_sql)
}
pub const fn finish(self) -> [u8; N] {
if self.pos != N {
panic!("pgrx schema entry length mismatch");
}
self.buf
}
}
impl<const N: usize> Default for EntryWriter<N> {
fn default() -> Self {
Self::new()
}
}
pub const fn schema_section_sentinel_entry() -> [u8; SECTION_SENTINEL_ENTRY_LEN] {
EntryWriter::<SECTION_SENTINEL_ENTRY_LEN>::new()
.u32(SECTION_SENTINEL_PAYLOAD_LEN as u32)
.bytes(&SECTION_SENTINEL_PAYLOAD)
.finish()
}
pub struct EntryReader<'a> {
bytes: &'a [u8],
pos: usize,
}
impl<'a> EntryReader<'a> {
pub fn new(bytes: &'a [u8]) -> Self {
Self { bytes, pos: 0 }
}
pub fn is_empty(&self) -> bool {
self.pos >= self.bytes.len()
}
pub fn remaining(&self) -> usize {
self.bytes.len().saturating_sub(self.pos)
}
pub fn read_u8(&mut self) -> Result<u8> {
if self.remaining() < 1 {
bail!("unexpected end of schema entry");
}
let value = self.bytes[self.pos];
self.pos += 1;
Ok(value)
}
pub fn read_bool(&mut self) -> Result<bool> {
match self.read_u8()? {
0 => Ok(false),
1 => Ok(true),
other => Err(eyre!("invalid bool tag in schema entry: {other}")),
}
}
pub fn read_u32(&mut self) -> Result<u32> {
if self.remaining() < 4 {
bail!("unexpected end of schema entry");
}
let start = self.pos;
self.pos += 4;
Ok(u32::from_le_bytes(
self.bytes[start..self.pos].try_into().expect("checked slice length"),
))
}
pub fn read_bytes(&mut self) -> Result<&'a [u8]> {
let len = self.read_u32()? as usize;
if self.remaining() < len {
bail!("unexpected end of schema entry");
}
let start = self.pos;
self.pos += len;
Ok(&self.bytes[start..self.pos])
}
pub fn read_string(&mut self) -> Result<String> {
let bytes = self.read_bytes()?;
Ok(std::str::from_utf8(bytes)
.map_err(|err| eyre!("schema entry contained invalid utf8: {err}"))?
.to_owned())
}
pub fn read_str(&mut self) -> Result<&'a str> {
let bytes = self.read_bytes()?;
std::str::from_utf8(bytes)
.map_err(|err| eyre!("schema entry contained invalid utf8: {err}"))
}
pub fn read_option_str(&mut self) -> Result<Option<&'a str>> {
if self.read_bool()? { Ok(Some(self.read_str()?)) } else { Ok(None) }
}
pub fn read_option_string(&mut self) -> Result<Option<String>> {
if self.read_bool()? { Ok(Some(self.read_string()?)) } else { Ok(None) }
}
pub fn read_sql_mapping_owned(&mut self) -> Result<SqlMapping> {
match self.read_u8()? {
SQL_MAPPING_AS => Ok(SqlMapping::As(self.read_string()?)),
SQL_MAPPING_ARRAY => Ok(SqlMapping::Array(self.read_sql_array_mapping_owned()?)),
SQL_MAPPING_NUMERIC => {
let has_precision = self.read_bool()?;
let precision = self.read_u32()?;
let has_scale = self.read_bool()?;
let scale = self.read_u32()?;
Ok(SqlMapping::As(crate::metadata::numeric_sql_string(
has_precision.then_some(precision),
has_scale.then_some(scale),
)))
}
SQL_MAPPING_COMPOSITE => Ok(SqlMapping::Composite),
SQL_MAPPING_SKIP => Ok(SqlMapping::Skip),
other => Err(eyre!("invalid sql mapping tag in schema entry: {other}")),
}
}
pub fn read_sql_array_mapping_owned(&mut self) -> Result<SqlArrayMapping> {
match self.read_u8()? {
SQL_MAPPING_AS => Ok(SqlArrayMapping::As(self.read_string()?)),
SQL_MAPPING_NUMERIC => {
let has_precision = self.read_bool()?;
let precision = self.read_u32()?;
let has_scale = self.read_bool()?;
let scale = self.read_u32()?;
Ok(SqlArrayMapping::As(crate::metadata::numeric_sql_string(
has_precision.then_some(precision),
has_scale.then_some(scale),
)))
}
SQL_MAPPING_COMPOSITE => Ok(SqlArrayMapping::Composite),
other => Err(eyre!("invalid sql array mapping tag in schema entry: {other}")),
}
}
pub fn read_returns_owned(&mut self) -> Result<Returns> {
match self.read_u8()? {
RETURNS_ONE => Ok(Returns::One(self.read_sql_mapping_owned()?)),
RETURNS_SET_OF => Ok(Returns::SetOf(self.read_sql_mapping_owned()?)),
RETURNS_TABLE => {
let count = self.read_u32()? as usize;
let mut items = Vec::with_capacity(count);
for _ in 0..count {
items.push(self.read_sql_mapping_owned()?);
}
Ok(Returns::Table(items))
}
other => Err(eyre!("invalid returns tag in schema entry: {other}")),
}
}
pub fn read_argument_sql_owned(&mut self) -> Result<Result<SqlMapping, ArgumentError>> {
match self.read_u8()? {
RESULT_OK => Ok(Ok(self.read_sql_mapping_owned()?)),
RESULT_ERR => Ok(Err(self.read_argument_error()?)),
other => Err(eyre!("invalid argument sql tag in schema entry: {other}")),
}
}
pub fn read_return_sql_owned(&mut self) -> Result<Result<Returns, ReturnsError>> {
match self.read_u8()? {
RESULT_OK => Ok(Ok(self.read_returns_owned()?)),
RESULT_ERR => Ok(Err(self.read_returns_error()?)),
other => Err(eyre!("invalid return sql tag in schema entry: {other}")),
}
}
pub fn read_argument_error(&mut self) -> Result<ArgumentError> {
match self.read_u8()? {
ARG_ERROR_SET_OF => Ok(ArgumentError::SetOf),
ARG_ERROR_TABLE => Ok(ArgumentError::Table),
ARG_ERROR_NESTED_ARRAY => Ok(ArgumentError::NestedArray),
ARG_ERROR_BARE_U8 => Ok(ArgumentError::BareU8),
ARG_ERROR_SKIP_IN_ARRAY => Ok(ArgumentError::SkipInArray),
ARG_ERROR_DATUM => Ok(ArgumentError::Datum),
ARG_ERROR_NOT_VALID => {
Ok(ArgumentError::NotValidAsArgument(Box::leak(
self.read_string()?.into_boxed_str(),
)))
}
other => Err(eyre!("invalid argument error tag in schema entry: {other}")),
}
}
pub fn read_returns_error(&mut self) -> Result<ReturnsError> {
match self.read_u8()? {
RETURNS_ERROR_NESTED_SET_OF => Ok(ReturnsError::NestedSetOf),
RETURNS_ERROR_NESTED_TABLE => Ok(ReturnsError::NestedTable),
RETURNS_ERROR_NESTED_ARRAY => Ok(ReturnsError::NestedArray),
RETURNS_ERROR_SET_OF_CONTAINING_TABLE => Ok(ReturnsError::SetOfContainingTable),
RETURNS_ERROR_TABLE_CONTAINING_SET_OF => Ok(ReturnsError::TableContainingSetOf),
RETURNS_ERROR_SET_OF_IN_ARRAY => Ok(ReturnsError::SetOfInArray),
RETURNS_ERROR_TABLE_IN_ARRAY => Ok(ReturnsError::TableInArray),
RETURNS_ERROR_BARE_U8 => Ok(ReturnsError::BareU8),
RETURNS_ERROR_SKIP_IN_ARRAY => Ok(ReturnsError::SkipInArray),
RETURNS_ERROR_DATUM => Ok(ReturnsError::Datum),
other => Err(eyre!("invalid returns error tag in schema entry: {other}")),
}
}
pub fn read_positioning_ref(&mut self) -> Result<PositioningRef> {
match self.read_u8()? {
POSITIONING_REF_FULL_PATH => Ok(PositioningRef::FullPath(self.read_string()?)),
POSITIONING_REF_NAME => Ok(PositioningRef::Name(self.read_string()?)),
other => Err(eyre!("invalid positioning ref tag in schema entry: {other}")),
}
}
pub fn read_to_sql_config(&mut self) -> Result<ToSqlConfigEntity<'a>> {
let enabled = self.read_bool()?;
let content = self.read_option_str()?;
Ok(ToSqlConfigEntity { enabled, content })
}
pub fn read_function_metadata_type(&mut self) -> Result<FunctionMetadataTypeEntity<'a>> {
let resolution = if self.read_bool()? {
Some(crate::metadata::FunctionMetadataTypeResolutionEntity {
type_ident: self.read_str()?,
type_origin: self.read_type_origin()?,
})
} else {
None
};
Ok(FunctionMetadataTypeEntity {
resolution,
argument_sql: self.read_argument_sql_owned()?,
return_sql: self.read_return_sql_owned()?,
})
}
pub fn read_type_origin(&mut self) -> Result<TypeOrigin> {
match self.read_u8()? {
TYPE_ORIGIN_THIS_EXTENSION => Ok(TypeOrigin::ThisExtension),
TYPE_ORIGIN_EXTERNAL => Ok(TypeOrigin::External),
value => Err(eyre!("Unknown type origin discriminator `{value}`")),
}
}
pub fn read_used_type(&mut self) -> Result<UsedTypeEntity<'a>> {
Ok(UsedTypeEntity {
ty_source: self.read_str()?,
full_path: self.read_str()?,
composite_type: self.read_option_str()?,
variadic: self.read_bool()?,
default: self.read_option_str()?,
optional: self.read_bool()?,
metadata: self.read_function_metadata_type()?,
})
}
pub fn read_pg_extern_argument(&mut self) -> Result<PgExternArgumentEntity<'a>> {
Ok(PgExternArgumentEntity { pattern: self.read_str()?, used_ty: self.read_used_type()? })
}
pub fn read_pg_extern_return(&mut self) -> Result<PgExternReturnEntity<'a>> {
match self.read_u8()? {
EXTERN_RET_NONE => Ok(PgExternReturnEntity::None),
EXTERN_RET_TYPE => Ok(PgExternReturnEntity::Type { ty: self.read_used_type()? }),
EXTERN_RET_SET_OF => Ok(PgExternReturnEntity::SetOf { ty: self.read_used_type()? }),
EXTERN_RET_ITERATED => {
let count = self.read_u32()? as usize;
let mut tys = Vec::with_capacity(count);
for _ in 0..count {
tys.push(PgExternReturnEntityIteratedItem {
name: self.read_option_str()?,
ty: self.read_used_type()?,
});
}
Ok(PgExternReturnEntity::Iterated { tys })
}
EXTERN_RET_TRIGGER => Ok(PgExternReturnEntity::Trigger),
other => Err(eyre!("invalid extern return tag in schema entry: {other}")),
}
}
pub fn read_extern_arg(&mut self) -> Result<ExternArgs> {
match self.read_u8()? {
EXTERN_ARG_CREATE_OR_REPLACE => Ok(ExternArgs::CreateOrReplace),
EXTERN_ARG_IMMUTABLE => Ok(ExternArgs::Immutable),
EXTERN_ARG_STRICT => Ok(ExternArgs::Strict),
EXTERN_ARG_STABLE => Ok(ExternArgs::Stable),
EXTERN_ARG_VOLATILE => Ok(ExternArgs::Volatile),
EXTERN_ARG_RAW => Ok(ExternArgs::Raw),
EXTERN_ARG_NO_GUARD => Ok(ExternArgs::NoGuard),
EXTERN_ARG_SECURITY_DEFINER => Ok(ExternArgs::SecurityDefiner),
EXTERN_ARG_SECURITY_INVOKER => Ok(ExternArgs::SecurityInvoker),
EXTERN_ARG_PARALLEL_SAFE => Ok(ExternArgs::ParallelSafe),
EXTERN_ARG_PARALLEL_UNSAFE => Ok(ExternArgs::ParallelUnsafe),
EXTERN_ARG_PARALLEL_RESTRICTED => Ok(ExternArgs::ParallelRestricted),
EXTERN_ARG_SHOULD_PANIC => Ok(ExternArgs::ShouldPanic(self.read_string()?)),
EXTERN_ARG_SCHEMA => Ok(ExternArgs::Schema(self.read_string()?)),
EXTERN_ARG_SUPPORT => Ok(ExternArgs::Support(self.read_positioning_ref()?)),
EXTERN_ARG_NAME => Ok(ExternArgs::Name(self.read_string()?)),
EXTERN_ARG_COST => Ok(ExternArgs::Cost(self.read_string()?)),
EXTERN_ARG_REQUIRES => {
let count = self.read_u32()? as usize;
let mut items = Vec::with_capacity(count);
for _ in 0..count {
items.push(self.read_positioning_ref()?);
}
Ok(ExternArgs::Requires(items))
}
other => Err(eyre!("invalid extern arg tag in schema entry: {other}")),
}
}
pub fn read_search_path(&mut self) -> Result<Option<Vec<&'a str>>> {
if !self.read_bool()? {
return Ok(None);
}
let count = self.read_u32()? as usize;
let mut values = Vec::with_capacity(count);
for _ in 0..count {
values.push(self.read_str()?);
}
Ok(Some(values))
}
pub fn read_operator(&mut self) -> Result<PgOperatorEntity<'a>> {
Ok(PgOperatorEntity {
opname: self.read_option_str()?,
commutator: self.read_option_str()?,
negator: self.read_option_str()?,
restrict: self.read_option_str()?,
join: self.read_option_str()?,
hashes: self.read_bool()?,
merges: self.read_bool()?,
})
}
pub fn read_cast(&mut self) -> Result<PgCastEntity> {
match self.read_u8()? {
OPERATOR_CAST_DEFAULT => Ok(PgCastEntity::Default),
OPERATOR_CAST_ASSIGNMENT => Ok(PgCastEntity::Assignment),
OPERATOR_CAST_IMPLICIT => Ok(PgCastEntity::Implicit),
other => Err(eyre!("invalid cast tag in schema entry: {other}")),
}
}
pub fn read_sql_declared(&mut self) -> Result<SqlDeclaredEntity> {
let kind = self.read_u8()?;
let name = self.read_string()?;
match kind {
SQL_DECLARED_TYPE | SQL_DECLARED_ENUM => {
let type_ident = self.read_string()?;
let sql = match self.read_argument_sql_owned()? {
Ok(crate::metadata::SqlMapping::As(sql)) => sql,
Ok(other) => {
bail!("invalid SQL declaration mapping in schema entry: {other:?}")
}
Err(err) => return Err(err.into()),
};
let data = SqlDeclaredTypeEntityData { sql, name, type_ident };
Ok(match kind {
SQL_DECLARED_TYPE => SqlDeclaredEntity::Type(data),
SQL_DECLARED_ENUM => SqlDeclaredEntity::Enum(data),
_ => unreachable!(),
})
}
SQL_DECLARED_FUNCTION => {
let sql = name
.split("::")
.last()
.ok_or_else(|| eyre!("function declaration was missing a name"))?
.to_owned();
Ok(SqlDeclaredEntity::Function(SqlDeclaredFunctionEntityData { sql, name }))
}
other => Err(eyre!("invalid SQL declared tag in schema entry: {other}")),
}
}
pub fn read_aggregate_type(&mut self) -> Result<AggregateTypeEntity<'a>> {
Ok(AggregateTypeEntity { name: self.read_option_str()?, used_ty: self.read_used_type()? })
}
pub fn read_aggregate_type_list(&mut self) -> Result<Vec<AggregateTypeEntity<'a>>> {
let count = self.read_u32()? as usize;
let mut items = Vec::with_capacity(count);
for _ in 0..count {
items.push(self.read_aggregate_type()?);
}
Ok(items)
}
pub fn read_finalize_modify(&mut self) -> Result<FinalizeModify> {
match self.read_u8()? {
AGGREGATE_FINALIZE_READ_ONLY => Ok(FinalizeModify::ReadOnly),
AGGREGATE_FINALIZE_SHAREABLE => Ok(FinalizeModify::Shareable),
AGGREGATE_FINALIZE_READ_WRITE => Ok(FinalizeModify::ReadWrite),
other => Err(eyre!("invalid finalize modify tag in schema entry: {other}")),
}
}
pub fn read_parallel_option(&mut self) -> Result<ParallelOption> {
match self.read_u8()? {
AGGREGATE_PARALLEL_SAFE => Ok(ParallelOption::Safe),
AGGREGATE_PARALLEL_RESTRICTED => Ok(ParallelOption::Restricted),
AGGREGATE_PARALLEL_UNSAFE => Ok(ParallelOption::Unsafe),
other => Err(eyre!("invalid parallel option tag in schema entry: {other}")),
}
}
pub fn finish(&self) -> Result<()> {
if self.is_empty() {
Ok(())
} else {
Err(eyre!("schema entry had {} trailing bytes", self.remaining()))
}
}
}
pub fn entry_payloads(section: &[u8]) -> Result<Vec<&[u8]>> {
let mut out = Vec::new();
let mut reader = EntryReader::new(section);
while !reader.is_empty() {
if reader.remaining() < 4 {
if section[reader.pos..].iter().all(|byte| *byte == 0) {
break;
}
bail!("invalid trailing bytes in pgrx schema section");
}
let len = reader.read_u32()? as usize;
if len == 0 {
if section[reader.pos..].iter().all(|byte| *byte == 0) {
break;
}
bail!("invalid zero-length pgrx schema entry");
}
if reader.remaining() < len {
bail!("invalid pgrx schema section payload length");
}
let start = reader.pos;
reader.pos += len;
out.push(§ion[start..reader.pos]);
}
Ok(out)
}
pub fn decode_entity<'a>(payload: &'a [u8]) -> Result<SqlGraphEntity<'a>> {
let mut reader = EntryReader::new(payload);
let entity = match reader.read_u8()? {
ENTITY_SCHEMA => SqlGraphEntity::Schema(SchemaEntity {
module_path: reader.read_str()?,
name: reader.read_str()?,
file: reader.read_str()?,
line: reader.read_u32()?,
}),
ENTITY_CUSTOM_SQL => {
let sql = reader.read_str()?;
let module_path = reader.read_str()?;
let full_path = reader.read_str()?;
let file = reader.read_str()?;
let line = reader.read_u32()?;
let name = reader.read_str()?;
let bootstrap = reader.read_bool()?;
let finalize = reader.read_bool()?;
let require_count = reader.read_u32()? as usize;
let mut requires = Vec::with_capacity(require_count);
for _ in 0..require_count {
requires.push(reader.read_positioning_ref()?);
}
let create_count = reader.read_u32()? as usize;
let mut creates = Vec::with_capacity(create_count);
for _ in 0..create_count {
creates.push(reader.read_sql_declared()?);
}
SqlGraphEntity::CustomSql(ExtensionSqlEntity {
module_path,
full_path,
sql,
file,
line,
name,
bootstrap,
finalize,
requires,
creates,
})
}
ENTITY_FUNCTION => {
let name = reader.read_str()?;
let unaliased_name = reader.read_str()?;
let module_path = reader.read_str()?;
let full_path = reader.read_str()?;
let arg_count = reader.read_u32()? as usize;
let mut fn_args = Vec::with_capacity(arg_count);
for _ in 0..arg_count {
fn_args.push(reader.read_pg_extern_argument()?);
}
let fn_return = reader.read_pg_extern_return()?;
let schema = reader.read_option_str()?;
let file = reader.read_str()?;
let line = reader.read_u32()?;
let extern_attr_count = reader.read_u32()? as usize;
let mut extern_attrs = Vec::with_capacity(extern_attr_count);
for _ in 0..extern_attr_count {
extern_attrs.push(reader.read_extern_arg()?);
}
let search_path = reader.read_search_path()?;
let operator = if reader.read_bool()? { Some(reader.read_operator()?) } else { None };
let cast = if reader.read_bool()? { Some(reader.read_cast()?) } else { None };
let to_sql_config = reader.read_to_sql_config()?;
SqlGraphEntity::Function(PgExternEntity {
name,
unaliased_name,
module_path,
full_path,
fn_args,
fn_return,
schema,
file,
line,
extern_attrs,
search_path,
operator,
cast,
to_sql_config,
})
}
ENTITY_TYPE => {
let name = reader.read_str()?;
let file = reader.read_str()?;
let line = reader.read_u32()?;
let module_path = reader.read_str()?;
let full_path = reader.read_str()?;
let type_ident = reader.read_str()?;
let in_fn_path = reader.read_str()?;
let out_fn_path = reader.read_str()?;
let receive_fn_path = reader.read_option_str()?;
let send_fn_path = reader.read_option_str()?;
let to_sql_config = reader.read_to_sql_config()?;
let alignment =
if reader.read_bool()? { Some(reader.read_u32()? as usize) } else { None };
SqlGraphEntity::Type(PostgresTypeEntity {
name,
file,
line,
full_path,
module_path,
type_ident,
in_fn_path,
out_fn_path,
receive_fn_path,
send_fn_path,
to_sql_config,
alignment,
})
}
ENTITY_ENUM => {
let name = reader.read_str()?;
let file = reader.read_str()?;
let line = reader.read_u32()?;
let module_path = reader.read_str()?;
let full_path = reader.read_str()?;
let type_ident = reader.read_str()?;
let variant_count = reader.read_u32()? as usize;
let mut variants = Vec::with_capacity(variant_count);
for _ in 0..variant_count {
variants.push(reader.read_str()?);
}
let to_sql_config = reader.read_to_sql_config()?;
SqlGraphEntity::Enum(PostgresEnumEntity {
name,
file,
line,
full_path,
module_path,
type_ident,
variants,
to_sql_config,
})
}
ENTITY_ORD => SqlGraphEntity::Ord(PostgresOrdEntity {
name: reader.read_str()?,
file: reader.read_str()?,
line: reader.read_u32()?,
full_path: reader.read_str()?,
module_path: reader.read_str()?,
type_ident: reader.read_str()?,
to_sql_config: reader.read_to_sql_config()?,
}),
ENTITY_HASH => SqlGraphEntity::Hash(PostgresHashEntity {
name: reader.read_str()?,
file: reader.read_str()?,
line: reader.read_u32()?,
full_path: reader.read_str()?,
module_path: reader.read_str()?,
type_ident: reader.read_str()?,
to_sql_config: reader.read_to_sql_config()?,
}),
ENTITY_AGGREGATE => {
let full_path = reader.read_str()?;
let module_path = reader.read_str()?;
let file = reader.read_str()?;
let line = reader.read_u32()?;
let name = reader.read_str()?;
let ordered_set = reader.read_bool()?;
let args = reader.read_aggregate_type_list()?;
let direct_args =
if reader.read_bool()? { Some(reader.read_aggregate_type_list()?) } else { None };
let stype = reader.read_aggregate_type()?;
let sfunc = reader.read_str()?;
let finalfunc = reader.read_option_str()?;
let finalfunc_modify =
if reader.read_bool()? { Some(reader.read_finalize_modify()?) } else { None };
let combinefunc = reader.read_option_str()?;
let serialfunc = reader.read_option_str()?;
let deserialfunc = reader.read_option_str()?;
let initcond = reader.read_option_str()?;
let msfunc = reader.read_option_str()?;
let minvfunc = reader.read_option_str()?;
let mstype = if reader.read_bool()? { Some(reader.read_used_type()?) } else { None };
let mfinalfunc = reader.read_option_str()?;
let mfinalfunc_modify =
if reader.read_bool()? { Some(reader.read_finalize_modify()?) } else { None };
let minitcond = reader.read_option_str()?;
let sortop = reader.read_option_str()?;
let parallel =
if reader.read_bool()? { Some(reader.read_parallel_option()?) } else { None };
let hypothetical = reader.read_bool()?;
let to_sql_config = reader.read_to_sql_config()?;
SqlGraphEntity::Aggregate(PgAggregateEntity {
full_path,
module_path,
file,
line,
name,
ordered_set,
args,
direct_args,
stype,
sfunc,
finalfunc,
finalfunc_modify,
combinefunc,
serialfunc,
deserialfunc,
initcond,
msfunc,
minvfunc,
mstype,
mfinalfunc,
mfinalfunc_modify,
minitcond,
sortop,
parallel,
hypothetical,
to_sql_config,
})
}
ENTITY_TRIGGER => SqlGraphEntity::Trigger(PgTriggerEntity {
function_name: reader.read_str()?,
file: reader.read_str()?,
line: reader.read_u32()?,
module_path: reader.read_str()?,
full_path: reader.read_str()?,
to_sql_config: reader.read_to_sql_config()?,
}),
other => return Err(eyre!("invalid entity tag in schema entry: {other}")),
};
reader.finish()?;
Ok(entity)
}
pub fn decode_entities<'a>(section: &'a [u8]) -> Result<Vec<SqlGraphEntity<'a>>> {
entry_payloads(section)?
.into_iter()
.filter(|payload| *payload != SECTION_SENTINEL_PAYLOAD.as_slice())
.map(decode_entity)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_basic_entry() {
const PAYLOAD_LEN: usize = u8_len() + str_len("hello") + bool_len() + u32_len();
const TOTAL_LEN: usize = u32_len() + PAYLOAD_LEN;
const ENTRY: [u8; TOTAL_LEN] = EntryWriter::<TOTAL_LEN>::new()
.u32(PAYLOAD_LEN as u32)
.u8(7)
.str("hello")
.bool(true)
.u32(42)
.finish();
let payloads = entry_payloads(&ENTRY).unwrap();
assert_eq!(payloads.len(), 1);
let mut reader = EntryReader::new(payloads[0]);
assert_eq!(reader.read_u8().unwrap(), 7);
assert_eq!(reader.read_string().unwrap(), "hello");
assert!(reader.read_bool().unwrap());
assert_eq!(reader.read_u32().unwrap(), 42);
assert!(reader.is_empty());
}
#[test]
fn ignores_trailing_zero_padding() {
const PAYLOAD_LEN: usize = u8_len();
const TOTAL_LEN: usize = u32_len() + PAYLOAD_LEN;
const ENTRY: [u8; TOTAL_LEN] =
EntryWriter::<TOTAL_LEN>::new().u32(PAYLOAD_LEN as u32).u8(ENTITY_SCHEMA).finish();
let mut section = ENTRY.to_vec();
section.extend_from_slice(&[0, 0, 0, 0]);
let payloads = entry_payloads(§ion).unwrap();
assert_eq!(payloads.len(), 1);
assert_eq!(payloads[0], &[ENTITY_SCHEMA]);
}
#[test]
fn zero_filled_section_decodes_as_empty() {
assert!(decode_entities(&[0]).unwrap().is_empty());
}
#[test]
fn sentinel_entry_decodes_as_empty() {
assert!(decode_entities(&schema_section_sentinel_entry()).unwrap().is_empty());
}
#[test]
fn sentinel_entry_is_ignored_mid_section() {
const PAYLOAD_LEN: usize =
u8_len() + str_len("module") + str_len("tests") + str_len("file.rs") + u32_len();
const TOTAL_LEN: usize = u32_len() + PAYLOAD_LEN;
const ENTRY: [u8; TOTAL_LEN] = EntryWriter::<TOTAL_LEN>::new()
.u32(PAYLOAD_LEN as u32)
.u8(ENTITY_SCHEMA)
.str("module")
.str("tests")
.str("file.rs")
.u32(42)
.finish();
let mut section = Vec::new();
section.extend_from_slice(&ENTRY);
section.extend_from_slice(&schema_section_sentinel_entry());
section.extend_from_slice(&ENTRY);
let entities = decode_entities(§ion).unwrap();
assert_eq!(entities.len(), 2);
assert!(entities.iter().all(|entity| matches!(entity, SqlGraphEntity::Schema(_))));
}
#[test]
fn recognizes_macho_qualified_section_name() {
assert!(is_schema_section_name(MACHO_SECTION_NAME));
assert!(is_schema_section_name(MACHO_SECTION_PATH));
assert!(is_schema_section_name(ELF_SECTION_NAME));
assert!(is_schema_section_name(".pgrx_schema"));
assert!(is_schema_section_name("__pgrx_schema"));
assert!(is_schema_section_name("__DATA,__pgrx_schema"));
assert!(!is_schema_section_name("__TEXT,__text"));
}
#[test]
fn schema_section_names_fit_windows_image_limits() {
assert!(ELF_SECTION_NAME.len() <= 8);
assert!(MACHO_SECTION_NAME.len() <= 8);
}
#[test]
fn round_trip_function_metadata_type_preserves_type_origin() {
const TYPE_IDENT: &str = "tests::FancyText";
const SQL: &str = "TEXT";
const PAYLOAD_LEN: usize = function_metadata_type_len(
Some(TYPE_IDENT),
Ok(SqlMappingRef::literal(SQL)),
Ok(ReturnsRef::One(SqlMappingRef::literal(SQL))),
);
const TOTAL_LEN: usize = u32_len() + PAYLOAD_LEN;
const ENTRY: [u8; TOTAL_LEN] = EntryWriter::<TOTAL_LEN>::new()
.u32(PAYLOAD_LEN as u32)
.function_metadata_type(
Some((TYPE_IDENT, TypeOrigin::External)),
Ok(SqlMappingRef::literal(SQL)),
Ok(ReturnsRef::One(SqlMappingRef::literal(SQL))),
)
.finish();
let payloads = entry_payloads(&ENTRY).unwrap();
let mut reader = EntryReader::new(payloads[0]);
let metadata = reader.read_function_metadata_type().unwrap();
assert_eq!(metadata.type_ident(), Some(TYPE_IDENT));
assert_eq!(metadata.type_origin(), Some(TypeOrigin::External));
assert_eq!(metadata.argument_sql, Ok(SqlMapping::literal(SQL)));
assert_eq!(metadata.return_sql, Ok(Returns::One(SqlMapping::literal(SQL))));
assert!(reader.is_empty());
}
#[test]
fn round_trip_function_metadata_type_preserves_array_mappings() {
const TYPE_IDENT: &str = "tests::FancyNumeric";
const PAYLOAD_LEN: usize = function_metadata_type_len(
Some(TYPE_IDENT),
Ok(SqlMappingRef::Array(SqlArrayMappingRef::As("INT"))),
Ok(ReturnsRef::One(SqlMappingRef::Array(SqlArrayMappingRef::Numeric {
precision: Some(10),
scale: Some(2),
}))),
);
const TOTAL_LEN: usize = u32_len() + PAYLOAD_LEN;
const ENTRY: [u8; TOTAL_LEN] = EntryWriter::<TOTAL_LEN>::new()
.u32(PAYLOAD_LEN as u32)
.function_metadata_type(
Some((TYPE_IDENT, TypeOrigin::External)),
Ok(SqlMappingRef::Array(SqlArrayMappingRef::As("INT"))),
Ok(ReturnsRef::One(SqlMappingRef::Array(SqlArrayMappingRef::Numeric {
precision: Some(10),
scale: Some(2),
}))),
)
.finish();
let payloads = entry_payloads(&ENTRY).unwrap();
let mut reader = EntryReader::new(payloads[0]);
let metadata = reader.read_function_metadata_type().unwrap();
assert_eq!(
metadata.argument_sql,
Ok(SqlMapping::Array(SqlArrayMapping::As("INT".to_string())))
);
assert_eq!(
metadata.return_sql,
Ok(Returns::One(SqlMapping::Array(SqlArrayMapping::As("NUMERIC(10, 2)".to_string(),))))
);
assert!(reader.is_empty());
}
#[test]
fn round_trip_function_metadata_type_preserves_composite_array_mappings() {
const PAYLOAD_LEN: usize = function_metadata_type_len(
None,
Ok(SqlMappingRef::Array(SqlArrayMappingRef::Composite)),
Ok(ReturnsRef::One(SqlMappingRef::Array(SqlArrayMappingRef::Composite))),
);
const TOTAL_LEN: usize = u32_len() + PAYLOAD_LEN;
const ENTRY: [u8; TOTAL_LEN] = EntryWriter::<TOTAL_LEN>::new()
.u32(PAYLOAD_LEN as u32)
.function_metadata_type(
None,
Ok(SqlMappingRef::Array(SqlArrayMappingRef::Composite)),
Ok(ReturnsRef::One(SqlMappingRef::Array(SqlArrayMappingRef::Composite))),
)
.finish();
let payloads = entry_payloads(&ENTRY).unwrap();
let mut reader = EntryReader::new(payloads[0]);
let metadata = reader.read_function_metadata_type().unwrap();
assert_eq!(metadata.argument_sql, Ok(SqlMapping::Array(SqlArrayMapping::Composite)));
assert_eq!(
metadata.return_sql,
Ok(Returns::One(SqlMapping::Array(SqlArrayMapping::Composite)))
);
assert_eq!(metadata.type_ident(), None);
assert_eq!(metadata.type_origin(), None);
assert!(reader.is_empty());
}
#[test]
fn round_trip_function_metadata_type_preserves_nested_array_errors() {
const TYPE_IDENT: &str = "tests::NestedArrayError";
const PAYLOAD_LEN: usize = function_metadata_type_len(
Some(TYPE_IDENT),
Err(ArgumentError::NestedArray),
Err(ReturnsError::NestedArray),
);
const TOTAL_LEN: usize = u32_len() + PAYLOAD_LEN;
const ENTRY: [u8; TOTAL_LEN] = EntryWriter::<TOTAL_LEN>::new()
.u32(PAYLOAD_LEN as u32)
.function_metadata_type(
Some((TYPE_IDENT, TypeOrigin::External)),
Err(ArgumentError::NestedArray),
Err(ReturnsError::NestedArray),
)
.finish();
let payloads = entry_payloads(&ENTRY).unwrap();
let mut reader = EntryReader::new(payloads[0]);
let metadata = reader.read_function_metadata_type().unwrap();
assert_eq!(metadata.argument_sql, Err(ArgumentError::NestedArray));
assert_eq!(metadata.return_sql, Err(ReturnsError::NestedArray));
assert!(reader.is_empty());
}
#[test]
fn round_trip_sql_declared_type_preserves_type_ident_and_sql() {
const NAME: &str = "tests::FancyText";
const TYPE_IDENT: &str = "tests::FancyText";
const SQL: &str = "fancy_text";
const PAYLOAD_LEN: usize = u8_len()
+ str_len(NAME)
+ str_len(TYPE_IDENT)
+ argument_sql_len(Ok(SqlMappingRef::literal(SQL)));
const TOTAL_LEN: usize = u32_len() + PAYLOAD_LEN;
const ENTRY: [u8; TOTAL_LEN] = EntryWriter::<TOTAL_LEN>::new()
.u32(PAYLOAD_LEN as u32)
.u8(SQL_DECLARED_TYPE)
.str(NAME)
.str(TYPE_IDENT)
.argument_sql(Ok(SqlMappingRef::literal(SQL)))
.finish();
let payloads = entry_payloads(&ENTRY).unwrap();
let mut reader = EntryReader::new(payloads[0]);
let declared = reader.read_sql_declared().unwrap();
assert_eq!(declared.type_ident(), Some(TYPE_IDENT));
assert_eq!(declared.sql(), SQL);
assert!(reader.is_empty());
}
#[test]
fn round_trip_sql_declared_function_skips_type_ident() {
const NAME: &str = "tests::helper_fn";
const PAYLOAD_LEN: usize = u8_len() + str_len(NAME);
const TOTAL_LEN: usize = u32_len() + PAYLOAD_LEN;
const ENTRY: [u8; TOTAL_LEN] = EntryWriter::<TOTAL_LEN>::new()
.u32(PAYLOAD_LEN as u32)
.u8(SQL_DECLARED_FUNCTION)
.str(NAME)
.finish();
let payloads = entry_payloads(&ENTRY).unwrap();
let mut reader = EntryReader::new(payloads[0]);
let declared = reader.read_sql_declared().unwrap();
assert_eq!(declared.type_ident(), None);
assert_eq!(declared.sql(), "helper_fn");
assert!(reader.is_empty());
}
}