ios – Options to embed an UPDATE inside a SELECT assertion for standing code processing?


I’ve searched by means of this web site and the overall consensus is that you simply can’t embed an UPDATE inside a SELECT assertion — UPDATEs don’t return information and SELECTs don’t modify information. That being mentioned, I’ve discovered 2 choices to repair my downside however neither is nice: ready assertion transactions or synchronizing the calls to the database in my code.

I’m on the lookout for alternate options for the easy case of standing code processing: I need to discover the report with the bottom ID quantity that has a code worth=0. I then need to atomically / thread safely retain that report’s ID quantity and set the code worth=1. Principally I desire a strategy to replace a single row and retain the rowID of what I up to date. I’ve a number of threads making an attempt to get the subsequent worth and I need to safeguard in opposition to 2 threads processing the identical report. It appears SQLite provides you with the rowID of the final row INSERTed however not UPDATEd, however I am undecided if it is thread secure or not. I am additionally not optimistic that sqlite3_changes() is thread secure.

I’ve a easy desk in SQLite3 (iOS 14.0, Swift, https://github.com/stephencelis/SQLite.swift package deal):

CREATE TABLE IF NOT EXISTS Transactions (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, url TEXT NOT NULL, code INTEGER NOT NULL)

| id        | url               | code          |
| --------- | ----------------- | ------------- |
| 1         | https:/x.com/?a=1 | 0             |
| 2         | https:/x.com/?a=2 | 0             |
| 3         | https:/x.com/?a=3 | 0             |

I then:

BEGIN;
SELECT id,url FROM Transactions WHERE code=0 ORDER BY id ASC LIMIT 1;
// Learn the 'id' in code
UPDATE Transactions SET code=1 WHERE code=0 AND id='id';
COMMIT;

I want to discover a method to do that fully in SQL, if attainable. I’m utilizing SQLite as a light-weight DBMS and I do know it has its limitations, like no SELECT… FOR UPDATE. I additionally know there are tons of threads on the Web the place somebody means that SQLite is leaking reminiscence after which a half dozen individuals say that’s inconceivable. I do not need to go down that route, however I’ve present in my case that operating this on iOS in a synchronous DispatchQueue with out the ready assertion (simply assuming that just one thread is operating) will run for 96 hours straight with solely 82MB of RAM used, however utilizing the ready assertion like I described above ends in the app crashing with 1.81GB of RAM utilization in 56 hours (the code is on the backside of this query – I maintain a database connection open and I create an OpaquePointer for every Assertion in every operate name, put together with the _v2(), and finalize the Assertion with calls to sqlite3_finalize()).

So, to that finish I hoped some SQL guru may assist me out with another choice since I would prefer to not assume synchronization in my app. Thanks!

SQLite3 Ready Assertion Code:

    // Retrieve the Subsequent Machine Transaction - Returns Clean String if No Information Stay
    class func getNextDeviceTransaction() throws -> String {

        // Database Assertion and Worth Buffers
        var stmt: OpaquePointer?
        var id = -1
        var url = ""
        
        // Put together the Start
        if sqlite3_prepare_v2( db, "BEGIN", -1, &stmt, nil ) != SQLITE_OK {
            let errorMessage = String( cString: sqlite3_errmsg( db )! )
            sqlite3_finalize( stmt )            // Finalize the Ready Assertion to Keep away from Reminiscence Leaks - https://www.sqlite.org/malloc.html
            throw NSError( area: "com.", code: 921, userInfo: [ "Error": "Error Preparing Begin Transaction: ( errorMessage )" ] )
        }
        
        // Start the Transaction
        if sqlite3_step( stmt ) != SQLITE_DONE {
            let errorMessage = String( cString: sqlite3_errmsg( db )! )
            sqlite3_finalize( stmt )            // Finalize the Ready Assertion to Keep away from Reminiscence Leaks - https://www.sqlite.org/malloc.html
            throw NSError( area: "com.", code: 922, userInfo: [ "Error": "Database Transaction Malfunction: ( errorMessage )" ] )
        }
        
        // Choose Question
        var queryString = "SELECT id,url FROM Transactions WHERE code=0 ORDER BY id ASC LIMIT 1"
        
        // Put together the Question
        if sqlite3_prepare_v2( db, queryString, -1, &stmt, nil ) != SQLITE_OK {
            let errorMessage = String( cString: sqlite3_errmsg( db )! )
            sqlite3_finalize( stmt )            // Finalize the Ready Assertion to Keep away from Reminiscence Leaks - https://www.sqlite.org/malloc.html
            throw NSError( area: "com.", code: 923, userInfo: [ "Error": "Error Preparing Select: ( errorMessage )" ] )
        }
        
        // Traverse Via Information
        if sqlite3_step( stmt ) == SQLITE_ROW {
            
            // Retrieve Worth and Return
            id = Int( sqlite3_column_int( stmt, 0 ) )
            url = String( cString: sqlite3_column_text( stmt, 1 ) )
        }
        
        // Consider if No Information Discovered
        if id == -1 || url == "" {
            
            // Rollback
            sqlite3_prepare_v2( db, "ROLLBACK", -1, &stmt, nil )
            sqlite3_step( stmt )
            
            // Finalize the Ready Assertion to Keep away from Reminiscence Leaks - https://www.sqlite.org/malloc.html
            sqlite3_finalize( stmt )
            
            // No Information Exist
            return ""
        }
        
        // Choose Question
        queryString = "UPDATE Transactions SET code=1 WHERE code=0 AND id=( id )"
        
        // Put together the Replace Question
        if sqlite3_prepare_v2( db, queryString, -1, &stmt, nil ) != SQLITE_OK {
            let errorMessage = String( cString: sqlite3_errmsg( db )! )
            sqlite3_finalize( stmt )            // Finalize the Ready Assertion to Keep away from Reminiscence Leaks - https://www.sqlite.org/malloc.html
            throw NSError( area: "com.", code: 924, userInfo: [ "Error": "Error Preparing Update: ( errorMessage )" ] )
        }
        
        // Execute the Replace
        if sqlite3_step( stmt ) != SQLITE_DONE {
            let errorMessage = String( cString: sqlite3_errmsg( db )! )
            
            // Rollback
            sqlite3_prepare( db, "ROLLBACK", -1, &stmt, nil )
            sqlite3_step( stmt )
            
            sqlite3_finalize( stmt )            // Finalize the Ready Assertion to Keep away from Reminiscence Leaks - https://www.sqlite.org/malloc.html
            throw NSError( area: "com.", code: 925, userInfo: [ "Error": "Transaction Update Malfunction: ( errorMessage )" ] )
        }
        
        // Put together the Commit
        if sqlite3_prepare_v2( db, "COMMIT", -1, &stmt, nil ) != SQLITE_OK {
            let errorMessage = String( cString: sqlite3_errmsg( db )! )
            
            // Rollback
            sqlite3_prepare_v2( db, "ROLLBACK", -1, &stmt, nil )
            sqlite3_step( stmt )
            
            sqlite3_finalize( stmt )            // Finalize the Ready Assertion to Keep away from Reminiscence Leaks - https://www.sqlite.org/malloc.html
            throw NSError( area: "com.", code: 926, userInfo: [ "Error": "Error Preparing Commit: ( errorMessage )" ] )
        }
        
        // Commit the Transaction
        if sqlite3_step( stmt ) != SQLITE_DONE {
            let errorMessage = String( cString: sqlite3_errmsg( db )! )
            
            // Rollback
            sqlite3_prepare_v2( db, "ROLLBACK", -1, &stmt, nil )
            sqlite3_step( stmt )
            
            sqlite3_finalize( stmt )            // Finalize the Ready Assertion to Keep away from Reminiscence Leaks - https://www.sqlite.org/malloc.html
            throw NSError( area: "com.", code: 927, userInfo: [ "Error": "Database Commit Transaction Malfunction: ( errorMessage )" ] )
        }
        
        // Affirm a Single Row Touched
        if sqlite3_changes( db ) != 1 {
            
            let errorMessage = String( cString: sqlite3_errmsg( db )! )
            
            sqlite3_finalize( stmt )            // Finalize the Ready Assertion to Keep away from Reminiscence Leaks - https://www.sqlite.org/malloc.html
            throw NSError( area: "com.", code: ALLOWABLE_DATABASE_COLLISION_ERROR, userInfo: [ "Error": "Database Update Count Malfunction or Simple Transaction Collision: ( errorMessage )" ] )                 // 928
        }

        // Finalize the Ready Assertion to Keep away from Reminiscence Leaks - https://www.sqlite.org/malloc.html
        sqlite3_finalize( stmt )
        
        // Return Subsequent Out there URL
        return url
    }

iOS DispatchQueue Synchronized Code:


    // Retrieve the Subsequent Machine Transaction - Returns Clean String if No Information Stay - MUST BE CALLED FROM SYNCHRONIZED DISPATCH QUEUE
    class func getNextDeviceTransaction() throws -> String {

        // Database Assertion and Worth Buffers
        var stmt: OpaquePointer?
        var id: Int = -1
        var url: String = ""
        
        // Choose Question
        var queryString = "SELECT id,url FROM Transactions WHERE code=0 ORDER BY id ASC LIMIT 1"
        
        // Put together the Question
        if sqlite3_prepare_v2( db, queryString, -1, &stmt, nil ) != SQLITE_OK {
            
            // Finalize the Ready Assertion to Keep away from Reminiscence Leaks - https://www.sqlite.org/malloc.html
            sqlite3_finalize( stmt )
            
            let errorMessage = String( cString: sqlite3_errmsg( db )! )
            print( "Error Getting ready Choose: ( errorMessage )" )
            throw NSError( area: "com.", code: 921, userInfo: [ "Error": "Error Querying Device Transactions: ( errorMessage )" ] )
        }
        
        // Traverse Via the Single Report
        if sqlite3_step( stmt ) == SQLITE_ROW {
            
            // Retrieve IDs and URLs
            id = Int( sqlite3_column_int( stmt, 0 ) )
            url = String( cString: sqlite3_column_text( stmt, 1 ) )
            
            // Finalize the Ready Assertion to Keep away from Reminiscence Leaks - https://www.sqlite.org/malloc.html
            sqlite3_finalize( stmt )
            
            // Consider IDs and URLs
            if id > 0 && url != "" {
                
                // Replace Question to Synchronously Set the Information Standing Code
                queryString = "UPDATE Transactions SET code=1 WHERE code=0 AND id=( id )"
                
                // Put together the Replace Question
                if sqlite3_exec( db, queryString, nil, nil, nil ) != SQLITE_OK {
                    
                    let errorMessage = String( cString: sqlite3_errmsg( db )! )
                    print( "Error Getting ready Replace: ( errorMessage )" )
                    throw NSError( area: "com.", code: 922, userInfo: [ "Error": "Error Setting Transaction Status Code: ( errorMessage )" ] )
                }
                
                // Affirm a Single Row Touched
                if sqlite3_changes( db ) == 1 {
                    
                    // Success - Return the Subsequent Report's URL
                    return url
                }
                else {

                    let errorMessage = String( cString: sqlite3_errmsg( db )! )
                    print( "Machine Transaction Not Captured: ( errorMessage )" )
                    throw NSError( area: "com.", code: 922, userInfo: [ "Error": "Device Transaction Not Captured: ( errorMessage )" ] )
                }
            }
        }
        
        // Finalize the Ready Assertion to Keep away from Reminiscence Leaks - https://www.sqlite.org/malloc.html
        sqlite3_finalize( stmt )
        
        // No Information Exist
        return ""
    }

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles