Option to copy both schema and data

This commit is contained in:
Diego Imbert
2026-03-12 13:45:55 +01:00
parent 6a6e769bd6
commit 45a83cf427
3 changed files with 44 additions and 25 deletions
@@ -1323,9 +1323,14 @@ async fn get_datatable_schema(db: &DB, w_id: &str, datatable_name: &str) -> Resu
Ok(schema_map)
}
/// Export the schema of a datatable using pg_dump.
/// Returns a list of SQL statements that recreate the entire schema (no data).
pub async fn dump_datatable(db: &DB, w_id: &str, datatable_name: &str) -> Result<String> {
/// Export a datatable using pg_dump.
/// If `schema_only` is true, only the schema is exported. Otherwise, schema and data are exported.
pub async fn dump_datatable(
db: &DB,
w_id: &str,
datatable_name: &str,
schema_only: bool,
) -> Result<String> {
let db_resource = get_datatable_resource_from_db_unchecked(db, w_id, datatable_name).await?;
let pg_db: PgDatabase = serde_json::from_value(db_resource)
@@ -1337,9 +1342,11 @@ pub async fn dump_datatable(db: &DB, w_id: &str, datatable_name: &str) -> Result
let dbname = &pg_db.dbname;
let mut cmd = tokio::process::Command::new("pg_dump");
cmd.arg("--format=plain")
.arg("--schema-only")
.arg("--host")
cmd.arg("--format=plain");
if schema_only {
cmd.arg("--schema-only");
}
cmd.arg("--host")
.arg(host)
.arg("--port")
.arg(&port)
@@ -1376,6 +1383,8 @@ struct ForkDatatableRequest {
source_datatable_name: String,
new_datatable_name: String,
new_custom_instance_database_name: String,
#[serde(default)]
include_data: bool,
}
async fn fork_datatable(
@@ -1480,7 +1489,7 @@ async fn fork_datatable(
serde_json::to_value(new_settings).map_err(|err| Error::internal_err(err.to_string()))?;
// Export the schema from the source BEFORE updating the config (which points to the new empty DB)
let dump = dump_datatable(&db, &w_id, &req.source_datatable_name).await?;
let dump = dump_datatable(&db, &w_id, &req.source_datatable_name, !req.include_data).await?;
sqlx::query!(
"UPDATE workspace_settings SET datatable = $1 WHERE workspace_id = $2",
+4
View File
@@ -3357,6 +3357,10 @@ paths:
new_custom_instance_database_name:
type: string
description: "Name for the new database. Use $current_name to interpolate the original database name."
include_data:
type: boolean
default: false
description: "If true, fork both schema and data. If false (default), fork schema only."
responses:
"200":
description: status
@@ -195,29 +195,35 @@
workspace: $workspaceStore!
})
if (datatables.includes('main')) {
const forkDatatableAction = async (includeData: boolean) => {
try {
await WorkspaceService.forkDatatable({
workspace: prefixed_id,
requestBody: {
source_datatable_name: 'main',
new_datatable_name: 'main',
new_custom_instance_database_name: `__wmfork__${prefixed_id.replace(/-/g, '_')}__$current_name`,
include_data: includeData
}
})
sendUserToast(
`Successfully forked "main" datatable${includeData ? ' with data' : ''} to workspace "${prefixed_id}"`
)
} catch (e) {
sendUserToast(`Failed to fork datatable: ${e?.body ?? e}`, 'error')
}
}
sendUserToast(
`Fork the main datatable ?`,
`Fork the main datatable?`,
'info',
[
{
label: 'Fork schema only',
callback: async () => {
try {
await WorkspaceService.forkDatatable({
workspace: prefixed_id,
requestBody: {
source_datatable_name: 'main',
new_datatable_name: 'main',
new_custom_instance_database_name: `__wmfork__${prefixed_id.replace(/-/g, '_')}__$current_name`
}
})
sendUserToast(
`Successfully forked "main" datatable to workspace "${prefixed_id}"`
)
} catch (e) {
sendUserToast(`Failed to fork datatable: ${e?.body ?? e}`, 'error')
}
}
callback: () => forkDatatableAction(false)
},
{
label: 'Fork schema and data',
callback: () => forkDatatableAction(true)
}
],
undefined,