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

Hashable Value #1254

Closed
tyt2y3 opened this issue Nov 24, 2022 · 9 comments · Fixed by SeaQL/sea-query#598
Closed

Hashable Value #1254

tyt2y3 opened this issue Nov 24, 2022 · 9 comments · Fixed by SeaQL/sea-query#598
Milestone

Comments

@tyt2y3
Copy link
Member

tyt2y3 commented Nov 24, 2022

Sometimes (#1238) we need to use the Value as HashKey, but we cannot impl Hash and Eq for Value. The only thing preventing this is f32/f64.

So I propose we make a new enum with a subset of Value variants (and also cannot be null). To reduce the maintainence burden, I think realistically we only need to consider the Rust built-in types (with the exception of UUID because it is likely to be used as primary key):

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum HashableValue {
    Bool(bool),
    TinyInt(i8),
    SmallInt(i16),
    Int(i32),
    BigInt(i64),
    TinyUnsigned(u8),
    SmallUnsigned(u16),
    Unsigned(u32),
    BigUnsigned(u64),
    String(Box<String>),
    Char(char),
    #[allow(clippy::box_collection)]
    Bytes(Box<Vec<u8>>),
    #[cfg(feature = "with-uuid")]
    #[cfg_attr(docsrs, doc(cfg(feature = "with-uuid")))]
    Uuid(Box<Uuid>),
}

and a few conversion functions:

impl std::convert::TryFrom<Value> for HashableValue {
    fn try_from(value: Value) -> Result<Self, Self::Error> {
        match value {
            Int(Some(v)) => Ok(Self::Int(v)),
            String(v) | Bytes(v) => // these should be non-empty
            ...
            _ => Err(...),
        }
    }
}

impl std::convert::From<HashableValue> for Value {
    fn from(value: HashableValue) -> Self {
        // this should always succeed
    }
}
@tyt2y3
Copy link
Member Author

tyt2y3 commented Nov 24, 2022

@ikrivosheev what's your thought on this design?

@ikrivosheev
Copy link
Member

@tyt2y3 can you explain, why we need HashableValue?

@tyt2y3
Copy link
Member Author

tyt2y3 commented Nov 24, 2022

The use case in SeaORM / Seaography was that we'd basically want to group objects by primary keys.

Say we have 1->N relationship for Entity A & B, and we selected a list of : A.id, B.* and we now want to use a HashMap to group all B belonging to A by primary key. Right now we convert the primary key to string, which works, but is inefficient.

@ikrivosheev
Copy link
Member

@tyt2y3, now i understand, thank you! Well, why do you want to implement this in SeaQuery? I think this can be a part of SeaORM. If we want to change something in the future it will be easier do this in SeaORM. And SeaORM can also control this.

@tyt2y3
Copy link
Member Author

tyt2y3 commented Nov 25, 2022

Yeah can live in SeaORM as well. No preference on that.

@tyt2y3 tyt2y3 transferred this issue from SeaQL/sea-query Nov 25, 2022
@tyt2y3 tyt2y3 mentioned this issue Nov 25, 2022
1 task
@karatakis
Copy link
Contributor

Why we just don't implement Hash for Value ?

impl std::hash::Hash for Value {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        match self {
            Self::Bool(value) => {
                value.hash(state)
            },
            Self::TinyInt(value) => {
                value.hash(state)
            },
            Self::SmallInt(value) => {
                value.hash(state)
            },
            Self::Int(value) => {
                value.hash(state)
            },
            Self::BigInt(value) => {
                value.hash(state)
            },
            Self::TinyUnsigned(value) => {
                value.hash(state)
            },
            Self::SmallUnsigned(value) => {
                value.hash(state)
            },
            Self::Unsigned(value) => {
                value.hash(state)
            },
            Self::BigUnsigned(value) => {
                value.hash(state)
            },
            Self::Float(value) => {
                let value = format!("{:?}", value);
                value.hash(state)
            },
            Self::Double(value) => {
                let value = format!("{:?}", value);
                value.hash(state)
            },
            Self::String(value) => {
                value.hash(state)
            },
            Self::Char(value) => {
                value.hash(state)
            },
            Self::Bytes(value) => {
                value.hash(state)
            },
            #[cfg(feature = "with-json")]
            #[cfg_attr(docsrs, doc(cfg(feature = "with-json")))]
            Self::Json(value) => {
                value.hash(state)
            },
            #[cfg(feature = "with-chrono")]
            #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))]
            Self::ChronoDate(value) => {
                value.hash(state)
            },
            #[cfg(feature = "with-chrono")]
            #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))]
            Self::ChronoTime(value) => {
                value.hash(state)
            },
            #[cfg(feature = "with-chrono")]
            #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))]
            Self::ChronoDateTime(value) => {
                value.hash(state)
            },
            #[cfg(feature = "with-chrono")]
            #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))]
            Self::ChronoDateTimeUtc(value) => {
                value.hash(state)
            },
            #[cfg(feature = "with-chrono")]
            #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))]
            Self::ChronoDateTimeLocal(value) => {
                value.hash(state)
            },
            #[cfg(feature = "with-chrono")]
            #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))]
            Self::ChronoDateTimeWithTimeZone(value) => {
                value.hash(state)
            },
            #[cfg(feature = "with-time")]
            #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))]
            Self::TimeDate(value) => {
                value.hash(state)
            },
            #[cfg(feature = "with-time")]
            #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))]
            Self::TimeTime(value) => {
                value.hash(state)
            },
            #[cfg(feature = "with-time")]
            #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))]
            Self::TimeDateTime(value) => {
                value.hash(state)
            },
            #[cfg(feature = "with-time")]
            #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))]
            Self::TimeDateTimeWithTimeZone(value) => {
                value.hash(state)
            },
            #[cfg(feature = "with-uuid")]
            #[cfg_attr(docsrs, doc(cfg(feature = "with-uuid")))]
            Self::Uuid(value) => {
                value.hash(state)
            },
            #[cfg(feature = "with-rust_decimal")]
            #[cfg_attr(docsrs, doc(cfg(feature = "with-rust_decimal")))]
            Self::Decimal(value) => {
                value.hash(state)
            },
            #[cfg(feature = "with-bigdecimal")]
            #[cfg_attr(docsrs, doc(cfg(feature = "with-bigdecimal")))]
            Self::BigDecimal(value) => {
                value.hash(state)
            },
            #[cfg(feature = "postgres-array")]
            #[cfg_attr(docsrs, doc(cfg(feature = "postgres-array")))]
            Self::Array(value) => {
                value.hash(state)
            },
            #[cfg(feature = "with-ipnetwork")]
            #[cfg_attr(docsrs, doc(cfg(feature = "with-ipnetwork")))]
            Self::IpNetwork(value) => {
                value.hash(state)
            },
            #[cfg(feature = "with-mac_address")]
            #[cfg_attr(docsrs, doc(cfg(feature = "with-mac_address")))]
            Self::MacAddress(value) => {
                value.hash(state)
            },
        }
    }
}

The f32 and f64 needs some work

@tyt2y3
Copy link
Member Author

tyt2y3 commented Nov 25, 2022

That would do, although we can only put this inside sea-query (because of orphan rule)

@karatakis
Copy link
Contributor

Should I open a pull request?

@tyt2y3
Copy link
Member Author

tyt2y3 commented Nov 28, 2022

And we'd need to impl Eq as well to be usable as hash key.
What's your thought on this? @ikrivosheev

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants