Sqlite_exec callback function and double indirection

Sqlite_exec callback function in Swift

This is about the sqlite_exec(…) callback function in Swift and double indirection operators

The code presented here shows a test program that passes SQL statements to Sqlite. The code was pieced together from examples found online and from error messages that emerged.

At the top is some C++ code that shows a working callback function. In that code we see that the double indirection operators for **argv and **azColName.

The call to sqlite3_exec(db, sql, nil , nil , nil ) works because it does not include the callback function. The call to sqlite3_exec(db, sql, callback, nil , nil ) does not work because there are errors in the callback function.

Based on the Swift error messages, I derived the following parameter in the callback function. It is one of two that return character strings. the Optional comes from error messages that suggested this format, and I find that this code does not return error messages.

argv: Optional<UnsafeMutablePointer<Optional<UnsafeMutablePointer>>>

As I see it, this line shows a double pointer, where the C++ program suggests double indirection (deference).

In Swift, one can dereference a single pointer with

s2 = argv!.pointee

However, I cannot find any way to dereference a double pointer and recover the string value that is in an array. How can I make this work in Swift? And then there’s the problem that the callback returns an array of character strings.

This is my first post to a Swift forum. I'm not seeing a way to specifically format the following text as code so may need advice on how to do that.

//  Swift_Sqlite_Experiment.xcodeproj

import Foundation
import SQLite3
 

/*

 Example C++ callback function from sqlite3_exec
 Note the double indirection operators for **argv and **azColName
 
 static int callback(void *NotUsed, int argc, char **argv, char **azColName)
{
    
    int i;
    for(i=0; i<argc; i++)
    {
        printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
    }
    printf("\n");
    return 0;
}
*/

// Test Swift callback function that was pieced together from error messages
// Note the double pointers that were the result of error resolution

func callback(
   NotUsed:  Optional<UnsafeMutableRawPointer>,
   argc: Int32,
   argv: Optional<UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>>,
    azColName: Optional<UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>>  ) -> Int32
    {
     
    for i in 0 ... Int(argc)
    {
        // Need to recover strings from pointers, below.
        var s1: String
        s1 = azColName!.pointee // See error message for text
        var s2: String
        s2 = argv!.pointee       // See error message for text
        
        print("%i  %s  %s \n", i, s1, s2 )  //
        
    }
    
        // Error Cannot assign value of type 'Optional<UnsafeMutalePointer<Int8>>' to type 'Int8'
        
    return 0
}

// Path to test.db file
let file1 = "XcodeProgramFiles/Swift_Sqlite_Experiment/Swift_Sqlite_Experiment/test.db"

let fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
            .appendingPathComponent(file1)

print(fileURL)

var db: OpaquePointer?
 
sqlite3_open(fileURL.path, &db )
if sqlite3_open(fileURL.path, &db) != SQLITE_OK {
           print("error opening database")
       }

 
var sql = "CREATE TABLE COMPANY("
sql += "ID INT PRIMARY KEY     NOT NULL,"
sql += "NAME           TEXT    NOT NULL,"
sql += "AGE            INT     NOT NULL,"
sql += "ADDRESS        CHAR(50),"
sql += "SALARY         REAL );";


// Creating table
if sqlite3_exec(db, sql, nil, nil, nil) != SQLITE_OK {
            let errmsg = String(cString: sqlite3_errmsg(db)!)
            print("error creating table: \(errmsg)")
        } else
        {
         print( "Table created -- check")
        }

sql = "SELECT * from COMPANY WHERE ID>0;";
var s1 = ""
var a = 5  // was nil
func x(){}

// Need an adequate example of the settings for sqlite3_exec that are shown here as nil
// This works but needs a callback function

if  sqlite3_exec(db, sql, nil, nil, nil) != SQLITE_OK {
            let errmsg = String(cString: sqlite3_errmsg(db)!)
            print("error -->: \(errmsg)")
        }
       else
        {
        print( "Exec Command Completed")
        }

// This uses the callback function shown above

if  sqlite3_exec(db, sql, callback, nil, nil) != SQLITE_OK {
            let errmsg = String(cString: sqlite3_errmsg(db)!)
            print("error -->: \(errmsg)")
        }
       else
        {
        print( "Exec Command Completed")
        }


sqlite3_close(db)


print("Done")

Hey, welcome to the forums!

If creating the callback function's type signature is particularly tricky, one neat thing you can do is use a closure and let Swift infer things for you:

var db: OpaquePointer? = nil
sqlite3_open_v2("Calendar.sqlitedb", &db, SQLITE_OPEN_READONLY, nil)
sqlite3_exec(db, "select title, color from Calendar", { _, columnCount, columnText, columnName in
    let elements = UnsafeMutableBufferPointer(start: columnText, count: Int(columnCount))
    
    print(elements.reduce("") { message, element in
        guard let element = element else { return message }
        return message + String(cString: element) + " | "
    })
    // Prints various calendar titles and colors.
    
    return 0
}, nil, nil)

There's a string initializer that helps here, and you can use UnsafeMutableBufferPointer to iterate over an array of pointers like that. Hope this helps.

Given this prototype:

/*
public func sqlite3_exec(
    _ db: OpaquePointer!,
    _ sql: UnsafePointer<CChar>!,
    _ callback: (@convention(c) (UnsafeMutableRawPointer?, Int32, UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?, UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?) -> Int32)!,
    _ refCon: UnsafeMutableRawPointer!,
    _ errmsg: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>!
) -> Int32
*/

I'd do this callback:

func callback(refCon: UnsafeMutableRawPointer?, numColumns: Int32, colums: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?, strings: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?) -> Int32 {
    for i in 0 ..< Int(numColumns) {
        let column = String(cString: colums![i]!)
        print(column)
    }
    for i in 0 ..< Int(numColumns) {
        let string = String(cString: strings![i]!)
        print(string)
    }
    return noErr
}

sqlite3_exec(db, sql, callback, refCon, errorMsg)

It is untested and it is likely you'll need to fix it here and there (and check for nil instead of !).

Note that callback must be "convention (c)" - either an immediate closure that doesn't capture variables, or a static method of your class or a standalone (global) function.

You may also want to pass the relevant context parameter in the "refCon". For that I'd recommend to have your class instance around (for the duration that will outlive any callout of your callback done by sqlite) and then pass your class instance reference at +0 (see below). Once you have an object of your class you can call the actual realCallback which can be a normal method of your class without "convention (c)" restrictions. The sketch implementation:

class MyClass {
    ...
    func exec(sql: String) {
        let refCon = unsafeBitCast(self, to: UnsafeMutablePointer.self)
        sqlite3_exec(self.db, sql, Self.callback, refCon, errorMsg)
    }

    static func callback(refCon: UnsafeMutableRawPointer?, other parameters) -> Int32 {
        let myObject = unsafeBitCast(refCon, to: MyClass.self)
        return myObject.realCallback(other parameters)
    }

    func realCallback(numColumns: Int32, other parameters) -> Int32 {
        actual work here
    }
}

Thank you. This is very helpful. I learned much from your post.

Thank you. This is very helpful, and it works. I learned a lot from it.