All Projects → ulrikdamm → Sqlable

ulrikdamm / Sqlable

Licence: MIT license
Swift library for making storing data in a SQLite database simple and magic-free

Programming Languages

swift
15916 projects

Projects that are alternatives of or similar to Sqlable

Dots
Lightweight Concurrent Networking Framework
Stars: ✭ 35 (-57.83%)
Mutual labels:  carthage, concurrency, swift-package-manager
Multipeer
📱📲 A wrapper for the MultipeerConnectivity framework for automatic offline data transmission between devices
Stars: ✭ 170 (+104.82%)
Mutual labels:  carthage, swift-package-manager
Kvkcalendar
A most fully customization calendar and timeline library for iOS 📅
Stars: ✭ 160 (+92.77%)
Mutual labels:  carthage, swift-package-manager
Carting
🚘 A simple tool for updating Carthage script phase
Stars: ✭ 182 (+119.28%)
Mutual labels:  carthage, swift-package-manager
Coregpx
A library for parsing and creation of GPX location files. Purely Swift.
Stars: ✭ 132 (+59.04%)
Mutual labels:  carthage, swift-package-manager
Ducttape
📦 KeyPath dynamicMemberLookup based syntax sugar for Swift.
Stars: ✭ 138 (+66.27%)
Mutual labels:  carthage, swift-package-manager
AirPlay
Small framework that lets users track iOS AirPlay availability and extra features.
Stars: ✭ 46 (-44.58%)
Mutual labels:  carthage, swift-package-manager
Randomkit
Random data generation in Swift
Stars: ✭ 1,458 (+1656.63%)
Mutual labels:  carthage, swift-package-manager
Amplitude Ios
Native iOS/tvOS/macOS SDK
Stars: ✭ 216 (+160.24%)
Mutual labels:  carthage, swift-package-manager
Swipycell
Easy to use UITableViewCell implementing swiping to trigger actions.
Stars: ✭ 230 (+177.11%)
Mutual labels:  carthage, swift-package-manager
BJOTPViewController
Entering OTP made simpler.
Stars: ✭ 42 (-49.4%)
Mutual labels:  simple, swift-package-manager
Natrium
A pre-build (Swift) script to alter your Xcode project at pre-build-time per environment, build configuration and target.
Stars: ✭ 131 (+57.83%)
Mutual labels:  carthage, swift-package-manager
Bettersegmentedcontrol
An easy to use, customizable replacement for UISegmentedControl & UISwitch.
Stars: ✭ 1,782 (+2046.99%)
Mutual labels:  carthage, swift-package-manager
Cdmarkdownkit
An extensive Swift framework providing simple and customizable markdown parsing.
Stars: ✭ 158 (+90.36%)
Mutual labels:  carthage, swift-package-manager
Skeletonview
☠️ An elegant way to show users that something is happening and also prepare them to which contents they are awaiting
Stars: ✭ 10,804 (+12916.87%)
Mutual labels:  carthage, swift-package-manager
L10n Swift
Localization of the application with ability to change language "on the fly" and support for plural form in any language.
Stars: ✭ 177 (+113.25%)
Mutual labels:  carthage, swift-package-manager
Swifterswift
A handy collection of more than 500 native Swift extensions to boost your productivity.
Stars: ✭ 10,706 (+12798.8%)
Mutual labels:  carthage, swift-package-manager
Alamofire
Elegant HTTP Networking in Swift
Stars: ✭ 36,896 (+44353.01%)
Mutual labels:  carthage, swift-package-manager
Aksidemenu
Beautiful iOS side menu library with parallax effect. Written in Swift
Stars: ✭ 216 (+160.24%)
Mutual labels:  carthage, swift-package-manager
Bree
🚥 The best job scheduler for Node.js and JavaScript with cron, dates, ms, later, and human-friendly support. Works in Node v10+ and browsers, uses workers to spawn sandboxed processes, and supports async/await, retries, throttling, concurrency, and graceful shutdown. Simple, fast, and lightweight. Made for @ForwardEmail and @ladjs.
Stars: ✭ 933 (+1024.1%)
Mutual labels:  simple, concurrency

Sqlable

Sqlable is a Swift library for storing data in a SQLite database easy as pie. Create a struct, make it Sqlable, and then read, write, update and delete to your heart’s content!

Why would I want that?

