Skip to content

Commit d25e5bc

Browse files
authored
Merge pull request #1072 from stephencelis/cleanup-aggregation
Clean up aggregation
2 parents 2ae7b9b + e5a10f5 commit d25e5bc

File tree

6 files changed

+234
-235
lines changed

6 files changed

+234
-235
lines changed

.swiftlint.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,6 @@ line_length:
3838
ignores_comments: true
3939

4040
file_length:
41-
warning: 900
42-
error: 900
41+
warning: 500
42+
error: 500
43+
ignore_comment_only_lines: true

SQLite.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; };
5151
19A17073552293CA063BEA66 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; };
5252
19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; };
53+
19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; };
5354
19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; };
5455
19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; };
5556
19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; };
@@ -67,6 +68,7 @@
6768
19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; };
6869
19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; };
6970
19A175DFF47B84757E547C62 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; };
71+
19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; };
7072
19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; };
7173
19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; };
7274
19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; };
@@ -75,6 +77,7 @@
7577
19A1785195182AF8731A8BDA /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; };
7678
19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; };
7779
19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; };
80+
19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; };
7881
19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; };
7982
19A179E76EA6207669B60C1B /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; };
8083
19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; };
@@ -237,6 +240,7 @@
237240
19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctionTests.swift; sourceTree = "<group>"; };
238241
19A1730E4390C775C25677D1 /* FTS5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5.swift; sourceTree = "<group>"; };
239242
19A17399EA9E61235D5D77BF /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = "<group>"; };
243+
19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+Aggregation.swift"; sourceTree = "<group>"; };
240244
19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowTests.swift; sourceTree = "<group>"; };
241245
19A178A39ACA9667A62663CC /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = "<group>"; };
242246
19A1794B7972D14330A65BBD /* Linux.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Linux.md; sourceTree = "<group>"; };
@@ -462,6 +466,7 @@
462466
19A1710E73A46D5AC721CDA9 /* Errors.swift */,
463467
02A43A9722738CF100FEC494 /* Backup.swift */,
464468
19A17E723300E5ED3771DCB5 /* Result.swift */,
469+
19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */,
465470
);
466471
path = Core;
467472
sourceTree = "<group>";
@@ -850,6 +855,7 @@
850855
19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */,
851856
19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */,
852857
19A17073552293CA063BEA66 /* Result.swift in Sources */,
858+
19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */,
853859
);
854860
runOnlyForDeploymentPostprocessing = 0;
855861
};
@@ -945,6 +951,7 @@
945951
19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */,
946952
19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */,
947953
19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */,
954+
19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */,
948955
);
949956
runOnlyForDeploymentPostprocessing = 0;
950957
};
@@ -1008,6 +1015,7 @@
10081015
19A17490543609FCED53CACC /* Errors.swift in Sources */,
10091016
19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */,
10101017
19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */,
1018+
19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */,
10111019
);
10121020
runOnlyForDeploymentPostprocessing = 0;
10131021
};
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import Foundation
2+
#if SQLITE_SWIFT_STANDALONE
3+
import sqlite3
4+
#elseif SQLITE_SWIFT_SQLCIPHER
5+
import SQLCipher
6+
#elseif os(Linux)
7+
import CSQLite
8+
#else
9+
import SQLite3
10+
#endif
11+
12+
extension Connection {
13+
private typealias Aggregate = @convention(block) (Int, Context, Int32, Argv) -> Void
14+
15+
/// Creates or redefines a custom SQL aggregate.
16+
///
17+
/// - Parameters:
18+
///
19+
/// - aggregate: The name of the aggregate to create or redefine.
20+
///
21+
/// - argumentCount: The number of arguments that the aggregate takes. If
22+
/// `nil`, the aggregate may take any number of arguments.
23+
///
24+
/// Default: `nil`
25+
///
26+
/// - deterministic: Whether or not the aggregate is deterministic (_i.e._
27+
/// the aggregate always returns the same result for a given input).
28+
///
29+
/// Default: `false`
30+
///
31+
/// - step: A block of code to run for each row of an aggregation group.
32+
/// The block is called with an array of raw SQL values mapped to the
33+
/// aggregate’s parameters, and an UnsafeMutablePointer to a state
34+
/// variable.
35+
///
36+
/// - final: A block of code to run after each row of an aggregation group
37+
/// is processed. The block is called with an UnsafeMutablePointer to a
38+
/// state variable, and should return a raw SQL value (or nil).
39+
///
40+
/// - state: A block of code to run to produce a fresh state variable for
41+
/// each aggregation group. The block should return an
42+
/// UnsafeMutablePointer to the fresh state variable.
43+
public func createAggregation<T>(
44+
_ functionName: String,
45+
argumentCount: UInt? = nil,
46+
deterministic: Bool = false,
47+
step: @escaping ([Binding?], UnsafeMutablePointer<T>) -> Void,
48+
final: @escaping (UnsafeMutablePointer<T>) -> Binding?,
49+
state: @escaping () -> UnsafeMutablePointer<T>) {
50+
51+
let argc = argumentCount.map { Int($0) } ?? -1
52+
let box: Aggregate = { (stepFlag: Int, context: Context, argc: Int32, argv: Argv) in
53+
let nBytes = Int32(MemoryLayout<UnsafeMutablePointer<Int64>>.size)
54+
guard let aggregateContext = sqlite3_aggregate_context(context, nBytes) else {
55+
fatalError("Could not get aggregate context")
56+
}
57+
let mutablePointer = aggregateContext.assumingMemoryBound(to: UnsafeMutableRawPointer.self)
58+
if stepFlag > 0 {
59+
let arguments = argv.getBindings(argc: argc)
60+
if aggregateContext.assumingMemoryBound(to: Int64.self).pointee == 0 {
61+
mutablePointer.pointee = UnsafeMutableRawPointer(mutating: state())
62+
}
63+
step(arguments, mutablePointer.pointee.assumingMemoryBound(to: T.self))
64+
} else {
65+
let result = final(mutablePointer.pointee.assumingMemoryBound(to: T.self))
66+
context.set(result: result)
67+
}
68+
}
69+
70+
func xStep(context: Context, argc: Int32, value: Argv) {
71+
unsafeBitCast(sqlite3_user_data(context), to: Aggregate.self)(1, context, argc, value)
72+
}
73+
74+
func xFinal(context: Context) {
75+
unsafeBitCast(sqlite3_user_data(context), to: Aggregate.self)(0, context, 0, nil)
76+
}
77+
78+
let flags = SQLITE_UTF8 | (deterministic ? SQLITE_DETERMINISTIC : 0)
79+
let resultCode = sqlite3_create_function_v2(
80+
handle,
81+
functionName,
82+
Int32(argc),
83+
flags,
84+
/* pApp */ unsafeBitCast(box, to: UnsafeMutableRawPointer.self),
85+
/* xFunc */ nil, xStep, xFinal, /* xDestroy */ nil
86+
)
87+
if let result = Result(errorCode: resultCode, connection: self) {
88+
fatalError("Error creating function: \(result)")
89+
}
90+
register(functionName, argc: argc, value: box)
91+
}
92+
93+
func createAggregation<T: AnyObject>(
94+
_ aggregate: String,
95+
argumentCount: UInt? = nil,
96+
deterministic: Bool = false,
97+
initialValue: T,
98+
reduce: @escaping (T, [Binding?]) -> T,
99+
result: @escaping (T) -> Binding?
100+
) {
101+
let step: ([Binding?], UnsafeMutablePointer<UnsafeMutableRawPointer>) -> Void = { (bindings, ptr) in
102+
let pointer = ptr.pointee.assumingMemoryBound(to: T.self)
103+
let current = Unmanaged<T>.fromOpaque(pointer).takeRetainedValue()
104+
let next = reduce(current, bindings)
105+
ptr.pointee = Unmanaged.passRetained(next).toOpaque()
106+
}
107+
108+
let final: (UnsafeMutablePointer<UnsafeMutableRawPointer>) -> Binding? = { ptr in
109+
let pointer = ptr.pointee.assumingMemoryBound(to: T.self)
110+
let obj = Unmanaged<T>.fromOpaque(pointer).takeRetainedValue()
111+
let value = result(obj)
112+
ptr.deallocate()
113+
return value
114+
}
115+
116+
let state: () -> UnsafeMutablePointer<UnsafeMutableRawPointer> = {
117+
let pointer = UnsafeMutablePointer<UnsafeMutableRawPointer>.allocate(capacity: 1)
118+
pointer.pointee = Unmanaged.passRetained(initialValue).toOpaque()
119+
return pointer
120+
}
121+
122+
createAggregation(aggregate, step: step, final: final, state: state)
123+
}
124+
125+
func createAggregation<T>(
126+
_ aggregate: String,
127+
argumentCount: UInt? = nil,
128+
deterministic: Bool = false,
129+
initialValue: T,
130+
reduce: @escaping (T, [Binding?]) -> T,
131+
result: @escaping (T) -> Binding?
132+
) {
133+
134+
let step: ([Binding?], UnsafeMutablePointer<T>) -> Void = { (bindings, pointer) in
135+
let current = pointer.pointee
136+
let next = reduce(current, bindings)
137+
pointer.pointee = next
138+
}
139+
140+
let final: (UnsafeMutablePointer<T>) -> Binding? = { pointer in
141+
let value = result(pointer.pointee)
142+
pointer.deallocate()
143+
return value
144+
}
145+
146+
let state: () -> UnsafeMutablePointer<T> = {
147+
let pointer = UnsafeMutablePointer<T>.allocate(capacity: 1)
148+
pointer.initialize(to: initialValue)
149+
return pointer
150+
}
151+
152+
createAggregation(aggregate, step: step, final: final, state: state)
153+
}
154+
155+
}

0 commit comments

Comments
 (0)