Skip to content

Commit 89dcef7

Browse files
committed
SQLAny
1 parent 04395b1 commit 89dcef7

4 files changed

Lines changed: 109 additions & 7 deletions

File tree

Sources/Compiler/Gen/SwiftLanguage.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public struct SwiftLanguage: Language {
2222
case "INTEGER": "Int"
2323
case "TEXT": "String"
2424
case "BLOB": "Data"
25-
default: "Any"
25+
default: "SQLAny"
2626
}
2727
case let .optional(ty): "\(builtinType(for: ty))?"
2828
case let .row(.unknown(ty)): "[\(builtinType(for: ty))]"

Sources/Compiler/Sema/InferenceState.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,7 @@ extension InferenceState {
191191
case let (ty, .var(tv)):
192192
validateCanUnify(type: ty, with: tv.kind, at: location)
193193
substitute(tv, for: ty)
194-
case (.integer, .real):
195-
return // Not equal but valid to use together
196-
case (.real, .integer):
194+
case (.integer, .real), (.real, .integer), (.any, _), (_, .any):
197195
return // Not equal but valid to use together
198196
case let (.fn(args1, ret1), .fn(args2, ret2)):
199197
unify(args1, with: args2, at: location)

Sources/Feather/SQLAny.swift

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//
2+
// SQLAny.swift
3+
// Feather
4+
//
5+
// Created by Wes Wickwire on 5/10/25.
6+
//
7+
8+
import Foundation
9+
import SQLite3
10+
11+
/// SQLite supports an `ANY` type. Mapping to a `Swift.Any` would
12+
/// not be a smart idea and would not implement things like
13+
/// `Hashable` and `Sendable`. This wraps any possible value that
14+
/// SQLite can throw at us.
15+
///
16+
/// SQLite type can be `NULL` but that is not acknowledged here
17+
/// and nullability is handled at the constraint level like
18+
/// every other type.
19+
public enum SQLAny: Sendable, Hashable {
20+
/// Maps to `TEXT`
21+
case string(String)
22+
/// Maps to `INTEGER` and `INT`
23+
case int(Int)
24+
/// Maps to `REAL`
25+
case double(Double)
26+
/// Maps to `BLOB`
27+
case data(Data)
28+
29+
/// The value if it is a `.string`
30+
public var string: String? {
31+
guard case let .string(value) = self else { return nil }
32+
return value
33+
}
34+
35+
/// The value if it is a `.int`
36+
public var int: Int? {
37+
guard case let .int(value) = self else { return nil }
38+
return value
39+
}
40+
41+
/// The value if it is a `.double`
42+
public var double: Double? {
43+
guard case let .double(value) = self else { return nil }
44+
return value
45+
}
46+
47+
/// The value if it is a `.data`
48+
public var data: Data? {
49+
guard case let .data(value) = self else { return nil }
50+
return value
51+
}
52+
}
53+
54+
extension SQLAny: CustomStringConvertible {
55+
public var description: String {
56+
switch self {
57+
case .string(let string): string
58+
case .int(let int): int.description
59+
case .double(let double): double.description
60+
case .data(let data): data.description
61+
}
62+
}
63+
}
64+
65+
extension SQLAny: ExpressibleByStringLiteral {
66+
public init(stringLiteral value: StringLiteralType) {
67+
self = .string(value)
68+
}
69+
}
70+
71+
extension SQLAny: ExpressibleByIntegerLiteral {
72+
public init(integerLiteral value: IntegerLiteralType) {
73+
self = .int(value)
74+
}
75+
}
76+
77+
extension SQLAny: ExpressibleByFloatLiteral {
78+
public init(floatLiteral value: FloatLiteralType) {
79+
self = .double(value)
80+
}
81+
}
82+
83+
extension SQLAny: DatabasePrimitive {
84+
@inlinable public init(from cursor: OpaquePointer, at index: Int32) throws(FeatherError) {
85+
let type = sqlite3_column_type(cursor, index)
86+
switch type {
87+
case SQLITE_TEXT: self = try .string(String(from: cursor, at: index))
88+
case SQLITE_INTEGER: self = try .int(Int(from: cursor, at: index))
89+
case SQLITE_FLOAT: self = try .double(Double(from: cursor, at: index))
90+
case SQLITE_BLOB: self = try .data(Data(from: cursor, at: index))
91+
case SQLITE_NULL: throw .decodingError("Expected non nil value for `ANY` column type")
92+
default: fatalError("Unknown column type code: \(type)")
93+
}
94+
}
95+
96+
@inlinable public func bind(to statement: OpaquePointer, at index: Int32) throws(FeatherError) {
97+
switch self {
98+
case .string(let string): try string.bind(to: statement, at: index)
99+
case .int(let int): try int.bind(to: statement, at: index)
100+
case .double(let double): try double.bind(to: statement, at: index)
101+
case .data(let data): try data.bind(to: statement, at: index)
102+
}
103+
}
104+
}

Tests/FeatherTests/DatabaseMacroTests.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ struct DatabaseMacroTests {
1313
@Test func insertAndSelect() async throws {
1414
let database = try TestDB.inMemory()
1515

16-
try await database.insertFooQuery.execute(with: .init(bar: 1, baz: "Meow"))
16+
try await database.insertFooQuery.execute(with: .init(bar: 1, baz: "Meow", qux: 1))
1717

1818
let foos = try await database.selectFooQuery.execute()
1919

@@ -27,12 +27,12 @@ struct DatabaseMacroTests {
2727
@Query("SELECT * FROM foo")
2828
var selectFooQuery: SelectFooDatabaseQuery
2929

30-
@Query("INSERT INTO foo (bar, baz) VALUES (?, ?)", inputName: "Meow")
30+
@Query("INSERT INTO foo (bar, baz, qux) VALUES (?, ?, ?)", inputName: "Meow")
3131
var insertFooQuery: InsertFooDatabaseQuery
3232

3333
static var migrations: [String] {
3434
return [
35-
"CREATE TABLE foo (bar INTEGER, baz TEXT);"
35+
"CREATE TABLE foo (bar INTEGER, baz TEXT, qux ANY);"
3636
]
3737
}
3838
}

0 commit comments

Comments
 (0)