Replace "DynIden" with "IdenImpl"#892
Conversation
|
If you don't mind, I'll review when the CI passes. But feel free to ask if you need my review earlier |
|
Because it is too troublesome to fix the tests and examples, I also implemented the corresponding behavior for the derive macro Iden in this PR. |
|
It seems that all errors are caused by deprecation. |
|
sorry, it passed CI already. then there's a conflict |
src/extension/mysql/column.rs
Outdated
| impl From<MySqlType> for IdenImpl { | ||
| fn from(value: MySqlType) -> Self { | ||
| Self::from(match value { | ||
| MySqlType::TinyBlob => "tinyblob", | ||
| MySqlType::MediumBlob => "mediumblob", | ||
| MySqlType::LongBlob => "longblob", | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
This duplicated match (with impl Iden right above) doesn't look right. I think, in this iteration (this PR that doesn't delete trait Iden yet) we should blanket impl<T> Iden for T where T: Into<IdenImpl>.
Also, your PR description should mention the plan to delete the trait, so that it's easier for Chris to review and understand the whole picture (there will be nothing called IdenImpl eventually)
There was a problem hiding this comment.
After this PR, the Iden trait is no longer used in the actual code. I think we can remove these impls?
There was a problem hiding this comment.
If the trait is already unused (there's no meaningful "intermediate" state of this trait in this PR), then I'd say we should just delete it and rename IdenImpl -> Iden right in this PR
There was a problem hiding this comment.
You're right. My current choice is mainly for compatibility, but since users will have to re-implement Into<IdenImpl> anyway, it makes sense to replace Iden.
There was a problem hiding this comment.
Hmm. As I think about it, there's another, more compatibible option. The blanket impl could go the other way around: impl<T> From<T> for IdenImpl where T: Iden. That way, if users have manual Iden impls, these types will continue to work where IntoIden is accepted.
But the downside is that the struct will be called IdenImpl or something like that (Iden is still taken by the trait). And there's more complexity overall because the trait is still around.
I'd say, we should clean things up and delete the trait.
There was a problem hiding this comment.
I'm having some issues while cleaning up the code. Keeping IdenStatic requires a lot of (dirty) fixes. But since the current Iden is already static, I think we can remove IdenStatic as well?
There was a problem hiding this comment.
Yeah, right. Nuke it. The lifetime of Rust identifier types no longer matters, because we no longer keep them inside DynIden. Instead, we eagerly "render" them into an owned struct Iden, and then deal only with that.
I really like your new design and cleanups. Less traits and lifetimes; more simple, concrete, owned values.
|
|
||
| fn main() { | ||
| assert_eq!(CustomName.to_string(), "another_name"); | ||
| assert_eq!(Iden::from(CustomName).to_string(), "another_name"); |
There was a problem hiding this comment.
Hmm. Is this pattern common in the actual user code? I see a lot of this in the diff. Perhaps, we should find a way to reduce this breakage and verbosity. E.g., add a helper trait ToIdenSting that's blanket-implemented for Into<Iden> and provides this to_string method from the old Iden trait?
I think, it won't be as bad as the old Iden trait, because SeaQuery interfaces wouldn't depend on ToIdenSting for anything. It's just a helper "extension trait", and we can document that
There was a problem hiding this comment.
I don't think that's very common. But we could add a trait to provide the corresponding Iden methods for any T: Into.
There was a problem hiding this comment.
Yeah, I think we can provide that extension trait as IdenTrait or something like that.
On one hand, it's a little messy because we keep much of the old API surface.
On the other hand, the diff indicates that the trait was actually used outside of DynIden. So, it's probably worth keeping. And we still improve the API by making it an optional extension trait and no longer depending on it via DynIden.
There was a problem hiding this comment.
Oh, I have an even crazier (or even more boring and conservative?) idea. What if we keep even the name of trait Iden the same, and keep your Cow struct called DynIden? This naming even makes sense, because struct DynIden is a "raw" identifier string that's no longer "type safe" and could refer to any SQL entity
There was a problem hiding this comment.
This even preserves backward-compatibility with user code that uses the actual dyn Iden. We'll just no longer use it in SeaQuery
There was a problem hiding this comment.
For even more compatibility, we can:
- Reverse the blanket impl:
impl<T> From<T> for DynIden where T: Iden. - Keep generating
impl Idenin#[derive(Iden)]. - Keep the users' manual
impl Idenworking
🤯🤯🤯
There was a problem hiding this comment.
That's the issues with this implementation:
-
The Iden trait doesn't provide a method to get a
&'static str, so we can only implement
impl<T> From<T> for DynIden where T: IdenStatic. -
Therefore, we cannot solve the problem of
derive(Iden)because this trait can be implemented in a non-static way. -
One option is to add a method to
Identrait that returns aCow, but in practice, I think this isn't much different from implementing theIdentrait for types that implementInto<IdenImpl>.
There was a problem hiding this comment.
There are still more issues: users can implement the Iden trait themselves to override the default behavior—for example, to ignore quote as a way to bypass escaping. The current idenimpl cannot provide the same behavior.
There was a problem hiding this comment.
impl<T> From<T> for DynIden where T: Iden we can, but we have to always return Owned, which is not ideal.
yeah, IdenStatic is somehow added later than Iden that made things complicated.
for example, to ignore quote as a way to bypass escaping
I think we ultimately have to reject this use case as part of the breaking change
There was a problem hiding this comment.
I think we ultimately have to reject this use case as part of the breaking change
Ok, that means that we must delete the provided Iden methods, so that they can not be overridden [1]. We can keep their calls working [2] by adding a blanket-implemented trait IdenExt. This solution makes sense to me.
[1]: Whether we decide to keep trait Iden for some compatibility, or not.
[2]: Actually, the calls will break initially, but the users only need to use sea_query::IdenExt as the compiler suggests.
| use sea_query::{Iden, IdenStatic}; | ||
| use strum::{EnumIter, IntoEnumIterator}; | ||
|
|
||
| #[derive(Copy, Clone, IdenStatic, EnumIter)] |
There was a problem hiding this comment.
Sorry for noticing this only now, but perhaps we should keep IdenStatic derive macro for compatibility? Just as an alias that does the same thing as #[derive(Iden)].
Hmm, perhaps it shouldn't even be an alias, and we should keep the trait IdenStatic and blaknet-impl<T> From<T> for Iden where T: IdenStatic? That would be nice for compatibility, and also for providing a compile-time guarantee that you can cheaply get a &static str
There was a problem hiding this comment.
We could make the derive(IdenStatic) do nothing and implement as_str and AsRef for Iden.
There was a problem hiding this comment.
On this line of code that I have reviewed, #[derive(IdenStatic)] is used without #[derive(Iden)]. It used to implicitly generate impl Iden in addition to impl IdenStatic.
If we make #[derive(IdenStatic)] do nothing, then code like this breaks completely (no longer gives us an IntoIden type), instead of generating a partially-compatible impl From<_> for Iden that at least still allows the type to be passed into methods that accept IntoIden
There was a problem hiding this comment.
Oh, I see — IdenStatic actually implements both Iden and IdenStatic, so maybe we just need to re-export the derive(Iden) macro as derive(IdenStatic)?
There was a problem hiding this comment.
That's what I've initially suggested here, yeah. That's the minimal viable solution, IMO.
Whether we should additionally preserve trait IdenStatic, is a more debatable design tradeoff
|
sorry for the late reply. I think this is in the right direction. however, I am afraid there's still too much breaking change. having said that, I think most of the changes can be polyfilled:
|
|
I agree with you in principle, but we're faced with a peculiar task of balancing between APIs that make more sense and APIs that are more compatible. I think, the right way is keeping
There is one that I proposed here: blanket This will still break manual The only really-non-breaking alternative is reversing the blanket
Yeah, we can keep if for compatibility and later deprecate in
This PR already keeps This still breaks manual If the type manually implements
I'm also strongly in favor of adding an |
|
I would probably even not put in the
well, I guess my argument is that String is data and I don't want it to be confused as identifier. example: let a = String::new("a");
let b = String::new("b");
`a.eq(b)` would result in `"a" = 'b'`
`b.eq(a)` would result in `"b" = 'a'`one is quoted as identifier, one is quoted as literal. am I right that it will cause this confusion ultimately? |
Yeah, agree. Definitely not in
That's an interesting argument and we should explore it. But it needs a convincing example. Your pseudocode example is incorrect. The non-pseudocode // [dependencies]
// sea-query = { version = "0.32.3", features = ["tests-cfg"] }
use sea_query::{ExprTrait, PostgresQueryBuilder, QueryBuilder};
fn main() {
let a = String::from("a");
let b = String::from("b");
let mut sql = String::new();
PostgresQueryBuilder.prepare_simple_expr(&a.clone().eq(&b), &mut sql);
println!("{}", sql);
let mut sql = String::new();
PostgresQueryBuilder.prepare_simple_expr(&b.eq(&a), &mut sql);
println!("{}", sql);
}prints 'a' = 'b'
'b' = 'a'And to me, that's obvious and expected. Identifiers don't even implement |
|
@Huliiiiii, we've chatted with Chris and decided to split the PR in order to proceed. Initially, we want to merge these two small changes as separate PRs:
That's a blocker for now. |
I haven’t had time to work on this lately. I’m totally fine if you wants to take over or use this as a base for further changes. |
I will try some experiments |
|
thank you so much for the initiative! |
Background: #795 (comment)
PR Info
New Features
Bug Fixes
Breaking Changes
Replaced
DynIdenwith concretestruct IdenImpl.Iden, you don't need to do anything.Idenmanually, you need to replace it withimpl From<YourType> for IdenImpl.Unlike
DynIden,IdemImplis compared purely by string. Same strings rendered from different Rust identifier types will now compare as equal.Remove
SeaRc.SeaRcwas only used forDynIden. So, after you fix the errors aroundDynIden, you shouldn't have any errors left.SeaRcfor your own purposes outside ofDynIden, there can be more errors. There, you can try replacingSeaRcwithRcOrArc(which was the underlying implementation ofSeaRcanyway).Changes