Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a top-level derive for a new SQL Type ( general ideas, needs refinement ) #76

Closed
mehcode opened this issue Jan 19, 2020 · 9 comments
Labels
enhancement New feature or request question Further information is requested

Comments

@mehcode
Copy link
Member

mehcode commented Jan 19, 2020

The following are ideas and not final.

Tuple Struct (of 1)

#[derive(sqlx::Type)]
// transparent indicates that this type is purely for 
// documentation and it should act exactly as if it were the wrapped type in all aspects
#[sqlx(transparent)]
struct MyType(i32);

Enum

#[derive(sqlx::Type)]
// specify the OID explicitly for the type (optionally the OID for when its used as an array)
#[sqlx(postgres(oid = 1212, array_oid = 21341))]
// by default we encode and decode as a string and rely on first-class support
// for enumerations in the database server
#[sqlx(repr(i32))]
// by default we should snake_case the type name and look up its OID
// you should be able to use a container-level attribute to rename the type name 
#[sqlx(rename = "myenum")]
enum MyEnum {
  TheFoo,
  // By default we should snake_case rename but also allow renames
  #[sqlx(rename = "foothe")]
  FooThe
}

Struct

// In Postgres you can define your own composites or structs
// We should be able to support this
#[derive(sqlx::Type)]
struct Foo { a: i32 }

// Additionally, perhaps something like the following for strongly typed JSON
// from Postgres or MySQL
#[derive(sqlx::Type)]
#[sqlx(repr(json))]
struct Bar { b: i64 }

Basic idea of implementation is these derives would implement HasSqlType and add the Encode and Decode derives. Can proc derives be recursive (e.g., can this derive generate more derive macro invocations)?

@mehcode mehcode added enhancement New feature or request question Further information is requested labels Jan 19, 2020
@thedodd
Copy link
Contributor

thedodd commented Jan 24, 2020

This will be an excellent addition to the library, for sure!

@abonander
Copy link
Collaborator

abonander commented Jan 24, 2020

Can proc derives be recursive (e.g., can this derive generate more derive macro invocations)?

No because they cannot emit more #[derive] attributes for the same item (their output is purely additive). However, we can simply call into the derive implementations for Encode and Decode and merge the token streams instead.

@Freax13
Copy link
Contributor

Freax13 commented Jan 24, 2020

tuple structs of one and enums seem pretty straight forward. how does encode & decode work for structs though? and how would repr(json) do? add derives for serde and use serde_json?

@mehcode
Copy link
Member Author

mehcode commented Jan 28, 2020

  • Case 1 - Transparent or "New Type"
Rust
#[derive(sqlx::Type)]
#[repr(transparent)]
struct Health(i32);
SQL
INT

  • Case 2 - Strong Enum
Rust
#[derive(sqlx::Type)]
#[sqlx(postgres(oid = 101010))]
enum Direction { Up, Down, Left, Right };
Postgres
CREATE TYPE direction AS ENUM ('up', 'down', 'left', 'right');
MySQL
ENUM('up', 'down', 'left', 'right')

  • Case 3 - Weak Enum
Rust
#[derive(sqlx::Type)]
#[repr(i32)]
enum Direction { Up, Down, Left, Right };
SQL
INT

  • Case 4 - Struct (Postgres-only)
Rust
#[derive(sqlx::Type)]
#[sqlx(postgres(oid = 101010))]
struct Point { x: i16, y: i16 };
Postgres
CREATE TYPE point AS (
  x INT2,
  y INT2
);

Unresolved Questions

  • repr(i32) or sqlx(repr(i32)) for Weak Enums ?

Future Extensions

  • Strongly typed JSON
  • Remove need for explicit OID

@Freax13 Let me know if you have any other questions.

Encode and Decode for structs in Postgres is a touch complicated. The only documentation is in the source code. It's a specific binary encoding. If you don't feel comfortable tackling this, that's totally fine.

Here is the best "docs" I can find on it.

https://github.com/scrive/hpqtypes/blob/master/libpqtypes/src/record.c

@Freax13
Copy link
Contributor

Freax13 commented Jan 28, 2020

turns out deriving HasSqlType for transparent types (and weak enums?) isnt as straight forward as I thought it would be. ideally we would want something like

struct Foo(i32);
impl<DB: sqlx::Database> sqlx::types::HasSqlType<Foo> for DB where DB: sqlx::types::HasSqlType<i32> {
    fn type_info() -> Self::TypeInfo {
        <Self as HasSqlType<i32>>::type_info()
    }
}

however this doesnt work due to the orphan rules (the two foreign traits being HasSqlType and Database)

so we could either

  1. derive implementations for specific Databases using derive inputs like
#[derive(HasSqlType(MySql,Postgres,OtherDB))]
struct Foo(i32);
  1. just hardcode the MySql and Postgres DBs
  2. default to MySql and Postgres unless something else is specified in the derive inputs

@thedodd
Copy link
Contributor

thedodd commented Jan 28, 2020

I like option 1. here. Users would already need to write the derive for their code, and an additional param for HasSqlType specifying the DB type being used seems pretty logical, especially given that doing so will reduce the amount of code which would need to be generated.

@mehcode
Copy link
Member Author

mehcode commented Jan 28, 2020

however this doesnt work due to the orphan rules (the two foreign traits being HasSqlType and Database)

This is actually changing in 1.41 to be allowed. Orphan rules are relaxed if the type parameter of a foreign trait is your crate-local type.

See: https://github.com/rust-lang/rust/blob/master/RELEASES.md#language


Until 1.41 is out and we depend on it, I would recommend exposing a boolean or something from sqlx as #[doc(hidden)] that we can look at and produce implementations.

@Freax13
Copy link
Contributor

Freax13 commented Jan 28, 2020

some rules will be relaxed, but it still wont work
playground example

@mehcode
Copy link
Member Author

mehcode commented Jan 28, 2020

Right.. It's the for T part that still won't be allowed.


Regardless, thinking about this again, the derive should probably just follow the feature flags that are being applied to the sqlx-macros crate.

If the postgres feature is present, emit an explicit for Postgres impl.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants