Skip to content

Commit 4b6614e

Browse files
committed
Cursor and Row tests
1 parent aa4b2a2 commit 4b6614e

7 files changed

Lines changed: 538 additions & 137 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: 34 additions & 61 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,44 +105,34 @@ 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)
108+
109+
/// Fetches a single row returned by the statement
110+
public consuming func fetchOne<Element: RowDecodable>(
111+
of _: Element.Type = Element.self
112+
) throws(OtterError) -> Element? {
113+
var cursor = Cursor<Element>(of: self)
114+
return try cursor.next()
117115
}
118116

119117
/// Fetches a single row returned by the statement
120118
@_disfavoredOverload
121-
public consuming func fetchOne<Element: RowDecodable>() throws(OtterError) -> Element {
122-
guard let row = try fetchOne(of: Element.self) else {
119+
public consuming func fetchOne<Element: RowDecodable>(
120+
of value: Element.Type = Element.self
121+
) throws(OtterError) -> Element {
122+
guard let row = try fetchOne(of: value) else {
123123
throw OtterError.queryReturnedNoValue
124124
}
125125

126126
return row
127127
}
128-
129-
/// Fetches a single row returned by the statement
130-
public consuming func fetchOne<Element: RowDecodable>(
131-
of _: Element.Type
132-
) throws(OtterError) -> Element? {
133-
var cursor = Cursor<Element>(of: self)
134-
return try cursor.next()
135-
}
136128
}
137129

138130
// MARK: - Fetch with RowDecodableWithAdapters
139131

140132
extension Statement {
141133
/// Fetches all rows returned by the statement
142134
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,
135+
of _: Element.Type = Element.self,
151136
adapters: Element.Adapters
152137
) throws(OtterError) -> [Element] {
153138
var cursor = Cursor<Element>(of: self)
@@ -159,50 +144,36 @@ extension Statement {
159144

160145
return result
161146
}
162-
163-
/// Optionally fetches a single row returned by the statement
147+
148+
/// Fetches a single row returned by the statement
164149
public consuming func fetchOne<Element: RowDecodableWithAdapters>(
150+
of _: Element.Type = Element.self,
165151
adapters: Element.Adapters
166152
) throws(OtterError) -> Element? {
167-
return try fetchOne(of: Element.self, adapters: adapters)
153+
var cursor = Cursor<Element>(of: self)
154+
return try cursor.next(adapters: adapters)
168155
}
169156

170157
/// Fetches a single row returned by the statement
171158
@_disfavoredOverload
172159
public consuming func fetchOne<Element: RowDecodableWithAdapters>(
160+
of value: Element.Type = Element.self,
173161
adapters: Element.Adapters
174162
) throws(OtterError) -> Element {
175-
guard let row = try fetchOne(of: Element.self, adapters: adapters) else {
163+
guard let row = try fetchOne(of: value, adapters: adapters) else {
176164
throw OtterError.queryReturnedNoValue
177165
}
178166

179167
return row
180168
}
181-
182-
/// Fetches a single row returned by the statement
183-
public consuming func fetchOne<Element: RowDecodableWithAdapters>(
184-
of _: Element.Type,
185-
adapters: Element.Adapters
186-
) throws(OtterError) -> Element? {
187-
var cursor = Cursor<Element>(of: self)
188-
return try cursor.next(adapters: adapters)
189-
}
190169
}
191170

192171
// MARK: - Fetch with specific adapter
193172

194173
extension Statement {
195174
/// Fetches all rows returned by the statement
196175
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,
176+
of _: Adapter.Value.Type = Adapter.Value.self,
206177
adapter: Adapter,
207178
storage: Storage.Type
208179
) throws(OtterError) -> [Adapter.Value] {
@@ -215,26 +186,28 @@ extension Statement {
215186

216187
return result
217188
}
218-
189+
219190
/// Fetches a single row returned by the statement
220-
@_disfavoredOverload
221191
public consuming func fetchOne<Adapter: DatabaseValueAdapter, Storage: DatabasePrimitive>(
192+
of _: Adapter.Value.Type = Adapter.Value.self,
222193
adapter: Adapter,
223194
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
195+
) throws(OtterError) -> Adapter.Value? {
196+
var cursor = Cursor<Adapter.Value>(of: self)
197+
return try cursor.next(adapter: adapter, storage: storage)
230198
}
231199

232200
/// Fetches a single row returned by the statement
201+
@_disfavoredOverload
233202
public consuming func fetchOne<Adapter: DatabaseValueAdapter, Storage: DatabasePrimitive>(
203+
of value: Adapter.Value.Type = Adapter.Value.self,
234204
adapter: Adapter,
235205
storage: Storage.Type
236-
) throws(OtterError) -> Adapter.Value? {
237-
var cursor = Cursor<Adapter.Value>(of: self)
238-
return try cursor.next(adapter: adapter, storage: storage)
206+
) throws(OtterError) -> Adapter.Value {
207+
guard let row = try fetchOne(of: value, adapter: adapter, storage: storage) else {
208+
throw OtterError.queryReturnedNoValue
209+
}
210+
211+
return row
239212
}
240213
}

0 commit comments

Comments
 (0)