Skip to content

Commit

Permalink
wip: support bare paths in value position of name value arg
Browse files Browse the repository at this point in the history
  • Loading branch information
bitwalker committed Feb 4, 2022
1 parent e60c8bc commit 344f685
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 59 deletions.
2 changes: 1 addition & 1 deletion pgx-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1022,7 +1022,7 @@ e.g. `#[pgx(sql = false)]`
Currently `sql` can be provided one of the following:
* Disable SQL generation with `#[pgx(sql = false)]`
* Call custom SQL generator function with `#[pgx(sql = "path::to_function")]`
* Call custom SQL generator function with `#[pgx(sql = path::to_function)]`
* Render a specific fragment of SQL with a string `#[pgx(sql = "CREATE OR REPLACE FUNCTION ...")]`
*/
Expand Down
4 changes: 2 additions & 2 deletions pgx-tests/src/tests/schema_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ mod test_schema {
#[pg_extern(sql = false)]
fn func_elided_from_schema() {}

#[pg_extern(sql = "generate_function")]
#[pg_extern(sql = generate_function)]
fn func_generated_with_custom_sql() {}

#[derive(Debug, PostgresType, Serialize, Deserialize)]
Expand All @@ -25,7 +25,7 @@ mod test_schema {
pub struct ElidedType(pub u64);

#[derive(Debug, PostgresType, Serialize, Deserialize)]
#[pgx(sql = "generate_type")]
#[pgx(sql = generate_type)]
pub struct OtherType(pub u64);

#[derive(Debug, PostgresType, Serialize, Deserialize)]
Expand Down
2 changes: 2 additions & 0 deletions pgx-utils/src/sql_entity_graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod extension_sql;
mod pg_aggregate;
mod pg_extern;
mod pg_schema;
mod pgx_attribute;
mod positioning_ref;
mod postgres_enum;
mod postgres_hash;
Expand All @@ -14,6 +15,7 @@ pub use extension_sql::{ExtensionSql, ExtensionSqlFile, SqlDeclared};
pub use pg_aggregate::PgAggregate;
pub use pg_extern::{Argument, PgExtern, PgOperator};
pub use pg_schema::Schema;
pub use pgx_attribute::{ArgValue, NameValueArg, PgxArg, PgxAttribute};
pub use positioning_ref::PositioningRef;
pub use postgres_enum::PostgresEnum;
pub use postgres_hash::PostgresHash;
Expand Down
14 changes: 8 additions & 6 deletions pgx-utils/src/sql_entity_graph/pg_extern/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,16 +112,18 @@ impl Parse for Attribute {
Self::Requires(content.parse_terminated(PositioningRef::parse)?)
}
"sql" => {
use crate::sql_entity_graph::ArgValue;
use syn::Lit;

let _eq: Token![=] = input.parse()?;
match input.parse::<syn::Lit>()? {
Lit::Bool(b) => Self::Sql(ToSqlConfig::from(b.value)),
Lit::Str(s) => Self::Sql(ToSqlConfig::from(s)),
expr => {
match input.parse::<ArgValue>()? {
ArgValue::Path(p) => Self::Sql(ToSqlConfig::from(p)),
ArgValue::Lit(Lit::Bool(b)) => Self::Sql(ToSqlConfig::from(b.value)),
ArgValue::Lit(Lit::Str(s)) => Self::Sql(ToSqlConfig::from(s)),
ArgValue::Lit(other) => {
return Err(syn::Error::new(
expr.span(),
"expected boolean or string literal",
other.span(),
"expected boolean, path, or string literal",
))
}
}
Expand Down
80 changes: 80 additions & 0 deletions pgx-utils/src/sql_entity_graph/pgx_attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use syn::parse::{Parse, ParseStream};
use syn::{parenthesized, punctuated::Punctuated};
use syn::{token, Token};

/// This struct is intented to represent the contents of the `#[pgx]` attribute when parsed.
///
/// The intended usage is to parse an `Attribute`, then use `attr.parse_args::<PgxAttribute>()?` to
/// parse the contents of the attribute into this struct.
///
/// We use this rather than `Attribute::parse_meta` because it is not supported to parse bare paths
/// as values of a `NameValueMeta`, and we want to support that to avoid conflating SQL strings with
/// paths-as-strings. We re-use as much of the standard `parse_meta` structure types as possible though.
pub struct PgxAttribute {
pub args: Vec<PgxArg>,
}

impl Parse for PgxAttribute {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let parser = Punctuated::<PgxArg, Token![,]>::parse_terminated;
let punctuated = input.call(parser)?;
let args = punctuated
.into_pairs()
.map(|p| p.into_value())
.collect::<Vec<_>>();
Ok(Self { args })
}
}

/// This enum is akin to `syn::Meta`, but supports a custom `NameValue` variant which allows
/// for bare paths in the value position.
pub enum PgxArg {
Path(syn::Path),
List(syn::MetaList),
NameValue(NameValueArg),
}

impl Parse for PgxArg {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let path = input.parse::<syn::Path>()?;
if input.peek(token::Paren) {
let content;
Ok(Self::List(syn::MetaList {
path,
paren_token: parenthesized!(content in input),
nested: content.parse_terminated(syn::NestedMeta::parse)?,
}))
} else if input.peek(Token![=]) {
Ok(Self::NameValue(NameValueArg {
path,
eq_token: input.parse()?,
value: input.parse()?,
}))
} else {
Ok(Self::Path(path))
}
}
}

/// This struct is akin to `syn::NameValueMeta`, but allows for more than just `syn::Lit` as a value.
pub struct NameValueArg {
pub path: syn::Path,
pub eq_token: syn::token::Eq,
pub value: ArgValue,
}

/// This is the type of a value that can be used in the value position of a `name = value` attribute argument.
pub enum ArgValue {
Path(syn::Path),
Lit(syn::Lit),
}

impl Parse for ArgValue {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
if input.peek(syn::Lit) {
return Ok(Self::Lit(input.parse()?));
}

Ok(Self::Path(input.parse()?))
}
}
99 changes: 49 additions & 50 deletions pgx-utils/src/sql_entity_graph/to_sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use std::hash::Hash;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::spanned::Spanned;
use syn::{AttrStyle, Attribute, Lit, Meta, MetaList, MetaNameValue, NestedMeta};
use syn::{AttrStyle, Attribute, Lit};

use super::{ArgValue, PgxArg, PgxAttribute};

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ToSqlConfig {
Expand All @@ -20,20 +22,21 @@ impl From<bool> for ToSqlConfig {
}
}
}
impl From<syn::Path> for ToSqlConfig {
fn from(path: syn::Path) -> Self {
Self {
enabled: true,
callback: Some(path),
content: None,
}
}
}
impl From<syn::LitStr> for ToSqlConfig {
fn from(content: syn::LitStr) -> Self {
if let Ok(path) = content.parse::<syn::Path>() {
return Self {
enabled: true,
callback: Some(path),
content: None,
};
} else {
return Self {
enabled: true,
callback: None,
content: Some(content),
};
Self {
enabled: true,
callback: None,
content: Some(content),
}
}
}
Expand All @@ -60,47 +63,43 @@ impl ToSqlConfig {
));
}

