// Copyright 2020 ZomboDB, LLC <zombodb@gmail.com>. All rights reserved. Use of this source code is
// governed by the MIT license that can be found in the LICENSE file.

extern crate proc_macro;

mod operators;
mod rewriter;
use operators::{impl_postgres_eq, impl_postgres_hash, impl_postgres_ord};

use pgx_utils::*;
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::{quote, quote_spanned, ToTokens};
use rewriter::*;
use std::collections::HashSet;
use syn::spanned::Spanned;
use syn::{parse_macro_input, Attribute, Data, DeriveInput, Item, ItemFn};

/// Declare a function as `#[pg_guard]` to indicate that it is called from a Postgres `extern "C"`
/// function so that Rust `panic!()`s (and Postgres `elog(ERROR)`s) will be properly handled by `pgx`
#[proc_macro_attribute]
pub fn pg_guard(_attr: TokenStream, item: TokenStream) -> TokenStream {
    // get a usable token stream
    let ast = parse_macro_input!(item as syn::Item);

    let rewriter = PgGuardRewriter::new();

    match ast {
        // this is for processing the members of extern "C" { } blocks
        // functions inside the block get wrapped as public, top-level unsafe functions that are not "extern"
        Item::ForeignMod(block) => rewriter.extern_block(block).into(),

        // process top-level functions
        // these functions get wrapped as public extern "C" functions with #[no_mangle] so they
        // can also be called from C code
        Item::Fn(func) => rewriter.item_fn(func, None, false, false, false).0.into(),
        _ => {
            panic!("#[pg_guard] can only be applied to extern \"C\" blocks and top-level functions")
        }
    }
}

