Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions Playgrounds/README.playground/Contents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

[Documentation](https://alexander-ignition.github.io/SQLyra/documentation/sqlyra/)

- Note: this readme file is available as Xcode playground in Playgrounds/README.playground
> this readme file is available as Xcode playground in Playgrounds/README.playground

## Open

Expand All @@ -26,21 +26,21 @@ let database = try Database.open(

Create table for contacts with fields `id` and `name`.
*/
try database.execute(
"""
let sql = """
CREATE TABLE contacts(
id INT PRIMARY KEY NOT NULL,
name TEXT
);
"""
)
try database.execute(sql)
/*:
## Insert

Insert new contacts Paul and John.
*/
try database.execute("INSERT INTO contacts (id, name) VALUES (1, 'Paul');")
try database.execute("INSERT INTO contacts (id, name) VALUES (2, 'John');")
let insert = try database.prepare("INSERT INTO contacts (id, name) VALUES (?, ?);")
try insert.bind(parameters: 1, "Paul").execute().reset()
try insert.bind(parameters: 2, "John").execute()
/*:
## Select

Expand All @@ -51,4 +51,5 @@ struct Contact: Codable {
let name: String
}

let contacts = try database.prepare("SELECT * FROM contacts;").array(decoding: Contact.self)
let select = try database.prepare("SELECT * FROM contacts;")
let contacts = try select.array(Contact.self)
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Swift SQLite wrapper.

[Documentation](https://alexander-ignition.github.io/SQLyra/documentation/sqlyra/)

- Note: this readme file is available as Xcode playground in Playgrounds/README.playground
> this readme file is available as Xcode playground in Playgrounds/README.playground

## Open

Expand All @@ -25,21 +25,21 @@ let database = try Database.open(

Create table for contacts with fields `id` and `name`.
```swift
try database.execute(
"""
let sql = """
CREATE TABLE contacts(
id INT PRIMARY KEY NOT NULL,
name TEXT
);
"""
)
try database.execute(sql)
```
## Insert

Insert new contacts Paul and John.
```swift
try database.execute("INSERT INTO contacts (id, name) VALUES (1, 'Paul');")
try database.execute("INSERT INTO contacts (id, name) VALUES (2, 'John');")
let insert = try database.prepare("INSERT INTO contacts (id, name) VALUES (?, ?);")
try insert.bind(parameters: 1, "Paul").execute().reset()
try insert.bind(parameters: 2, "John").execute()
```
## Select

Expand All @@ -50,5 +50,6 @@ struct Contact: Codable {
let name: String
}

let contacts = try database.prepare("SELECT * FROM contacts;").array(decoding: Contact.self)
let select = try database.prepare("SELECT * FROM contacts;")
let contacts = try select.array(Contact.self)
```
2 changes: 1 addition & 1 deletion Sources/SQLyra/DatabaseError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public struct DatabaseError: Error, Equatable, Hashable {
public let code: Int32

/// A short error description.
public var message: String?
public let message: String?

/// A complete sentence (or more) describing why the operation failed.
public let details: String?
Expand Down
163 changes: 95 additions & 68 deletions Sources/SQLyra/PreparedStatement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ public final class PreparedStatement: DatabaseHandle {
/// Find the database handle of a prepared statement.
var db: OpaquePointer! { sqlite3_db_handle(stmt) }

private(set) lazy var columnIndexByName = [String: Int32](
private(set) lazy var columnIndexByName = [String: Int](
uniqueKeysWithValues: (0..<columnCount).compactMap { index in
column(at: index).name.map { name in (name, index) }
columnName(at: index).map { name in (name, index) }
}
)

Expand All @@ -34,20 +34,6 @@ public final class PreparedStatement: DatabaseHandle {
try check(sqlite3_step(stmt), SQLITE_DONE)
}

/// The new row of data is ready for processing.
///
/// - Throws: ``DatabaseError``
public func step() throws -> Bool {
switch sqlite3_step(stmt) {
case SQLITE_DONE:
return false
case SQLITE_ROW:
return true
case let code:
throw DatabaseError(code: code, db: db)
}
}

/// Reset the prepared statement.
///
/// The ``PreparedStatement/reset()`` function is called to reset a prepared statement object back to its initial state, ready to be re-executed.
Expand All @@ -59,32 +45,6 @@ public final class PreparedStatement: DatabaseHandle {
public func reset() throws -> PreparedStatement {
try check(sqlite3_reset(stmt))
}

/// Reset all bindings on a prepared statement.
///
/// Contrary to the intuition of many, ``PreparedStatement/reset()`` does not reset the bindings on a prepared statement.
/// Use this routine to reset all host parameters to NULL.
///
/// - Throws: ``DatabaseError``
@discardableResult
public func clearBindings() throws -> PreparedStatement {
try check(sqlite3_clear_bindings(stmt))
}

// MARK: - Decodable

public func array<T>(decoding type: T.Type) throws -> [T] where T: Decodable {
var array: [T] = []
while try step() {
let value = try decode(type)
array.append(value)
}
return array
}

public func decode<T>(_ type: T.Type) throws -> T where T: Decodable {
try StatementDecoder().decode(type, from: self)
}
}

// MARK: - Retrieving Statement SQL
Expand Down Expand Up @@ -112,16 +72,16 @@ extension PreparedStatement {

extension PreparedStatement {
/// Number of SQL parameters.
public var parameterCount: Int32 { sqlite3_bind_parameter_count(stmt) }
public var parameterCount: Int { Int(sqlite3_bind_parameter_count(stmt)) }

/// Name of a SQL parameter.
public func parameterName(at index: Int32) -> String? {
sqlite3_bind_parameter_name(stmt, index).map { String(cString: $0) }
public func parameterName(at index: Int) -> String? {
sqlite3_bind_parameter_name(stmt, Int32(index)).map { String(cString: $0) }
}

/// Index of a parameter with a given name.
public func parameterIndex(for name: String) -> Int32 {
sqlite3_bind_parameter_index(stmt, name)
public func parameterIndex(for name: String) -> Int {
Int(sqlite3_bind_parameter_index(stmt, name))
}
}

Expand All @@ -138,13 +98,14 @@ extension PreparedStatement {
@discardableResult
public func bind(parameters: SQLParameter...) throws -> PreparedStatement {
for (index, parameter) in parameters.enumerated() {
try bind(index: Int32(index + 1), parameter: parameter)
try bind(index: index + 1, parameter: parameter)
}
return self
}

@discardableResult
public func bind(index: Int32, parameter: SQLParameter) throws -> PreparedStatement {
public func bind(index: Int, parameter: SQLParameter) throws -> PreparedStatement {
let index = Int32(index)
let code =
switch parameter {
case .null:
Expand All @@ -162,52 +123,118 @@ extension PreparedStatement {
}
return try check(code)
}

/// Reset all bindings on a prepared statement.
///
/// Contrary to the intuition of many, ``PreparedStatement/reset()`` does not reset the bindings on a prepared statement.
/// Use this routine to reset all host parameters to NULL.
///
/// - Throws: ``DatabaseError``
@discardableResult
public func clearBindings() throws -> PreparedStatement {
try check(sqlite3_clear_bindings(stmt))
}
}

// MARK: - Result values from a Query
// MARK: - Columns

extension PreparedStatement {
/// Return the number of columns in the result set.
public var columnCount: Int32 { sqlite3_column_count(stmt) }
public var columnCount: Int { Int(sqlite3_column_count(stmt)) }

public func column(at index: Int32) -> Column {
Column(index: index, statement: self)
/// Returns the name assigned to a specific column in the result set of the SELECT statement.
///
/// The name of a result column is the value of the "AS" clause for that column, if there is an AS clause.
/// If there is no AS clause then the name of the column is unspecified and may change from one release of SQLite to the next.
public func columnName(at index: Int) -> String? {
sqlite3_column_name(stmt, Int32(index)).string
}
}

public func column(for name: String) -> Column? {
columnIndexByName[name].map { Column(index: $0, statement: self) }
// MARK: - Result values from a Query

extension PreparedStatement {
/// The new row of data is ready for processing.
///
/// - Throws: ``DatabaseError``
public func row() throws -> Row? {
switch sqlite3_step(stmt) {
case SQLITE_DONE: nil
case SQLITE_ROW: Row(statement: self)
case let code: throw DatabaseError(code: code, db: db)
}
}

/// Information about a single column of the current result row of a query.
public struct Column {
let index: Int32
public func array<T>(_ type: T.Type) throws -> [T] where T: Decodable {
try array(type, using: RowDecoder.default)
}

public func array<T>(_ type: T.Type, using decoder: RowDecoder) throws -> [T] where T: Decodable {
var array: [T] = []
while let row = try row() {
let value = try row.decode(type, using: decoder)
array.append(value)
}
return array
}

@dynamicMemberLookup
public struct Row {
let statement: PreparedStatement
private var stmt: OpaquePointer { statement.stmt }

/// Returns the name assigned to a specific column in the result set of the SELECT statement.
///
/// The name of a result column is the value of the "AS" clause for that column, if there is an AS clause.
/// If there is no AS clause then the name of the column is unspecified and may change from one release of SQLite to the next.
public var name: String? { sqlite3_column_name(stmt, index).string }
public subscript(dynamicMember name: String) -> Value? {
self[name]
}

public subscript(name: String) -> Value? {
statement.columnIndexByName[name].flatMap { self[$0] }
}

public subscript(index: Int) -> Value? {
if sqlite3_column_type(statement.stmt, Int32(index)) == SQLITE_NULL {
return nil
}
return Value(index: Int32(index), statement: statement)
}

public var isNull: Bool { sqlite3_column_type(stmt, index) == SQLITE_NULL }
public func decode<T>(_ type: T.Type) throws -> T where T: Decodable {
try decode(type, using: RowDecoder.default)
}

public func decode<T>(_ type: T.Type, using decoder: RowDecoder) throws -> T where T: Decodable {
try decoder.decode(type, from: self)
}
}

/// Result value from a query.
public struct Value {
let index: Int32
let statement: PreparedStatement
private var stmt: OpaquePointer { statement.stmt }

/// 64-bit INTEGER result.
public var int64: Int64 { sqlite3_column_int64(stmt, index) }

/// 32-bit INTEGER result.
public var int32: Int32 { sqlite3_column_int(stmt, index) }

/// A platform-specific integer.
public var int: Int { Int(int64) }

/// 64-bit IEEE floating point number.
public var double: Double { sqlite3_column_double(stmt, index) }

/// Size of a BLOB or a UTF-8 TEXT result in bytes.
public var count: Int { Int(sqlite3_column_bytes(stmt, index)) }

/// UTF-8 TEXT result.
public var string: String? {
sqlite3_column_text(stmt, index).flatMap { String(cString: $0) }
}

/// BLOB result.
public var blob: Data? {
sqlite3_column_blob(stmt, index).map { bytes in
Data(bytes: bytes, count: Int(sqlite3_column_bytes(stmt, index)))
}
sqlite3_column_blob(stmt, index).map { Data(bytes: $0, count: count) }
}
}
}
Expand Down
Loading