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

question: ToSql implementation to insert any custom type into a composite type #1055

Open
stevenliebregt opened this issue Jul 26, 2023 · 1 comment

Comments

@stevenliebregt
Copy link

I'm trying to implement a type that has a data field of type serde_json::Value which should be an object, that can be inserted into any composite type so long as the name and fields match.

pub struct CompositeType {
    composite_name: String,
    data: serde_json::Value
}

So if for example I have a composite type

CREATE TYPE my_type AS (a integer, b text);

An instance of CompositeType like the following should be allowed to be inserted there:

CompositeType {
    composite_name: "my_type",
    data: {
        a: Number(123),
        b: String("hello")
    }
}

Now I took the generated ToSql implementation of the derive macro that normally should work for a struct with named fields and tried to adapt it, but it keeps giving the error: db error: ERROR: insufficient data left in message, and I'm not sure what the problem is.

The ToSql implementation is as follows:

impl ToSql for CompositeType {
    fn to_sql(&self, ty: &Type, buf: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> where Self: Sized {
        // Make sure our data is an object
        let data = match &self.data {
            serde_json::Value::Object(data) => data,
            _ => return Err(format!("(@impl ToSql for CompositeType) Expected data to be an object, got: {:?}", self.data).into())
        };

        // Make sure it is a composite type we're inserting into
        let fields = match ty.kind() {
            &Kind::Composite(ref fields) => {
                // Make sure the name matches
                if ty.name() != self.composite_name {
                    return Err(format!("(@impl ToSql for CompositeType) Type name mismatch, expected: {}, got: {}", ty.name(), self.composite_name).into());
                }

                fields
            },
            kind => return Err(format!("(@impl ToSql for CompositeType) Unsupported type: {:?}, can only insert into composite types", kind).into())
        };

// This is copied from the derive macro
        buf.extend_from_slice(&(fields.len() as i32).to_be_bytes());
        for field in fields {
            buf.extend_from_slice(&field.type_().oid().to_be_bytes());
            let base = buf.len();
            buf.extend_from_slice(&[0; 4]);

// Only this line is different, no more matching field names, just get the data
            let r = postgres_types::ToSql::to_sql(&self.data.get(field.name()), field.type_(), buf);

            let count = match r? {
                postgres_types::IsNull::Yes => -1,
                postgres_types::IsNull::No => {
                    let len = buf.len() - base - 4;
                    if len > i32::max_value() as usize { return std::result::Result::Err(std::convert::Into::into("value too large to transmit")); }
                    len as i32
                }
            };
            buf[base..base + 4].copy_from_slice(&count.to_be_bytes());
        }
        std::result::Result::Ok(postgres_types::IsNull::No)
    }

    fn accepts(ty: &Type) -> bool where Self: Sized {
        true
    }

    to_sql_checked!();
}
@stevenliebregt
Copy link
Author

I think I may have found the issue, a serde_json::Value containing a number encodes as b"5", while a i32 encodes as b"\0\0\0\x05". So I guess I'll have to do a nice match statement to cast data before encoding it

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

No branches or pull requests

1 participant