RxSwiftCommunity / RxGRDB

Reactive extensions for SQLite
MIT License
218 stars 35 forks source link

ReactiveKit support #43

Closed cfilipov closed 6 years ago

cfilipov commented 6 years ago

In case you are interested in collaborating or taking a look, I made a quick and dirty port of RxGRDB for ReactiveKit.

Since the libraries aren't that different a lot of work was mechanical though some areas required more thought such as dealing with the explicit error type in ReactiveKit (I took the lazy way out for now and defined an AnyError type). I got all unit tests to pass except for testPrimaryKeyDiffScanner (still trying to figure out what's goin on with that one).

groue commented 6 years ago

That's great! Support for more reactive engines is a much welcomed contribution. It happens that I started my reactive life with RxSwift, because @fpillet is a very good evangelist. But I know there exist other engines out there. I'll be very happy to add cfilipov/ReactiveGRDB to the list of GRDB companion libraries when you think it is ready.

Tell me if you want any specific help.

cfilipov commented 6 years ago

I do have one specific area I would love help with if you can. I still haven't been able to get the testPrimaryKeyDiffScanner test to pass on my fork (all other tests pass). The test crashes with the following error:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'API violation - multiple calls made to -[XCTestExpectation fulfill] for .'

This appears to be caused by calling fulfill multiple times in EventRecorder. However, when I run the same test on upstream RxGRDB, it also appears to be calling fulfill for every PrimaryKeyDiff, but no crash, and the test passes.

I placed a print call at the beginning of the on method of EventRecorder in both RxGRDB and ReactiveGRDB like so:

    func on(_ event: Event<E>) {
        print("Event: \(event)")
        recordedEvents.append(event)
        expectation.fulfill()
    }

See the output below. One thing to note is that my version is sending an extra initial event which is unexpected, but that still does not explain why calling fulfill multiple times crashes for my version and not for yours.

