sfackler / rust-postgres

Native PostgreSQL driver for the Rust programming language
Apache License 2.0
3.5k stars 443 forks source link

`copy_in` with composite type error problem #898

Closed bzdula closed 2 years ago

bzdula commented 2 years ago

Hi, I have a large amount of data that i would like to insert with copy_in method. Beforahand i want to say that I am able to use them in regular queries and such, so there shouldn't be problem with definitions [I hope]. Let say i have structure[s]:

#[derive(Debug, PartialEq, Clone, ToSql, FromSql, Deserialize, Serialize)]
#[postgres(name = "address")]
pub struct Address {
    pub street: String,
    pub zip: Zip,
    pub city: String,
}

#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, ToSql, InsertableMacro)]
//InsertableMacro provides me with methods:
//fn to_reference_slice(&self, field_names: &[&str]) -> Box<[&(dyn ToSql + Sync)]>
//fn to_reference_slice_full(&self) -> Box<[&(dyn ToSql + Sync)]>
//which provides me with references to elements of the structure (there are others) 

pub struct Companies {
    pub id: Option<i32>,
    pub name: String,
    pub nip: i64,
    #[serde(default = "private_company")]
    pub company_type: CompanyType,
    pub opt: Option<serde_json::Value>,
    pub old_id: Option<i32>,
    pub address: Address,
    pub addr_opt: Option<Address>,
    pub bank_account: Option<String>,
}

with corresponding sql definition:

  CREATE TABLE nds.companies (
      id integer NOT NULL,
      name text NOT NULL,
      nip bigint NOT NULL,
      type nds.company_type NOT NULL,
      opt json,
      old_id integer NOT NULL,
      address nds.address NOT NULL,
      addr_opt nds.address
  );

When i try to perform a following code:

    #[tokio::test]
    async fn test_copy() -> Result<(), Error> {

        let table = "nds.companies";

        let company = Companies{ 
            id: None, 
            name: String::from("TEST"), 
            nip: 3124123131i64, 
            company_type: wbs_types::company::CompanyType::PRIVATE, 
            opt: None, 
            old_id: None, 
            address: Address{ street: String::from("Teststreet"), zip: wbs_types::Zip(String::from("33-110")), city: String::from("Testciteh") }, 
            addr_opt: None, 
            bank_account: None 
        };

        let client = connect_db().await?;

        let type_stmt = client.prepare(&format!(r#"SELECT $1::{}"#, table)).await?;
        let types = type_stmt.params()[0].clone();
        let sink = client.copy_in(&format!("COPY {} FROM STDIN BINARY;", table)).await?;
        let writer = BinaryCopyInWriter::new(sink, &[types]);
        pin_mut!(writer);

        writer.as_mut().write(&[&company]).await?;
        writer.finish().await?;

        Ok(())
    }

I recieve following error:

Error: Error { kind: Db, cause: Some(DbError { severity: "ERROR", parsed_severity: Some(Error), code: SqlState(E22P04), message: "row field count is 1, expected 9", detail: None, hint: None, position: None, where_: Some("COPY companies, line 1"), schema: None, table: None, column: None, datatype: None, constraint: None, file: Some("copy.c"), line: Some(3777), routine: Some("NextCopyFrom") }) }
thread 'tests::test_copy' panicked at 'assertion failed: `(left == right)`
  left: `1`,
 right: `0`: the test returned a termination value with a non-zero status code (1) which indicates a failure', /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/test/src/lib.rs:186:5

If i change write to use my macro

let field_refs: Box<[&(dyn ToSql + Sync)]> = company.to_reference_slice_full();
writer.as_mut().write(&*field_refs).await?; //Debox
writer.finish().await?;

The error changes to:

thread 'tests::test_copy' panicked at 'expected 1 values but got 9', /home/marcin/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-postgres-0.7.6/src/binary_copy.rs:72:9

I feel like I almost understand it, so can you please point out what exactly I am doing wrong here? Thanks, and thank you for your fantastic library :clap: . PS. I've attatched the {:?} dump of types variable types.txt

sfackler commented 2 years ago

Your type_stmt query has one parameter - the composite type that exists for every table. The copy query is instead expecting every column in the table as separate parameters.

I think changing your type_stmt to SELECT * FROM {} and looking at stmt.columns() should give you what you need.

bzdula commented 2 years ago

Yes! That was it, than you very much, I sorta misunderstood the #681, and assumed that Composites are automatically broken down into base types