{"id":32146616,"url":"https://github.com/moshegottlieb/swiftsqlite","last_synced_at":"2025-10-21T08:16:46.509Z","repository":{"id":63918734,"uuid":"289270340","full_name":"moshegottlieb/SwiftSQLite","owner":"moshegottlieb","description":"SQLite wrapper for swift, nothing more, nothing less","archived":false,"fork":false,"pushed_at":"2024-12-05T11:49:34.000Z","size":2621,"stargazers_count":5,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-10-11T10:46:11.032Z","etag":null,"topics":["package","sqlite","sqlite3","sqlite3-database","swift"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/moshegottlieb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-08-21T12:59:12.000Z","updated_at":"2025-09-18T23:43:11.000Z","dependencies_parsed_at":"2023-01-14T14:00:52.644Z","dependency_job_id":null,"html_url":"https://github.com/moshegottlieb/SwiftSQLite","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/moshegottlieb/SwiftSQLite","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moshegottlieb%2FSwiftSQLite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moshegottlieb%2FSwiftSQLite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moshegottlieb%2FSwiftSQLite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moshegottlieb%2FSwiftSQLite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/moshegottlieb","download_url":"https://codeload.github.com/moshegottlieb/SwiftSQLite/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moshegottlieb%2FSwiftSQLite/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280225908,"owners_count":26293906,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-21T02:00:06.614Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["package","sqlite","sqlite3","sqlite3-database","swift"],"created_at":"2025-10-21T08:16:45.639Z","updated_at":"2025-10-21T08:16:46.493Z","avatar_url":"https://github.com/moshegottlieb.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SwiftSQLite\n\n![Swift](https://github.com/moshegottlieb/SwiftSQLite/workflows/Swift/badge.svg)\n![License](https://img.shields.io/badge/License-MIT-informational?style=flat)\n![Apple Platforms](https://img.shields.io/badge/Apple-000000?logo=apple\u0026logoColor=F0F0F0)\n![Linux](https://img.shields.io/badge/Linux-FCC624?logo=linux\u0026logoColor=black)\n![SQLite](https://img.shields.io/badge/SQLite-%3E%3D3.19.0-informational?style=flat\u0026logo=SQLite)\n![SQLCipher](https://img.shields.io/badge/SQLCipher-%20v4.6.1-informational?style=flat)\n\n\nSQLite wrapper for swift, nothing more, nothing less.  \n\n## What is it?\nA simple straight forward wrapper for the C API of SQLite.  \nConnect to SQLite databases, run queries, prepare statements and bind parameters, just like you'd do with the regular SQLite API, but in swift.  \nIf you need a light local database API without all the bells and whistles - this library is for you.  \n\n## What it is **NOT**\n- This is **not** another ORM database\n- It will not guess your scheme, create it, maintain it, and automagically sync to a remote server with zero code on your part - if you like the idea of zero coding - you're in the wrong line of work\n\n## Cook book\n\n### Create a DB connection\n```swift\n// For example, place the database in the user's library folder\nguard let path = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first?.appendingPathComponent(\"db.sqlite\").absoluteString else { fatalError(\"Could not create path\") }\nlet db = try Database(path:path)\n```\n### Open or close a DB connection explicitly\nSometimes you'd want to close or open a databasse explicitly, and not just using the CTOR and DTOR.  \n```swift\ndb.close() // will silently do nothing if already closed\ntry db.open(pathToFile) // Open a new connection, the old handle is closed first\n```\n\n### Run a simple SQL statement\n```swift\ntry db.exec(\"CREATE TABLE demo(a INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, b INTEGER NOT NULL)\")\n```\n### Prepare a statement and run with parameters\n```swift\n// Prepare once\nlet insert = try db.statement(sql: \"INSERT INTO demo (b) VALUES (?)\")\nfor i in 0..\u003c10 {\n    // Parameters are 1 based, this is how SQLite works\n    try insert.bind(param: 1,i)\n    try insert.step() // Run the statement\n    let last_row_id = db.lastInsertRowId\n    print(\"Last row id is: \\(last_row_id)\")\n    try insert.reset() // must reset before we can run it again\n    try insert.clearBindings() // Bindings are not cleared automatically, since we bind the same param again, this is not strictly required in this example, but it's good practice to clear the bindings.\n}\n```\n### Run SELECT queries\n```swift\nlet select = try db.statement(sql: \"SELECT a,b FROM demo WHERE b \u003e ?\")\ntry select.bind(param: 1, 5)\nwhile try select.step() {\n    guard let a = select.integer(column: 0), let b = select.string(column: 1) else {\n        fatalError(\"Expected b to be non nil\")\n    }\n    print(\"a: \\(a), b: \\(b)\")\n}\n```\n\n## Additional helpers and wrappers\n\n### Use codables\n\n```swift\n\nstruct Student : Codable {\n    let name:String\n    let grade:Int\n    let city:String\n}\n\ndb.useJSON1 = true\ntry db.exec(\"CREATE TABLE students (value json)\") // JSON1 extension, JSON is actually TEXT\nlet ins = try db.statement(sql: \"INSERT INTO students (value) VALUES (?)\")\nlet student = Student(name:\"Bart Simpson\",grade:4,city:\"Springfield\")\ntry ins.bind(param: 1,student) // Bind a decodable object\ntry ins.step() // Execute the statement\nlet sel = try db.statement(sql: \"SELECT json_extract(value,\"$.name\") FROM students\")\nguard try sel.step() else { fatalError(\"Expected step to succeed\") }\nguard let the_student:Student? = sel.object(column: 0) // deduce that the object is C by the return type, which must be an optional Decodable\nelse { fatalError(\"Expected object to be decoded to a C instance\") }\n\n```\n\n### Set [journal mode](https://www.sqlite.org/pragma.html#pragma_journal_mode) \n\n```swift\ntry db.set(journalMode: .wal) // Set journaling mode to WAL, useful when several processes read the database file, such as with an app and an app extension\nlet current_mode = try db.journalMode()\n```\n### [Auto vacuum](https://sqlite.org/pragma.html#pragma_auto_vacuum)\n```swift\nlet db.set(autoVacuum:.incremental)\n// do some inserts, deletes here\ntry db.incrementalVacuum()\n```\n\n### [Vacuum](https://sqlite.org/lang_vacuum.html)\n```swift\ntry db.vacuum()\n```\n\n### [Foreign keys on/off](https://sqlite.org/pragma.html#pragma_foreign_keys)\n```swift\ndb.foreignKeys = true\n// foreign keys are now enforced\ntry db.withoutForeignKeys {\n    // This code will run without foreign keys enforcement \n}\ntry db.withForeignKeys {\n    // This code will run with foreign keys enforcement\n}\n```\n\n### [Recursive triggers on/off](https://www.sqlite.org/pragma.html#pragma_recursive_triggers)\nRecursive triggers are off by default, but according to the docs, _may be turned on by default in future versions_.  \nAn example of a self limiting recursive trigger:\n```sql\nCREATE TABLE rt(a INTEGER);\nCREATE TRIGGER rt_trigger AFTER INSERT ON rt WHEN new.a \u003c 10\nBEGIN\n    INSERT INTO rt VALUES (new.a + 1);\nEND;\n```\n\n```swift\ndb.recursiveTriggers = true\ntry db.exec(\"INSERT INTO rt VALUES (1)\")\n// rt should now have the 10 values (1..10)\n// if recursiveTriggers was off - rt would only have 2 rows (1,2) as the trigger would not trigger itself.\n```\n\n\n\n### Set busy timeout\n```swift\ntry db.set(busyTimoeut:30)\n```\nThis will install a busy handler that will sleep until the database unlocks or until the timeout expires, useful for WAL mode.  \nSee [busy handler](http://sqlite.org/c3ref/busy_handler.html) and [PRAGMA busy_timouet](https://sqlite.org/pragma.html#pragma_busy_timeout).      \nNote that there can be only a single busy handler for a database connection.  \n\n### Versions\n\nSet the user version or get the user, data or schema versions.   \nSee [PRAGMA data_version](https://sqlite.org/pragma.html#pragma_data_version)  \nSee [PRAGMA schema_version](https://sqlite.org/pragma.html#pragma_schema_version)  \nSee [PRAGMA user_version](https://sqlite.org/pragma.html#pragma_user_version)\n\n```swift\nlet user_version = try db.get(version: .user) // 0 by default\nlet schema_version = try db.get(version: .schema) \nlet data_version = try db.get(version: .data) \ntry db.set(version:12)\n\n```\n\n### Custom functions\n\nSQLite lets you create user defined functions, and SwiftSQLite lets you do that in swift 🤓.  \nWe'll be using the `Value` and `Result` classes here.  \nA `Value` is an argument provided to your functions, and a `Result`, is a result from your functions.  \n  \nHere's an example of a scalar function: \n```swift\ntry db.createScalarFunction(name: \"custom_sum_all_args\", nArgs: 1, function: { (values:[Value]?) in\n    var sum = 0\n        values?.forEach({ value in\n            sum += value.intValue\n        })\n    return Result(sum)\n})\n```\n\nNow you can call:\n\n```sql\nSELECT custom_sum_all_args(1,2,3)\n```\nThe returned value would be 6! (1+2+3).  \n\nAggregate functions are a bit more complex, but not too much.  \nHere's a similar example, but as an aggregate function:\n  \n```swift\ntry db.createAggregateFunction(name: \"custom_agg_test\", step: { (values:[Value]?,result:Result) in\n    // Sum all arguments\n    var sum = 0\n    values?.forEach({ v in\n        sum += v.intValue\n    })\n    // Is it the first value we're setting?\n    if result.resultType == .Null {\n        // Set the initial value, result type will be automatically set to Int\n        result.intValue = sum\n    } else {\n        // Nope, not the first time, sum with previous value\n        result.intValue! += sum\n    }\n})\n```\n\nYou can now use it as an aggrageted function:  \n```sql\nSELECT custom_agg_test(value,1) FROM json_each(json_array(1,2,3))\n``` \nThe resulting value should be 9. ( (1 + 1) + (2 + 1) + (3 + 1) )\n\n### Logging\n\nIt is possible to install a logger by implementing the protocol `Log`:\n```swift\n/// Log protocol\npublic protocol Log  {\n    /// Log SQL\n    /// - parameters:\n    ///   - prepare: SQL statement being prepared\n    func log(prepare:String)\n    /// Log error\n    /// - parameters:\n    ///  - error: Error text\n    ///  - code: Error code (SQLite state)\n    func log(error:String,code:Int)\n    /// Log statement execution\n    /// - parameters:\n    ///  - sql: Executed SQL\n    func log(sql:String)\n    /// Log a message\n    /// - parameters:\n    ///   - message: Message to log (open DB, etc.)\n    func log(message:String)\n}\n```\nSet the static property `logger` for the `Database` class and you're ready to go.  \nA built in console logger is available, to use it, just add:  \n`Database.logger = ConsoleLog()`   \nBetter set it up before using the library (but can be set in any point).\n\n# SQLCipher\n\n## Support \n\nSQLCipher is supported from version 1.1.0 and higher  \n\n## SwiftSQLCipher or SwiftSQLite? \n\nTo use SQLCipher instead of SQLite, please refer to `LICENSE.sqlcipher.md` license file.  \nIf you do not wish to use it, you can still use the standard package `SwiftSQLite`.  \nIf you do not plan on using encryption, I suggest you use `SwiftSQLite` as it will slightly decrease your binary size.    \n\n## How to use SwiftSQLCipher\n\nThe follwoing methods are available for SQLCipher only:  \nInstead of importing `SwiftSQLite`, import `SwiftSQLCipher`.  \nAfter opening a database, call `setKey(:)`  \n\n```swift\ntry db.setKey(\"TopSecretPassword\")\n```\n\nThis will encrypt the database if it's not already encrypted, and allow reading it if it's encrypted.  \nThis must be the first action on your newly created / opened database.  \nYou cannot use this method to encrypt already existing data.    \n\nYou can also change the database password by calling `reKey(:)`.  \nThe database must be opened, encrypted, and the `setKey(:)` method must have been already called.  \n\n```swift\ntry db.setKey(\"TopSecretPassword\") // must call setKey(:) BEFORE calling reKey\ntry db.reKey(\"EvenMoreSecretPassword\") // will replace the password to a new key\ntry db.reKey(nil) // remove encryption altogether, can now read without SQLCipher\ntry db.removeKey() // same as reKey(nil) \n```\n\n## Advanced SQLCipher support\n\n```swift\n\n// var cipherSalt:Data? { get throws }\nlet salt:Data? = try db.cipherSalt // database salt (16 bytes), nil if not encrypted or plain text header\n...\n// func setCipherSalt(_ salt:Data) throws\nlet salt:Data = ....\ntry db.setCipherSalt(salt) // set the salt for the database. you need this when using plain text header\n...\n// func setPlainTextHeader(size:Int32) throws\n// Set a plain text header for the DB.\n// This causes sqlcipher not to be able to read the salt part of your database, make sure you store it if you use it\ntry db.setPlainTextHeader(size:32) // 32 is recommended for iOS WAL journaling in a shared container in iOS\n\n// func flushHeader() throws\n// This simply reads the user version and writes it, you should call this after creating databases with plain text headers\ntry db.flushHeader()\n\n```\n\n## iOS mode with WAL mode and shared containers\n\nAccording to SQLCipher, iOS will check if your database file is an SQLite database in WAL mode, and if so, will allow it to lock the file in the background.  \nOtherwise, your app will be killed when attempting it from the background.  \nSince SQLCipher databases are encrypted, iOS cannot verify the files meet the requirement.  \nRead about it in the [SQLCipher documentation](https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_plaintext_header_size).    \nSee more in [Apple's documentation](https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_plaintext_header_size).  \nThe correct sequence you should use is as follows:  \n```swift\n\n    // new database\n    let db = try Database(path: filename)\n    try db.setKey(password)\n    // SAVE this salt somewhere safe\n    guard let salt = try db.cipherSalt else {\n        throw E.error(\"Could not get salt\")\n    }\n    try db.setPlainTextHeader(size: 32)\n    try db.set(journalMode: .wal)\n    try db.flushHeader()\n    \n    // Open an existing database\n    try db.open(path: filename)\n    try db.setKey(password)\n    try db.setPlainTextHeader(size: 32)\n    try db.setCipherSalt(salt)\n    try db.set(journalMode: .wal)\n    // Good to go\n```\n\nThere's also a keychain supported version (Apple platforms only) which saves you the trouble.  \n\n```swift\n\n    // func openSharedWalDatabase(path:String,accessGroup:String? = nil,identifier:String) throws\n    \n    let db = try Database()\n    // Use a different identifier for each database!\n    try db.openSharedWalDatabase(path:\"path/to/file.db\",accessGroup:\"com.your.shared.identifier\",identifier:\"MyDB\")\n    // Your database is now opened, encrypted, in WAL mode, and the key and salt are stored in the keychain\n    // (even though the salt is not really a secret)\n    \n    // func deleteCredentials(accessGroup:String? = nil,identifier:String) throws\n    // This will REMOVE the credentials from the keychain\n    // Important: you cannot access your database after calling this method\n    // Use it when deleting your database file\n    try db.deleteCredentials(accessGroup:\"com.your.shared.identifier\",identifier:\"MyDB\")\n\n```\n\n## Keychain support (Apple platforms only)\n\nA keychain helper is included to save the password in the keychain.  \n\n```swift\n    \n    // Save key to keychain\n    // Pass nil to delete the key from the keychain\n    try db.saveToKeyChain(account:\"mydb\",key:\"MySecretPassword\")\n    // When sharing the database using a group identifier:\n    try db.saveToKeyChain(account:\"mydb\",key:\"MySecretPassword\",accessGroup:\"your.group.identifier.if.you.have.it\")\n    \n    // Delete the key from the keychain\n    \n    try db.deleteFromKeyChain(account:\"mydb\")\n    try db.deleteFromKeyChain(account:\"mydb\",accessGroup:\"your.group.identifier.if.you.have.it\")\n    \n    // Read the password from the keychain\n    if let password = try db.readFromKeyChain(account:\"mydb\") {\n        try db.setKey(password)\n    }\n    // of course, readFromKeyChain accepts also an accessGroup:\n    if let password = try db.readFromKeyChain(account:\"mydb\", accessGroup:\"your.group.identifier.if.you.have.it\") {\n        try db.setKey(password)\n    }\n```\n\n# Install\n\n## Swift Package Manager\n\nAdd the following to your Package.swift dependencies:\n\n```swift\ndependencies: [\n...\n.package(url: \"https://github.com/moshegottlieb/SwiftSQLite.git\", from: \"1.1.0\")\n...\n]\n\nimport SwiftSQLite // for standard SQLite\nimport SwiftSQLCipher // for SQLCipher version\n\n```\n## How to add to an existing Xcode project\n\nSelect your project, in the *general* tab, under *Frameworks and Libraries*, hit the **+** button.  \nEnter the URL:  \n`https://github.com/moshegottlieb/SwiftSQLite.git`  \nChoose your version, and you're done.\n\n## Linux dependencies\n\nThe swift package manager does not automatically install the required dependencies.  \nOn ubuntu/debian flavors:  \n`sudo apt-get install libsqlite3-dev`  \nFor SQLCipher:  \n`sudo apt-get install sqlciper-dev`    \n  \nOn RedHat/Centos flavors:  \n`sudo yum install sqlite-devel`  \nFor SQLCipher:  \n`sudo yum install sqlciper-devel`    \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoshegottlieb%2Fswiftsqlite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoshegottlieb%2Fswiftsqlite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoshegottlieb%2Fswiftsqlite/lists"}