Skip to content

Latest commit

 

History

History
547 lines (387 loc) · 14.8 KB

File metadata and controls

547 lines (387 loc) · 14.8 KB

v18.0 Migration Guide

This release changes how pgrx generates extension SQL.

The main point of the release is simple: schema generation now happens in a single compilation pass.

Older pgrx versions needed two builds:

  1. build the extension shared library
  2. build and run a separate pgrx_embed binary to collect SQL metadata

That second pass is gone now. Metadata is embedded into the shared library during the normal build, and cargo-pgrx reads it back out afterward.

Why you should care:

  • local edit/build/test loops get shorter
  • CI jobs spend less time rebuilding the same extension just to extract schema
  • the schema-generation pipeline is easier to understand and debug (debatable!)

The short version:

  • schema generation now uses one compilation pass instead of two
  • the old pgrx_embed build step is gone
  • manual SqlTranslatable implementations now use associated consts
  • schema dependency matching now uses TYPE_IDENT

Most extensions won't need much work. If your extension only uses ordinary #[pg_extern], #[derive(PostgresType)], #[derive(PostgresEnum)], and the default templates, you can probably skim this file and move on.

You should read this guide if any of the following are true:

  • your crate still has src/bin/pgrx_embed.rs
  • your Cargo.toml still declares a [[bin]] target for pgrx_embed
  • you wrote unsafe impl SqlTranslatable for ... by hand
  • you gate code on cfg(pgrx_embed)
  • you use extension_sql!(..., creates = [Type(T)]/[Enum(T)]) with manual types

Why This Changed

Older pgrx versions generated SQL by doing a second build, compiling a pgrx_embed binary, and running that binary to collect metadata.

That second build is gone now. Metadata is embedded directly into the shared library during the normal build, then cargo-pgrx reads it back out.

The whole point is to get schema generation down to one compilation pass. That means faster local iteration and shorter CI builds, especially in workspaces where the extra embed build was noticeable.

The user-visible changes are:

  1. the old embed binary is no longer part of the pipeline
  2. manual SQL metadata has to be available as compile-time constants

The new design is simpler, faster, and easier to reason about. It also means manual SqlTranslatable impls need to move from methods to associated consts.

If You Still Have pgrx_embed

Delete it.

If your crate used to look like this:

[lib]
crate-type = ["cdylib", "lib"]

[[bin]]
name = "pgrx_embed_my_extension"
path = "./src/bin/pgrx_embed.rs"

and:

::pgrx::pgrx_embed!();

it should now look like this:

[lib]
crate-type = ["cdylib"]

and the src/bin/pgrx_embed.rs file should be deleted.

Why: there is no second embed build anymore, so keeping the old bin target doesn't help and can only confuse the migration. The current templates and examples also no longer need "lib" in crate-type.

If You Wrote SqlTranslatable By Hand

This is the main breaking change.

The old shape looked like this:

use pgrx::pgrx_sql_entity_graph::metadata::{
    ArgumentError, Returns, ReturnsError, SqlMapping, SqlTranslatable,
};

pub struct MyType;

unsafe impl SqlTranslatable for MyType {
    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
        Ok(SqlMapping::As("my_type".into()))
    }

    fn return_sql() -> Result<Returns, ReturnsError> {
        Ok(Returns::One(SqlMapping::As("my_type".into())))
    }
}

The new shape looks like this:

use pgrx::pgrx_sql_entity_graph::metadata::{
    ArgumentError, ReturnsError, ReturnsRef, SqlMappingRef, SqlTranslatable,
    TypeOrigin,
};

pub struct MyType;

unsafe impl SqlTranslatable for MyType {
    const TYPE_IDENT: &'static str = pgrx::pgrx_resolved_type!(MyType);
    const TYPE_ORIGIN: TypeOrigin = TypeOrigin::ThisExtension;
    const ARGUMENT_SQL: Result<SqlMappingRef, ArgumentError> =
        Ok(SqlMappingRef::literal("my_type"));
    const RETURN_SQL: Result<ReturnsRef, ReturnsError> =
        Ok(ReturnsRef::One(SqlMappingRef::literal("my_type")));
}

