Skip to content

Commit 8d1478a

Browse files
committed
add preserve_rowids query parameter to align with native SQLite shell feature
1 parent 21f405b commit 8d1478a

File tree

3 files changed

+42
-9
lines changed

3 files changed

+42
-9
lines changed

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

Lines changed: 23 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,7 @@ 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) = self.list_table_columns(txn, table_str, preserve_rowids)?;
7172
let mut insert = String::new();
7273
write!(&mut insert, "INSERT INTO {}", Quoted(table_str))?;
7374

@@ -146,11 +147,12 @@ impl<W: Write> DumpState<W> {
146147
&self,
147148
txn: &rusqlite::Connection,
148149
table: &str,
150+
preserve_rowids: bool,
149151
) -> anyhow::Result<(Option<String>, Vec<String>)> {
150152
let mut cols = Vec::new();
151153
let mut num_primary_keys = 0;
152154
let mut is_integer_primary_key = false;
153-
let mut preserve_row_id = false;
155+
let mut preserve_rowids = preserve_rowids;
154156
let mut row_id_col = None;
155157

156158
txn.pragma(None, "table_info", table, |row| {
@@ -186,14 +188,14 @@ impl<W: Write> DumpState<W> {
186188
[table],
187189
|_| {
188190
// re-set preserve_row_id if there is a row
189-
preserve_row_id = true;
191+
preserve_rowids = true;
190192
Ok(())
191193
},
192194
)
193195
.optional()?;
194196
}
195197

196-
if preserve_row_id {
198+
if preserve_rowids {
197199
const ROW_ID_NAMES: [&str; 3] = ["rowid", "_row_id_", "oid"];
198200

199201
for row_id_name in ROW_ID_NAMES {
@@ -430,7 +432,7 @@ fn find_unused_str(haystack: &str, needle1: &str, needle2: &str) -> String {
430432
}
431433
}
432434

433-
pub fn export_dump(db: &mut rusqlite::Connection, writer: impl Write) -> anyhow::Result<()> {
435+
pub fn export_dump(db: &mut rusqlite::Connection, writer: impl Write, preserve_rowids: bool) -> anyhow::Result<()> {
434436
let mut txn = db.transaction()?;
435437
txn.execute("PRAGMA writable_schema=ON", ())?;
436438
let savepoint = txn.savepoint_with_name("dump")?;
@@ -451,7 +453,7 @@ pub fn export_dump(db: &mut rusqlite::Connection, writer: impl Write) -> anyhow:
451453
WHERE type=='table'
452454
AND sql NOT NULL
453455
ORDER BY tbl_name='sqlite_sequence', rowid";
454-
state.run_schema_dump_query(&savepoint, q)?;
456+
state.run_schema_dump_query(&savepoint, q, preserve_rowids)?;
455457

456458
let q = "SELECT sql FROM sqlite_schema AS o
457459
WHERE sql NOT NULL
@@ -508,7 +510,21 @@ mod test {
508510
conn.execute(r#"create table test ("limit")"#, ()).unwrap();
509511

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

513529
insta::assert_snapshot!(std::str::from_utf8(&out).unwrap());
514530
}
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: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ use std::future::Future;
22
use std::pin::Pin;
33
use std::task;
44

5-
use axum::extract::State as AxumState;
5+
use serde::Deserialize;
6+
use axum::extract::{Query, State as AxumState};
67
use futures::StreamExt;
78
use hyper::HeaderMap;
89
use pin_project_lite::pin_project;
@@ -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,7 @@ 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| export_dump(conn, writer, query.preserve_row_ids.unwrap_or(false)).map_err(Into::into))
106113
});
107114

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

0 commit comments

Comments
 (0)