match attr.parse_meta()? {
Meta::List(MetaList { nested, .. }) => {
for nm in nested.iter() {
if let NestedMeta::Meta(ref meta) = nm {
match meta {
Meta::NameValue(MetaNameValue {
ref path, ref lit, ..
}) if path.is_ident("sql") => match lit {
Lit::Bool(ref b) => {
return Ok(Some(Self {
enabled: b.value,
callback: None,
content: None,
}));
}
Lit::Str(ref s) => {
if let Ok(path) = s.parse::<syn::Path>() {
return Ok(Some(Self {
enabled: true,
callback: Some(path),
content: None,
}));
} else {
return Ok(Some(Self {
enabled: true,
callback: None,
content: Some(s.clone()),
}));
}
}
_ => return Err(syn::Error::new(lit.span(), INVALID_ATTR_CONTENT)),
},
_ => return Ok(None),
}
}
let attr = attr.parse_args::<PgxAttribute>()?;
for arg in attr.args.iter() {
if let PgxArg::NameValue(ref nv) = arg {
if !nv.path.is_ident("sql") {
continue;
}

Ok(None)
match nv.value {
ArgValue::Path(ref callback_path) => {
return Ok(Some(Self {
enabled: true,
callback: Some(callback_path.clone()),
content: None,
}));
}
ArgValue::Lit(Lit::Bool(ref b)) => {
return Ok(Some(Self {
enabled: b.value,
callback: None,
content: None,
}));
}
ArgValue::Lit(Lit::Str(ref s)) => {
return Ok(Some(Self {
enabled: true,
callback: None,
content: Some(s.clone()),
}));
}
ArgValue::Lit(ref other) => {
return Err(syn::Error::new(other.span(), INVALID_ATTR_CONTENT));
}
}
}
_ => Ok(None),
}

Ok(None)
}

/// Used to parse a generator config from a set of item attributes
Expand Down

0 comments on commit 344f685

Please sign in to comment.