Persistence is often a pain when making iOS apps. If you want to only use 1st party frameworks from Apple, your choice is either Core Data, or serializing to JSON or plist files. there’s a few nice 3rd party options, like Realm, but that’s mostly active objects doing a lot of dynamic things behind your back. If you want to just operate on plain structs, and just want to read and write to a database, Sqlable is the simplest option.

Show me how to use it!

Suppose you have this struct:

struct Bicycle {
	let id : Int?
	var name : String
	var color : String
}

And you want to persist this in a database. To do this, you just make your struct Sqlable by implementing the Sqlable protocol like this:

extension Bicycle : Sqlable {
	static let id = Column("id", .integer, PrimaryKey(autoincrement: true))
	static let name = Column("name", .text)
	static let color = Column("color", .text)
	static let tableLayout = [id, name, color]
	
	func valueForColumn(column : Column) -> SqlValue? {
		switch column {
		case Bicycle.id: return id
		case Bicycle.name: return name
		case Bicycle.color: return color
		case _: return nil
		}
	}
	
	init(row : ReadRow) throws {
		id = try row.get(Bicycle.id)
		name = try row.get(Bicycle.name)
		color = try row.get(Bicycle.color)
	}
}

These are the only three things you need to specify:

• The table layout (which columns do you want stored)

• How to save a column

• How to read a row

And when you’ve done that, you can start using your struct with SQLite!

The first step to using it is to setup a new SQLite database and create the table for the bicycle:

db = try SqliteDatabase(filepath: documentsPath() + "/db.sqlite")
try db.createTable(Bicycle.self)

And then you’re ready to write, read, update and delete bicycles from the database!

// Insert bicycle
var bike = Bicycle(id: 1, name: "My bike", color: "Black")
try bike.insert().run(db)

// Read all bicycles
let bicycles = try Bicycle.read().run(db)

// Read some bicycles
let redBikes = try Bicycle.read().filter(Bicycle.color == "red").limit(3).run(db)

// Count all bicycles
let bicycleCount = try Bicycle.count().run(db)

// Update a bicycle
bike.name = "Sportsbike"
try bike.update().run(db)

// Delete bike
try bike.delete().run(db)

New in version 1.2

Update callbacks

You can now very easily observe changes via update callbacks on inserts, deletes and updates.

db.observe(.insert, on: Bicycle.self) { id in
	print("Inserted bicycle \(id)")
}

db.observe(.update, on: Bicycle.self, id: 2) { id in
	print("bicycle 2 has been updated")
}

Concurrency

Sqlable now uses SQLites write-ahead log, which makes concurrent operations fast and easy. And since Sqlable is operating with plain structs, you can freely pass information between different threads.

let child = try db.createChild()

dispatch_async(background_queue) {
	let bicycles = try! Bicycle.read().run(child)
	
	dispatch_async(main_queue) {
		self.displayData(bicycles)
	}
}

You can use transactions to lock the database, and other threads will wait until the transaction is done.

Full documentation

All public functions now have documentation 😎

New in 1.1 (+1.1.1)

• Nested transactions

• SQL function calling for filters (e.g. "like", "upper" and "lower")

What other cool features does it have?

Transactions

Transactions are very simple to do:

try db.transaction { db in
	if try Bicycle.count().run(db) == 0 {
		try bike.insert().run(db)
	}
}

You can also do rollbacks:

try db.beginTransaction()
try bike.insert().run(db)
try db.rollbackTransaction()

And nested transactions! (new in 1.1.1!)

try db.transaction { db in
	try db.transaction { db in
		if try Bicycle.count().run(db) == 0 {
			try bike.insert().run(db)
		}
	}
}

Foreign key constraints

