Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove shadow tables from database dump #1477

Merged
merged 4 commits into from
Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions GRDB/Dump/Database+Dump.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ extension Database {
///
/// > Note: Internal SQLite and GRDB schema objects are not recorded
/// > (those with a name that starts with "sqlite_" or "grdb_").
/// >
/// > [Shadow tables](https://www.sqlite.org/vtab.html#xshadowname) are
/// > not recorded, starting SQLite 3.37+.
///
/// - Parameters:
/// - format: The output format.
Expand Down Expand Up @@ -252,7 +255,7 @@ extension Database {
""")
for row in sqlRows {
let name: String = row[1]
if Database.isSQLiteInternalTable(name) || Database.isGRDBInternalTable(name) {
if try ignoresObject(named: name) {
continue
}
stream.writeln(row[0])
Expand All @@ -266,12 +269,42 @@ extension Database {
ORDER BY name COLLATE NOCASE
""")
.filter {
!(Database.isSQLiteInternalTable($0) || Database.isGRDBInternalTable($0))
try !ignoresObject(named: $0)
}
if tables.isEmpty { return }
stream.write("\n")
try _dumpTables(tables, format: format, tableHeader: .always, stableOrder: true, to: &stream)
}

private func ignoresObject(named name: String) throws -> Bool {
if Database.isSQLiteInternalTable(name) { return true }
if Database.isGRDBInternalTable(name) { return true }
if try isShadowTable(name) { return true }
return false
}

private func isShadowTable(_ tableName: String) throws -> Bool {
#if GRDBCUSTOMSQLITE || GRDBCIPHER
// Maybe SQLCipher is too old: check actual version
if sqlite3_libversion_number() >= 3037000 {
guard let table = try table(tableName) else {
// Not a table
return false
}
return table.kind == .shadow
}
#else
if #available(iOS 15.4, macOS 12.4, tvOS 15.4, watchOS 8.5, *) { // SQLite 3.37+
guard let table = try table(tableName) else {
// Not a table
return false
}
return table.kind == .shadow
}
#endif
// Don't know
return false
}
}

/// Options for printing table names.
Expand Down
3 changes: 3 additions & 0 deletions GRDB/Dump/DatabaseReader+dump.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ extension DatabaseReader {
///
/// > Note: Internal SQLite and GRDB schema objects are not recorded
/// > (those with a name that starts with "sqlite_" or "grdb_").
/// >
/// > [Shadow tables](https://www.sqlite.org/vtab.html#xshadowname) are
/// > not recorded, starting SQLite 3.37+.
///
/// - Parameters:
/// - format: The output format.
Expand Down
72 changes: 72 additions & 0 deletions Tests/GRDBTests/DatabaseDumpTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,78 @@ final class DatabaseDumpTests: GRDBTestCase {
}
}

func test_dumpContent_ignores_shadow_tables() throws {
guard sqlite3_libversion_number() >= 3037000 else {
throw XCTSkip("Can't detect shadow tables")
}

try makeDatabaseQueue().write { db in
try db.create(table: "document") { t in
t.autoIncrementedPrimaryKey("id")
t.column("body")
}

try db.execute(sql: "INSERT INTO document VALUES (1, 'Hello world!')")

try db.create(virtualTable: "document_ft", using: FTS4()) { t in
t.synchronize(withTable: "document")
t.column("body")
}

let stream = TestStream()
try db.dumpContent(to: stream)
print(stream.output)
XCTAssertEqual(stream.output, """
sqlite_master
CREATE TABLE "document" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "body");
CREATE TRIGGER "__document_ft_ai" AFTER INSERT ON "document" BEGIN
INSERT INTO "document_ft"("docid", "body") VALUES(new."id", new."body");
END;
CREATE TRIGGER "__document_ft_au" AFTER UPDATE ON "document" BEGIN
INSERT INTO "document_ft"("docid", "body") VALUES(new."id", new."body");
END;
CREATE TRIGGER "__document_ft_bd" BEFORE DELETE ON "document" BEGIN
DELETE FROM "document_ft" WHERE docid=old."id";
END;
CREATE TRIGGER "__document_ft_bu" BEFORE UPDATE ON "document" BEGIN
DELETE FROM "document_ft" WHERE docid=old."id";
END;
CREATE VIRTUAL TABLE "document_ft" USING fts4(body, content="document");

document
1|Hello world!

document_ft
Hello world!

""")
}
}

func test_dumpContent_ignores_GRDB_internal_tables() throws {
let dbQueue = try makeDatabaseQueue()
var migrator = DatabaseMigrator()
migrator.registerMigration("v1") { db in
try db.create(table: "player") { t in
t.autoIncrementedPrimaryKey("id")
}
}
try migrator.migrate(dbQueue)

try dbQueue.read { db in
let stream = TestStream()
try db.dumpContent(to: stream)
print(stream.output)
XCTAssertEqual(stream.output, """
sqlite_master
CREATE TABLE "player" ("id" INTEGER PRIMARY KEY AUTOINCREMENT);

player

""")
}
}

// MARK: - Support Databases

private func makeValuesDatabase() throws -> DatabaseQueue {
Expand Down