RxGRDB: testPrimaryKeyDiffScanner ``` Test Suite 'Selected tests' started at 2018-10-24 11:30:03.961 Test Suite 'RxGRDBiOSTests.xctest' started at 2018-10-24 11:30:03.962 Test Suite 'TypedRequestTests' started at 2018-10-24 11:30:03.962 Test Case '-[RxGRDBiOSTests.TypedRequestTests testPrimaryKeyDiffScanner]' started. Event: next(PrimaryKeyDiff(inserted: [RxGRDBiOSTests.(unknown context at 0x1225fdd1c).Player(id: Optional(1), name: "Arthur", email: Optional("arthur@example.com")), RxGRDBiOSTests.(unknown context at 0x1225fdd1c).Player(id: Optional(2), name: "Barbara", email: nil)], updated: [], deleted: [])) Event: next(PrimaryKeyDiff(inserted: [], updated: [], deleted: [])) Event: next(PrimaryKeyDiff(inserted: [], updated: [RxGRDBiOSTests.(unknown context at 0x1225fdd1c).Player(id: Optional(2), name: "Barbie", email: nil)], deleted: [])) Event: next(PrimaryKeyDiff(inserted: [], updated: [], deleted: [RxGRDBiOSTests.(unknown context at 0x1225fdd1c).Player(id: Optional(1), name: "Arthur", email: Optional("arthur@example.com")), RxGRDBiOSTests.(unknown context at 0x1225fdd1c).Player(id: Optional(2), name: "Barbie", email: nil)])) Event: next(PrimaryKeyDiff(inserted: [RxGRDBiOSTests.(unknown context at 0x1225fdd1c).Player(id: Optional(1), name: "Craig", email: nil), RxGRDBiOSTests.(unknown context at 0x1225fdd1c).Player(id: Optional(2), name: "David", email: Optional("david@example.com")), RxGRDBiOSTests.(unknown context at 0x1225fdd1c).Player(id: Optional(3), name: "Elena", email: Optional("elena@example.com"))], updated: [], deleted: [])) Event: next(PrimaryKeyDiff(inserted: [RxGRDBiOSTests.(unknown context at 0x1225fdd1c).Player(id: Optional(1), name: "Arthur", email: Optional("arthur@example.com")), RxGRDBiOSTests.(unknown context at 0x1225fdd1c).Player(id: Optional(2), name: "Barbara", email: nil)], updated: [], deleted: [])) Event: next(PrimaryKeyDiff(inserted: [], updated: [], deleted: [])) Event: next(PrimaryKeyDiff(inserted: [], updated: [RxGRDBiOSTests.(unknown context at 0x1225fdd1c).Player(id: Optional(2), name: "Barbie", email: nil)], deleted: [])) Event: next(PrimaryKeyDiff(inserted: [], updated: [], deleted: [RxGRDBiOSTests.(unknown context at 0x1225fdd1c).Player(id: Optional(1), name: "Arthur", email: Optional("arthur@example.com")), RxGRDBiOSTests.(unknown context at 0x1225fdd1c).Player(id: Optional(2), name: "Barbie", email: nil)])) Event: next(PrimaryKeyDiff(inserted: [RxGRDBiOSTests.(unknown context at 0x1225fdd1c).Player(id: Optional(1), name: "Craig", email: nil), RxGRDBiOSTests.(unknown context at 0x1225fdd1c).Player(id: Optional(2), name: "David", email: Optional("david@example.com")), RxGRDBiOSTests.(unknown context at 0x1225fdd1c).Player(id: Optional(3), name: "Elena", email: Optional("elena@example.com"))], updated: [], deleted: [])) Test Case '-[RxGRDBiOSTests.TypedRequestTests testPrimaryKeyDiffScanner]' passed (0.107 seconds). Test Suite 'TypedRequestTests' passed at 2018-10-24 11:30:04.087. Executed 1 test, with 0 failures (0 unexpected) in 0.107 (0.125) seconds Test Suite 'RxGRDBiOSTests.xctest' passed at 2018-10-24 11:30:04.088. Executed 1 test, with 0 failures (0 unexpected) in 0.107 (0.126) seconds Test Suite 'Selected tests' passed at 2018-10-24 11:30:04.089. Executed 1 test, with 0 failures (0 unexpected) in 0.107 (0.127) seconds Program ended with exit code: 0 ```
ReactiveGRDB: testPrimaryKeyDiffScanner ``` Test Suite 'Selected tests' started at 2018-10-24 11:31:57.283 Test Suite 'ReactiveGRDBiOSTests.xctest' started at 2018-10-24 11:31:57.284 Test Suite 'TypedRequestTests' started at 2018-10-24 11:31:57.284 Test Case '-[ReactiveGRDBiOSTests.TypedRequestTests testPrimaryKeyDiffScanner]' started. Event: next(ReactiveGRDB.PrimaryKeyDiff(inserted: [], updated: [], deleted: [])) Event: next(ReactiveGRDB.PrimaryKeyDiff(inserted: [ReactiveGRDBiOSTests.(unknown context at 0x12164287c).Player(id: Optional(1), name: "Arthur", email: Optional("arthur@example.com")), ReactiveGRDBiOSTests.(unknown context at 0x12164287c).Player(id: Optional(2), name: "Barbara", email: nil)], updated: [], deleted: [])) Event: next(ReactiveGRDB.PrimaryKeyDiff(inserted: [], updated: [], deleted: [])) Event: next(ReactiveGRDB.PrimaryKeyDiff(inserted: [], updated: [ReactiveGRDBiOSTests.(unknown context at 0x12164287c).Player(id: Optional(2), name: "Barbie", email: nil)], deleted: [])) Event: next(ReactiveGRDB.PrimaryKeyDiff(inserted: [], updated: [], deleted: [ReactiveGRDBiOSTests.(unknown context at 0x12164287c).Player(id: Optional(1), name: "Arthur", email: Optional("arthur@example.com")), ReactiveGRDBiOSTests.(unknown context at 0x12164287c).Player(id: Optional(2), name: "Barbie", email: nil)])) Event: next(ReactiveGRDB.PrimaryKeyDiff(inserted: [ReactiveGRDBiOSTests.(unknown context at 0x12164287c).Player(id: Optional(1), name: "Craig", email: nil), ReactiveGRDBiOSTests.(unknown context at 0x12164287c).Player(id: Optional(2), name: "David", email: Optional("david@example.com")), ReactiveGRDBiOSTests.(unknown context at 0x12164287c).Player(id: Optional(3), name: "Elena", email: Optional("elena@example.com"))], updated: [], deleted: [])) 2018-10-24 11:31:58.057666-0700 xctest[98011:5638948] *** Assertion failure in -[XCTestExpectation fulfill], /Library/Caches/com.apple.xbs/Sources/XCTest_Sim/XCTest-14302.9/Sources/XCTestFramework/Async/XCTestExpectation.m:79 2018-10-24 11:31:58.060319-0700 xctest[98011:5638948] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'API violation - multiple calls made to -[XCTestExpectation fulfill] for .' *** First throw call stack: ( 0 CoreFoundation 0x0000000105bb529b __exceptionPreprocess + 331 1 libobjc.A.dylib 0x0000000105151735 objc_exception_throw + 48 2 CoreFoundation 0x0000000105bb5022 +[NSException raise:format:arguments:] + 98 3 Foundation 0x00000001049c4b47 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 194 4 XCTest 0x00000001050659bc -[XCTestExpectation fulfill] + 652 5 ReactiveGRDBiOSTests 0x000000012160548a $S20ReactiveGRDBiOSTests13EventRecorderC2onyy0A3Kit0D0Oyxq_GF + 874 6 ReactiveGRDBiOSTests 0x0000000121605688 $S20ReactiveGRDBiOSTests13EventRecorderCyxq_G0A3Kit16ObserverProtocolAaeFP2onyyAE0D0Oy7ElementQz5ErrorQzGFTW + 24 7 ReactiveKit 0x0000000121bd3806 $STA + 102 8 ReactiveKit 0x0000000121bd2588 $S11ReactiveKit14AtomicObserverC2onyyAA5EventOyxq_GF + 776 9 ReactiveKit 0x0000000121bd28b8 $S11ReactiveKit14AtomicObserverCyxq_GAA0D8ProtocolA2aEP2onyyAA5EventOy7ElementQz5ErrorQzGFTW + 24 10 ReactiveKit 0x0000000121bd2a81 $S11ReactiveKit16ObserverProtocolPAAE4nextyy7ElementQzF + 449 11 ReactiveKit 0x0000000121bea138 $S11ReactiveKit14SignalProtocolPAAE3mapyAA0C0Vyqd__5ErrorQzGqd__7ElementQzclFAA10Disposable_pAA14AtomicObserverCyqd__AHGcfU_yAA5EventOyAkHGcfU_ + 728 12 ReactiveKit 0x0000000121c1d038 $S11ReactiveKit14SignalProtocolPAAE3mapyAA0C0Vyqd__5ErrorQzGqd__7ElementQzclFAA10Disposable_pAA14AtomicObserverCyqd__AHGcfU_yAA5EventOyAkHGcfU_TA + 40 13 ReactiveKit 0x0000000121bd2588 $S11ReactiveKit14AtomicObserverC2onyyAA5EventOyxq_GF + 776 14 ReactiveKit 0x0000000121bd28b8 $S11ReactiveKit14AtomicObserverCyxq_GAA0D8ProtocolA2aEP2onyyAA5EventOy7ElementQz5ErrorQzGFTW + 24 15 ReactiveKit 0x0000000121bd2a81 $S11ReactiveKit16ObserverProtocolPAAE4nextyy7ElementQzF + 449 16 ReactiveKit 0x0000000121beb5c2 $S11ReactiveKit14SignalProtocolPAAE4scanyAA0C0Vyqd__5ErrorQzGqd___qd__qd___7ElementQztctlFAA10Disposable_pAA14AtomicObserverCyqd__AHGcfU_yAA5EventOyAkHGcfU_ + 1122 17 ReactiveKit 0x0000000121c1cc6a $S11ReactiveKit14SignalProtocolPAAE4scanyAA0C0Vyqd__5ErrorQzGqd___qd__qd___7ElementQztctlFAA10Disposable_pAA14AtomicObserverCyqd__AHGcfU_yAA5EventOyAkHGcfU_TA + 58 18 ReactiveKit 0x0000000121bd2588 $S11ReactiveKit14AtomicObserverC2onyyAA5EventOyxq_GF + 776 19 ReactiveKit 0x0000000121bb993c $STA + 28 20 ReactiveKit 0x0000000121bd2588 $S11ReactiveKit14AtomicObserverC2onyyAA5EventOyxq_GF + 776 21 ReactiveKit 0x0000000121bd28b8 $S11ReactiveKit14AtomicObserverCyxq_GAA0D8ProtocolA2aEP2onyyAA5EventOy7ElementQz5ErrorQzGFTW + 24 22 ReactiveKit 0x0000000121bd2a81 $S11ReactiveKit16ObserverProtocolPAAE4nextyy7ElementQzF + 449 23 ReactiveGRDB 0x0000000121ce76a1 $S11ReactiveKit16ObserverProtocolP0A4GRDBE8onResultyyAA0G0Oy7ElementQz5ErrorQzGF + 497 24 ReactiveGRDB 0x0000000121ce55cb $S12ReactiveGRDB8MapFetchC7observe4with0A3Kit10Disposable_pyAF5EventOyxq_Gc_tFyAIyAA0D5TokenVq_GcfU_yycfU1_yycfU0_ + 299 25 ReactiveGRDB 0x0000000121ce6cf5 $S12ReactiveGRDB8MapFetchC7observe4with0A3Kit10Disposable_pyAF5EventOyxq_Gc_tFyAIyAA0D5TokenVq_GcfU_yycfU1_yycfU0_TA + 21 26 ReactiveGRDB 0x0000000121ce560d $SIeg_IeyB_TR + 45 27 libdispatch.dylib 0x0000000107d0e51d _dispatch_call_block_and_release + 12 28 libdispatch.dylib 0x0000000107d0f587 _dispatch_client_callout + 8 29 libdispatch.dylib 0x0000000107d1b3bc _dispatch_main_queue_callback_4CF + 1290 30 CoreFoundation 0x0000000105b187f9 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9 31 CoreFoundation 0x0000000105b12e86 __CFRunLoopRun + 2342 32 CoreFoundation 0x0000000105b12221 CFRunLoopRunSpecific + 625 33 XCTest 0x000000010503356b -[XCTWaiter waitForExpectations:timeout:enforceOrder:] + 955 34 XCTest 0x0000000104fcf47e -[XCTestCase(AsynchronousTesting) waitForExpectations:timeout:enforceOrder:] + 113 35 ReactiveGRDBiOSTests 0x000000012160578c $SSo10XCTestCaseC20ReactiveGRDBiOSTestsE4wait3for7timeoutyAC13EventRecorderCyxq_G_Sdts5ErrorR_r0_lF + 252 36 ReactiveGRDBiOSTests 0x0000000121630651 $S20ReactiveGRDBiOSTests17TypedRequestTestsC25testPrimaryKeyDiffScanner6writer10disposeBagy4GRDB14DatabaseWriter_p_0A3Kit07DisposeN0CtKF + 5761 37 ReactiveGRDBiOSTests 0x0000000121634fb7 $S20ReactiveGRDBiOSTests17TypedRequestTestsC25testPrimaryKeyDiffScanner6writer10disposeBagy4GRDB14DatabaseWriter_p_0A3Kit07DisposeN0CtKFTA + 23 38 ReactiveGRDBiOSTests 0x0000000121604881 $S20ReactiveGRDBiOSTests4TestC3runyACXD4GRDB14DatabaseWriter_pSSKXEKF + 1073 39 ReactiveGRDBiOSTests 0x000000012162ee55 $S20ReactiveGRDBiOSTests17TypedRequestTestsC25testPrimaryKeyDiffScanneryyKF + 133 40 ReactiveGRDBiOSTests 0x0000000121631cf5 $S20ReactiveGRDBiOSTests17TypedRequestTestsC25testPrimaryKeyDiffScanneryyKFTo + 69 41 CoreFoundation 0x0000000105bbc11c __invoking___ + 140 42 CoreFoundation 0x0000000105bb95b5 -[NSInvocation invoke] + 325 43 XCTest 0x0000000104fe1b6e __24-[XCTestCase invokeTest]_block_invoke.192 + 78 44 XCTest 0x00000001050392e8 -[XCTestCase(Failures) performFailableBlock:testCaseRun:shouldInterruptTest:] + 57 45 XCTest 0x0000000105039205 -[XCTestCase(Failures) _performTurningExceptionsIntoFailuresInterruptAfterHandling:block:] + 96 46 XCTest 0x0000000104fe182f __24-[XCTestCase invokeTest]_block_invoke + 848 47 XCTest 0x000000010503f1ee -[XCUITestContext performInScope:] + 248 48 XCTest 0x0000000104fe1424 -[XCTestCase testContextPerformInScope:] + 98 49 XCTest 0x0000000104fe14d2 -[XCTestCase invokeTest] + 137 50 XCTest 0x0000000104fe300d __26-[XCTestCase performTest:]_block_invoke_2 + 43 51 XCTest 0x00000001050392e8 -[XCTestCase(Failures) performFailableBlock:testCaseRun:shouldInterruptTest:] + 57 52 XCTest 0x0000000105039205 -[XCTestCase(Failures) _performTurningExceptionsIntoFailuresInterruptAfterHandling:block:] + 96 53 XCTest 0x0000000104fe2f24 __26-[XCTestCase performTest:]_block_invoke.326 + 88 54 XCTest 0x0000000105049a3b +[XCTContext runInContextForTestCase:block:] + 225 55 XCTest 0x0000000104fe2653 -[XCTestCase performTest:] + 675 56 XCTest 0x0000000105025802 -[XCTest runTest] + 57 57 XCTest 0x0000000104fdd85b __27-[XCTestSuite performTest:]_block_invoke + 365 58 XCTest 0x0000000104fdd033 -[XCTestSuite _performProtectedSectionForTest:testSection:] + 55 59 XCTest 0x0000000104fdd2f6 -[XCTestSuite performTest:] + 296 60 XCTest 0x0000000105025802 -[XCTest runTest] + 57 61 XCTest 0x0000000104fdd85b __27-[XCTestSuite performTest:]_block_invoke + 365 62 XCTest 0x0000000104fdd033 -[XCTestSuite _performProtectedSectionForTest:testSection:] + 55 63 XCTest 0x0000000104fdd2f6 -[XCTestSuite performTest:] + 296 64 XCTest 0x0000000105025802 -[XCTest runTest] + 57 65 XCTest 0x0000000104fdd85b __27-[XCTestSuite performTest:]_block_invoke + 365 66 XCTest 0x0000000104fdd033 -[XCTestSuite _performProtectedSectionForTest:testSection:] + 55 67 XCTest 0x0000000104fdd2f6 -[XCTestSuite performTest:] + 296 68 XCTest 0x0000000105025802 -[XCTest runTest] + 57 69 XCTest 0x0000000105053ea6 __44-[XCTTestRunSession runTestsAndReturnError:]_block_invoke + 171 70 XCTest 0x0000000105053fc7 __44-[XCTTestRunSession runTestsAndReturnError:]_block_invoke.80 + 68 71 XCTest 0x0000000104ff586a -[XCTestObservationCenter _observeTestExecutionForBlock:] + 594 72 XCTest 0x0000000105053c1a -[XCTTestRunSession runTestsAndReturnError:] + 623 73 XCTest 0x0000000104fc225a -[XCTestDriver runTestsAndReturnError:] + 422 74 XCTest 0x0000000105045fbd _XCTestMain + 1478 75 xctest 0x000000010464700c main + 282 76 libdyld.dylib 0x0000000107d7f551 start + 1 ) libc++abi.dylib: terminating with uncaught exception of type NSException (lldb) ```
groue commented 6 years ago

