我正在尝试使用最新的 SQLite 实现一个相当简单的工作流程:插入带有空 ID 列的行,以便自动生成它,并从
INSERT
语句返回这些自动生成的 ID。由于 SQLite 3.35+ 支持 RETURNING
子句,并且 Diesel 也通过 returning_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();
我做错了什么?
这是柴油如何处理插入语句的一个不幸的边缘情况。值得注意的是,如果结构中存在
None
值,diesel 支持插入默认值。在 postgresqldies 上,使用 DEFAULT
value 关键字来实现这一点。 Sqlite不支持该关键字,因此diesel需要模拟它。如果值为 None
,则只需跳过相关行即可完成此操作,但这仅适用于单行。这就是为什么单个插入查询可以工作,而批量插入查询无法编译的原因。
现在这里还有另一件事:.execute(&conn)
(即不返回 id)也适用于批量插入。这是因为diesel通过在内部一一插入所有元素来模拟批量插入。不幸的是,返回变体是不可能的,因为这会在柴油机内部特征设置中遇到冲突的特征实现。
可以说,这些都没有在柴油文档中得到很好的记录,因此提交一份改进相关文档的 PR 肯定会很棒。