Skip to content

Commit 7b34a6f

Browse files
committed
Cursor and Row tests
1 parent aa4b2a2 commit 7b34a6f

7 files changed

Lines changed: 490 additions & 150 deletions

File tree

Sources/Otter/Cursor.swift

Lines changed: 7 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ public struct Cursor<Element>: ~Copyable {
1414
self.statement = statement
1515
}
1616

17+
public mutating func nextRow() throws(OtterError) -> Row? {
18+
switch try statement.step() {
19+
case .row: Row(sqliteStatement: statement.raw)
20+
case .done: nil
21+
}
22+
}
23+
1724
public mutating func next<Adapter: DatabaseValueAdapter, Storage: DatabasePrimitive>(
1825
adapter: Adapter,
1926
storage: Storage.Type
@@ -51,77 +58,3 @@ extension Cursor where Element: RowDecodableWithAdapters {
5158
}
5259
}
5360
}
54-
55-
public struct Row: ~Copyable {
56-
@usableFromInline let sqliteStatement: OpaquePointer
57-
58-
/// Decodes the column at the index as the `Value` type.
59-
@inlinable public func value<Value: DatabasePrimitive>(
60-
at column: Int32,
61-
as _: Value.Type = Value.self
62-
) throws(OtterError) -> Value {
63-
return try Value(from: sqliteStatement, at: column)
64-
}
65-
66-
/// Decodes the column at the index as the `Storage.Value` type
67-
@inlinable public func value<Storage: DatabasePrimitive, Coder: DatabaseValueAdapter>(
68-
at column: Int32,
69-
using adapter: Coder,
70-
storage: Storage.Type
71-
) throws(OtterError) -> Coder.Value {
72-
let storage = try Storage(from: sqliteStatement, at: column)
73-
return try storage.decode(from: adapter)
74-
}
75-
76-
/// Decodes the column at the index as the `Storage.Value` type
77-
/// if it has a value.
78-
@inlinable public func optionalValue<Storage: DatabasePrimitive, Coder: DatabaseValueAdapter>(
79-
at column: Int32,
80-
using adapter: Coder,
81-
storage: Storage.Type
82-
) throws(OtterError) -> Coder.Value? {
83-
guard let storage = try Storage?(from: sqliteStatement, at: column) else { return nil}
84-
return try storage.decode(from: adapter)
85-
}
86-
87-
/// Decodes the struct embeeded at the start index as the `Value` type.
88-
@inlinable public func embedded<Value: RowDecodable>(
89-
at column: Int32,
90-
as _: Value.Type = Value.self
91-
) throws(OtterError) -> Value {
92-
return try Value(row: self, startingAt: column)
93-
}
94-
95-
/// Decodes the struct embeeded at the start index as the `Value` type
96-
/// if it exists
97-
@inlinable public func optionallyEmbedded<Value: RowDecodable>(
98-
at column: Int32,
99-
as _: Value.Type = Value.self
100-
) throws(OtterError) -> Value? {
101-
return try Value(row: self, optionallyAt: column)
102-
}
103-
104-
/// Decodes the struct embeeded at the start index as the `Value` type.
105-
@inlinable public func embedded<Value: RowDecodableWithAdapters>(
106-
at column: Int32,
107-
as _: Value.Type = Value.self,
108-
adapters: Value.Adapters
109-
) throws(OtterError) -> Value {
110-
return try Value(row: self, startingAt: column, adapters: adapters)
111-
}
112-
113-
/// Decodes the struct embeeded at the start index as the `Value` type
114-
/// if it exists
115-
@inlinable public func optionallyEmbedded<Value: RowDecodableWithAdapters>(
116-
at column: Int32,
117-
as _: Value.Type = Value.self,
118-
adapters: Value.Adapters
119-
) throws(OtterError) -> Value? {
120-
return try Value(row: self, optionallyAt: column, adapters: adapters)
121-
}
122-
123-
/// Whether or not the column has a non null table at the column index
124-
@inlinable public func hasValue(at column: Int32) -> Bool {
125-
sqlite3_column_type(sqliteStatement, column) != SQLITE_NULL
126-
}
127-
}

