3

In a document-based SwiftUI application, I’d like to persist each document to a separate Sqlite file using GRDB as a Sqlite wrapper. It’s straightforward to load Sqlite files in a document that implements the FileDocument protocol by creating a DatabaseQueue for the file to be loaded and using its .backup(to:) method to copy to an in-memory DatabaseQueue. How should I implement saving in the func fileWrapper(configuration: WriteConfiguration) method? There doesn’t seem to be an obvious way to use the same .backup(to:) approach.

I found an example application by Andre Yonadam that approaches this in the same way in a subclass of NSDocument:

override func write(to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType, originalContentsURL absoluteOriginalContentsURL: URL?) throws {
    let destination = try DatabaseQueue(path: url.path)
    do {
        try memoryDBQueue.backup(to: destination)
    } catch {
        throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
    }
}

override func read(from url: URL, ofType typeName: String) throws {
    let source = try DatabaseQueue(path: url.path)
    do {
        try source.backup(to: memoryDBQueue)
    } catch {
        throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
    }
}

1 Answer 1

2

It’s perhaps not the cleanest solution but I worked around this by implementing a subclass of FileWrapper that knows how to write to Sqlite files:

class SqliteFileWrapper: FileWrapper {

    var databaseQueue: DatabaseQueue

    init (fromDatabaseQueue databaseQueue: DatabaseQueue) {
        self.databaseQueue = databaseQueue
        super.init(regularFileWithContents: "".data(using: .utf8)!)
    }

    required init?(coder inCoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func write(
        to url: URL,
        options: FileWrapper.WritingOptions = [],
        originalContentsURL: URL?
    ) throws {
        let destination = try DatabaseQueue(path: url.path)
        do {
            try databaseQueue.backup(to: destination)
        } catch {
            throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
        }
    }

}

and then in my FileDocument subclass I create a SqliteFileWrapper instead of a FileWrapper:

func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
    SqliteFileWrapper(fromDatabaseQueue: memoryDBQueue)
}
Sign up to request clarification or add additional context in comments.

3 Comments

Is this safe to do? From the docs: "Serialization and deserialization occur on a background thread." The file wrapper contents are handed over to a background thread which then does the actual writing to disk. Could the system not just write the contents to a temp location and then move it to its final destination. In that case, it is not be save to use with database files.
With the proviso that I’m not an expert, I think it should be safe because it’s just doing a backup of an in-memory database to the file location on write and a restore to a fresh in-memory database on read. It’s not opening the file as a live database.
Ok. Yes that would be save. More care is needed for a live database.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.