/// `#[pg_test]` functions are test functions (akin to `#[test]`), but they run in-process inside
/// Postgres during `cargo pgx test`.
#[proc_macro_attribute]
pub fn pg_test(attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut stream = proc_macro2::TokenStream::new();
    let args = parse_extern_attributes(proc_macro2::TokenStream::from(attr.clone()));

    let mut expected_error = None;
    args.into_iter().for_each(|v| {
        if let ExternArgs::Error(message) = v {
            expected_error = Some(message)
        }
    });

    stream.extend(proc_macro2::TokenStream::from(pg_extern(
        attr,
        item.clone(),
    )));

    let expected_error = match expected_error {
        Some(msg) => quote! {Some(#msg)},
        None => quote! {None},
    };

    let ast = parse_macro_input!(item as syn::Item);
    match ast {
        Item::Fn(func) => {
            let sql_funcname = func.sig.ident.to_string();
            let test_func_name =
                Ident::new(&format!("pg_{}", func.sig.ident.to_string()), func.span());

            let attributes = func.attrs;
            let mut att_stream = proc_macro2::TokenStream::new();

            for a in attributes.iter() {
                let as_str = a.tokens.to_string();
                att_stream.extend(quote! {
                    options.push(#as_str);
                });
            }

            stream.extend(quote! {
                #[test]
                fn #test_func_name() {
                    let mut options = Vec::new();
                    #att_stream

                    crate::pg_test::setup(options);
                    pgx_tests::run_test(#sql_funcname, #expected_error, crate::pg_test::postgresql_conf_options())
                }
            });
        }

        _ => panic!("#[pg_test] can only be applied to top-level functions"),
    }

    stream.into()
}

/// Associated macro for `#[pg_test]` to provide context back to your test framework to indicate
/// that the test system is being initialized
#[proc_macro_attribute]
pub fn initialize(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

/// Declare a function as `#[pg_operator]` to indicate that it represents a Postgres operator
/// `cargo pgx schema` will automatically generate the underlying SQL
#[proc_macro_attribute]
pub fn pg_operator(attr: TokenStream, item: TokenStream) -> TokenStream {
    pg_extern(attr, item)
}

/// Used with `#[pg_operator]`.  1 value which is the operator name itself
#[proc_macro_attribute]
pub fn opname(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

/// Used with `#[pg_operator]`.  1 value which is the function name
#[proc_macro_attribute]
pub fn commutator(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

/// Used with `#[pg_operator]`.  1 value which is the function name
#[proc_macro_attribute]
pub fn negator(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

/// Used with `#[pg_operator]`.  1 value which is the function name
#[proc_macro_attribute]
pub fn restrict(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

/// Used with `#[pg_operator]`.  1 value which is the function name
#[proc_macro_attribute]
pub fn join(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

/// Used with `#[pg_operator]`.  no values
#[proc_macro_attribute]
pub fn hashes(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

/// Used with `#[pg_operator]`.  no values
#[proc_macro_attribute]
pub fn merges(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

/**
Declare a Rust module and its contents to be in a schema.

The schema name will always be the `mod`'s identifier. So `mod flop` will create a `flop` schema.

If there is a schema inside a schema, the most specific schema is chosen.

In this example, the created `example` function is in the `dsl_filters` schema.

```rust,ignore
use pgx::*;

#[pg_schema]
mod dsl {
    use pgx::*;
    #[pg_schema]
    mod dsl_filters {
        use pgx::*;
        #[pg_extern]
        fn example() { todo!() }
    }
}
```

File modules (like `mod name;`) aren't able to be supported due to [`rust/#54725`](https://github.com/rust-lang/rust/issues/54725).

*/
#[proc_macro_attribute]
pub fn pg_schema(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let pgx_schema = parse_macro_input!(item as sql_entity_graph::Schema);
    pgx_schema.to_token_stream().into()
}

/**
Declare SQL to be included in generated extension script.

Accepts a String literal, a `name` attribute, and optionally others:

* `name = "item"`: Set the unique identifer to `"item"` for use in `requires` declarations.
* `requires = [item, item_two]`: References to other `name`s or Rust items which this SQL should be present after.
* `creates = [ Type(submod::Cust), Enum(Pre), Function(defined)]`: Communicates that this SQL block creates certain entities.
  Please note it **does not** create matching Rust types.
* `bootstrap` (**Unique**): Communicates that this is SQL intended to go before all other generated SQL.
* `finalize` (**Unique**): Communicates that this is SQL intended to go after all other generated SQL.

You can declare some SQL without any positioning information, meaning it can end up anywhere in the generated SQL:

```rust,ignore
use pgx_macros::extension_sql;

extension_sql!(
    r#"
    -- SQL statements
    "#,
    name = "demo",
);
```

To cause the SQL to be output at the start of the generated SQL:

```rust,ignore
use pgx_macros::extension_sql;

extension_sql!(
    r#"
    -- SQL statements
    "#,
    name = "demo",
    bootstrap,
);
```

To cause the SQL to be output at the end of the generated SQL:

```rust,ignore
use pgx_macros::extension_sql;

extension_sql!(
    r#"
    -- SQL statements
    "#,
    name = "demo",
    finalize,
);
```

To declare the SQL dependant, or a dependency of, other items:

```rust,ignore
use pgx_macros::extension_sql;

struct Treat;

mod dog_characteristics {
    enum DogAlignment {
        Good
    }
}

extension_sql!(r#"
    -- SQL statements
    "#,
    name = "named_one",
);

extension_sql!(r#"
    -- SQL statements
    "#,
    name = "demo",
    requires = [ "named_one", dog_characteristics::DogAlignment ],
);
```

To declare the SQL defines some entity (**Caution:** This is not recommended usage):

```rust,ignore
use pgx::stringinfo::StringInfo;
use pgx::*;
use pgx_utils::get_named_capture;

#[derive(Debug)]
#[repr(C)]
struct Complex {
    x: f64,
    y: f64,
}

extension_sql!(r#"\
        CREATE TYPE complex;\
    "#,
    name = "create_complex_type",
    creates = [Type(Complex)],
);

#[pg_extern(immutable)]
fn complex_in(input: &std::ffi::CStr) -> PgBox<Complex> {
    todo!()
}

#[pg_extern(immutable)]
fn complex_out(complex: PgBox<Complex>) -> &'static std::ffi::CStr {
    todo!()
}

extension_sql!(r#"\
        CREATE TYPE complex (
            internallength = 16,
            input = complex_in,
            output = complex_out,
            alignment = double
        );\
    "#,
    name = "demo",
    requires = ["create_complex_type", complex_in, complex_out],
);

```
*/
#[proc_macro]
pub fn extension_sql(input: TokenStream) -> TokenStream {
    fn wrapped(input: TokenStream) -> Result<TokenStream, syn::Error> {
        let ext_sql: sql_entity_graph::ExtensionSql = syn::parse(input)?;
        Ok(ext_sql.to_token_stream().into())
    }

    match wrapped(input) {
        Ok(tokens) => tokens,
        Err(e) => {
            let msg = e.to_string();
            TokenStream::from(quote! {
              compile_error!(#msg);
            })
        }
    }
}

/**
Declare SQL (from a file) to be included in generated extension script.

Accepts the same options as [`macro@extension_sql`]. `name` is automatically set to the file name (not the full path).

You can declare some SQL without any positioning information, meaning it can end up anywhere in the generated SQL:

```rust,ignore
use pgx_macros::extension_sql_file;
extension_sql_file!(
    "../static/demo.sql",
);
```

To override the default name:

```rust,ignore
use pgx_macros::extension_sql_file;

extension_sql_file!(
    "../static/demo.sql",
    name = "singlular",
);
```

For all other options, and examples of them, see [`macro@extension_sql`].
*/
#[proc_macro]
pub fn extension_sql_file(input: TokenStream) -> TokenStream {
    fn wrapped(input: TokenStream) -> Result<TokenStream, syn::Error> {
        let ext_sql: sql_entity_graph::ExtensionSqlFile = syn::parse(input)?;
        Ok(ext_sql.to_token_stream().into())
    }

    match wrapped(input) {
        Ok(tokens) => tokens,
        Err(e) => {
            let msg = e.to_string();
            TokenStream::from(quote! {
              compile_error!(#msg);
            })
        }
    }
}

/// Associated macro for `#[pg_extern]` or `#[macro@pg_operator]`.  Used to set the `SEARCH_PATH` option
/// on the `CREATE FUNCTION` statement.
#[proc_macro_attribute]
pub fn search_path(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

/**
Declare a function as `#[pg_extern]` to indicate that it can be used by Postgres as a UDF.

Optionally accepts the following attributes:

* `immutable`: Corresponds to [`IMMUTABLE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
* `strict`: Corresponds to [`STRICT`](https://www.postgresql.org/docs/current/sql-createfunction.html).
  + In most cases, `#[pg_extern]` can detect when no `Option<T>`s are used, and automatically set this.
* `stable`: Corresponds to [`STABLE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
* `volatile`: Corresponds to [`VOLATILE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
* `raw`: Corresponds to [`RAW`](https://www.postgresql.org/docs/current/sql-createfunction.html).
* `parallel_safe`: Corresponds to [`PARALLEL SAFE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
* `parallel_unsafe`: Corresponds to [`PARALLEL UNSAFE`](https://www.postgresql.org/docs/current/sql-createfunction.html).
* `parallel_restricted`: Corresponds to [`PARALLEL RESTRICTED`](https://www.postgresql.org/docs/current/sql-createfunction.html).
* `no_guard`: Do not use `#[pg_guard]` with the function.

Functions can accept and return any type which `pgx` supports. `pgx` supports many PostgreSQL types by default.
New types can be defined via [`macro@PostgresType`] or [`macro@PostgresEnum`].


Without any arguments or returns:
```rust,ignore
use pgx::*;
#[pg_extern]
fn foo() { todo!() }
```

# Arguments
It's possible to pass even complex arguments:

```rust,ignore
use pgx::*;
#[pg_extern]
fn boop(
    a: i32,
    b: Option<i32>,
    c: Vec<i32>,
    d: Option<Vec<Option<i32>>>
) { todo!() }
```

It's possible to set argument defaults, set by PostgreSQL when the function is invoked:

```rust,ignore
use pgx::*;
#[pg_extern]
fn boop(a: default!(i32, 11111)) { todo!() }
#[pg_extern]
fn doop(
    a: default!(Vec<Option<&str>>, "ARRAY[]::text[]"),
    b: default!(String, "'note the inner quotes!'")
) { todo!() }
```

The `default!()` macro may only be used in argument position.

It accepts 2 arguments:

* A type
* A `bool`, numeric, or SQL string to represent the default. `"NULL"` is a possible value, as is `"'string'"`

**If the default SQL entity created by the extension:** ensure it is added to `requires` as a dependency:

```rust,ignore
use pgx::*;
#[pg_extern]
fn default_value() -> i32 { todo!() }

#[pg_extern(
    requires = [ default_value, ],
)]
fn do_it(
    a: default!(i32, "default_value()"),
) { todo!() }
```

# Returns

It's possible to return even complex values, as well:

```rust,ignore
use pgx::*;
#[pg_extern]
fn boop() -> i32 { todo!() }
#[pg_extern]
fn doop() -> Option<i32> { todo!() }
#[pg_extern]
fn swoop() -> Option<Vec<Option<i32>>> { todo!() }
#[pg_extern]
fn floop() -> (i32, i32) { todo!() }
```

Like in PostgreSQL, it's possible to return tables using iterators and the `name!()` macro:

```rust,ignore
use pgx::*;
#[pg_extern]
fn floop() -> impl Iterator<Item = (name!(a, i32), name!(b, i32))> {
    None.into_iter() // Help type inference...
}

#[pg_extern]
fn singlular_floop() -> (name!(a, i32), name!(b, i32)) {
    todo!()
}
```

The `name!()` macro may only be used in return position inside the `Item` of an `impl Iterator`.

It accepts 2 arguments:

* A name, such as `example`
* A type

# Special Cases

`pg_sys::Oid` is a special cased type alias, in order to use it as an argument or return it must be
passed with it's full module path (`pg_sys::Oid`) in order to be resolved.

```rust,ignore
use pgx::*;

#[pg_extern]
fn example_arg(animals: pg_sys::Oid) {
    todo!()
}

#[pg_extern]
fn example_return() -> pg_sys::Oid {
    todo!()
}
```

*/
#[proc_macro_attribute]
pub fn pg_extern(attr: TokenStream, item: TokenStream) -> TokenStream {
    let args = parse_extern_attributes(proc_macro2::TokenStream::from(attr.clone()));

    let sql_graph_entity_item =
        sql_entity_graph::PgExtern::new(attr.clone().into(), item.clone().into()).unwrap();

    let ast = parse_macro_input!(item as syn::Item);
    match ast {
        Item::Fn(func) => rewrite_item_fn(func, args, &sql_graph_entity_item).into(),
        _ => panic!("#[pg_extern] can only be applied to top-level functions"),
    }
}

fn rewrite_item_fn(
    mut func: ItemFn,
    extern_args: HashSet<ExternArgs>,
    sql_graph_entity_submission: &sql_entity_graph::PgExtern,
) -> proc_macro2::TokenStream {
    let is_raw = extern_args.contains(&ExternArgs::Raw);
    let no_guard = extern_args.contains(&ExternArgs::NoGuard);

    let finfo_name = syn::Ident::new(
        &format!("pg_finfo_{}_wrapper", func.sig.ident),
        Span::call_site(),
    );

    // use the PgGuardRewriter to go ahead and wrap the function here, rather than applying
    // a #[pg_guard] macro to the original function.  This is necessary so that compiler
    // errors/warnings indicate the proper line numbers
    let rewriter = PgGuardRewriter::new();

    // make the function 'extern "C"' because this is for the #[pg_extern[ macro
    func.sig.abi = Some(syn::parse_str("extern \"C\"").unwrap());
    let func_span = func.span();
    let (rewritten_func, need_wrapper) = rewriter.item_fn(
        func,
        Some(sql_graph_entity_submission),
        true,
        is_raw,
        no_guard,
    );

    if need_wrapper {
        quote_spanned! {func_span=>
            #[no_mangle]
            pub extern "C" fn #finfo_name() -> &'static pg_sys::Pg_finfo_record {
                const V1_API: pg_sys::Pg_finfo_record = pg_sys::Pg_finfo_record { api_version: 1 };
                &V1_API
            }

            #rewritten_func
        }
    } else {
        quote_spanned! {func_span=>

            #rewritten_func
        }
    }
}

/**
Generate necessary bindings for using the enum with PostgreSQL.

```rust,ignore
# use pgx_pg_sys as pg_sys;
use pgx::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PostgresEnum)]
enum DogNames {
    Nami,
    Brandy,
}
```

*/
#[proc_macro_derive(PostgresEnum, attributes(requires))]
pub fn postgres_enum(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as syn::DeriveInput);

    impl_postgres_enum(ast).into()
}

fn impl_postgres_enum(ast: DeriveInput) -> proc_macro2::TokenStream {
    let mut stream = proc_macro2::TokenStream::new();
    let sql_graph_entity_ast = ast.clone();
    let enum_ident = ast.ident;
    let enum_name = enum_ident.to_string();

    // validate that we're only operating on an enum
    let enum_data = match ast.data {
        Data::Enum(e) => e,
        _ => panic!("#[derive(PostgresEnum)] can only be applied to enums"),
    };

    let mut from_datum = proc_macro2::TokenStream::new();
    let mut into_datum = proc_macro2::TokenStream::new();

    for d in enum_data.variants.clone() {
        let label_ident = &d.ident;
        let label_string = label_ident.to_string();

        from_datum.extend(quote! { #label_string => Some(#enum_ident::#label_ident), });
        into_datum.extend(quote! { #enum_ident::#label_ident => Some(pgx::lookup_enum_by_label(#enum_name, #label_string)), });
    }

    stream.extend(quote! {
        impl pgx::FromDatum for #enum_ident {
            #[inline]
            unsafe fn from_datum(datum: pgx::pg_sys::Datum, is_null: bool, typeoid: pgx::pg_sys::Oid) -> Option<#enum_ident> {
                if is_null {
                    None
                } else {
                    let (name, _, _) = pgx::lookup_enum_by_oid(datum as pgx::pg_sys::Oid);
                    match name.as_str() {
                        #from_datum
                        _ => panic!("invalid enum value: {}", name)
                    }
                }
            }
        }

        impl pgx::IntoDatum for #enum_ident {
            #[inline]
            fn into_datum(self) -> Option<pgx::pg_sys::Datum> {
                match self {
                    #into_datum
                }
            }

            fn type_oid() -> pg_sys::Oid {
                pgx::regtypein(#enum_name)
            }

        }
    });

    let sql_graph_entity_item =
        sql_entity_graph::PostgresEnum::from_derive_input(sql_graph_entity_ast).unwrap();
    sql_graph_entity_item.to_tokens(&mut stream);

    stream
}

/**
Generate necessary bindings for using the type with PostgreSQL.

```rust,ignore
# use pgx_pg_sys as pg_sys;
use pgx::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PostgresType)]
struct Dog {
    treats_recieved: i64,
    pets_gotten: i64,
}
```

Optionally accepts the following attributes:

* `inoutfuncs(some_in_fn, some_out_fn)`: Define custom in/out functions for the type.
* `pgvarlena_inoutfuncs(some_in_fn, some_out_fn)`: Define custom in/out functions for the `PgVarlena` of this type.

*/
#[proc_macro_derive(PostgresType, attributes(inoutfuncs, pgvarlena_inoutfuncs, requires))]
pub fn postgres_type(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as syn::DeriveInput);

    impl_postgres_type(ast).into()
}

fn impl_postgres_type(ast: DeriveInput) -> proc_macro2::TokenStream {
    let name = &ast.ident;
    let generics = &ast.generics;
    let has_lifetimes = generics.lifetimes().next();
    let funcname_in = Ident::new(&format!("{}_in", name).to_lowercase(), name.span());
    let funcname_out = Ident::new(&format!("{}_out", name).to_lowercase(), name.span());
    let mut args = parse_postgres_type_args(&ast.attrs);
    let mut stream = proc_macro2::TokenStream::new();

    // validate that we're only operating on a struct
    match ast.data {
        Data::Struct(_) => { /* this is okay */ }
        _ => panic!("#[derive(PostgresType)] can only be applied to structs"),
    }

    if args.is_empty() {
        // assume the user wants us to implement the InOutFuncs
        args.insert(PostgresTypeAttribute::Default);
    }

    let lifetime = match has_lifetimes {
        Some(lifetime) => quote! {#lifetime},
        None => quote! {'static},
    };

    // all #[derive(PostgresType)] need to implement that trait
    stream.extend(quote! {
        impl #generics pgx::PostgresType for #name #generics { }
    });

    // and if we don't have custom inout/funcs, we use the JsonInOutFuncs trait
    // which implements _in and _out #[pg_extern] functions that just return the type itself
    if args.contains(&PostgresTypeAttribute::Default) {
        let inout_generics = if has_lifetimes.is_some() {
            quote! {#generics}
        } else {
            quote! {<'_>}
        };

        stream.extend(quote! {
            impl #generics JsonInOutFuncs #inout_generics for #name #generics {}

            #[pg_extern(immutable,parallel_safe)]
            pub fn #funcname_in #generics(input: &#lifetime std::ffi::CStr) -> #name #generics {
                #name::input(input)
            }

            #[pg_extern(immutable,parallel_safe)]
            pub fn #funcname_out #generics(input: #name #generics) -> &#lifetime std::ffi::CStr {
                let mut buffer = StringInfo::new();
                input.output(&mut buffer);
                buffer.into()
            }

        });
    } else if args.contains(&PostgresTypeAttribute::InOutFuncs) {
        // otherwise if it's InOutFuncs our _in/_out functions use an owned type instance
        stream.extend(quote! {
            #[pg_extern(immutable,parallel_safe)]
            pub fn #funcname_in #generics(input: &#lifetime std::ffi::CStr) -> #name #generics {
                #name::input(input)
            }

            #[pg_extern(immutable,parallel_safe)]
            pub fn #funcname_out #generics(input: #name #generics) -> &#lifetime std::ffi::CStr {
                let mut buffer = StringInfo::new();
                input.output(&mut buffer);
                buffer.into()
            }
        });
    } else if args.contains(&PostgresTypeAttribute::PgVarlenaInOutFuncs) {
        // otherwise if it's PgVarlenaInOutFuncs our _in/_out functions use a PgVarlena
        stream.extend(quote! {
            #[pg_extern(immutable,parallel_safe)]
            pub fn #funcname_in #generics(input: &#lifetime std::ffi::CStr) -> pgx::PgVarlena<#name #generics> {
                #name::input(input)
            }

            #[pg_extern(immutable,parallel_safe)]
            pub fn #funcname_out #generics(input: pgx::PgVarlena<#name #generics>) -> &#lifetime std::ffi::CStr {
                let mut buffer = StringInfo::new();
                input.output(&mut buffer);
                buffer.into()
            }
        });
    }

    let sql_graph_entity_item = sql_entity_graph::PostgresType::from_derive_input(ast).unwrap();
    sql_graph_entity_item.to_tokens(&mut stream);

    stream
}

#[proc_macro_derive(PostgresGucEnum, attributes(hidden))]
pub fn postgres_guc_enum(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as syn::DeriveInput);

    impl_guc_enum(ast).into()
}

fn impl_guc_enum(ast: DeriveInput) -> proc_macro2::TokenStream {
    let mut stream = proc_macro2::TokenStream::new();

    // validate that we're only operating on an enum
    let enum_data = match ast.data {
        Data::Enum(e) => e,
        _ => panic!("#[derive(PostgresGucEnum)] can only be applied to enums"),
    };
    let enum_name = ast.ident;
    let enum_len = enum_data.variants.len();

    let mut from_match_arms = proc_macro2::TokenStream::new();
    for (idx, e) in enum_data.variants.iter().enumerate() {
        let label = &e.ident;
        let idx = idx as i32;
        from_match_arms.extend(quote! { #idx => #enum_name::#label, })
    }
    from_match_arms.extend(quote! { _ => panic!("Unrecognized ordinal ")});

    let mut ordinal_match_arms = proc_macro2::TokenStream::new();
    for (idx, e) in enum_data.variants.iter().enumerate() {
        let label = &e.ident;
        let idx = idx as i32;
        ordinal_match_arms.extend(quote! { #enum_name::#label => #idx, });
    }

    let mut build_array_body = proc_macro2::TokenStream::new();
    for (idx, e) in enum_data.variants.iter().enumerate() {
        let label = e.ident.to_string();
        let mut hidden = false;

        for att in e.attrs.iter() {
            let att = quote! {#att}.to_string();
            if att == "# [hidden]" {
                hidden = true;
                break;
            }
        }

        build_array_body.extend(quote! {
            pgx::PgBox::<_, pgx::AllocatedByPostgres>::with(&mut slice[#idx], |v| {
                v.name = pgx::PgMemoryContexts::TopMemoryContext.pstrdup(#label);
                v.val = #idx as i32;
                v.hidden = #hidden;
            });
        });
    }

    stream.extend(quote! {
        impl pgx::GucEnum<#enum_name> for #enum_name {
            fn from_ordinal(ordinal: i32) -> #enum_name {
                match ordinal {
                    #from_match_arms
                }
            }

            fn to_ordinal(&self) -> i32 {
                match *self {
                    #ordinal_match_arms
                }
            }

            unsafe fn config_matrix(&self) -> *const pgx::pg_sys::config_enum_entry {
                let slice = pgx::PgMemoryContexts::TopMemoryContext.palloc0_slice::<pg_sys::config_enum_entry>(#enum_len + 1usize);

                #build_array_body

                slice.as_ptr()
            }
        }
    });

    stream
}

#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
enum PostgresTypeAttribute {
    InOutFuncs,
    PgVarlenaInOutFuncs,
    Default,
}

fn parse_postgres_type_args(attributes: &[Attribute]) -> HashSet<PostgresTypeAttribute> {
    let mut categorized_attributes = HashSet::new();

    for a in attributes {
        let path = &a.path;
        let path = quote! {#path}.to_string();
        match path.as_str() {
            "inoutfuncs" => {
                categorized_attributes.insert(PostgresTypeAttribute::InOutFuncs);
            }

            "pgvarlena_inoutfuncs" => {
                categorized_attributes.insert(PostgresTypeAttribute::PgVarlenaInOutFuncs);
            }

            _ => {
                // we can just ignore attributes we don't understand
            }
        };
    }

    categorized_attributes
}

/**
Generate necessary code using the type in operators like `==` and `!=`.

```rust,ignore
# use pgx_pg_sys as pg_sys;
use pgx::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PostgresEnum, PartialEq, Eq, PostgresEq)]
enum DogNames {
    Nami,
    Brandy,
}
```

*/
#[proc_macro_derive(PostgresEq)]
pub fn postgres_eq(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as syn::DeriveInput);
    impl_postgres_eq(ast).into()
}

/**
Generate necessary code using the type in operators like `>`, `<`, `<=`, and `>=`.

```rust,ignore
# use pgx_pg_sys as pg_sys;
use pgx::*;
use serde::{Deserialize, Serialize};
#[derive(
    Debug, Serialize, Deserialize, PartialEq, Eq,
     PartialOrd, Ord, PostgresEnum, PostgresOrd
)]
enum DogNames {
    Nami,
    Brandy,
}
```

*/
#[proc_macro_derive(PostgresOrd)]
pub fn postgres_ord(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as syn::DeriveInput);
    impl_postgres_ord(ast).into()
}

/**
Generate necessary code for stable hashing the type so it can be used with `USING hash` indexes.

```rust,ignore
# use pgx_pg_sys as pg_sys;
use pgx::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, PostgresEnum, PostgresHash)]
enum DogNames {
    Nami,
    Brandy,
}
```

*/
#[proc_macro_derive(PostgresHash)]
pub fn postgres_hash(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as syn::DeriveInput);
    impl_postgres_hash(ast).into()
}
