-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Implement field length introspection and make available in schema #3552
Conversation
Thanks for working on this 👍 Use a trait that is implemented for the column types for the size limit.Something like that: // the name of that trait is obviously up for discussion
trait SizeRestrictedColumn: Column {
// maybe that should be even `Option<usize>`
// so that we can implement it for all columns?
pub const MAX_LENGTH: usize;
}
// generated via table!
impl SizeRestrictedColumn for users::name {
pub const MAX_LENGTH: usize = 42;
} This allows to write generic code that abstracts over columns with certain properties. Additionally that would give a good place to document what's there. Use const genericsA other probably more crazy idea is to use const generics on the sql type for this. So instead of having #[derive(SqlType)]
struct VarChar; we would have something like #[derive(SqlType)]
struct VarChar<const SIZE: u32 = 0>;
// ideally we would use `Option<NonZeroU32>` as type there,
// but that's not supported yet We could then include these information directly into the generated type: table! {
users {
id -> Integer,
// unrelated note: We might want to prefer
// that syntax anyway?
name -> VarChar<255>,
}
} that feels like a natural extension of the existing system. On the other hand I'm not sure yet whether that would be result in type coercing issues around the "different" text types (as each of |
I agree this current implementation doesn't enable building on top of it generically. I had considered these alternatives and eventually chose the current implementation, because Const genericsThere wouldn't be a 1-to-1 mapping between actual SQL types and our SQL types, which I suspect would make a bunch of stuff much more complex that it would otherwise need to be, while I'm not sure what concrete abilities we gain from doing that vs the trait variant. The downsides of that would for instance be the point you underlined about coercion support, plus even if we make everything generic we would probably end up accidentally monomorphizing a bunch of times a bunch of different functions that don't need to be monomorphized, e.g. an implementation of The trait variantIt's easier to reference without a trait as that way you don't need to have the trait in scope, and that's enough for the original use-case. Not creating a such trait right now doesn't prevent us from doing it later when we actually need it (and design it with Option or not Option depending on the actual use-case). That being said one thing I hadn't considered was that we could simply put in the Diesel prelude, so arguably that would be reasonably easy to use then, and that would enable some generic possibilities on the user side. Also I hadn't considered where to put the documentation. I'm going to rewrite it this way :) The option variantThe original use-case is basically to be able to write: let abc_def = input.abc_def.as_str().validated_len_lte(schema::abc::def::MAX_LENGTH)?; when validating endpoint inputs. I think it's nice in that context to be able to check at compile time that all input fields that end up being directly registered in the database are length-constrained. That also means users will typically not need a dedicated interface that takes an Option to perform such validation, and we avoid things such as We can always add that as another trait later if that turns out to be useful. We might want to prefer
|
Thanks for the explanation. Sounds reasonable for me 👍 If we ever decide that using const generics is the better solution that remains possible, so it's fine to go with the trait solution. Also the reasoning about The only minor thing I would adjust is probably using
I must admit that using table! {
whatever {
#[size = 123]
name -> Varchar,
}
} I feel that's closer to rusts syntax than the other solution and it should be easier to parse. |
Didn't think of that option! That's indeed a much more readable syntax :)
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Looks good now (beside the two minor things)
We should likely add a changelog entry for this as well.
I think we haven't resolved your comment about u32 yet: #3552 (comment) |
Co-authored-by: Georg Semmler <github@weiznich.de>
I totally missed that. I think I would go with variant 2, even if that involves unsafe code. |
Mysql goes from 0 to u16::MAX |
In that case |
Co-authored-by: Georg Semmler <github@weiznich.de>
Should we consider the fact that we update the schema file that we generate a breaking change? |
I wouldn't consider that as breaking change, because otherwise we cannot reasonable change anything in the generated schema.rs file without putting it behind an option. |
…when field length is not specified at creation
forgot to remove that after turning it into a trait
I will most likely squash-and-merge this once I'm done testing it on our codebase, most likely tomorrow. |
👍 |
Resolves #3551