1

In each of the examples below, does blob.0 or text need to be copied? How do you know?

Setup

import SQLite3
private let static_destructor = unsafeBitCast(0, to: sqlite3_destructor_type.self)
private let transient_destructor = unsafeBitCast(-1, to: sqlite3_destructor_type.self)

Examples

  1. bind_blob

    func bind_blob(_ stmt: OpaquePointer, _ blob: (UnsafeRawPointer, Int32)) {
      sqlite3_bind_blob(stmt, 1, blob.0, blob.1, transient_destructor)
    }
    
  2. bind_blobs

    func bind_blobs(_ stmt: OpaquePointer, _ blobs: [(UnsafeRawPointer, Int32)]) {
      for (index, blob) in blobs.enumerated() {
        sqlite3_bind_blob(stmt, Int32(index+1), blob.0, blob.1, transient_destructor)
      }
    }
    
  3. bind_text

    func bind_text(_ stmt: OpaquePointer, _ text: String) {
      sqlite3_bind_text(stmt, 1, text, -1, transient_destructor)
    }
    
  4. bind_texts

    func bind_texts(_ stmt: OpaquePointer, _ texts: [String]) {
      for (index, text) in texts.enumerated() {
        sqlite3_bind_text(stmt, Int32(index+1), text, -1, transient_destructor)
      }
    }
    

Ie, should I use static_destructor instead of transient_destructor in any of the examples?

Related question: When to use SQLITE_TRANSIENT vs SQLITE_STATIC?

ma11hew28
  • 121,420
  • 116
  • 450
  • 651

1 Answers1

2

In your bind_text and bind_texts functions, you need to use the transient destructor. When you pass a Swift String to a C function as a const char * argument, Swift does not guarantee that the pointer will remain valid after the C function returns. The Calling Functions With Pointer Parameters article says this:

The pointer you pass to the function is only guaranteed to be valid for the duration of the function call. Do not persist the pointer and access it after the function has returned.

In your bind_blob and bind_blobs functions, it depends where the UnsafeRawPointer comes from and when you execute the SQL statement. If you're getting the pointer using any sort of Swift withUnsafeWhatever function, then the pointer is not valid after the withUnsafeWhatever function returns. For example, the withUnsafeBytes(of:_:) documentation says this:

The buffer pointer argument is valid only for the duration of the closure’s execution.

If you bind your blob, execute the statement, and then no longer use the binding, then you can use the static destructor. This is okay with the static destructor:

let data: Data = ...
data.withUnsafeBytes { rawBuffer in
    if let pointer = rawBuffer.baseAddress {
        bind_blob(statement, (pointer, rawBuffer.count))
        execute(statement)
        // No more use of statement unless the parameter is rebound.
    }
}

But this is not okay with the static destructor:

let data: Data = ...
data.withUnsafeBytes { rawBuffer in
    if let pointer = rawBuffer.baseAddress {
        bind_blob(statement, (pointer, rawBuffer.count))
    }
}
execute(statement)
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Thank you! What a beautiful answer. I appreciate you for providing links to and quoting the relevant documentation and for providing clear explanations and elaborating with code examples. And yes, I am using `withUnsafeBytes`. One follow-up question: How do I know when SQLite is finished with the blob or string? – ma11hew28 Mar 09 '20 at 20:36
  • SQLite is finished with it when you stop using the `sqlite3_stmt` to which it is bound. – rob mayoff Mar 09 '20 at 20:41
  • OK. Thank you. I figured it'd be finished with the blob or string after the first call to `sqlite3_step()`, or after calling`sqlite3_bind_*()` again with a different value for the same parameter (which I'm not sure is even allowed), or after calling `sqlite3_clear_bindings()`. – ma11hew28 Mar 09 '20 at 20:52
  • Well, yes, if you overwrite the binding with another call to `sqlite3_bind_*`, or clear it with `sqlite3_clear_bindings`, then SQLite will also be “finished” with the value. – rob mayoff Mar 09 '20 at 23:34
  • OK. Cool. Thank you. :-) – ma11hew28 Mar 09 '20 at 23:35