Give me a few days, @cfilipov, I'll have a look.

cfilipov commented 6 years ago

Thank @groue! For what it's worth I've been using the ReactiveKit version for a few days now in non-trivial ways and have not seen any problems so far. But 100% test passed would definitely get more confidence.

Once the tests all pass I'll also have to go in and update the docs on my fork and comments to reflect the differences. So far it appears to be a very simple search & replace since none of the API changed except for a couple names (basically: "rx" -> "reactive", "Observable" -> "Signal" etc...).

groue commented 6 years ago

I've almost finished https://github.com/groue/GRDB.swift/pull/435, which introduces a new GRDB type: ValueObservation.

It aims at solving most value observation needs (including observation of requests that write: #42), as well as providing the base toolkit for reactive extensions. #46 has RxGRDB rely exclusively on ValueObservation for its value observables, and it looks that it works like charm.

I hope your ReactiveGRDB will be able to profit from it as well: I'll be interested in your feedback.

Now I'll be able to look at your scanner tests that fail!

groue commented 6 years ago

Now I'll be able to look at your scanner tests that fail!

I added a few .doOn(next:) calls in order to track the behavior of your observables. And I think that the difference comes from the different behavior of scan in RxSwift and in ReactiveKit. ReactiveKit's scan immediately starts outputting values, when RxSwift's scan waits until its base observable emits its initial value. ReactiveKit is not RxSwift ¯\_(ツ)_/¯.

groue commented 6 years ago

OK @cfilipov, I think I can close this issue. You'll have to do as I did with RxSwift, which is to think a little bit about how the database should interact with ReactiveKit, and how to give a useful API to users of cfilipov/ReactiveGRDB. Don't hesitate if you have more question!