Skip to content

Commit 7252d04

Browse files
committed
feat: auto-generate type alignment (#[pgrx(alignment = "on")])
Postgres allows for types to specify their alignment in the `CREATE TYPE` statement. This change adds the ability to derive Postgres' alignment configuration from the type's Rust alignment (`std::mem::align_of::<T>()`). This functionality is opt-in through by adding the `#[pgrx(alignment = "on")]` attribute: ``` #[derive(PostgresType)] #[pgrx(alignment = "on")] struct AlignedTo8Bytes { v1: u64, v2: [u64; 3] } ```
1 parent 7b68a18 commit 7252d04

File tree

6 files changed

+159
-4
lines changed

6 files changed

+159
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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. <[email protected]>
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+
use pgrx::prelude::*;
11+
use serde::*;
12+
13+
#[derive(PostgresType, Serialize, Deserialize)]
14+
#[pgrx(alignment = "on")]
15+
pub struct AlignedTo4Bytes {
16+
v1: u32,
17+
v2: [u32; 3],
18+
}
19+
20+
#[derive(PostgresType, Serialize, Deserialize)]
21+
#[pgrx(alignment = "on")]
22+
pub struct AlignedTo8Bytes {
23+
v1: u64,
24+
v2: [u64; 3],
25+
}
26+
27+
#[derive(PostgresType, Serialize, Deserialize)]
28+
#[pgrx(alignment = "off")]
29+
pub struct NotAlignedTo8Bytes {
30+
v1: u64,
31+
v2: [u64; 3],
32+
}
33+
34+
#[cfg(any(test, feature = "pg_test"))]
35+
#[pg_schema]
36+
mod tests {
37+
use pgrx::prelude::*;
38+
39+
#[cfg(not(feature = "no-schema-generation"))]
40+
#[pg_test]
41+
fn test_alignment_is_correct() {
42+
let val = Spi::get_one::<String>(r#"SELECT typalign::text FROM pg_type WHERE typname = 'alignedto4bytes'"#).unwrap().unwrap();
43+
44+
assert!(val == "i");
45+
46+
let val = Spi::get_one::<String>(r#"SELECT typalign::text FROM pg_type WHERE typname = 'alignedto8bytes'"#).unwrap().unwrap();
47+
48+
assert!(val == "d");
49+
50+
let val = Spi::get_one::<String>(r#"SELECT typalign::text FROM pg_type WHERE typname = 'notalignedto8bytes'"#).unwrap().unwrap();
51+
52+
assert!(val == "i");
53+
}
54+
}

pgrx-examples/custom_types/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//LICENSE All rights reserved.
88
//LICENSE
99
//LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file.
10+
mod alignment;
1011
mod complex;
1112
mod fixed_size;
1213
mod generic_enum;

pgrx-macros/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,7 @@ Optionally accepts the following attributes:
781781
782782
* `inoutfuncs(some_in_fn, some_out_fn)`: Define custom in/out functions for the type.
783783
* `pgvarlena_inoutfuncs(some_in_fn, some_out_fn)`: Define custom in/out functions for the `PgVarlena` of this type.
784+
* `pgrx(alignment = "<align>")`: Derive Postgres alignment from Rust type. One of `"on"`, or `"off"`.
784785
* `sql`: Same arguments as [`#[pgrx(sql = ..)]`](macro@pgrx).
785786
*/
786787
#[proc_macro_derive(

pgrx-sql-entity-graph/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ pub trait SqlGraphIdentifier {
9494
fn line(&self) -> Option<u32>;
9595
}
9696

97+
pub use postgres_type::Alignment;
98+
9799
/// An entity corresponding to some SQL required by the extension.
98100
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
99101
pub enum SqlGraphEntity {

pgrx-sql-entity-graph/src/postgres_type/entity.rs

+87-2
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,81 @@
1616
1717
*/
1818
use crate::mapping::RustSqlMapping;
19+
use crate::pgrx_attribute::{ArgValue, PgrxArg, PgrxAttribute};
1920
use crate::pgrx_sql::PgrxSql;
2021
use crate::to_sql::entity::ToSqlConfigEntity;
2122
use crate::to_sql::ToSql;
2223
use crate::{SqlGraphEntity, SqlGraphIdentifier, TypeMatch};
24+
use eyre::eyre;
25+
use proc_macro2::TokenStream;
26+
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
2327
use std::collections::BTreeSet;
28+
use syn::spanned::Spanned;
29+
use syn::{AttrStyle, Attribute, Lit};
30+
31+
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
32+
pub enum Alignment {
33+
On,
34+
Off,
35+
}
36+
37+
const INVALID_ATTR_CONTENT: &str = r#"expected `#[pgrx(alignment = align)]`, where `align` is "on", or "off""#;
38+
39+
impl ToTokens for Alignment {
40+
fn to_tokens(&self, tokens: &mut TokenStream) {
41+
let value = match self {
42+
Alignment::On => format_ident!("On"),
43+
Alignment::Off => format_ident!("Off"),
44+
};
45+
let quoted = quote! {
46+
::pgrx::pgrx_sql_entity_graph::Alignment::#value
47+
};
48+
tokens.append_all(quoted);
49+
}
50+
}
51+
52+
impl Alignment {
53+
pub fn from_attribute(attr: &Attribute) -> Result<Option<Self>, syn::Error> {
54+
if attr.style != AttrStyle::Outer {
55+
return Err(syn::Error::new(
56+
attr.span(),
57+
"#[pgrx(alignment = ..)] is only valid in an outer context",
58+
));
59+
}
60+
61+
let attr = attr.parse_args::<PgrxAttribute>()?;
62+
for arg in attr.args.iter() {
63+
let PgrxArg::NameValue(ref nv) = arg;
64+
if !nv.path.is_ident("alignment") {
65+
continue;
66+
}
67+
68+
return match nv.value {
69+
ArgValue::Lit(Lit::Str(ref s)) => match s.value().as_ref() {
70+
"on" => Ok(Some(Self::On)),
71+
"off" => Ok(Some(Self::Off)),
72+
_ => Err(syn::Error::new(s.span(), INVALID_ATTR_CONTENT)),
73+
},
74+
ArgValue::Path(ref p) => Err(syn::Error::new(p.span(), INVALID_ATTR_CONTENT)),
75+
ArgValue::Lit(ref l) => Err(syn::Error::new(l.span(), INVALID_ATTR_CONTENT)),
76+
};
77+
}
78+
79+
Ok(None)
80+
}
81+
82+
pub fn from_attributes(attrs: &[Attribute]) -> Result<Self, syn::Error> {
83+
for attr in attrs {
84+
if attr.path().is_ident("pgrx") {
85+
if let Some(v) = Self::from_attribute(attr)? {
86+
return Ok(v)
87+
}
88+
}
89+
}
90+
Ok(Self::Off)
91+
}
92+
}
2493

25-
use eyre::eyre;
2694
/// The output of a [`PostgresType`](crate::postgres_type::PostgresTypeDerive) from `quote::ToTokens::to_tokens`.
2795
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
2896
pub struct PostgresTypeEntity {
@@ -37,6 +105,7 @@ pub struct PostgresTypeEntity {
37105
pub out_fn: &'static str,
38106
pub out_fn_module_path: String,
39107
pub to_sql_config: ToSqlConfigEntity,
108+
pub alignment: Option<usize>,
40109
}
41110

42111
impl TypeMatch for PostgresTypeEntity {
@@ -82,6 +151,7 @@ impl ToSql for PostgresTypeEntity {
82151
out_fn,
83152
out_fn_module_path,
84153
in_fn,
154+
alignment,
85155
..
86156
}) = item_node
87157
else {
@@ -155,6 +225,21 @@ impl ToSql for PostgresTypeEntity {
155225
schema = context.schema_prefix_for(&self_index),
156226
);
157227

228+
let alignment = alignment.map(|alignment| {
229+
assert!(alignment.is_power_of_two());
230+
let alignment = match alignment {
231+
1 => "char",
232+
2 => "int2",
233+
4 => "int4",
234+
8 | _ => "double",
235+
};
236+
format!(
237+
",\n\
238+
\tALIGNMENT = {}",
239+
alignment
240+
)
241+
}).unwrap_or_default();
242+
158243
let materialized_type = format! {
159244
"\n\
160245
-- {file}:{line}\n\
@@ -163,7 +248,7 @@ impl ToSql for PostgresTypeEntity {
163248
\tINTERNALLENGTH = variable,\n\
164249
\tINPUT = {schema_prefix_in_fn}{in_fn}, /* {in_fn_path} */\n\
165250
\tOUTPUT = {schema_prefix_out_fn}{out_fn}, /* {out_fn_path} */\n\
166-
\tSTORAGE = extended\n\
251+
\tSTORAGE = extended{alignment}\n\
167252
);\
168253
",
169254
schema = context.schema_prefix_for(&self_index),

pgrx-sql-entity-graph/src/postgres_type/mod.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use quote::{format_ident, quote};
2323
use syn::parse::{Parse, ParseStream};
2424
use syn::{DeriveInput, Generics, ItemStruct, Lifetime, LifetimeParam};
2525

26+
pub use crate::postgres_type::entity::Alignment;
2627
use crate::{CodeEnrichment, ToSqlConfig};
2728

2829
/// A parsed `#[derive(PostgresType)]` item.
@@ -55,6 +56,7 @@ pub struct PostgresTypeDerive {
5556
in_fn: Ident,
5657
out_fn: Ident,
5758
to_sql_config: ToSqlConfig,
59+
alignment: Alignment,
5860
}
5961

6062
impl PostgresTypeDerive {
@@ -64,11 +66,12 @@ impl PostgresTypeDerive {
6466
in_fn: Ident,
6567
out_fn: Ident,
6668
to_sql_config: ToSqlConfig,
69+
alignment: Alignment,
6770
) -> Result<CodeEnrichment<Self>, syn::Error> {
6871
if !to_sql_config.overrides_default() {
6972
crate::ident_is_acceptable_to_postgres(&name)?;
7073
}
71-
Ok(CodeEnrichment(Self { generics, name, in_fn, out_fn, to_sql_config }))
74+
Ok(CodeEnrichment(Self { generics, name, in_fn, out_fn, to_sql_config, alignment }))
7275
}
7376

7477
pub fn from_derive_input(
@@ -90,12 +93,14 @@ impl PostgresTypeDerive {
9093
&format!("{}_out", derive_input.ident).to_lowercase(),
9194
derive_input.ident.span(),
9295
);
96+
let alignment = Alignment::from_attributes(derive_input.attrs.as_slice())?;
9397
Self::new(
9498
derive_input.ident,
9599
derive_input.generics,
96100
funcname_in,
97101
funcname_out,
98102
to_sql_config,
103+
alignment,
99104
)
100105
}
101106
}
@@ -129,6 +134,11 @@ impl ToEntityGraphTokens for PostgresTypeDerive {
129134

130135
let to_sql_config = &self.to_sql_config;
131136

137+
let alignment = match &self.alignment {
138+
Alignment::On => quote! { Some(::std::mem::align_of::<#name>()) },
139+
Alignment::Off => quote !{ None },
140+
};
141+
132142
quote! {
133143
unsafe impl #impl_generics ::pgrx::pgrx_sql_entity_graph::metadata::SqlTranslatable for #name #ty_generics #where_clauses {
134144
fn argument_sql() -> core::result::Result<::pgrx::pgrx_sql_entity_graph::metadata::SqlMapping, ::pgrx::pgrx_sql_entity_graph::metadata::ArgumentError> {
@@ -190,6 +200,7 @@ impl ToEntityGraphTokens for PostgresTypeDerive {
190200
path_items.join("::")
191201
},
192202
to_sql_config: #to_sql_config,
203+
alignment: #alignment,
193204
};
194205
::pgrx::pgrx_sql_entity_graph::SqlGraphEntity::Type(submission)
195206
}
@@ -205,6 +216,7 @@ impl Parse for CodeEnrichment<PostgresTypeDerive> {
205216
let to_sql_config = ToSqlConfig::from_attributes(attrs.as_slice())?.unwrap_or_default();
206217
let in_fn = Ident::new(&format!("{}_in", ident).to_lowercase(), ident.span());
207218
let out_fn = Ident::new(&format!("{}_out", ident).to_lowercase(), ident.span());
208-
PostgresTypeDerive::new(ident, generics, in_fn, out_fn, to_sql_config)
219+
let alignment = Alignment::from_attributes(attrs.as_slice())?;
220+
PostgresTypeDerive::new(ident, generics, in_fn, out_fn, to_sql_config, alignment)
209221
}
210222
}

0 commit comments

Comments
 (0)