From ba320a08e30850f5d23ac34d7a2725f9093bbf78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gwendal=20Roue=CC=81?= Date: Sat, 28 Oct 2023 15:46:31 +0200 Subject: [PATCH] Make dumpTables able to print views --- GRDB/Core/Database.swift | 2 +- GRDB/Core/DatabaseReader.swift | 2 +- GRDB/Dump/Database+Dump.swift | 34 ++++++++++++-- GRDB/Dump/DatabaseReader+dump.swift | 16 ++++++- Tests/GRDBTests/DatabaseDumpTests.swift | 62 ++++++++++++++++++++++++- 5 files changed, 106 insertions(+), 10 deletions(-) diff --git a/GRDB/Core/Database.swift b/GRDB/Core/Database.swift index f226b455c4..9826711728 100644 --- a/GRDB/Core/Database.swift +++ b/GRDB/Core/Database.swift @@ -70,7 +70,7 @@ let SQLITE_TRANSIENT = unsafeBitCast(OpaquePointer(bitPattern: -1), to: sqlite3_ /// - ``dumpContent(format:to:)`` /// - ``dumpRequest(_:format:to:)`` /// - ``dumpSQL(_:format:to:)`` -/// - ``dumpTables(_:format:tableHeader:to:)`` +/// - ``dumpTables(_:format:tableHeader:stableOrder:to:)`` /// - ``DumpFormat`` /// - ``DumpTableHeaderOptions`` /// diff --git a/GRDB/Core/DatabaseReader.swift b/GRDB/Core/DatabaseReader.swift index 2f064dc153..a07abf9ec4 100644 --- a/GRDB/Core/DatabaseReader.swift +++ b/GRDB/Core/DatabaseReader.swift @@ -38,7 +38,7 @@ import Dispatch /// - ``dumpContent(format:to:)`` /// - ``dumpRequest(_:format:to:)`` /// - ``dumpSQL(_:format:to:)`` -/// - ``dumpTables(_:format:tableHeader:to:)`` +/// - ``dumpTables(_:format:tableHeader:stableOrder:to:)`` /// - ``DumpFormat`` /// - ``DumpTableHeaderOptions`` /// diff --git a/GRDB/Dump/Database+Dump.swift b/GRDB/Dump/Database+Dump.swift index eb327eb283..146efed162 100644 --- a/GRDB/Dump/Database+Dump.swift +++ b/GRDB/Dump/Database+Dump.swift @@ -59,7 +59,7 @@ extension Database { try _dumpRequest(request, format: format, to: &dumpStream) } - /// Prints the contents of the provided tables. + /// Prints the contents of the provided tables and views. /// /// For example: /// @@ -80,17 +80,29 @@ extension Database { /// - tables: The table names. /// - format: The output format. /// - tableHeader: Options for printing table names. + /// - stableOrder: A boolean value that controls the ordering of + /// rows fetched from views. If false (the default), rows are + /// printed in the order specified by the view (which may be + /// undefined). It true, outputted rows are always printed in the + /// same stable order. The purpose of this stable order is to make + /// the output suitable for testing. /// - stream: A stream for text output, which directs output to the /// console by default. public func dumpTables( _ tables: [String], format: some DumpFormat = .debug(), tableHeader: DumpTableHeaderOptions = .automatic, + stableOrder: Bool = false, to stream: (any TextOutputStream)? = nil) throws { var dumpStream = DumpStream(stream) - try _dumpTables(tables, format: format, tableHeader: tableHeader, to: &dumpStream) + try _dumpTables( + tables, + format: format, + tableHeader: tableHeader, + stableOrder: stableOrder, + to: &dumpStream) } /// Prints the contents of the database. @@ -186,7 +198,8 @@ extension Database { func _dumpTables( _ tables: [String], format: some DumpFormat, - tableHeader: DumpTableHeaderOptions = .automatic, + tableHeader: DumpTableHeaderOptions, + stableOrder: Bool, to stream: inout DumpStream) throws { @@ -203,10 +216,21 @@ extension Database { } else { stream.write("\n") } + if header { stream.writeln(table) } - try _dumpRequest(Table(table).orderByPrimaryKey(), format: format, to: &stream) + + if try tableExists(table) { + // Always sort tables by primary key + try _dumpRequest(Table(table).orderByPrimaryKey(), format: format, to: &stream) + } else if stableOrder { + // View with stable order + try _dumpRequest(Table(table).all().withStableOrder(), format: format, to: &stream) + } else { + // Use view ordering, if any (no guarantee of stable order). + try _dumpRequest(Table(table).all(), format: format, to: &stream) + } } } @@ -246,7 +270,7 @@ extension Database { } if tables.isEmpty { return } stream.write("\n") - try _dumpTables(tables, format: format, tableHeader: .always, to: &stream) + try _dumpTables(tables, format: format, tableHeader: .always, stableOrder: true, to: &stream) } } diff --git a/GRDB/Dump/DatabaseReader+dump.swift b/GRDB/Dump/DatabaseReader+dump.swift index bff43b777a..e18807d058 100644 --- a/GRDB/Dump/DatabaseReader+dump.swift +++ b/GRDB/Dump/DatabaseReader+dump.swift @@ -53,7 +53,7 @@ extension DatabaseReader { } } - /// Prints the contents of the provided tables. + /// Prints the contents of the provided tables and views. /// /// For example: /// @@ -72,17 +72,29 @@ extension DatabaseReader { /// - tables: The table names. /// - format: The output format. /// - tableHeader: Options for printing table names. + /// - stableOrder: A boolean value that controls the ordering of + /// rows fetched from views. If false (the default), rows are + /// printed in the order specified by the view (which may be + /// undefined). It true, outputted rows are always printed in the + /// same stable order. The purpose of this stable order is to make + /// the output suitable for testing. /// - stream: A stream for text output, which directs output to the /// console by default. public func dumpTables( _ tables: [String], format: some DumpFormat = .debug(), tableHeader: DumpTableHeaderOptions = .automatic, + stableOrder: Bool = false, to stream: (any TextOutputStream)? = nil) throws { try unsafeReentrantRead { db in - try db.dumpTables(tables, format: format, tableHeader: tableHeader, to: stream) + try db.dumpTables( + tables, + format: format, + tableHeader: tableHeader, + stableOrder: stableOrder, + to: stream) } } diff --git a/Tests/GRDBTests/DatabaseDumpTests.swift b/Tests/GRDBTests/DatabaseDumpTests.swift index bedd64de07..0665e44eee 100644 --- a/Tests/GRDBTests/DatabaseDumpTests.swift +++ b/Tests/GRDBTests/DatabaseDumpTests.swift @@ -1072,7 +1072,7 @@ final class DatabaseDumpTests: GRDBTestCase { } } - func test_dumpTables_single() throws { + func test_dumpTables_single_table() throws { try makeRugbyDatabase().read { db in do { // Default format @@ -1099,6 +1099,66 @@ final class DatabaseDumpTests: GRDBTestCase { } } + func test_dumpTables_single_view() throws { + try makeRugbyDatabase().write { db in + try db.create(view: "playerName", as: Player + .orderByPrimaryKey() + .select(Column("name"))) + + do { + // Default order: use the view ordering + do { + // Default format + let stream = TestStream() + try db.dumpTables(["playerName"], to: stream) + XCTAssertEqual(stream.output, """ + Antoine Dupond + Owen Farrell + Gwendal Roué + + """) + } + do { + // Custom format + let stream = TestStream() + try db.dumpTables(["playerName"], format: .json(), to: stream) + XCTAssertEqual(stream.output, """ + [{"name":"Antoine Dupond"}, + {"name":"Owen Farrell"}, + {"name":"Gwendal Roué"}] + + """) + } + } + + do { + // Stable order + do { + // Default format + let stream = TestStream() + try db.dumpTables(["playerName"], stableOrder: true, to: stream) + XCTAssertEqual(stream.output, """ + Antoine Dupond + Gwendal Roué + Owen Farrell + + """) + } + do { + // Custom format + let stream = TestStream() + try db.dumpTables(["playerName"], format: .json(), stableOrder: true, to: stream) + XCTAssertEqual(stream.output, """ + [{"name":"Antoine Dupond"}, + {"name":"Gwendal Roué"}, + {"name":"Owen Farrell"}] + + """) + } + } + } + } + func test_dumpTables_multiple() throws { try makeRugbyDatabase().read { db in do {