diff --git a/Plugins/DynamoDBDriverPlugin/DynamoDBPluginDriver.swift b/Plugins/DynamoDBDriverPlugin/DynamoDBPluginDriver.swift index da7c3302f..4a1f87cb2 100644 --- a/Plugins/DynamoDBDriverPlugin/DynamoDBPluginDriver.swift +++ b/Plugins/DynamoDBDriverPlugin/DynamoDBPluginDriver.swift @@ -19,9 +19,6 @@ internal final class DynamoDBPluginDriver: PluginDatabaseDriver, @unchecked Send // Table description cache to avoid repeated DescribeTable calls private var _tableDescriptionCache: [String: TableDescription] = [:] - // Pagination state per query - private var _paginationStates: [String: PaginationState] = [:] - private var connection: DynamoDBConnection? { lock.withLock { _connection } } @@ -82,7 +79,6 @@ internal final class DynamoDBPluginDriver: PluginDatabaseDriver, @unchecked Send _connection?.disconnect() _connection = nil _tableDescriptionCache.removeAll() - _paginationStates.removeAll() } } @@ -490,21 +486,7 @@ internal final class DynamoDBPluginDriver: PluginDatabaseDriver, @unchecked Send extractKeySchema(from: _tableDescriptionCache[table]) } - let typeNames: [String] = lock.withLock { - let matchingState = _paginationStates.values.first { state in - if let parsed = DynamoDBQueryBuilder.parseScanQuery(state.queryKey) { - return parsed.tableName == table - } - if let parsed = DynamoDBQueryBuilder.parseQueryQuery(state.queryKey) { - return parsed.tableName == table - } - return false - } - if let state = matchingState { - return state.columnTypeNames - } - return columns.map { _ in "S" } - } + let typeNames: [String] = columns.map { _ in "S" } let generator = DynamoDBStatementGenerator( tableName: table, @@ -817,60 +799,6 @@ internal final class DynamoDBPluginDriver: PluginDatabaseDriver, @unchecked Send throw DynamoDBError.serverError("Invalid tagged query format") } - private func executePaginatedTaggedQuery( - _ query: String, offset: Int, limit: Int, - conn: DynamoDBConnection, startTime: Date - ) async throws -> PluginQueryResult { - let queryKey = query - - // If offset is 0, start a fresh scan/query - if offset == 0 { - lock.withLock { _paginationStates.removeValue(forKey: queryKey) } - } - - var state = lock.withLock { _paginationStates[queryKey] } - - // If we have cached items that cover the requested range, serve from cache - if let existingState = state { - let end = min(offset + limit, existingState.allItems.count) - if offset < existingState.allItems.count { - let pageItems = Array(existingState.allItems[offset.. PluginQueryResult { - let keySchema = try await cachedKeySchema(parsed.tableName, conn: conn) - var state = existingState ?? PaginationState( - queryKey: queryKey, - allItems: [], - lastEvaluatedKey: nil, - isExhausted: false, - discoveredColumns: [], - columnTypeNames: [], - keySchema: keySchema - ) - - // Fetch until we have enough items or exhaust the table - let needed = offset + limit - while state.allItems.count < needed && !state.isExhausted { - let batchSize = min(needed - state.allItems.count, 1000) - let response = try await conn.scan( - tableName: parsed.tableName, - limit: batchSize, - exclusiveStartKey: state.lastEvaluatedKey - ) - - var newItems = response.Items ?? [] - - if !parsed.filters.isEmpty { - newItems = applyClientFilters( - items: newItems, filters: parsed.filters, logicMode: parsed.logicMode - ) - } - - state.allItems.append(contentsOf: newItems) - state.lastEvaluatedKey = response.LastEvaluatedKey - - if response.LastEvaluatedKey == nil { - state.isExhausted = true - } - - if state.allItems.count >= Self.maxItems { - state.isExhausted = true - state.allItems = Array(state.allItems.prefix(Self.maxItems)) - } - } - - // Update discovered columns from all items - state.discoveredColumns = DynamoDBItemFlattener.unionColumns(from: state.allItems, keySchema: keySchema) - state.columnTypeNames = DynamoDBItemFlattener.columnTypeNames( - for: state.discoveredColumns, items: state.allItems - ) - - lock.withLock { _paginationStates[queryKey] = state } - - // Serve the requested page - let end = min(offset + limit, state.allItems.count) - guard offset < state.allItems.count else { - return emptyResult(columns: state.discoveredColumns, typeNames: state.columnTypeNames, startTime: startTime) - } - let pageItems = Array(state.allItems[offset.. PluginQueryResult { - let keySchema = try await cachedKeySchema(parsed.tableName, conn: conn) - var state = existingState ?? PaginationState( - queryKey: queryKey, - allItems: [], - lastEvaluatedKey: nil, - isExhausted: false, - discoveredColumns: [], - columnTypeNames: [], - keySchema: keySchema - ) - - var expressionValues: [String: DynamoDBAttributeValue] = [:] - switch parsed.partitionKeyType { - case "N": - expressionValues[":pkval"] = .number(parsed.partitionKeyValue) - default: - expressionValues[":pkval"] = .string(parsed.partitionKeyValue) - } - let keyCondition = "\(parsed.partitionKeyName) = :pkval" - - let needed = offset + limit - while state.allItems.count < needed && !state.isExhausted { - let batchSize = min(needed - state.allItems.count, 1000) - let response = try await conn.query( - tableName: parsed.tableName, - keyConditionExpression: keyCondition, - expressionAttributeValues: expressionValues, - limit: batchSize, - exclusiveStartKey: state.lastEvaluatedKey - ) - - let fetchedItems = response.Items ?? [] - state.allItems.append(contentsOf: fetchedItems) - state.lastEvaluatedKey = response.LastEvaluatedKey - - if response.LastEvaluatedKey == nil { - state.isExhausted = true - } - - if state.allItems.count >= Self.maxItems { - state.isExhausted = true - state.allItems = Array(state.allItems.prefix(Self.maxItems)) - } - } - - state.discoveredColumns = DynamoDBItemFlattener.unionColumns(from: state.allItems, keySchema: keySchema) - state.columnTypeNames = DynamoDBItemFlattener.columnTypeNames( - for: state.discoveredColumns, items: state.allItems - ) - - lock.withLock { _paginationStates[queryKey] = state } - - let end = min(offset + limit, state.allItems.count) - guard offset < state.allItems.count else { - return emptyResult(columns: state.discoveredColumns, typeNames: state.columnTypeNames, startTime: startTime) - } - let pageItems = Array(state.allItems[offset.. PluginQueryResult { - PluginQueryResult( - columns: columns.isEmpty ? ["Result"] : columns, - columnTypeNames: typeNames.isEmpty ? ["String"] : typeNames, - rows: [], - rowsAffected: 0, - executionTime: Date().timeIntervalSince(startTime) - ) - } - private func formatBytes(_ bytes: Int64) -> String { let formatter = ByteCountFormatter() formatter.countStyle = .binary return formatter.string(fromByteCount: bytes) } } - -// MARK: - Pagination State - -private struct PaginationState { - let queryKey: String - var allItems: [[String: DynamoDBAttributeValue]] - var lastEvaluatedKey: [String: DynamoDBAttributeValue]? - var isExhausted: Bool - var discoveredColumns: [String] - var columnTypeNames: [String] - var keySchema: [(name: String, keyType: String)] -} diff --git a/TablePro/Resources/Localizable.xcstrings b/TablePro/Resources/Localizable.xcstrings index fd265165c..45af40dee 100644 --- a/TablePro/Resources/Localizable.xcstrings +++ b/TablePro/Resources/Localizable.xcstrings @@ -987,35 +987,6 @@ } } }, - "%@ of %@%@ rows" : { - "extractionState" : "stale", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$@ of %2$@%3$@ rows" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@/%@%@ satır" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ trên %@%@ dòng" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@/%@%@ 行" - } - } - } - }, "%@ on %@ completed successfully." : { "localizations" : { "en" : { @@ -1113,29 +1084,6 @@ } } }, - "%@ rows (more available)" : { - "extractionState" : "stale", - "localizations" : { - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ satır (daha fazla mevcut)" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ dòng (còn thêm)" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ 行(还有更多)" - } - } - } - }, "%@ s" : { "localizations" : { "tr" : { @@ -2140,64 +2088,6 @@ } } }, - "%lld-%lld of %@ rows" : { - "extractionState" : "stale", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$lld-%2$lld of %3$@ rows" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld-%lld / %@ satır" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "%1$lld-%2$lld trong %3$@ dòng" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld-%lld / %@ 行" - } - } - } - }, - "%lld-%lld of %@%@ rows" : { - "extractionState" : "stale", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$lld-%2$lld of %3$@%4$@ rows" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld-%lld / %@%@ satır" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "%1$lld-%2$lld của %3$@%4$@ dòng" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld-%lld / %@%@ 行" - } - } - } - }, "%lld." : { "localizations" : { "tr" : { @@ -15586,29 +15476,6 @@ } } }, - "Enforce query result limit" : { - "extractionState" : "stale", - "localizations" : { - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sorgu sonuç limitini uygula" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Áp dụng giới hạn kết quả truy vấn" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "强制查询结果限制" - } - } - } - }, "Engine" : { "localizations" : { "tr" : { @@ -23671,29 +23538,6 @@ } } }, - "Limit initial query results and load more on demand" : { - "extractionState" : "stale", - "localizations" : { - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "İlk sorgu sonuçlarını sınırla ve istendiğinde daha fazla yükle" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Giới hạn kết quả truy vấn ban đầu và tải thêm khi cần" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "限制初始查询结果并按需加载更多" - } - } - } - }, "Line %lld: %@" : { "localizations" : { "en" : { @@ -23879,29 +23723,6 @@ } } }, - "Load More" : { - "extractionState" : "stale", - "localizations" : { - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Daha Fazla Yükle" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tải thêm" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "加载更多" - } - } - } - }, "Load Table Template" : { "extractionState" : "stale", "localizations" : { @@ -32093,29 +31914,6 @@ } } }, - "Query results exceeding this limit show Load More / Fetch All buttons" : { - "extractionState" : "stale", - "localizations" : { - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bu limiti aşan sorgu sonuçları Daha Fazla Yükle / Tümünü Getir düğmelerini gösterir" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kết quả truy vấn vượt quá giới hạn sẽ hiển thị nút Tải thêm / Tải tất cả" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "超过此限制的查询结果将显示“加载更多”/“获取全部”按钮" - } - } - } - }, "Query timeout" : { "localizations" : { "tr" : {