Why:

  • TYPE_IDENT is now the dependency identity for the type
  • TYPE_ORIGIN tells the graph builder whether the SQL type comes from this extension or from somewhere else
  • ARGUMENT_SQL and RETURN_SQL must be const because metadata is embedded at compile time

If you see an error like this:

error[E0046]: not all trait items implemented, missing: `TYPE_IDENT`,
`TYPE_ORIGIN`, `ARGUMENT_SQL`, `RETURN_SQL`

this is the fix.

Use impl_sql_translatable! for fixed external mappings

If your wrapper maps to an existing SQL type and the argument and return SQL are the same, you don't need to write the full unsafe impl.

Use the helper macro from pgrx::prelude::*:

use pgrx::prelude::*;

pub struct UuidWrapper;

impl_sql_translatable!(UuidWrapper, "uuid");

There is also an argument-only form for pseudo-types:

use pgrx::prelude::*;

pub struct InternalArg(*mut core::ffi::c_void);

impl_sql_translatable!(InternalArg, arg_only = "internal");

The helper always uses pgrx_resolved_type!(T) for TYPE_IDENT and TypeOrigin::External for TYPE_ORIGIN. If the type is owned by this extension, or if the argument and return SQL differ, keep writing the SqlTranslatable impl by hand.

TYPE_IDENT Should Come From pgrx_resolved_type!

Don't hard-code a bare string unless you have a very specific reason and know exactly what you are doing.

This is fragile:

unsafe impl SqlTranslatable for MyType {
    const TYPE_IDENT: &'static str = "MyType";
    // ...
}

This is correct:

unsafe impl SqlTranslatable for MyType {
    const TYPE_IDENT: &'static str = pgrx::pgrx_resolved_type!(MyType);
    // ...
}

Why: schema matching now happens by TYPE_IDENT. The resolved form ties the identity to the impl or derive site plus the canonical type spelling you pass to the macro, which avoids drift between manual impls, re-exports, and derive-generated metadata.

Pick The Right TYPE_ORIGIN

This matters more than it used to.

Extension-owned SQL types

If the SQL type is created by your extension, use TypeOrigin::ThisExtension.

Example:

use pgrx::pgrx_sql_entity_graph::metadata::{
    ArgumentError, ReturnsError, ReturnsRef, SqlMappingRef, SqlTranslatable,
    TypeOrigin,
};
use pgrx::prelude::*;

#[derive(Debug)]
#[repr(C)]
pub struct Complex {
    pub r: f64,
    pub i: f64,
}

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

unsafe impl SqlTranslatable for Complex {
    const TYPE_IDENT: &'static str = pgrx::pgrx_resolved_type!(Complex);
    const TYPE_ORIGIN: TypeOrigin = TypeOrigin::ThisExtension;
    const ARGUMENT_SQL: Result<SqlMappingRef, ArgumentError> =
        Ok(SqlMappingRef::literal("complex"));
    const RETURN_SQL: Result<ReturnsRef, ReturnsError> =
        Ok(ReturnsRef::One(SqlMappingRef::literal("complex")));
}

Use this pattern when you are manually implementing SqlTranslatable for a type whose SQL type is owned by this extension.

If you use #[derive(PostgresType)] or #[derive(PostgresEnum)], pgrx generates the SqlTranslatable impl for you, so you do not need to write this by hand.

Typical cases are:

  • a hand-written custom datum type whose SQL type is created by your extension
  • a type created by your own extension_sql! block

Mappings to existing SQL types

If your Rust type maps to an existing SQL type, use TypeOrigin::External.

