Code Monkey home page Code Monkey logo

blackbird's People

Contributors

guyenglish avatar jgstew avatar kamcma avatar marcoarment avatar staykids avatar thealpa 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  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

blackbird's Issues

No data was written to the real db, all data is in Memory

I just create a new app and follow the tutorial in README,
After specifying a db path, the db was successfully created, but all the write(to: db) are not actually written to the db, but in memory. If the app is killed and restarted, no data will be left.

iOS 16.2, Simulator
iOS 16.3, 12 mini

@BlackbirdColumnObserver example doesn't work

This example does not work.

struct MyView: View {
     // title will be of type: String?
     @BlackbirdColumnObserver(\MyModel.$title, primaryKey: 123) var title

     var body: some View {
         Text(title ?? "Loading…")
     }
}

The error I am getting is:
Value of optional type 'String?' must be unwrapped to a value of type 'String'

Weirdly this seems to work by using a !:

struct MyView: View {
     // title will be of type: String?
     @BlackbirdColumnObserver(\MyModel.$title, primaryKey: 123) var title

     var body: some View {
         Text((title ?? "Loading…")!)
     }
}

I think this happens when the column is an optional already:

  @BlackbirdColumn public var title: String?

This results in:

     @BlackbirdColumnObserver(\MyModel.$title, primaryKey: 123) var title : String??

Related to: 2f15d7f

Custom column type does not pass schema validation

I attempted to create a custom column type (UUID) and implemented BlackbirdColumnWrappable and BlackbirdStorableAsText as follows:

extension UUID: BlackbirdColumnWrappable, BlackbirdStorableAsText {
    public static func fromValue(_ value: Blackbird.Value) -> UUID? {
        guard case let Blackbird.Value.text(stringValue) = value else {
            return nil
        }
        return UUID(uuidString: stringValue)
    }

    public func unifiedRepresentation() -> String {
        self.uuidString
    }
    
    public static func from(unifiedRepresentation: String) -> UUID {
        return UUID(uuidString: unifiedRepresentation)!
    }
}

I also added an init(from:) decoder to my model as show in the comments of BlackbirdSchema.swift.

However, my model does not pass the schema validation because instead of using the BlackbirdDefaultsDecoder, which supplies a valid default for UUID, it uses the BlackbirdSQLiteDecoder which bypasses the if statement in my init(from:) to test for the BlackbirdDefaultsDecoder and provide a valid default.

Should the decoder at the following line be BlackbirdDefaultsDecoder to ensure the testRow has the right default for the column?
https://github.com/marcoarment/Blackbird/blob/2f15d7fae9f3821b43b7a945af38273fcb955157/Sources/Blackbird/BlackbirdModel.swift#L826C1-L835C10

Or am I just on the wrong path and this is not a feature Blackbird is supporting?

Validate that the same column is not specified in both indexes and uniqueIndexes

TL;DR I had a hard time debugging a crash that was occurring while initializing the schema for my database. It looked like a crash related to a string enum not having an empty string case, but it was actually caused by having the same (unrelated) column listed in both indexes and uniqueIndexes for the affected model.

This is clearly a client mistake, however I think this could be validated in a way that makes it easier to spot, to prevent others from having to debug this on their own.

The Problem

I had a model where I accidentally had indexes and uniqueIndexes repeat the same column, such as the example below:

struct Post: BlackbirdModel {
    static var primaryKey: [BlackbirdColumnKeyPath] = [ \.$guid ]

    static var indexes: [[BlackbirdColumnKeyPath]] = [
        [ \.$guid ],
        [ \.$title ],
    ]

    static var uniqueIndexes: [[BlackbirdColumnKeyPath]] = [
        [ \.$guid ],
    ]

    enum Format: String, BlackbirdStringEnum {
        case markdown
        case html
    }

    @BlackbirdColumn var guid: String
    @BlackbirdColumn var title: String
    @BlackbirdColumn var format: Format
}

When running the app, as soon as the model was touched (causing Blackbird to initialize the schema) I'd get a force unwrap crash here:

T.init(rawValue: T.RawValue.from(unifiedRepresentation: unifiedRawValue))!

In the example above, the crash would be caused by attempting to initialize a BlackbirdStringEnum with an empty string as its rawValue, but since there's no such case in that enum, this resulted in a crash.

I tracked it down to here:

