<commit_before>import XCTest
#if GRDBCUSTOMSQLITE
    import GRDBCustomSQLite
#else
    #if SWIFT_PACKAGE
        import CSQLite
    #else
        import SQLite3
    #endif
    import GRDB
#endif

class ValueObservationCombineTests: GRDBTestCase {
    func testCombine2() throws {
        let dbQueue = try makeDatabaseQueue()
        try dbQueue.write {
            try $0.execute(sql: """
                CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT);
                """)
        }
        
        var values: [[Int]] = []
        let notificationExpectation = expectation(description: "notification")
        notificationExpectation.assertForOverFulfill = true
        notificationExpectation.expectedFulfillmentCount = 4
        
        struct T1: TableRecord { }
        struct T2: TableRecord { }
        let observation1 = T1.observationForCount()
        let observation2 = T2.observationForCount()
        let observation = ValueObservation.combine(observation1, observation2)
        let observer = observation.start(
            in: dbQueue,
            onError: { error in XCTFail("Unexpected error: \(error)") },
            onChange: { v0, v1 in
                values.append([v0, v1])
                notificationExpectation.fulfill()
        })
        try withExtendedLifetime(observer) {
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t1")
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t2")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t1")
                try db.execute(sql: "DELETE FROM t2")
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            }
            waitForExpectations(timeout: 1, handler: nil)
            XCTAssertEqual(values, [
                [0, 0],
                [1, 0],
                [1, 1],
                [2, 2]])
        }
    }
    
    func testCombine3() throws {
        let dbQueue = try makeDatabaseQueue()
        try dbQueue.write {
            try $0.execute(sql: """
                CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t3(id INTEGER PRIMARY KEY AUTOINCREMENT);
                """)
        }
        
        var values: [[Int]] = []
        let notificationExpectation = expectation(description: "notification")
        notificationExpectation.assertForOverFulfill = true
        notificationExpectation.expectedFulfillmentCount = 5
        
        struct T1: TableRecord { }
        struct T2: TableRecord { }
        struct T3: TableRecord { }
        let observation1 = T1.observationForCount()
        let observation2 = T2.observationForCount()
        let observation3 = T3.observationForCount()
        let observation = ValueObservation.combine(observation1, observation2, observation3)
        let observer = observation.start(
            in: dbQueue,
            onError: { error in XCTFail("Unexpected error: \(error)") },
            onChange: { v0, v1, v2 in
                values.append([v0, v1, v2])
                notificationExpectation.fulfill()
        })
        try withExtendedLifetime(observer) {
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t1")
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t2")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t3")
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t1")
                try db.execute(sql: "DELETE FROM t2")
                try db.execute(sql: "DELETE FROM t3")
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            }
            waitForExpectations(timeout: 1, handler: nil)
            XCTAssertEqual(values, [
                [0, 0, 0],
                [1, 0, 0],
                [1, 1, 0],
                [1, 1, 1],
                [2, 2, 2]])
        }
    }
    
    func testCombine4() throws {
        let dbQueue = try makeDatabaseQueue()
        try dbQueue.write {
            try $0.execute(sql: """
                CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t3(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t4(id INTEGER PRIMARY KEY AUTOINCREMENT);
                """)
        }
        
        var values: [[Int]] = []
        let notificationExpectation = expectation(description: "notification")
        notificationExpectation.assertForOverFulfill = true
        notificationExpectation.expectedFulfillmentCount = 6
        
        struct T1: TableRecord { }
        struct T2: TableRecord { }
        struct T3: TableRecord { }
        struct T4: TableRecord { }
        let observation1 = T1.observationForCount()
        let observation2 = T2.observationForCount()
        let observation3 = T3.observationForCount()
        let observation4 = T4.observationForCount()
        let observation = ValueObservation.combine(observation1, observation2, observation3, observation4)
        let observer = observation.start(
            in: dbQueue,
            onError: { error in XCTFail("Unexpected error: \(error)") },
            onChange: { v0, v1, v2, v3 in
                values.append([v0, v1, v2, v3])
                notificationExpectation.fulfill()
        })
        try withExtendedLifetime(observer) {
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t1")
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t2")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t3")
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t4")
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t1")
                try db.execute(sql: "DELETE FROM t2")
                try db.execute(sql: "DELETE FROM t3")
                try db.execute(sql: "DELETE FROM t4")
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            }
            waitForExpectations(timeout: 1, handler: nil)
            XCTAssertEqual(values, [
                [0, 0, 0, 0],
                [1, 0, 0, 0],
                [1, 1, 0, 0],
                [1, 1, 1, 0],
                [1, 1, 1, 1],
                [2, 2, 2, 2]])
        }
    }
    
    func testCombine5() throws {
        let dbQueue = try makeDatabaseQueue()
        try dbQueue.write {
            try $0.execute(sql: """
                CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t3(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t4(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t5(id INTEGER PRIMARY KEY AUTOINCREMENT);
                """)
        }
        
        var values: [[Int]] = []
        let notificationExpectation = expectation(description: "notification")
        notificationExpectation.assertForOverFulfill = true
        notificationExpectation.expectedFulfillmentCount = 7
        
        struct T1: TableRecord { }
        struct T2: TableRecord { }
        struct T3: TableRecord { }
        struct T4: TableRecord { }
        struct T5: TableRecord { }
        let observation1 = T1.observationForCount()
        let observation2 = T2.observationForCount()
        let observation3 = T3.observationForCount()
        let observation4 = T4.observationForCount()
        let observation5 = T5.observationForCount()
        let observation = ValueObservation.combine(observation1, observation2, observation3, observation4, observation5)
        let observer = observation.start(
            in: dbQueue,
            onError: { error in XCTFail("Unexpected error: \(error)") },
            onChange: { v0, v1, v2, v3, v4 in
                values.append([v0, v1, v2, v3, v4])
                notificationExpectation.fulfill()
        })
        try withExtendedLifetime(observer) {
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t1")
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t2")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t3")
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t4")
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t5")
                try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t1")
                try db.execute(sql: "DELETE FROM t2")
                try db.execute(sql: "DELETE FROM t3")
                try db.execute(sql: "DELETE FROM t4")
                try db.execute(sql: "DELETE FROM t5")
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
            }
            waitForExpectations(timeout: 1, handler: nil)
            XCTAssertEqual(values, [
                [0, 0, 0, 0, 0],
                [1, 0, 0, 0, 0],
                [1, 1, 0, 0, 0],
                [1, 1, 1, 0, 0],
                [1, 1, 1, 1, 0],
                [1, 1, 1, 1, 1],
                [2, 2, 2, 2, 2]])
        }
    }
    
    func testCombine6() throws {
        let dbQueue = try makeDatabaseQueue()
        try dbQueue.write {
            try $0.execute(sql: """
                CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t3(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t4(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t5(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t6(id INTEGER PRIMARY KEY AUTOINCREMENT);
                """)
        }
        
        var values: [[Int]] = []
        let notificationExpectation = expectation(description: "notification")
        notificationExpectation.assertForOverFulfill = true
        notificationExpectation.expectedFulfillmentCount = 8
        
        struct T1: TableRecord { }
        struct T2: TableRecord { }
        struct T3: TableRecord { }
        struct T4: TableRecord { }
        struct T5: TableRecord { }
        struct T6: TableRecord { }
        let observation1 = T1.observationForCount()
        let observation2 = T2.observationForCount()
        let observation3 = T3.observationForCount()
        let observation4 = T4.observationForCount()
        let observation5 = T5.observationForCount()
        let observation6 = T6.observationForCount()
        let observation = ValueObservation.combine(observation1, observation2, observation3, observation4, observation5, observation6)
        let observer = observation.start(
            in: dbQueue,
            onError: { error in XCTFail("Unexpected error: \(error)") },
            onChange: { v0, v1, v2, v3, v4, v5 in
                values.append([v0, v1, v2, v3, v4, v5])
                notificationExpectation.fulfill()
        })
        try withExtendedLifetime(observer) {
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t1")
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t2")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t3")
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t4")
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t5")
                try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t6")
                try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t1")
                try db.execute(sql: "DELETE FROM t2")
                try db.execute(sql: "DELETE FROM t3")
                try db.execute(sql: "DELETE FROM t4")
                try db.execute(sql: "DELETE FROM t5")
                try db.execute(sql: "DELETE FROM t6")
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
            }
            waitForExpectations(timeout: 1, handler: nil)
            XCTAssertEqual(values, [
                [0, 0, 0, 0, 0, 0],
                [1, 0, 0, 0, 0, 0],
                [1, 1, 0, 0, 0, 0],
                [1, 1, 1, 0, 0, 0],
                [1, 1, 1, 1, 0, 0],
                [1, 1, 1, 1, 1, 0],
                [1, 1, 1, 1, 1, 1],
                [2, 2, 2, 2, 2, 2]])
        }
    }
    
    func testCombine7() throws {
        let dbQueue = try makeDatabaseQueue()
        try dbQueue.write {
            try $0.execute(sql: """
                CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t3(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t4(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t5(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t6(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t7(id INTEGER PRIMARY KEY AUTOINCREMENT);
                """)
        }
        
        var values: [[Int]] = []
        let notificationExpectation = expectation(description: "notification")
        notificationExpectation.assertForOverFulfill = true
        notificationExpectation.expectedFulfillmentCount = 9
        
        struct T1: TableRecord { }
        struct T2: TableRecord { }
        struct T3: TableRecord { }
        struct T4: TableRecord { }
        struct T5: TableRecord { }
        struct T6: TableRecord { }
        struct T7: TableRecord { }
        let observation1 = T1.observationForCount()
        let observation2 = T2.observationForCount()
        let observation3 = T3.observationForCount()
        let observation4 = T4.observationForCount()
        let observation5 = T5.observationForCount()
        let observation6 = T6.observationForCount()
        let observation7 = T7.observationForCount()
        let observation = ValueObservation.combine(observation1, observation2, observation3, observation4, observation5, observation6, observation7)
        let observer = observation.start(
            in: dbQueue,
            onError: { error in XCTFail("Unexpected error: \(error)") },
            onChange: { v0, v1, v2, v3, v4, v5, v6 in
                values.append([v0, v1, v2, v3, v4, v5, v6])
                notificationExpectation.fulfill()
        })
        try withExtendedLifetime(observer) {
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t7 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t1")
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t2")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t3")
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t4")
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t5")
                try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t6")
                try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t7")
                try db.execute(sql: "INSERT INTO t7 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t1")
                try db.execute(sql: "DELETE FROM t2")
                try db.execute(sql: "DELETE FROM t3")
                try db.execute(sql: "DELETE FROM t4")
                try db.execute(sql: "DELETE FROM t5")
                try db.execute(sql: "DELETE FROM t6")
                try db.execute(sql: "DELETE FROM t7")
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t7 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t7 DEFAULT VALUES")
            }
            waitForExpectations(timeout: 1, handler: nil)
            XCTAssertEqual(values, [
                [0, 0, 0, 0, 0, 0, 0],
                [1, 0, 0, 0, 0, 0, 0],
                [1, 1, 0, 0, 0, 0, 0],
                [1, 1, 1, 0, 0, 0, 0],
                [1, 1, 1, 1, 0, 0, 0],
                [1, 1, 1, 1, 1, 0, 0],
                [1, 1, 1, 1, 1, 1, 0],
                [1, 1, 1, 1, 1, 1, 1],
                [2, 2, 2, 2, 2, 2, 2]])
        }
    }
    
    func testCombine8() throws {
        let dbQueue = try makeDatabaseQueue()
        try dbQueue.write {
            try $0.execute(sql: """
                CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t3(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t4(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t5(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t6(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t7(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t8(id INTEGER PRIMARY KEY AUTOINCREMENT);
                """)
        }
        
        var values: [[Int]] = []
        let notificationExpectation = expectation(description: "notification")
        notificationExpectation.assertForOverFulfill = true
        notificationExpectation.expectedFulfillmentCount = 10
        
        struct T1: TableRecord { }
        struct T2: TableRecord { }
        struct T3: TableRecord { }
        struct T4: TableRecord { }
        struct T5: TableRecord { }
        struct T6: TableRecord { }
        struct T7: TableRecord { }
        struct T8: TableRecord { }
        let observation1 = T1.observationForCount()
        let observation2 = T2.observationForCount()
        let observation3 = T3.observationForCount()
        let observation4 = T4.observationForCount()
        let observation5 = T5.observationForCount()
        let observation6 = T6.observationForCount()
        let observation7 = T7.observationForCount()
        let observation8 = T8.observationForCount()
        let observation = ValueObservation.combine(observation1, observation2, observation3, observation4, observation5, observation6, observation7, observation8)
        let observer = observation.start(
            in: dbQueue,
            onError: { error in XCTFail("Unexpected error: \(error)") },
            onChange: { v0, v1, v2, v3, v4, v5, v6, v7 in
                values.append([v0, v1, v2, v3, v4, v5, v6, v7])
                notificationExpectation.fulfill()
        })
        try withExtendedLifetime(observer) {
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t7 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t8 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t1")
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t2")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t3")
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t4")
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t5")
                try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t6")
                try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t7")
                try db.execute(sql: "INSERT INTO t7 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t8")
                try db.execute(sql: "INSERT INTO t8 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "DELETE FROM t1")
                try db.execute(sql: "DELETE FROM t2")
                try db.execute(sql: "DELETE FROM t3")
                try db.execute(sql: "DELETE FROM t4")
                try db.execute(sql: "DELETE FROM t5")
                try db.execute(sql: "DELETE FROM t6")
                try db.execute(sql: "DELETE FROM t7")
                try db.execute(sql: "DELETE FROM t8")
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t7 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t8 DEFAULT VALUES")
            }
            try dbQueue.write { db in
                try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t7 DEFAULT VALUES")
                try db.execute(sql: "INSERT INTO t8 DEFAULT VALUES")
            }
            waitForExpectations(timeout: 1, handler: nil)
            XCTAssertEqual(values, [
                [0, 0, 0, 0, 0, 0, 0, 0],
                [1, 0, 0, 0, 0, 0, 0, 0],
                [1, 1, 0, 0, 0, 0, 0, 0],
                [1, 1, 1, 0, 0, 0, 0, 0],
                [1, 1, 1, 1, 0, 0, 0, 0],
                [1, 1, 1, 1, 1, 0, 0, 0],
                [1, 1, 1, 1, 1, 1, 0, 0],
                [1, 1, 1, 1, 1, 1, 1, 0],
                [1, 1, 1, 1, 1, 1, 1, 1],
                [2, 2, 2, 2, 2, 2, 2, 2]])
        }
    }
    
    func testHeterogeneusCombine2() throws {
        struct V1 { }
        struct V2 { }
        let observation1 = ValueObservation.tracking(DatabaseRegion.fullDatabase, fetch: { _ in V1() })
        let observation2 = ValueObservation.tracking(DatabaseRegion.fullDatabase, fetch: { _ in V2() })
        let observation = ValueObservation.combine(observation1, observation2)
        var value: (V1, V2)?
        _ = try observation.start(
            in: makeDatabaseQueue(),
            onError: { error in XCTFail("Unexpected error: \(error)") },
            onChange: { value = $0 })
        XCTAssertNotNil(value)
    }
    
    func testHeterogeneusCombine3() throws {
        struct V1 { }
        struct V2 { }<commit_msg>More test refactoring with assertValueObservationRecordingMatch<commit_after>import XCTest
#if GRDBCUSTOMSQLITE
    import GRDBCustomSQLite
#else
    #if SWIFT_PACKAGE
        import CSQLite
    #else
        import SQLite3
    #endif
    import GRDB
#endif

class ValueObservationCombineTests: GRDBTestCase {
    func testCombine2() throws {
        let dbQueue = try makeDatabaseQueue()
        try dbQueue.write {
            try $0.execute(sql: """
                CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT);
                """)
        }
        
        struct T1: TableRecord { }
        struct T2: TableRecord { }
        let observation1 = T1.observationForCount()
        let observation2 = T2.observationForCount()
        let observation = ValueObservation.combine(observation1, observation2)
        let recorder = observation.record(in: dbQueue)
        
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t1")
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t2")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t1")
            try db.execute(sql: "DELETE FROM t2")
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
        }
        
        let expectedValues = [
            [0, 0],
            [1, 0],
            [1, 1],
            [2, 2]]
        let values = try wait(for: recorder.next(expectedValues.count), timeout: 0.5)
        XCTAssertEqual(values.map { [$0, $1] }, expectedValues)
    }
    
    func testCombine3() throws {
        let dbQueue = try makeDatabaseQueue()
        try dbQueue.write {
            try $0.execute(sql: """
                CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t3(id INTEGER PRIMARY KEY AUTOINCREMENT);
                """)
        }
        
        struct T1: TableRecord { }
        struct T2: TableRecord { }
        struct T3: TableRecord { }
        let observation1 = T1.observationForCount()
        let observation2 = T2.observationForCount()
        let observation3 = T3.observationForCount()
        let observation = ValueObservation.combine(observation1, observation2, observation3)
        let recorder = observation.record(in: dbQueue)
        
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t1")
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t2")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t3")
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t1")
            try db.execute(sql: "DELETE FROM t2")
            try db.execute(sql: "DELETE FROM t3")
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
        }
        
        let expectedValues = [
            [0, 0, 0],
            [1, 0, 0],
            [1, 1, 0],
            [1, 1, 1],
            [2, 2, 2]]
        let values = try wait(for: recorder.next(expectedValues.count), timeout: 0.5)
        XCTAssertEqual(values.map { [$0, $1, $2] }, expectedValues)
    }
    
    func testCombine4() throws {
        let dbQueue = try makeDatabaseQueue()
        try dbQueue.write {
            try $0.execute(sql: """
                CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t3(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t4(id INTEGER PRIMARY KEY AUTOINCREMENT);
                """)
        }
        
        struct T1: TableRecord { }
        struct T2: TableRecord { }
        struct T3: TableRecord { }
        struct T4: TableRecord { }
        let observation1 = T1.observationForCount()
        let observation2 = T2.observationForCount()
        let observation3 = T3.observationForCount()
        let observation4 = T4.observationForCount()
        let observation = ValueObservation.combine(observation1, observation2, observation3, observation4)
        let recorder = observation.record(in: dbQueue)
        
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t1")
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t2")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t3")
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t4")
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t1")
            try db.execute(sql: "DELETE FROM t2")
            try db.execute(sql: "DELETE FROM t3")
            try db.execute(sql: "DELETE FROM t4")
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
        }
        
        let expectedValues = [
            [0, 0, 0, 0],
            [1, 0, 0, 0],
            [1, 1, 0, 0],
            [1, 1, 1, 0],
            [1, 1, 1, 1],
            [2, 2, 2, 2]]
        let values = try wait(for: recorder.next(expectedValues.count), timeout: 0.5)
        XCTAssertEqual(values.map { [$0, $1, $2, $3] }, expectedValues)
    }
    
    func testCombine5() throws {
        let dbQueue = try makeDatabaseQueue()
        try dbQueue.write {
            try $0.execute(sql: """
                CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t3(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t4(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t5(id INTEGER PRIMARY KEY AUTOINCREMENT);
                """)
        }
        
        struct T1: TableRecord { }
        struct T2: TableRecord { }
        struct T3: TableRecord { }
        struct T4: TableRecord { }
        struct T5: TableRecord { }
        let observation1 = T1.observationForCount()
        let observation2 = T2.observationForCount()
        let observation3 = T3.observationForCount()
        let observation4 = T4.observationForCount()
        let observation5 = T5.observationForCount()
        let observation = ValueObservation.combine(observation1, observation2, observation3, observation4, observation5)
        let recorder = observation.record(in: dbQueue)
        
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t1")
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t2")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t3")
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t4")
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t5")
            try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t1")
            try db.execute(sql: "DELETE FROM t2")
            try db.execute(sql: "DELETE FROM t3")
            try db.execute(sql: "DELETE FROM t4")
            try db.execute(sql: "DELETE FROM t5")
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
        }
        
        let expectedValues = [
            [0, 0, 0, 0, 0],
            [1, 0, 0, 0, 0],
            [1, 1, 0, 0, 0],
            [1, 1, 1, 0, 0],
            [1, 1, 1, 1, 0],
            [1, 1, 1, 1, 1],
            [2, 2, 2, 2, 2]]
        let values = try wait(for: recorder.next(expectedValues.count), timeout: 0.5)
        XCTAssertEqual(values.map { [$0, $1, $2, $3, $4] }, expectedValues)
    }
    
    func testCombine6() throws {
        let dbQueue = try makeDatabaseQueue()
        try dbQueue.write {
            try $0.execute(sql: """
                CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t3(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t4(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t5(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t6(id INTEGER PRIMARY KEY AUTOINCREMENT);
                """)
        }
        
        struct T1: TableRecord { }
        struct T2: TableRecord { }
        struct T3: TableRecord { }
        struct T4: TableRecord { }
        struct T5: TableRecord { }
        struct T6: TableRecord { }
        let observation1 = T1.observationForCount()
        let observation2 = T2.observationForCount()
        let observation3 = T3.observationForCount()
        let observation4 = T4.observationForCount()
        let observation5 = T5.observationForCount()
        let observation6 = T6.observationForCount()
        let observation = ValueObservation.combine(observation1, observation2, observation3, observation4, observation5, observation6)
        let recorder = observation.record(in: dbQueue)
        
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t1")
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t2")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t3")
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t4")
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t5")
            try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t6")
            try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t1")
            try db.execute(sql: "DELETE FROM t2")
            try db.execute(sql: "DELETE FROM t3")
            try db.execute(sql: "DELETE FROM t4")
            try db.execute(sql: "DELETE FROM t5")
            try db.execute(sql: "DELETE FROM t6")
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
        }
        
        let expectedValues = [
            [0, 0, 0, 0, 0, 0],
            [1, 0, 0, 0, 0, 0],
            [1, 1, 0, 0, 0, 0],
            [1, 1, 1, 0, 0, 0],
            [1, 1, 1, 1, 0, 0],
            [1, 1, 1, 1, 1, 0],
            [1, 1, 1, 1, 1, 1],
            [2, 2, 2, 2, 2, 2]]
        let values = try wait(for: recorder.next(expectedValues.count), timeout: 0.5)
        XCTAssertEqual(values.map { [$0, $1, $2, $3, $4, $5] }, expectedValues)
    }
    
    func testCombine7() throws {
        let dbQueue = try makeDatabaseQueue()
        try dbQueue.write {
            try $0.execute(sql: """
                CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t3(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t4(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t5(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t6(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t7(id INTEGER PRIMARY KEY AUTOINCREMENT);
                """)
        }
        
        struct T1: TableRecord { }
        struct T2: TableRecord { }
        struct T3: TableRecord { }
        struct T4: TableRecord { }
        struct T5: TableRecord { }
        struct T6: TableRecord { }
        struct T7: TableRecord { }
        let observation1 = T1.observationForCount()
        let observation2 = T2.observationForCount()
        let observation3 = T3.observationForCount()
        let observation4 = T4.observationForCount()
        let observation5 = T5.observationForCount()
        let observation6 = T6.observationForCount()
        let observation7 = T7.observationForCount()
        let observation = ValueObservation.combine(observation1, observation2, observation3, observation4, observation5, observation6, observation7)
        let recorder = observation.record(in: dbQueue)
        
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t7 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t1")
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t2")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t3")
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t4")
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t5")
            try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t6")
            try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t7")
            try db.execute(sql: "INSERT INTO t7 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t1")
            try db.execute(sql: "DELETE FROM t2")
            try db.execute(sql: "DELETE FROM t3")
            try db.execute(sql: "DELETE FROM t4")
            try db.execute(sql: "DELETE FROM t5")
            try db.execute(sql: "DELETE FROM t6")
            try db.execute(sql: "DELETE FROM t7")
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t7 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t7 DEFAULT VALUES")
        }
        
        let expectedValues = [
            [0, 0, 0, 0, 0, 0, 0],
            [1, 0, 0, 0, 0, 0, 0],
            [1, 1, 0, 0, 0, 0, 0],
            [1, 1, 1, 0, 0, 0, 0],
            [1, 1, 1, 1, 0, 0, 0],
            [1, 1, 1, 1, 1, 0, 0],
            [1, 1, 1, 1, 1, 1, 0],
            [1, 1, 1, 1, 1, 1, 1],
            [2, 2, 2, 2, 2, 2, 2]]
        let values = try wait(for: recorder.next(expectedValues.count), timeout: 0.5)
        XCTAssertEqual(values.map { [$0, $1, $2, $3, $4, $5, $6] }, expectedValues)
    }
    
    func testCombine8() throws {
        let dbQueue = try makeDatabaseQueue()
        try dbQueue.write {
            try $0.execute(sql: """
                CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t2(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t3(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t4(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t5(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t6(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t7(id INTEGER PRIMARY KEY AUTOINCREMENT);
                CREATE TABLE t8(id INTEGER PRIMARY KEY AUTOINCREMENT);
                """)
        }
        
        struct T1: TableRecord { }
        struct T2: TableRecord { }
        struct T3: TableRecord { }
        struct T4: TableRecord { }
        struct T5: TableRecord { }
        struct T6: TableRecord { }
        struct T7: TableRecord { }
        struct T8: TableRecord { }
        let observation1 = T1.observationForCount()
        let observation2 = T2.observationForCount()
        let observation3 = T3.observationForCount()
        let observation4 = T4.observationForCount()
        let observation5 = T5.observationForCount()
        let observation6 = T6.observationForCount()
        let observation7 = T7.observationForCount()
        let observation8 = T8.observationForCount()
        let observation = ValueObservation.combine(observation1, observation2, observation3, observation4, observation5, observation6, observation7, observation8)
        let recorder = observation.record(in: dbQueue)
        
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t7 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t8 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t1")
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t2")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t3")
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t4")
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t5")
            try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t6")
            try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t7")
            try db.execute(sql: "INSERT INTO t7 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t8")
            try db.execute(sql: "INSERT INTO t8 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "DELETE FROM t1")
            try db.execute(sql: "DELETE FROM t2")
            try db.execute(sql: "DELETE FROM t3")
            try db.execute(sql: "DELETE FROM t4")
            try db.execute(sql: "DELETE FROM t5")
            try db.execute(sql: "DELETE FROM t6")
            try db.execute(sql: "DELETE FROM t7")
            try db.execute(sql: "DELETE FROM t8")
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t7 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t8 DEFAULT VALUES")
        }
        try dbQueue.write { db in
            try db.execute(sql: "INSERT INTO t1 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t2 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t3 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t4 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t5 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t6 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t7 DEFAULT VALUES")
            try db.execute(sql: "INSERT INTO t8 DEFAULT VALUES")
        }
        
        let expectedValues = [
            [0, 0, 0, 0, 0, 0, 0, 0],
            [1, 0, 0, 0, 0, 0, 0, 0],
            [1, 1, 0, 0, 0, 0, 0, 0],
            [1, 1, 1, 0, 0, 0, 0, 0],
            [1, 1, 1, 1, 0, 0, 0, 0],
            [1, 1, 1, 1, 1, 0, 0, 0],
            [1, 1, 1, 1, 1, 1, 0, 0],
            [1, 1, 1, 1, 1, 1, 1, 0],
            [1, 1, 1, 1, 1, 1, 1, 1],
            [2, 2, 2, 2, 2, 2, 2, 2]]
        let values = try wait(for: recorder.next(expectedValues.count), timeout: 0.5)
        XCTAssertEqual(values.map { [$0, $1, $2, $3, $4, $5, $6, $7] }, expectedValues)
    }
    
    func testHeterogeneusCombine2() throws {
        struct V1 { }
        struct V2 { }
        let observation1 = ValueObservation.tracking(DatabaseRegion.fullDatabase, fetch: { _ in V1() })
        let observation2 = ValueObservation.tracking(DatabaseRegion.fullDatabase, fetch: { _ in V2() })
        let observation = ValueObservation.combine(observation1, observation2)
        var value: (V1, V2)?
        _ = try observation.start(
            in: makeDatabaseQueue(),
            onError: { error in XCTFail("Unexpected error: \(error)") },
            onChange: { value = $0 })
        XCTAssertNotNil(value)
    }
    
    func testHeterogeneusCombine3() throws {
        struct V1 { }
        struct V2 { }
{'fraction_non_alphanumeric': 0.0937179841607988, 'fraction_numerical': 0.02455564047940416, 'mean_word_length': 1.662308424971859, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 9, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7385247', 'n_tokens_mistral': 18717, 'n_tokens_neox': 16956, 'n_words': 5616}