Implementing SqlTranslatable by itself does not emit a CREATE TYPE statement. The only things that create a SQL type are:

  • derive-generated type or enum entities
  • your own SQL declarations, such as extension_sql!(..., creates = [Type(T)])

Old code sometimes blurred two different intentions:

  1. define a new SQL type owned by the extension
  2. use a Rust wrapper whose SQL spelling should be an existing type like uuid

Those migrate differently.

If you are defining a new SQL type

Keep the extension_sql!() block, keep the type extension-owned, and make the SQL spelling match the type you are actually creating.

Before:

extension_sql!(
    r#"CREATE TYPE uuid_wrapper;"#,
    name = "create_uuid_wrapper",
    creates = [Type(UuidWrapper)]
);

unsafe impl SqlTranslatable for UuidWrapper {
    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
        Ok(SqlMapping::As("uuid_wrapper".into()))
    }

    fn return_sql() -> Result<Returns, ReturnsError> {
        Ok(Returns::One(SqlMapping::As("uuid_wrapper".into())))
    }
}

After:

use pgrx::pgrx_sql_entity_graph::metadata::{
    ArgumentError, ReturnsError, ReturnsRef, SqlMappingRef, SqlTranslatable,
    TypeOrigin,
};

extension_sql!(
    r#"CREATE TYPE uuid_wrapper;"#,
    name = "create_uuid_wrapper",
    creates = [Type(UuidWrapper)]
);

pub struct UuidWrapper;

unsafe impl SqlTranslatable for UuidWrapper {
    const TYPE_IDENT: &'static str = pgrx::pgrx_resolved_type!(UuidWrapper);
    const TYPE_ORIGIN: TypeOrigin = TypeOrigin::ThisExtension;
    const ARGUMENT_SQL: Result<SqlMappingRef, ArgumentError> =
        Ok(SqlMappingRef::literal("uuid_wrapper"));
    const RETURN_SQL: Result<ReturnsRef, ReturnsError> =
        Ok(ReturnsRef::One(SqlMappingRef::literal("uuid_wrapper")));
}

With that setup, a later #[pg_extern] that takes UuidWrapper will render its SQL argument as uuid_wrapper, and the extension_sql!() declaration is what creates the SQL type node in the schema graph.

If you are only wrapping an existing SQL type

Simply mark the mapping as external.

Before:

unsafe impl SqlTranslatable for UuidWrapper {
    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
        Ok(SqlMapping::As("uuid".into()))
    }

    fn return_sql() -> Result<Returns, ReturnsError> {
        Ok(Returns::One(SqlMapping::As("uuid".into())))
    }
}

After:

use pgrx::prelude::*;

pub struct UuidWrapper;

impl_sql_translatable!(UuidWrapper, "uuid");

With that setup, a later #[pg_extern] that takes UuidWrapper will render its SQL argument as uuid. No CREATE TYPE uuid_wrapper statement is emitted, because you are not defining a new SQL type.

Under the hood, the macro still sets TYPE_IDENT with pgrx_resolved_type!(UuidWrapper) and marks the mapping as External.

If you had old code that did both, a CREATE TYPE uuid_wrapper declaration and an argument_sql() that returned "uuid", decide which behavior you actually want. That old code was trying to describe two different SQL types at once.

If You Used Manual Wrapper Types In #[pg_extern]

This is still supported. You just need the new const-based metadata.

Before:

use pgrx::pgrx_sql_entity_graph::metadata::{
    ArgumentError, Returns, ReturnsError, SqlMapping, SqlTranslatable,
};

pub struct FakeAnyElement;

unsafe impl SqlTranslatable for FakeAnyElement {
    fn argument_sql() -> Result<SqlMapping, ArgumentError> {
        Ok(SqlMapping::As("anyelement".into()))
    }

    fn return_sql() -> Result<Returns, ReturnsError> {
        Err(ReturnsError::Datum)
    }
}

After:

use pgrx::pgrx_sql_entity_graph::metadata::{
    ArgumentError, ReturnsError, ReturnsRef, SqlMappingRef, SqlTranslatable,
    TypeOrigin,
};

pub struct FakeAnyElement;

unsafe impl SqlTranslatable for FakeAnyElement {
    const TYPE_IDENT: &'static str = pgrx::pgrx_resolved_type!(FakeAnyElement);
    const TYPE_ORIGIN: TypeOrigin = TypeOrigin::External;
    const ARGUMENT_SQL: Result<SqlMappingRef, ArgumentError> =
        Ok(SqlMappingRef::literal("anyelement"));
    const RETURN_SQL: Result<ReturnsRef, ReturnsError> =
        Err(ReturnsError::Datum);
}

This pattern also applies to wrappers around internal, pseudo-types, and other existing SQL spellings.

If You Gated Code On cfg(pgrx_embed)

Drop that part of the cfg.

Before:

#[cfg(all(feature = "pg_test", pgrx_embed))]
#[test]
fn round_trip() {
    // ...
}

After:

#[cfg(feature = "pg_test")]
#[test]
fn round_trip() {
    // ...
}

Use whatever remaining cfgs make sense for your crate. The important part is that pgrx_embed is not part of the build anymore, so it should not appear in new gating logic.

Versioned Shared-object Mode

Nothing new is required here.

If your .control file already keeps module_pathname, keep it.

module_pathname = '$libdir/my_extension'

If you were already using versioned shared-object mode by omitting module_pathname, keep omitting it.

# no module_pathname entry

This release does not change the switch for that feature. It only changes how schema metadata is collected.

Common Migration Errors

not all trait items implemented

Example:

error[E0046]: not all trait items implemented, missing: `TYPE_IDENT`,
`TYPE_ORIGIN`, `ARGUMENT_SQL`, `RETURN_SQL`

Meaning: you still have an old-style manual SqlTranslatable impl.

Fix: move to associated consts.

type ident ... did not resolve

Example:

function `foo` uses `MyType` as argument, but type ident `...` did not resolve

Meaning: pgrx saw a Rust type in a SQL-bearing position, but couldn't match its TYPE_IDENT to a declared type, enum, or creates = [Type(T)]/[Enum(T)] declaration.

Common causes:

  • TYPE_IDENT was hard-coded incorrectly
  • TYPE_ORIGIN should have been External
  • you forgot the matching #[derive(PostgresType)], #[derive(PostgresEnum)], or extension_sql!(..., creates = [...])

type ident ... matched multiple SQL entities

Meaning: two different schema entities claim the same TYPE_IDENT.

Fix: make sure each Rust type maps to one SQL owner. Don't declare the same type twice through a mix of derive metadata and custom extension_sql! declarations.

no embedded pgrx schema section found

Meaning: cargo-pgrx couldn't find the embedded metadata section in the shared library.

Common causes:

  • the artifact was built with an incompatible pgrx
  • the wrong binary or architecture slice was selected
  • something stripped the section out of the shared library

creates = [Type(T)] is only valid for extension-owned SQL types

Meaning: you used creates = [Type(T)] or creates = [Enum(T)] for a type that should really be External.

Fix: remove the creates declaration and set TYPE_ORIGIN to External.

A Quick Checklist

  • delete src/bin/pgrx_embed.rs
  • remove the [[bin]] target for pgrx_embed
  • convert manual SqlTranslatable impls to associated consts
  • use pgrx::pgrx_resolved_type!(T) for TYPE_IDENT
  • set TYPE_ORIGIN correctly
  • only use creates = [Type(T)]/[Enum(T)] for extension-owned types
  • remove cfg(pgrx_embed) from tests or helper code

What Did Not Change

  • ordinary #[pg_extern] functions still work the same way
  • derive-based types and enums still work the same way
  • versioned shared-object mode is still controlled by module_pathname
  • SQL ordering still comes from the dependency graph, you just don't need the extra embed build anymore