Sources/Otter/Migration.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
/// Executes the migrations and ensures it is up to date.
99
enum MigrationRunner {
10-
static let migrationTableName = "__otterMigrations"
10+
static let migrationTableName = "__otterMigration"
1111

1212
static func execute(migrations: [String], connection: SQLiteConnection) throws {
1313
let previouslyRunMigrations = try runMigrations(connection: connection)

Sources/Otter/Row.swift

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
//
2+
// Row.swift
3+
// Otter
4+
//
5+
// Created by Wes Wickwire on 9/6/25.
6+
//
7+
8+
import SQLite3
9+
10+
/// A raw SQLite row from a statement.
11+
public struct Row: ~Copyable {
12+
@usableFromInline let sqliteStatement: OpaquePointer
13+
14+
/// Decodes the column at the index as the `Value` type.
15+
@inlinable public func value<Value: DatabasePrimitive>(
16+
at column: Int32,
17+
as _: Value.Type = Value.self
18+
) throws(OtterError) -> Value {
19+
return try Value(from: sqliteStatement, at: column)
20+
}
21+
22+
/// Decodes the column at the index as the `Storage.Value` type
23+
@inlinable public func value<Storage: DatabasePrimitive, Coder: DatabaseValueAdapter>(
24+
at column: Int32,
25+
using adapter: Coder,
26+
storage: Storage.Type
27+
) throws(OtterError) -> Coder.Value {
28+
let storage = try Storage(from: sqliteStatement, at: column)
29+
return try storage.decode(from: adapter)
30+
}
31+
32+
/// Decodes the column at the index as the `Storage.Value` type
33+
/// if it has a value.
34+
@inlinable public func optionalValue<Storage: DatabasePrimitive, Coder: DatabaseValueAdapter>(
35+
at column: Int32,
36+
using adapter: Coder,
37+
storage: Storage.Type
38+
) throws(OtterError) -> Coder.Value? {
39+
guard let storage = try Storage?(from: sqliteStatement, at: column) else { return nil}
40+
return try storage.decode(from: adapter)
41+
}
42+
43+
/// Decodes the struct embeeded at the start index as the `Value` type.
44+
@inlinable public func embedded<Value: RowDecodable>(
45+
at column: Int32,
46+
as _: Value.Type = Value.self
47+
) throws(OtterError) -> Value {
48+
return try Value(row: self, startingAt: column)
49+
}
50+
51+
/// Decodes the struct embeeded at the start index as the `Value` type
52+
/// if it exists
53+
@inlinable public func optionallyEmbedded<Value: RowDecodable>(
54+
at column: Int32,
55+
as _: Value.Type = Value.self
56+
) throws(OtterError) -> Value? {
57+
return try Value(row: self, optionallyAt: column)
58+
}
59+
60+
/// Decodes the struct embeeded at the start index as the `Value` type.
61+
@inlinable public func embedded<Value: RowDecodableWithAdapters>(
62+
at column: Int32,
63+
as _: Value.Type = Value.self,
64+
adapters: Value.Adapters
65+
) throws(OtterError) -> Value {
66+
return try Value(row: self, startingAt: column, adapters: adapters)
67+
}
68+
69+
/// Decodes the struct embeeded at the start index as the `Value` type
70+
/// if it exists
71+
@inlinable public func optionallyEmbedded<Value: RowDecodableWithAdapters>(
72+
at column: Int32,
73+
as _: Value.Type = Value.self,
74+
adapters: Value.Adapters
75+
) throws(OtterError) -> Value? {
76+
return try Value(row: self, optionallyAt: column, adapters: adapters)
77+
}
78+
79+
/// Whether or not the column has a non null table at the column index
80+
@inlinable public func hasValue(at column: Int32) -> Bool {
81+
sqlite3_column_type(sqliteStatement, column) != SQLITE_NULL
82+
}
83+
}

Sources/Otter/Statement.swift

Lines changed: 8 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,9 @@ public struct Statement: ~Copyable {
9292
// MARK: - Fetch with RowDecodable
9393

9494
extension Statement {
95-
/// Fetches all rows returned by the statement
96-
public consuming func fetchAll<Element: RowDecodable>() throws(OtterError) -> [Element] {
97-
return try fetchAll(of: Element.self)
98-
}
99-
10095
/// Fetches all rows returned by the statement
10196
public consuming func fetchAll<Element: RowDecodable>(
102-
of _: Element.Type
97+
of _: Element.Type = Element.self
10398
) throws(OtterError) -> [Element] {
10499
var cursor = Cursor<Element>(of: self)
105100
var result: [Element] = []
@@ -110,25 +105,10 @@ extension Statement {
110105

111106
return result
112107
}
113-
114-
/// Optionally fetches a single row returned by the statement
115-
public consuming func fetchOne<Element: RowDecodable>() throws(OtterError) -> Element? {
116-
return try fetchOne(of: Element.self)
117-
}
118-
119-
/// Fetches a single row returned by the statement
120-
@_disfavoredOverload
121-
public consuming func fetchOne<Element: RowDecodable>() throws(OtterError) -> Element {
122-
guard let row = try fetchOne(of: Element.self) else {
123-
throw OtterError.queryReturnedNoValue
124-
}
125-
126-
return row
127-
}
128-
108+
129109
/// Fetches a single row returned by the statement
130110
public consuming func fetchOne<Element: RowDecodable>(
131-
of _: Element.Type
111+
of _: Element.Type = Element.self
132112
) throws(OtterError) -> Element? {
133113
var cursor = Cursor<Element>(of: self)
134114
return try cursor.next()
@@ -140,14 +120,7 @@ extension Statement {
140120
extension Statement {
141121
/// Fetches all rows returned by the statement
142122
public consuming func fetchAll<Element: RowDecodableWithAdapters>(
143-
adapters: Element.Adapters
144-
) throws(OtterError) -> [Element] {
145-
return try fetchAll(of: Element.self, adapters: adapters)
146-
}
147-
148-
/// Fetches all rows returned by the statement
149-
public consuming func fetchAll<Element: RowDecodableWithAdapters>(
150-
of _: Element.Type,
123+
of _: Element.Type = Element.self,
151124
adapters: Element.Adapters
152125
) throws(OtterError) -> [Element] {
153126
var cursor = Cursor<Element>(of: self)
@@ -159,29 +132,10 @@ extension Statement {
159132

160133
return result
161134
}
162-
163-
/// Optionally fetches a single row returned by the statement
164-
public consuming func fetchOne<Element: RowDecodableWithAdapters>(
165-
adapters: Element.Adapters
166-
) throws(OtterError) -> Element? {
167-
return try fetchOne(of: Element.self, adapters: adapters)
168-
}
169-
170-
/// Fetches a single row returned by the statement
171-
@_disfavoredOverload
172-
public consuming func fetchOne<Element: RowDecodableWithAdapters>(
173-
adapters: Element.Adapters
174-
) throws(OtterError) -> Element {
175-
guard let row = try fetchOne(of: Element.self, adapters: adapters) else {
176-
throw OtterError.queryReturnedNoValue
177-
}
178-
179-
return row
180-
}
181-
135+
182136
/// Fetches a single row returned by the statement
183137
public consuming func fetchOne<Element: RowDecodableWithAdapters>(
184-
of _: Element.Type,
138+
of _: Element.Type = Element.self,
185139
adapters: Element.Adapters
186140
) throws(OtterError) -> Element? {
187141
var cursor = Cursor<Element>(of: self)
@@ -194,15 +148,7 @@ extension Statement {
194148
extension Statement {
195149
/// Fetches all rows returned by the statement
196150
public consuming func fetchAll<Adapter: DatabaseValueAdapter, Storage: DatabasePrimitive>(
197-
adapter: Adapter,
198-
storage: Storage.Type
199-
) throws(OtterError) -> [Adapter.Value] {
200-
return try fetchAll(of: Adapter.Value.self, adapter: adapter, storage: storage)
201-
}
202-
203-
/// Fetches all rows returned by the statement
204-
public consuming func fetchAll<Adapter: DatabaseValueAdapter, Storage: DatabasePrimitive>(
205-
of _: Adapter.Value.Type,
151+
of _: Adapter.Value.Type = Adapter.Value.self,
206152
adapter: Adapter,
207153
storage: Storage.Type
208154
) throws(OtterError) -> [Adapter.Value] {
@@ -215,22 +161,10 @@ extension Statement {
215161

216162
return result
217163
}
218-
219-
/// Fetches a single row returned by the statement
220-
@_disfavoredOverload
221-
public consuming func fetchOne<Adapter: DatabaseValueAdapter, Storage: DatabasePrimitive>(
222-
adapter: Adapter,
223-
storage: Storage.Type
224-
) throws(OtterError) -> Adapter.Value {
225-
guard let row = try fetchOne(adapter: adapter, storage: storage) else {
226-
throw OtterError.queryReturnedNoValue
227-
}
228-
229-
return row
230-
}
231164

232165
/// Fetches a single row returned by the statement
233166
public consuming func fetchOne<Adapter: DatabaseValueAdapter, Storage: DatabasePrimitive>(
167+
of _: Adapter.Value.Type = Adapter.Value.self,
234168
adapter: Adapter,
235169
storage: Storage.Type
236170
) throws(OtterError) -> Adapter.Value? {

0 commit comments

Comments
 (0)