Skip to content

Commit a64a089

Browse files
authored
Merge pull request #1609 from tursodatabase/sqld-dump-preserve-rowids
add preserve_rowids query parameter to align with native SQLite shell feature
2 parents f1d4c4e + 4ceede9 commit a64a089

File tree

3 files changed

+52
-9
lines changed

3 files changed

+52
-9
lines changed

libsql-server/src/connection/dump/exporter.rs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ impl<W: Write> DumpState<W> {
2020
&mut self,
2121
txn: &rusqlite::Connection,
2222
stmt: &str,
23+
preserve_rowids: bool,
2324
) -> anyhow::Result<()> {
2425
let mut stmt = txn.prepare(stmt)?;
2526
let mut rows = stmt.query(())?;
@@ -67,7 +68,8 @@ impl<W: Write> DumpState<W> {
6768

6869
if ty == b"table" {
6970
let table_str = std::str::from_utf8(table)?;
70-
let (row_id_col, colss) = self.list_table_columns(txn, table_str)?;
71+
let (row_id_col, colss) =
72+
self.list_table_columns(txn, table_str, preserve_rowids)?;
7173
let mut insert = String::new();
7274
write!(&mut insert, "INSERT INTO {}", Quoted(table_str))?;
7375

@@ -146,11 +148,12 @@ impl<W: Write> DumpState<W> {
146148
&self,
147149
txn: &rusqlite::Connection,
148150
table: &str,
151+
preserve_rowids: bool,
149152
) -> anyhow::Result<(Option<String>, Vec<String>)> {
150153
let mut cols = Vec::new();
151154
let mut num_primary_keys = 0;
152155
let mut is_integer_primary_key = false;
153-
let mut preserve_row_id = false;
156+
let mut preserve_rowids = preserve_rowids;
154157
let mut row_id_col = None;
155158

156159
txn.pragma(None, "table_info", table, |row| {
@@ -186,14 +189,14 @@ impl<W: Write> DumpState<W> {
186189
[table],
187190
|_| {
188191
// re-set preserve_row_id if there is a row
189-
preserve_row_id = true;
192+
preserve_rowids = true;
190193
Ok(())
191194
},
192195
)
193196
.optional()?;
194197
}
195198

196-
if preserve_row_id {
199+
if preserve_rowids {
197200
const ROW_ID_NAMES: [&str; 3] = ["rowid", "_row_id_", "oid"];
198201

199202
for row_id_name in ROW_ID_NAMES {
@@ -430,7 +433,11 @@ fn find_unused_str(haystack: &str, needle1: &str, needle2: &str) -> String {
430433
}
431434
}
432435

433-
pub fn export_dump(db: &mut rusqlite::Connection, writer: impl Write) -> anyhow::Result<()> {
436+
pub fn export_dump(
437+
db: &mut rusqlite::Connection,
438+
writer: impl Write,
439+
preserve_rowids: bool,
440+
) -> anyhow::Result<()> {
434441
let mut txn = db.transaction()?;
435442
txn.execute("PRAGMA writable_schema=ON", ())?;
436443
let savepoint = txn.savepoint_with_name("dump")?;
@@ -451,7 +458,7 @@ pub fn export_dump(db: &mut rusqlite::Connection, writer: impl Write) -> anyhow:
451458
WHERE type=='table'
452459
AND sql NOT NULL
453460
ORDER BY tbl_name='sqlite_sequence', rowid";
454-
state.run_schema_dump_query(&savepoint, q)?;
461+
state.run_schema_dump_query(&savepoint, q, preserve_rowids)?;
455462

456463
let q = "SELECT sql FROM sqlite_schema AS o
457464
WHERE sql NOT NULL
@@ -508,7 +515,24 @@ mod test {
508515
conn.execute(r#"create table test ("limit")"#, ()).unwrap();
509516

510517
let mut out = Vec::new();
511-
export_dump(&mut conn, &mut out).unwrap();
518+
export_dump(&mut conn, &mut out, false).unwrap();
519+
520+
insta::assert_snapshot!(std::str::from_utf8(&out).unwrap());
521+
}
522+
523+
#[test]
524+
fn table_preserve_rowids() {
525+
let tmp = tempdir().unwrap();
526+
let mut conn = Connection::open(tmp.path().join("data")).unwrap();
527+
conn.execute(r#"create table test ( id TEXT PRIMARY KEY )"#, ())
528+
.unwrap();
529+
conn.execute(r#"insert into test values ( 'a' ), ( 'b' ), ( 'c' )"#, ())
530+
.unwrap();
531+
conn.execute(r#"delete from test where id = 'a'"#, ())
532+
.unwrap();
533+
534+
let mut out = Vec::new();
535+
export_dump(&mut conn, &mut out, true).unwrap();
512536

513537
insta::assert_snapshot!(std::str::from_utf8(&out).unwrap());
514538
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
source: libsql-server/src/connection/dump/exporter.rs
3+
expression: "std::str::from_utf8(&out).unwrap()"
4+
---
5+
PRAGMA foreign_keys=OFF;
6+
BEGIN TRANSACTION;
7+
CREATE TABLE IF NOT EXISTS test ( id TEXT PRIMARY KEY );
8+
INSERT INTO test(rowid,id) VALUES(2,'b');
9+
INSERT INTO test(rowid,id) VALUES(3,'c');
10+
COMMIT;

libsql-server/src/http/user/dump.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ use std::future::Future;
22
use std::pin::Pin;
33
use std::task;
44

5-
use axum::extract::State as AxumState;
5+
use axum::extract::{Query, State as AxumState};
66
use futures::StreamExt;
77
use hyper::HeaderMap;
88
use pin_project_lite::pin_project;
9+
use serde::Deserialize;
910

1011
use crate::auth::Authenticated;
1112
use crate::connection::dump::exporter::export_dump;
@@ -72,10 +73,16 @@ where
7273
}
7374
}
7475

76+
#[derive(Deserialize)]
77+
pub struct DumpQuery {
78+
preserve_row_ids: Option<bool>,
79+
}
80+
7581
pub(super) async fn handle_dump(
7682
auth: Authenticated,
7783
AxumState(state): AxumState<AppState>,
7884
headers: HeaderMap,
85+
query: Query<DumpQuery>,
7986
) -> crate::Result<axum::body::StreamBody<impl futures::Stream<Item = Result<bytes::Bytes, Error>>>>
8087
{
8188
let namespace = namespace_from_headers(
@@ -102,7 +109,9 @@ pub(super) async fn handle_dump(
102109

103110
let join_handle = BLOCKING_RT.spawn_blocking(move || {
104111
let writer = tokio_util::io::SyncIoBridge::new(writer);
105-
conn.with_raw(|conn| export_dump(conn, writer).map_err(Into::into))
112+
conn.with_raw(|conn| {
113+
export_dump(conn, writer, query.preserve_row_ids.unwrap_or(false)).map_err(Into::into)
114+
})
106115
});
107116

108117
let stream = tokio_util::io::ReaderStream::new(reader);

0 commit comments

Comments
 (0)