let currentIndexes = Set(schemaInDB.indexes)
let targetIndexes = Set(indexes)
if primaryKeysChanged || currentColumns != targetColumns || currentIndexes != targetIndexes {
try core.transaction { core in

When a column is mistakenly repeated in indexes and uniqueIndexes, currentIndexes != targetIndexes will always be true, initiating the chain of events that lead to that crash.

I'm not familiar with the whole code base enough to send a PR right now, but wanted to document this here in case anyone else runs into this issue.

FYI, marco's git username not set correctly when contributing

@marcoarment FYI, you don't have your username and/or email configured to match your github user when you contribute code to this repo so your contributions don't show up associated with your github user account.

See here: https://github.com/marcoarment/Blackbird/commits/main

image

In the above screenshot, the commit from Jan 17th is done through the GitHub interface and is correctly associated, while the commits on Jan 23rd are done locally and then pushed, but GitHub isn't associating them with @marcoarment user account.

This can be configured just for this repo locally on the mac you contribute / push to this repo from.

I think it is just: git config user.name "marcoarment" (on the command line within the repo folder)

It might also require configuring the email address to match as well, but I am not certain.

README - Shouldn't RootView var database be a @StateObject?

The Readme has this code:

struct RootView: View {
    // The database that all child views will automatically use
    var database = try! Blackbird.Database.inMemoryDatabase()
   ...

But I think that has the potential issue of the database being recreated if/when RootView is recreated, which SwiftUI can do at whim.

I think the @StateObject decorator is supposed to be used:

struct RootView: View {
    // The database that all child views will automatically use
    @StateObject
    var database = try! Blackbird.Database.inMemoryDatabase()
   ...

I notice that your actual example SwiftUI code doesn't have this issue because it stores the database in an App object rather than in a view.

Type reflection for automatic/cleaner schema definitions

Because BlackbirdModel extends Decodable it's possible to construct an empty instance of the correct type in order to interrogate it. I've created a package to do just that at https://github.com/jjrscott/EmptyInitializer but it's easy to roll your own, especially as you've already done it once (BlackbirdSQLiteDecoder).

let mirror = try Mirror(reflecting: EmptyInitializer.initialize(type: Post.self))

This doesn't solve all the issues however as property wrappers don't always do what one would expect when interacting with Decodable. Hopefully people comment on Property wrapper initialisation issues to either show what I did wrong or propose a fix.

Feature Request - something like GRDB's ImmediateValueObservationScheduler

I hacked up my toy image board viewer app to use Blackbird, and compared it to a version that uses GRDB.

Blackbird branch: https://github.com/jackpal/HD/commits/Blackbird
GRDB branch: https://github.com/jackpal/HD/commits/main

The main UX difference between the two versions is that the Blackbird-based list views start out blank, while the GRDB-based list views start out filled in.

I think this is due to GRDB's value change publisher having a default "immediate" mode that fetches the value immediately upon creation.

It makes a big difference in the perceived speed of the app.

So I would like to request similar Blackbird functionality.

FWIW the Blackbird version of my app was less code and ceremony than the GRDB version. Good job!

But, the GRDB version does more. It uses foreign keys with cascading deletes to update the local database when posts and threads are deleted on the service. I haven't tried implementing similar functionality in the Blackbird version yet.

Blackbird.Database does not conform to `ObservableObject`

When following the SwiftUI example from the README and initializing the database via @StateObject var database = try! Blackbird.Database.inMemoryDatabase(), I get an error message in Xcode stating the following:

Generic struct 'StateObject' requires that 'Blackbird.Database' conform to 'ObservableObject'

Any ideas? I assume I'm just missing something obvious here, but would love some insight! This is using Xcode 15, for what it's worth.

Really happy with how this is designed and would love to use it if I can get past the initialization hurdle. More than happy to provide whatever info you need, but hopefully I'm just doing something dumb.

Custom CodingKeys

Consider this model object:

struct Meal: BlackbirdModel, Identifiable {
    @BlackbirdColumn var id: String
    @BlackbirdColumn var name: String
    @BlackbirdColumn var thumbnailUrl: URL

    enum CodingKeys: String, CodingKey {
        case id = "idMeal"
        case name = "strMeal"
        case thumbnailUrl = "strMealThumb"
    }
}

For the sake of decoding a network resource with questionable naming conventions, I have introduced a CodingKeys to map their names to my own. Fine.

Blackbird creates a table with names of id, name, and thumbnailUrl. That seems good to me. But, when I try to save a record I get the following fatal error message:

2023-04-22 15:27:10.868259-0700 MyApp[49584:3615565] Blackbird/BlackbirdModel.swift:829: Fatal error: Table "Meal" definition defaults do not decode to model Meal: missingValue("idMeal")

Note, the database has the column name, but validateSchema(database:core:) is looking for strMeal (the name from CodingKeys). It looks like the logic it used for creating the column names (using the actual property names) is different than what validateSchema(database:core:) used. It strikes me that we should either use CodingKeys everywhere, or not at all. (I'd vote for the latter.)

Thoughts?

Restricted Combine row monitoring

I am not sure if this was intended but the following README.md example for monitoring row-level changes does not work anymore since the access level of these properties got changed to internal in 4b822c0.

let listener = Post.changePublisher(in: db).sink { change in
    print("Post IDs changed: \(change.primaryKeys ?? "all")")
    print(" Columns changed: \(change.columnNames ?? "all")")
}

Support SPM compatible tags

Currently SPM isn't supported as the tags are prefixed with v.
Please could you support SPM compatible tags by changing your tag from v0.5 to 0.5?

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.