Git Product home page Git Product logo

grdbcombine's Introduction

GRDBCombine

A set of extensions for SQLite, GRDB.swift, and Combine


GRDBCombine is embedded right into GRDB 5, so you do not need this library any longer.

Users of GRDB 4 can use GRDBCombine v0.8.1.

The master branch contains the latest GRDBCombine sources, used until GRDB 5.0.0-beta.5.

grdbcombine's People

Contributors

bellebethcooper avatar diatoming avatar gjeck avatar groue avatar haikusw avatar mallman avatar pocketpixels avatar skrew avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

grdbcombine's Issues

Using in a framework with backward compatibility?

Hi,

I'm currently building a framework which is shared between two applications, one with a minimum deployment target of iOS 13 and another with iOS 11.

I am able to use Combine in this scenario by utilising @available attributes to limit optional portions of the framework to iOS 13+, but GRDBCombine refuses to work when the target is lower.

Is it possible (or even desirable?) for a library such as GRDBCombine to allow the framework to be imported, but wrapped up with @available attributes everywhere such as what I would need to use it here?

Thanks

Is GRDBCombine suitable for production used?

Hey,

Thank you, and Congratulation for creating GRDB.swift.

When I compare against GRDB.swift, SQLite.swift and FMDB, we decide to go for GRDB.swift. I like how it works, and it seems closest to ORM taste.

I'm from Android background, our previous project relies heavily on Room's LiveData

May I know, is GRDBCombine having similar idea/ philosophy as Android Room's LiveData? Is it suitable for production usage at current stage?

Thank you.

Obtaining unique items and displaying them in a list with SwiftUI

I have a sample test project at https://github.com/EricG-Personal/grdb_test.git

In ContentView.swift, I currently have one list that shows all of the items in the Database.

Below that, I would like another list that shows all of the unique 'names' from the test table. While I think I know one or two ways to accomplish this task, I am pretty sure neither of them would be the recommended method.

How do you recommend I do this the correct way using GRDBCombine? Would it involve another publisher?

DEMO application crashes with Xcode 11.2

I am not sure what is going wrong, but after I upgraded to Xcode 11.2, the demo application now crashes at:

extension EncodableRecord where Self: Encodable {
    public func encode(to container: inout PersistenceContainer) {
        let encoder = RecordEncoder<Self>(persistenceContainer: container)
        **try! encode(to: encoder)**
        container = encoder.persistenceContainer
    }
}

with

Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

I will assume that this is easily reproducible. If it is not, I can provide more information. The only relevant difference between when it was working and now seems to be the version of Xcode.

I am using the tip of master -- 416dda2

I did notice that the Demo app was using GRDB 4.4.0. I attempted to use GRDB 4.5.0, but the results were unchanged...still crashes.

Should we use DatabaseWriter for reading from database?

In World.swift of the GRDBCombineDemo, only DatabaseWriter is tied to self.database:

import GRDB

/// Dependency Injection based on the "How to Control the World" article:
/// https://www.pointfree.co/blog/posts/21-how-to-control-the-world
struct World {
    /// Access to the players database
    func players() -> Players { Players(database: database()) }
    
    /// The database, private so that only high-level operations exposed by
    /// `players` are available to the rest of the application.
    private var database: () -> DatabaseWriter
    
    /// Creates a World with a database
    init(database: @escaping () -> DatabaseWriter) {
        self.database = database
    }
}

var Current = World(database: { fatalError("Database is uninitialized") })

