Skip to content

Commit 62d3cdc

Browse files
committed
fix: address third round of PR review feedback
1 parent bce0bd5 commit 62d3cdc

6 files changed

Lines changed: 248 additions & 156 deletions

File tree

Plugins/DynamoDBDriverPlugin/DynamoDBConnection.swift

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,16 @@ extension DynamoDBAttributeValue: Codable {
5151
} else if let values = try container.decodeIfPresent([String].self, forKey: .ns) {
5252
self = .numberSet(values)
5353
} else if let values = try container.decodeIfPresent([String].self, forKey: .bs) {
54-
self = .binarySet(values.compactMap { Data(base64Encoded: $0) })
54+
let decoded = try values.map { str -> Data in
55+
guard let data = Data(base64Encoded: str) else {
56+
throw DecodingError.dataCorruptedError(
57+
forKey: .bs, in: container,
58+
debugDescription: "Invalid base64 string in binary set"
59+
)
60+
}
61+
return data
62+
}
63+
self = .binarySet(decoded)
5564
} else {
5665
throw DecodingError.dataCorrupted(
5766
DecodingError.Context(
@@ -105,15 +114,15 @@ private enum DynamoDBTypeCodingKey: String, CodingKey {
105114

106115
// MARK: - AWS Credentials
107116

108-
struct AWSCredentials: Sendable {
117+
internal struct AWSCredentials: Sendable {
109118
let accessKeyId: String
110119
let secretAccessKey: String
111120
let sessionToken: String?
112121
}
113122

114123
// MARK: - DynamoDB Error
115124

116-
enum DynamoDBError: Error, LocalizedError {
125+
internal enum DynamoDBError: Error, LocalizedError {
117126
case notConnected
118127
case connectionFailed(String)
119128
case serverError(String)
@@ -141,16 +150,16 @@ enum DynamoDBError: Error, LocalizedError {
141150

142151
// MARK: - Response Types
143152

144-
struct ListTablesResponse: Decodable {
153+
internal struct ListTablesResponse: Decodable {
145154
let TableNames: [String]?
146155
let LastEvaluatedTableName: String?
147156
}
148157

149-
struct DescribeTableResponse: Decodable {
158+
internal struct DescribeTableResponse: Decodable {
150159
let Table: TableDescription
151160
}
152161

153-
struct TableDescription: Decodable {
162+
internal struct TableDescription: Decodable {
154163
let TableName: String
155164
let KeySchema: [KeySchemaElement]?
156165
let AttributeDefinitions: [AttributeDefinition]?
@@ -165,17 +174,17 @@ struct TableDescription: Decodable {
165174
let CreationDateTime: Double?
166175
}
167176

168-
struct KeySchemaElement: Decodable {
177+
internal struct KeySchemaElement: Decodable {
169178
let AttributeName: String
170179
let KeyType: String
171180
}
172181

173-
struct AttributeDefinition: Decodable {
182+
internal struct AttributeDefinition: Decodable {
174183
let AttributeName: String
175184
let AttributeType: String
176185
}
177186

178-
struct GlobalSecondaryIndexDescription: Decodable {
187+
internal struct GlobalSecondaryIndexDescription: Decodable {
179188
let IndexName: String
180189
let KeySchema: [KeySchemaElement]?
181190
let Projection: Projection?
@@ -185,43 +194,43 @@ struct GlobalSecondaryIndexDescription: Decodable {
185194
let IndexSizeBytes: Int64?
186195
}
187196

188-
struct LocalSecondaryIndexDescription: Decodable {
197+
internal struct LocalSecondaryIndexDescription: Decodable {
189198
let IndexName: String
190199
let KeySchema: [KeySchemaElement]?
191200
let Projection: Projection?
192201
let ItemCount: Int64?
193202
let IndexSizeBytes: Int64?
194203
}
195204

196-
struct ProvisionedThroughputDescription: Decodable {
205+
internal struct ProvisionedThroughputDescription: Decodable {
197206
let ReadCapacityUnits: Int64?
198207
let WriteCapacityUnits: Int64?
199208
}
200209

201-
struct BillingModeSummary: Decodable {
210+
internal struct BillingModeSummary: Decodable {
202211
let BillingMode: String?
203212
}
204213

205-
struct Projection: Decodable {
214+
internal struct Projection: Decodable {
206215
let ProjectionType: String?
207216
let NonKeyAttributes: [String]?
208217
}
209218

210-
struct ScanResponse: Decodable {
219+
internal struct ScanResponse: Decodable {
211220
let Items: [[String: DynamoDBAttributeValue]]?
212221
let Count: Int?
213222
let ScannedCount: Int?
214223
let LastEvaluatedKey: [String: DynamoDBAttributeValue]?
215224
}
216225

217-
struct QueryResponse: Decodable {
226+
internal struct QueryResponse: Decodable {
218227
let Items: [[String: DynamoDBAttributeValue]]?
219228
let Count: Int?
220229
let ScannedCount: Int?
221230
let LastEvaluatedKey: [String: DynamoDBAttributeValue]?
222231
}
223232

224-
struct ExecuteStatementResponse: Decodable {
233+
internal struct ExecuteStatementResponse: Decodable {
225234
let Items: [[String: DynamoDBAttributeValue]]?
226235
let NextToken: String?
227236
let LastEvaluatedKey: [String: DynamoDBAttributeValue]?
@@ -246,7 +255,7 @@ private struct DynamoDBErrorResponse: Decodable {
246255

247256
// MARK: - DynamoDB Connection
248257

249-
final class DynamoDBConnection: @unchecked Sendable {
258+
internal final class DynamoDBConnection: @unchecked Sendable {
250259
private let config: DriverConnectionConfig
251260
private let lock = NSLock()
252261
private var _session: URLSession?
@@ -385,10 +394,16 @@ final class DynamoDBConnection: @unchecked Sendable {
385394
return try await request(target: "DynamoDB_20120810.Query", body: body)
386395
}
387396

388-
func executeStatement(statement: String, limit: Int? = nil, nextToken: String? = nil) async throws
389-
-> ExecuteStatementResponse
390-
{
397+
func executeStatement(
398+
statement: String,
399+
parameters: [[String: Any]]? = nil,
400+
limit: Int? = nil,
401+
nextToken: String? = nil
402+
) async throws -> ExecuteStatementResponse {
391403
var body: [String: Any] = ["Statement": statement]
404+
if let parameters = parameters, !parameters.isEmpty {
405+
body["Parameters"] = parameters
406+
}
392407
if let limit = limit {
393408
body["Limit"] = limit
394409
}
@@ -465,8 +480,7 @@ final class DynamoDBConnection: @unchecked Sendable {
465480
}
466481
throw DynamoDBError.serverError("[\(errorType)] \(errorResponse.errorMessage)")
467482
}
468-
let bodyStr = String(data: data, encoding: .utf8) ?? "No response body"
469-
throw DynamoDBError.serverError("HTTP \(httpResponse.statusCode): \(bodyStr)")
483+
throw DynamoDBError.serverError("HTTP \(httpResponse.statusCode): Response body redacted (length: \(data.count))")
470484
}
471485

472486
do {

Plugins/DynamoDBDriverPlugin/DynamoDBPartiQLParser.swift

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77

88
import Foundation
99

10-
enum DynamoDBQueryType {
10+
internal enum DynamoDBQueryType {
1111
case select
1212
case insert
1313
case update
1414
case delete
1515
case unknown
1616
}
1717

18-
struct DynamoDBPartiQLParser {
18+
internal struct DynamoDBPartiQLParser {
1919
/// Classify a PartiQL statement by its first keyword.
2020
static func queryType(_ statement: String) -> DynamoDBQueryType {
2121
let trimmed = statement.trimmingCharacters(in: .whitespacesAndNewlines)
@@ -84,35 +84,56 @@ struct DynamoDBPartiQLParser {
8484
// MARK: - Private
8585

8686
/// Simple tokenizer that respects quoted identifiers and string literals.
87+
/// Handles PartiQL doubled single-quote escaping (e.g., `'O''Brien'`).
8788
private static func tokenize(_ sql: String) -> [String] {
8889
var tokens: [String] = []
8990
var current = ""
9091
var inDoubleQuote = false
9192
var inSingleQuote = false
9293
var isEscaped = false
9394

94-
for char in sql {
95+
let chars = Array(sql)
96+
var i = 0
97+
98+
while i < chars.count {
99+
let char = chars[i]
100+
95101
if isEscaped {
96102
current.append(char)
97103
isEscaped = false
104+
i += 1
98105
continue
99106
}
100107

101108
if char == "\\" {
102109
current.append(char)
103110
isEscaped = true
111+
i += 1
104112
continue
105113
}
106114

107115
if char == "\"" && !inSingleQuote {
108116
inDoubleQuote.toggle()
109117
current.append(char)
118+
i += 1
110119
continue
111120
}
112121

113122
if char == "'" && !inDoubleQuote {
114-
inSingleQuote.toggle()
123+
if inSingleQuote {
124+
// Check for doubled single-quote escape ('')
125+
if i + 1 < chars.count && chars[i + 1] == "'" {
126+
current.append(char)
127+
current.append(chars[i + 1])
128+
i += 2
129+
continue
130+
}
131+
inSingleQuote = false
132+
} else {
133+
inSingleQuote = true
134+
}
115135
current.append(char)
136+
i += 1
116137
continue
117138
}
118139

@@ -121,10 +142,12 @@ struct DynamoDBPartiQLParser {
121142
tokens.append(current)
122143
current = ""
123144
}
145+
i += 1
124146
continue
125147
}
126148

127149
current.append(char)
150+
i += 1
128151
}
129152

130153
if !current.isEmpty {

0 commit comments

Comments
 (0)