Rust Diesel:SQLite INSERT 返回多个 ID

Rust Diesel: SQLite INSERT RETURNING multiple ids

提问人:Alex Abdugafarov 提问时间:11/15/2023 更新时间:11/15/2023 访问量:43

问:

我正在尝试使用最新的 SQLite 实现一个相当简单的工作流程:插入一行带有空 ID 列的行,以便自动生成它,并从语句中返回这些自动生成的 ID。由于SQLite 3.35+支持子句,而Diesel也通过功能这样做,这应该是可能的。INSERTRETURNINGreturning_clauses_for_sqlite_3_35

所以我正在尝试这个:

Cargo.toml

[dependencies]
diesel = { version = "2.1.3", features = ["sqlite", "returning_clauses_for_sqlite_3_35"] }

main.rs

use diesel::prelude::*;
use diesel::sql_query;

table! {
    test_table (internal_id) {
        internal_id -> BigInt,
        content -> Text,
    }
}

#[derive(Debug, PartialEq, Clone, Identifiable, Selectable, Queryable, Insertable)]
#[diesel(primary_key(internal_id))]
#[diesel(table_name = test_table)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
pub struct TestTableRow {
    #[diesel(deserialize_as = i64)]
    pub internal_id: Option<i64>,
    pub content: String,
}

fn main() {
    let rows = vec![
        TestTableRow { internal_id: None, content: "Hello!".to_owned() },
        TestTableRow { internal_id: None, content: "World!".to_owned() },
    ];

    let mut conn = SqliteConnection::establish(":memory:").unwrap();
    sql_query(r"
        CREATE TABLE test_table(
            internal_id INTEGER PRIMARY KEY AUTOINCREMENT,
            content     TEXT NOT NULL
        ) STRICT;
    ").execute(&mut conn).unwrap();
    let internal_ids: Vec<i64> = diesel::insert_into(test_table::table)
        .values(&rows)
        .returning(test_table::columns::internal_id)
        .get_results(&mut conn)
        .unwrap();

    assert_eq!(internal_ids, vec![1, 2]);
}

但是,这不会进行类型检查:

error[E0277]: the trait bound `BatchInsert<Vec<diesel::query_builder::insert_statement::ValuesClause<(DefaultableColumnInsertValue<ColumnInsertValue<columns::internal_id, expression::bound::Bound<diesel::sql_types::BigInt, &i64>>>, DefaultableColumnInsertValue<ColumnInsertValue<columns::content, expression::bound::Bound<diesel::sql_types::Text, &String>>>), test_table::table>>, test_table::table, (), false>: QueryFragment<Sqlite, sqlite::backend::SqliteBatchInsert>` is not satisfied
    --> src/main.rs:37:22
     |
37   |         .get_results(&mut conn)
     |          ----------- ^^^^^^^^^ the trait `QueryFragment<Sqlite, sqlite::backend::SqliteBatchInsert>` is not implemented for `BatchInsert<Vec<diesel::query_builder::insert_statement::ValuesClause<(DefaultableColumnInsertValue<ColumnInsertValue<columns::internal_id, expression::bound::Bound<diesel::sql_types::BigInt, &i64>>>, DefaultableColumnInsertValue<ColumnInsertValue<columns::content, expression::bound::Bound<diesel::sql_types::Text, &String>>>), test_table::table>>, test_table::table, (), false>`
     |          |
     |          required by a bound introduced by this call
     |
     = help: the following other types implement trait `QueryFragment<DB, SP>`:
               <BatchInsert<Vec<diesel::query_builder::insert_statement::ValuesClause<V, Tab>>, Tab, QId, HAS_STATIC_QUERY_ID> as QueryFragment<DB, PostgresLikeBatchInsertSupport>>
               <BatchInsert<V, Tab, QId, HAS_STATIC_QUERY_ID> as QueryFragment<DB>>
     = note: required for `BatchInsert<Vec<ValuesClause<(DefaultableColumnInsertValue<...>, ...), ...>>, ..., ..., false>` to implement `QueryFragment<Sqlite>`
     = note: the full type name has been written to '/Users/fs/code/rust-diesel-issue/target/debug/deps/rust_diesel_issue-c1c37efb3df7ba71.long-type-1487086329238728488.txt'
     = note: 1 redundant requirement hidden
     = note: required for `InsertStatement<table, BatchInsert<Vec<ValuesClause<(..., ...), ...>>, ..., ..., false>, ..., ...>` to implement `QueryFragment<Sqlite>`
     = note: the full type name has been written to '/Users/fs/code/rust-diesel-issue/target/debug/deps/rust_diesel_issue-c1c37efb3df7ba71.long-type-8243152461613863375.txt'
     = note: required for `InsertStatement<table, BatchInsert<Vec<ValuesClause<(..., ...), ...>>, ..., ..., false>, ..., ...>` to implement `LoadQuery<'_, diesel::SqliteConnection, _>`
     = note: the full type name has been written to '/Users/fs/code/rust-diesel-issue/target/debug/deps/rust_diesel_issue-c1c37efb3df7ba71.long-type-8243152461613863375.txt'
note: required by a bound in `get_results`
    --> /Users/fs/.cargo/registry/src/index.crates.io-6f17d22bba15001f/diesel-2.1.4/src/query_dsl/mod.rs:1739:15
     |
1737 |     fn get_results<'query, U>(self, conn: &mut Conn) -> QueryResult<Vec<U>>
     |        ----------- required by a bound in this associated function
1738 |     where
1739 |         Self: LoadQuery<'query, Conn, U>,
     |               ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RunQueryDsl::get_results`

同时,它确实可以编译 - 并且工作!- 仅插入一行时:

    let internal_ids: Vec<i64> = diesel::insert_into(test_table::table)
        .values(&rows[0])                               // <--- Here!
        .returning(test_table::columns::internal_id)
        .get_results(&mut conn)
        .unwrap();

我做错了什么?

sqlite rust-diesel sql-return

评论


答:

2赞 weiznich 11/15/2023 #1

这是 diesel 处理插入语句的方式中一个不幸的边缘情况。值得注意的是,如果结构中存在值,diesel 支持插入默认值。在 postgresql 上,diesel 为此使用 value 关键字。Sqlite 不支持此关键字,因此 diesel 需要模拟它。如果值为 ,则只需跳过相关行即可完成此操作,但这仅适用于单行。这就是单次插入查询有效,而批量插入查询无法编译的原因。 现在这里还有另一件事:(即不返回 id)也适用于批量插入。这是因为 diesel 通过在内部逐个插入所有元素来模拟批量插入。不幸的是,这对于返回变体是不可能的,因为这在柴油机内部特征设置中遇到了冲突的特征实现。NoneDEFAULTNone.execute(&conn)

可以说,这一切都没有在柴油文档中得到很好的记录,所以提交一个改进相关文档的 PR 当然很棒。

评论

0赞 Alex Abdugafarov 11/16/2023
这真是太可惜了。感谢详细的解释!
0赞 weiznich 11/16/2023
是的,这有点不幸的是,这目前没有按预期工作,但另一种选择是破坏所有现有的代码,这些代码在仿真上中继,该代码的存在时间比sqlite支持关键字的时间长。也许有一天有人想出了如何在不遇到重叠的实现的情况下编写相关的特征隐含,或者语言改进,以便我们可以处理这种情况。DEFAULTRETURNING