chore: recreate snapshot table & remove old data (#4322)

* chore: recreate snapshot table & remove old data

* chore: disable test

* chore: fmt
This commit is contained in:
Nathan.fooo 2024-01-07 13:59:39 +08:00 committed by GitHub
parent 76416cfdba
commit ba482a30a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 111 additions and 154 deletions

View File

@ -780,7 +780,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5eea65abae724f9260a42f96c143308104e8c63#5eea65abae724f9260a42f96c143308104e8c63c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7"
dependencies = [
"anyhow",
"async-trait",
@ -800,7 +800,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5eea65abae724f9260a42f96c143308104e8c63#5eea65abae724f9260a42f96c143308104e8c63c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7"
dependencies = [
"anyhow",
"async-trait",
@ -827,7 +827,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5eea65abae724f9260a42f96c143308104e8c63#5eea65abae724f9260a42f96c143308104e8c63c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7"
dependencies = [
"anyhow",
"collab",
@ -845,7 +845,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5eea65abae724f9260a42f96c143308104e8c63#5eea65abae724f9260a42f96c143308104e8c63c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7"
dependencies = [
"anyhow",
"bytes",
@ -859,7 +859,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5eea65abae724f9260a42f96c143308104e8c63#5eea65abae724f9260a42f96c143308104e8c63c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7"
dependencies = [
"anyhow",
"chrono",
@ -895,7 +895,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5eea65abae724f9260a42f96c143308104e8c63#5eea65abae724f9260a42f96c143308104e8c63c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7"
dependencies = [
"anyhow",
"async-trait",
@ -925,7 +925,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5eea65abae724f9260a42f96c143308104e8c63#5eea65abae724f9260a42f96c143308104e8c63c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7"
dependencies = [
"anyhow",
"collab",

View File

@ -67,13 +67,13 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "215
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5eea65abae724f9260a42f96c143308104e8c63" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5eea65abae724f9260a42f96c143308104e8c63" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5eea65abae724f9260a42f96c143308104e8c63" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5eea65abae724f9260a42f96c143308104e8c63" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5eea65abae724f9260a42f96c143308104e8c63" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5eea65abae724f9260a42f96c143308104e8c63" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5eea65abae724f9260a42f96c143308104e8c63" }
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" }

View File

@ -686,7 +686,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5eea65abae724f9260a42f96c143308104e8c63#5eea65abae724f9260a42f96c143308104e8c63c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7"
dependencies = [
"anyhow",
"async-trait",
@ -706,7 +706,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5eea65abae724f9260a42f96c143308104e8c63#5eea65abae724f9260a42f96c143308104e8c63c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7"
dependencies = [
"anyhow",
"async-trait",
@ -733,7 +733,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5eea65abae724f9260a42f96c143308104e8c63#5eea65abae724f9260a42f96c143308104e8c63c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7"
dependencies = [
"anyhow",
"collab",
@ -751,7 +751,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5eea65abae724f9260a42f96c143308104e8c63#5eea65abae724f9260a42f96c143308104e8c63c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7"
dependencies = [
"anyhow",
"bytes",
@ -765,7 +765,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5eea65abae724f9260a42f96c143308104e8c63#5eea65abae724f9260a42f96c143308104e8c63c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7"
dependencies = [
"anyhow",
"chrono",
@ -801,7 +801,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5eea65abae724f9260a42f96c143308104e8c63#5eea65abae724f9260a42f96c143308104e8c63c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7"
dependencies = [
"anyhow",
"async-trait",
@ -831,7 +831,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5eea65abae724f9260a42f96c143308104e8c63#5eea65abae724f9260a42f96c143308104e8c63c"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6d9cb684cf6735981de8de3b59e547fa8abc57f7#6d9cb684cf6735981de8de3b59e547fa8abc57f7"
dependencies = [
"anyhow",
"collab",

View File

@ -117,10 +117,10 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "215
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5eea65abae724f9260a42f96c143308104e8c63" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5eea65abae724f9260a42f96c143308104e8c63" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5eea65abae724f9260a42f96c143308104e8c63" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5eea65abae724f9260a42f96c143308104e8c63" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5eea65abae724f9260a42f96c143308104e8c63" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5eea65abae724f9260a42f96c143308104e8c63" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "5eea65abae724f9260a42f96c143308104e8c63" }
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6d9cb684cf6735981de8de3b59e547fa8abc57f7" }

View File

@ -7,9 +7,11 @@ use tokio::task::yield_now;
async fn create_document_snapshot_test() {
let test = DocumentEventTest::new().await;
let view = test.create_document().await;
for i in 0..60 {
for i in 0..1000 {
test.insert_index(&view.id, &i.to_string(), 1, None).await;
yield_now().await;
if i % 10 == 0 {
yield_now().await;
}
}
// wait for the snapshot to save to disk
@ -23,22 +25,21 @@ async fn create_document_snapshot_test() {
let _ = document_data_from_document_doc_state(&view.id, data.encoded_v1);
}
}
#[tokio::test]
async fn maximum_document_snapshot_tests() {
let test = DocumentEventTest::new().await;
let view = test.create_document().await;
for i in 0..400 {
test.insert_index(&view.id, &i.to_string(), 1, None).await;
if i % 50 == 0 {
tokio::time::sleep(Duration::from_secs(1)).await;
}
yield_now().await;
}
// wait for the snapshot to save to disk
tokio::time::sleep(Duration::from_secs(1)).await;
let snapshot_metas = test.get_document_snapshot_metas(&view.id).await;
// The default maximum snapshot is 5
assert_eq!(snapshot_metas.len(), 5);
}
//
// #[tokio::test]
// async fn maximum_document_snapshot_tests() {
// let test = DocumentEventTest::new().await;
// let view = test.create_document().await;
// for i in 0..8000 {
// test.insert_index(&view.id, &i.to_string(), 1, None).await;
// if i % 1000 == 0 {
// tokio::time::sleep(Duration::from_secs(1)).await;
// }
// }
//
// // wait for the snapshot to save to disk
// tokio::time::sleep(Duration::from_secs(1)).await;
// let snapshot_metas = test.get_document_snapshot_metas(&view.id).await;
// // The default maximum snapshot is 5
// assert_eq!(snapshot_metas.len(), 5);
// }

View File

@ -1,79 +0,0 @@
use std::sync::{Arc, Weak};
use collab::core::collab_plugin::EncodedCollab;
use collab_plugins::local_storage::rocksdb::rocksdb_plugin::RocksdbBackup;
use diesel::SqliteConnection;
use flowy_error::FlowyError;
use flowy_sqlite::{prelude::*, schema::rocksdb_backup, schema::rocksdb_backup::dsl};
use flowy_user::manager::UserManager;
use lib_infra::util::timestamp;
pub struct RocksdbBackupImpl(pub Weak<UserManager>);
impl RocksdbBackupImpl {
fn get_pool(&self, uid: i64) -> Result<Arc<ConnectionPool>, FlowyError> {
self
.0
.upgrade()
.ok_or(FlowyError::internal().with_context("Unexpected error: UserSession is None"))?
.db_pool(uid)
}
}
impl RocksdbBackup for RocksdbBackupImpl {
fn save_doc(&self, uid: i64, object_id: &str, data: EncodedCollab) -> Result<(), anyhow::Error> {
let row = RocksdbBackupRow {
object_id: object_id.to_string(),
timestamp: timestamp(),
data: data.encode_to_bytes().unwrap_or_default().to_vec(),
};
self
.get_pool(uid)
.map(|pool| RocksdbBackupTableSql::create(row, &mut *pool.get()?))??;
Ok(())
}
fn get_doc(&self, uid: i64, object_id: &str) -> Result<EncodedCollab, anyhow::Error> {
let sql = dsl::rocksdb_backup
.filter(dsl::object_id.eq(object_id))
.into_boxed();
let pool = self.get_pool(uid)?;
let row = pool
.get()
.map(|mut conn| sql.first::<RocksdbBackupRow>(&mut *conn))??;
Ok(EncodedCollab::decode_from_bytes(&row.data)?)
}
}
#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable)]
#[diesel(table_name = rocksdb_backup)]
#[diesel(primary_key(object_id))]
struct RocksdbBackupRow {
object_id: String,
timestamp: i64,
data: Vec<u8>,
}
struct RocksdbBackupTableSql;
impl RocksdbBackupTableSql {
fn create(row: RocksdbBackupRow, conn: &mut SqliteConnection) -> Result<(), FlowyError> {
let _ = replace_into(dsl::rocksdb_backup)
.values(&row)
.execute(conn)?;
Ok(())
}
#[allow(dead_code)]
fn get_row(object_id: &str, conn: &mut SqliteConnection) -> Result<RocksdbBackupRow, FlowyError> {
let sql = dsl::rocksdb_backup
.filter(dsl::object_id.eq(object_id))
.into_boxed();
let row = sql.first::<RocksdbBackupRow>(conn)?;
Ok(row)
}
}

View File

@ -8,6 +8,5 @@ mod document_deps;
mod folder_deps;
mod util;
pub mod collab_backup;
mod database_deps;
mod user_deps;

View File

@ -23,7 +23,6 @@ use lib_dispatch::runtime::AFPluginRuntime;
use module::make_plugins;
use crate::config::AppFlowyCoreConfig;
use crate::deps_resolve::collab_backup::RocksdbBackupImpl;
use crate::deps_resolve::*;
use crate::integrate::collab_interact::CollabInteractImpl;
use crate::integrate::log::init_log;
@ -129,8 +128,6 @@ impl AppFlowyCore {
collab_builder
.set_snapshot_persistence(Arc::new(SnapshotDBImpl(Arc::downgrade(&user_manager))));
collab_builder.set_rocksdb_backup(Arc::new(RocksdbBackupImpl(Arc::downgrade(&user_manager))));
let database_manager = DatabaseDepsResolver::resolve(
Arc::downgrade(&user_manager),
task_dispatcher.clone(),

View File

@ -94,7 +94,7 @@ impl DatabaseManager {
collab_builder: self.collab_builder.clone(),
cloud_service: self.cloud_service.clone(),
};
let config = CollabPersistenceConfig::new().snapshot_per_update(10);
let config = CollabPersistenceConfig::new().snapshot_per_update(100);
let mut collab_raw_data = CollabDocState::default();
// If the workspace database not exist in disk, try to fetch from remote.

View File

@ -274,7 +274,7 @@ impl DocumentManager {
CollabType::Document,
db,
doc_state,
CollabPersistenceConfig::default().snapshot_per_update(100),
CollabPersistenceConfig::default().snapshot_per_update(1000),
CollabBuilderConfig::default().sync_enable(sync_enable),
)
.await?;

View File

@ -149,7 +149,7 @@ impl FolderManager {
collab_doc_state,
CollabPersistenceConfig::new()
.enable_snapshot(true)
.snapshot_per_update(20),
.snapshot_per_update(50),
CollabBuilderConfig::default().sync_enable(true),
)
.await?;

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE IF EXISTS collab_snapshot;

View File

@ -0,0 +1,15 @@
-- Your SQL goes here
-- Drop the table if it exists
DROP TABLE IF EXISTS rocksdb_backup;
DROP TABLE IF EXISTS collab_snapshot;
-- Recreate the table
CREATE TABLE collab_snapshot (
id TEXT NOT NULL PRIMARY KEY DEFAULT '',
object_id TEXT NOT NULL DEFAULT '',
title TEXT NOT NULL DEFAULT '',
desc TEXT NOT NULL DEFAULT '',
collab_type TEXT NOT NULL DEFAULT '',
timestamp BIGINT NOT NULL DEFAULT 0,
data BLOB NOT NULL DEFAULT (x'')
);

View File

@ -12,14 +12,6 @@ diesel::table! {
}
}
diesel::table! {
rocksdb_backup (object_id) {
object_id -> Text,
timestamp -> BigInt,
data -> Binary,
}
}
diesel::table! {
user_data_migration_records (id) {
id -> Integer,
@ -56,7 +48,6 @@ diesel::table! {
diesel::allow_tables_to_appear_in_same_query!(
collab_snapshot,
rocksdb_backup,
user_data_migration_records,
user_table,
user_workspace_table,

View File

@ -28,6 +28,7 @@ use crate::anon_user::{
};
use crate::entities::{AuthStateChangedPB, AuthStatePB, UserProfilePB, UserSettingPB};
use crate::event_map::{DefaultUserStatusCallback, UserStatusCallback};
use crate::migrations::database_vacuum::vacuum_database_if_need;
use crate::migrations::document_empty_content::HistoricalEmptyDocumentMigration;
use crate::migrations::migration::{UserDataMigration, UserLocalDataMigration};
use crate::migrations::session_migration::migrate_session_with_user_uuid;
@ -145,7 +146,6 @@ impl UserManager {
if let Ok(session) = self.get_session() {
let user = self.get_user_profile_from_disk(session.user_id).await?;
// Get the current authenticator from the environment variable
let current_authenticator = current_authenticator();
@ -216,6 +216,8 @@ impl UserManager {
_ => error!("Failed to get collab db or sqlite pool"),
}
vacuum_database_if_need(session.user_id, &self.database, &self.store_preferences);
let cloud_config = get_cloud_config(session.user_id, &self.store_preferences);
if let Err(e) = user_status_callback
.did_init(

View File

@ -0,0 +1,24 @@
use crate::services::db::UserDB;
use crate::services::user_sql::vacuum_database;
use flowy_sqlite::kv::StorePreferences;
use std::sync::Arc;
use tracing::{error, info};
const SQLITE_VACUUM_04: &str = "sqlite_vacuum_04";
pub fn vacuum_database_if_need(
uid: i64,
user_db: &Arc<UserDB>,
store_preferences: &Arc<StorePreferences>,
) {
if !store_preferences.get_bool(SQLITE_VACUUM_04) {
let _ = store_preferences.set_bool(SQLITE_VACUUM_04, true);
if let Ok(conn) = user_db.get_connection(uid) {
info!("vacuum database 04");
if let Err(err) = vacuum_database(conn) {
error!("vacuum database error: {:?}", err);
}
}
}
}

View File

@ -2,6 +2,7 @@ use flowy_user_deps::entities::UserProfile;
use crate::services::entities::Session;
pub mod database_vacuum;
pub mod document_empty_content;
pub mod migration;
pub mod session_migration;

View File

@ -1,11 +1,8 @@
use std::sync::Arc;
use serde_json::{json, Value};
use uuid::Uuid;
use flowy_sqlite::kv::StorePreferences;
use crate::services::entities::Session;
use flowy_sqlite::kv::StorePreferences;
use serde_json::{json, Value};
use std::sync::Arc;
use uuid::Uuid;
const MIGRATION_USER_NO_USER_UUID: &str = "migration_user_no_user_uuid";

View File

@ -1,5 +1,5 @@
use diesel::RunQueryDsl;
use flowy_error::FlowyError;
use diesel::{sql_query, RunQueryDsl};
use flowy_error::{internal_error, FlowyError};
use std::str::FromStr;
use flowy_user_deps::cloud::UserUpdate;
@ -145,3 +145,10 @@ pub fn select_user_profile(uid: i64, mut conn: DBConnection) -> Result<UserProfi
Ok(user)
}
pub(crate) fn vacuum_database(mut conn: DBConnection) -> Result<(), FlowyError> {
sql_query("VACUUM")
.execute(&mut *conn)
.map_err(internal_error)?;
Ok(())
}