extension Bicycle : Sqlable {
	static let ownerId = Column("owner_id", .Integer, ForeignKey<Person>())
	...

And you can also specify other columns and delete/update rules:

extension Bicycle : Sqlable {
	static let ownerId = Column("owner_id", .Integer, ForeignKey<Person>(column: Person.regId, onDelete: .cascade))
	...

Uniqueness constraints

(New in 1.1!)

If you want each bicycle in the database to have a unique name:

extension Bicycle : Sqlable {
	static let name = Column("name", .text)
	static let tableConstraints : [TableConstraint] = [Unique(Bicycle.name)]
	...

Or, if you want the combination of name and color to be unique:

extension Bicycle : Sqlable {
	static let name = Column("name", .text)
	static let color = Column("color", .text)
	static let tableConstraints : [TableConstraint] = [Unique(Bicycle.name, Bicycle.color)]
	...

DSL for query filters

Bicycle.read().filter(Bicycle.color == "red" && !(Bicycle.id == 0 || Bicycle.id > 1000))

Update callback

(New in 1.2!)

Register the didUpdate callback on your database handler to get notified when anything changes:

db.didUpdate = { table, id, change in
	switch change {
	case .insert: print("Inserted \(id) into \(table)")
	case .update: print("Updated \(id) in \(table)")
	case .delete: print("Deleted \(id) from \(table)")
	}
}

But even better, you can register event callbacks on specific actions, tables and ids:

db.observe(.insert, on: Bicycle.self) { id in
	print("Inserted bicycle \(id)")
}

db.observe(.update, on: Bicycle.self, id: 2) { id in
	print("bicycle 2 has been updated")
}

db.observe(.delete, on: Bicycle.self) { id in
	print("bicycle \(id) has been deleted")
}

db.observe(on: Bicycle.self) { id in
	print("Something was updated on bicycle \(id)")
}

Swift style error handling

Every function call that can fail is marked with throws, so you can handle every error that can possibly happen.

The APIs are also constructed so that you can set up your statements without touching the database, so you can set everything up without any error handling:

let idFilter = Bicycle.id > 2 && Bicyckle.id < 10
let read = Bicycle.read().filter(idFilter).limit(2)

do {
	let result = read.run(db)
} catch let error {
	// handle error
}

It also supports an optional handy callback for when any error occurs, which you can use if it fits into your app. Just call the fail method on your database handler when you encounter an error like this:

do {
	try Bicycle.read().limit(-1).run(db)
} catch let error {
	db.fail(error)
}

And it will be passed to your registered error handler:

db.didFail = { error in
	print("Oh no! \(error)")
}

Concurrency

(new in 1.2!)

Ever had to deal with concurrency in Core Data? Don't worry, Sqlable is nothing like that.

Each SqliteDatabase instance is unique to one thread/queue. If you want to run some code in the background, you can create a child instance:

let child = try db.createChild()

dispatch_async(background_queue) {
	for bicycle in try! Bicycle.read().run(child) {
		...
	}
}

That's the only rule: keep each instance unique on its thread/queue. Sqlable and Sqlite will take care of locking the database correctly. The parent will even get update notifications from changes made in the child. If you want some database operations to happen serially, you can put them in a transaction, which will automatically lock the database on all threads.

Also, since model instances are just value types, you can freely move them around on threads. If you need to load 10.000.000 bicycles from your database, just run the read query in the background, and dispatch the result to the main queue.

How do I install it?

If you’re using Carthage (you should!), just add this to your Cartfile:

github "ulrikdamm/Sqlable"

And then just in your source files:

import Sqlable

And you’re good to go!

Which features are coming soon?

• Migrations

• Joins

More technical details

Statements

When you make a struct Sqlable, it gains instance- and static methods for returning statements. These methods are read, count, insert, update and delete. All these returns a Statement struct, which you can then modify (with .filter, .limit, .onConflict, .orderBy). The statement is just an immutable struct, no magic going on. You can save it, serialize it, etc. When you want to run the statement, you just call the run method, which takes a database handler to run it in, and might throw an error, or give you a result. The type of the result depends on which initial method created the statement.

Query DSL

The query DSL supports following operators:

Is equal: column == value (e.g. Bicycle.id == 1)

Is not equal: column != value

Is less than: column < value

Is less than or equal: column <= value

Is greater than: column > value

Is greater than or equal: column => value

And: expression && expression

Or: expression || expression

Inverse: !expression

Is null: !column

Contains: column ∈ [value] or contains(column, value)

String lowercase: column.lowercase()

String uppercase: column.uppercase()

In-string search: column.like(value) (e.g. Bicycle.name.like("%bike%"))

column means an instance of a Column struct, e.g. Bicycle.id.

value means any value that works with SQL, like Int, String, Double, etc.

expression is anything returned by one of these operators

Who made this?

I did. My name is Ulrik Flænø Damm, I’m an iOS and Unity developer at Northplay in Copenhagen. You can follow me on Twitter, or visit my website.

If you want to contribute with code or ideas, open some issues or submit some pull requests!

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].