Then, in for example the refresh() function in Players.swift, database.write is used to read from the database:

    func refresh() throws {
        try database.write { db in
            if try Player.fetchCount(db) == 0 {
                // Insert new random players
                [...]
    }

Can database.write be used to read from the database? According to the GRDB documentation, the correct way of reading from the database is by using database.read, like:

// Read values:
try dbPool.read { db in
    let places = try Place.fetchAll(db)
    let placeCount = try Place.fetchCount(db)
}

So my question is: Is it good practice to read from the database using database.write? And if not, shouldn't the World.swift of the GRDBCombineDemo also implement DatabaseReader?

Compiler error with Swift 5.2 snapshot

This is more of a heads-up:
In the current Swift 5.2 toolchain snapshots GRDBCombine does not build because DispatchQueue appears to no longer conform to the Scheduler protocol.
Not sure if that is a bug in the current implementation or a change coming in Swift 5.2.

The error is:
Instance method 'readPublisher(receiveOn:value:)' requires that 'DispatchQueue' conform to 'Scheduler'

The Swift 5.2 toolchain has much improved error reporting for SwiftUI code, which is why I wanted to try using it. But this issue currently prevents that.

SQLite Error 14

I'm getting SQLite error 14 with the following code:

let localDataPath = NSSearchPathForDirectoriesInDomains(
        .documentDirectory, .userDomainMask, true).first!
let databaseURL = URL(fileURLWithPath: localDataPath).appendingPathComponent("foodyDB.sqlite")
dbPool = try! DatabasePool(path: databaseURL.path, configuration: config)

This worked in an earlier version of iOS, but is not working in iOS 13 beta 2.

let databaseURL = try! FileManager.default
        .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
        .appendingPathComponent("foodyDB.sqlite")
dbPool = try! DatabasePool(path: databaseURL.path, configuration: config)

I'm getting the same error with this URL code, copied from the GRDB Combine sample app. The same app ran, but the same code did not in my own project

I'm guessing I need to change something in my XCode Project settings to get directory access.
Any suggestions? What's new in iOS13 for directory access?

Use CXShim instead of hardcoded Combine (support Linux / macOS 10.10 / iOS 8.0)

CXShim is a virtual Combine API that allows end user (not library author) to choose concrete implementation. see Combine Compatible Package.

I'm exploring the real world use case of CXShim. SwiftWebUI/SwiftWebUI#49 is my previous attempt.

Advantage

  • Current clients remains unaffected.
  • This library will support Linux / macOS 10.10 / iOS 8.0 / tvOS 9.0 / watchOS 2.0 (use open-source Combine).
  • System Combine is used by default whenever possible. No additional dependency is introduced.
  • No matter which Combine implementation you client choose, the package adapt to it automatically.

I'd like to work on a pr with your permission.

How to prevent an infinite loop when the current view is updating the database?

I have a View with a segmented control picker which is used to update some column named status in my database.

Based on the demo app, I have created the following publisher:

// Publisher
func myPublisher(taskId: UUID) -> DatabasePublishers.Value<Task?> {
    ValueObservation
        .tracking { db in
            try Task
                .select(Task.Columns.status)
                .filterId(taskId)
                .fetchOne(db)
        }
        .removeDuplicates()
        .publisher(in: database)
}

The publisher is used in my ViewModel:

@Published var taskStatus: TaskStatus {
    didSet {
        // Updates database...
    }
}

// Subscriber
init(task: Task) {
    Current.tasks()
        .myPublisher(taskId: task.id)
        .fetchOnSubscription()
        .catch { _ in Empty() }
        .sink { [weak self] in
            if let task = $0 {
                // Update status
                self?.taskStatus = task.status
            }
        }
        .store(in: &cancellableSet)
}

On initialization, the picker gets its initial value from @Published var taskStatus as expected. However, when the picker's value is changed, the following happens:

  1. The @Published var taskStatus's didSet{} observes the change, and updates the status column in the database.
  2. myPublisher() observes the change in the status column, and the subscriber is triggered.
  3. When the subscriber is triggered, @Published var taskStatus is updated, and its didSet{} is fired, updating the database (again).
  4. This starts an infinite loop.

Is it possible to make the publisher (or subscriber) aware of the source of the database update, so that I can break this infinite loop?

Refreshing request

Hi Gwendal,

One day, one question 😇

Since you wanted concrete cases of use... 😅

I'm still evaluating SwiftUI / GRDBCombine, and i have 2 questions (in fact, one question, it's the same topic)

  • I have a list of TV channels with EPG datas displayed...
    In the iOS version, i'm refreshing the list every minute because the EPG data (time / program) can change every minutes.
    Is there any way to refresh the query / request for the same View / @ObservedObject ? And then manually send a refreshed request / datas to the view via something like objectWillChange.send() ?

  • Second question (the same)...
    User can change the order of the channel list, or filtering the list via many options...
    So it's the same question, rebuilding the query with new .filter or other things like .order

I've tried several methods but none of them work, I'm blocked because when I do a new query and plug to DatabasePublished(publisher), it loses (i think) the link with the @ObservedObject variable of the view, which is normal.

I haven't found a way to redo a query for the same @ObservedObject variable.

On the other hand, the associations works, and that's great !!! 👍🏻

Thanks

Question about sample code

Hi Gwendal,

I'm playing with GRDBCombine, but i have one question:

In the sample code, in HallOfFameViewModel, you have hardcoded the var maxPlayerCount:

@DatabasePublished(Current.players().hallOfFamePublisher(maxPlayerCount: 10))

How i can make this var dynamic so i can use it like HallOfFameViewModel(maxPlayerCount: 50) ?

When i try to make it dynamic, i got an error (which is normal):

Cannot use instance member 'maxPlayerCount' within property initializer; property initializers run before 'self' is available

TransactionObserver nil when using with SwiftUI

Try using GRDBCombine with inside a SwiftUI view,

I start my observation inside my view model:

var spendingObserver: TransactionObserver?

init() {
    ....
    let observation = ValueObservation.tracking(value: { db in
                                try Outcome.fetchAll(db, sql: "select cost from outcome where date >= ?", arguments: [startOf15DaysAgo])
                            })
    self.spendingObserver =
            try! observation.start(in: databasePool, onChange: { outcomes in
                
                self.todayTotal =
                    outcomes.filter { (outcome) -> Bool in
                        outcome.date >= startOfToday
                    }.map({ (outcome) -> Double in
                        outcome.cost
                    }).reduce(0, +)
                
                self.yesterdayTotal =
                    outcomes.filter({ (outcome) -> Bool in
                            outcome.date < startOfToday && outcome.date >= (startOfToday - 1.days)
                    }).map({ (outcome) -> Double in
                        outcome.cost
                    }).reduce(0, +)
            })
    ...
}

use the viewmodel inside SwiftUI View.

 @ObservedObject var viewModel = UserSpendingViewModel()

 ...

SpendingSection(title: "Today", total: viewModel.todayTotal)
# it shows the data correctly

But when I try to insert a new record to database. It fails ACCESS_BAD_ADDRESS, debug view shows as follows, looks like it needs a strong reference?

Screen Shot 2019-11-18 at 2 03 56 PM

Write publisher never completes.

I am trying the following code with combine on a DBPool.

let cancellable = dbPool.writePublisher(updates: { (db) -> Int  in
    let league = League(id: "123", name: "foo", alias: "foo")
    try league.insert(db)
    return try League.fetchCount(db)
})
.sink(receiveCompletion: { error in
    print("copletion")
}, receiveValue: { count in
    print ("\(count)")
})

however the sink or any other calls inserted in the chain are never called when the publisher completes.

Crash with Xcode 11.2

With latest Xcode Build 11.2.1 (11B500), GRDB Demo throws Thread 1: EXC_BAD_ACCESS (code=1, address=0x505d) under EncodableRecord. Used iPhone or iPads in the simulator, same result. Demo worked as expected on prior build of Xcode.

Attempting to write my own code (grdb 4.6.1, grdbCombine 0.7.0), and I'm new and confused, so unsure if the issue is my work or something else. Similar code to the demo, throwing Thread 1: EXC_BAD_ACCESS (code=2, address=0x1091f623d) in DatabasePublishers.Value.receive

Please let me know if you require further information and thank you for your work.

Tricky to use without SPM

Unless I'm missing something it's kind of tricky to use GRDBCombine today if the project is not setup to use SPM for everything.

I'm currently using GRDB through a git submodule and adding the project file to my workspace and building the framework as a dependency to my app.

The dependency on GRDB defined in the package manifest makes it so that GRDB is downloaded as a package when generate-xcodeproj is run to generate a Xcode project to use in a similar manner to how I'm currently using GRDB.

That same dependency also means that Xcode starts fetching a second copy of GRDB when the package is added to the workspace.

The only solution (except for switching to SPM for everything) that doesn't result in two downloads of GRDB in my project is to download the source files and import them directly into the project. Which becomes hard to maintain instead.

I can't say I have a good solution for this. Could a project without any references to SPM be included in the repo perhaps?

Is sink() preferred over assign()?

In all the examples in the documentation, the sink() subscriber is used. For example:

let cancellable = publisher.sink(
    receiveCompletion: { completion in ... },
    receiveValue: { (players: [Player]) in
        print("Fresh players: \(players)")
    })

Are there any reasons we should not use assign() with GRDBCombine?

Is it possible to briefly describe how "Database Observation" work behind the scene?

I believe achieving "Database Observation" is a rather complex task, as we need to notify correct table's observers, after executing the SQL statement.

Is it possible to briefly describe the working mechanism of "Database Observation"? Is it

  1. Rely on some "built-in" features provided by SQLite, out of the box?
  2. Base on smart algorithm code developed in "GRDBCombine"?
  3. ...

So that as lib user, we can have a big picture on how it works, and aware of possible limitation.

Just FYI, this is the information, on how Android's Room LiveData works - https://www.reddit.com/r/androiddev/comments/clm1pd/does_anyone_how_the_magic_works_for_rooms_livedata/

Thanks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.