@@ -23,7 +23,7 @@ final class EtcdPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
2323
2424 private static let logger = Logger ( subsystem: " com.TablePro.EtcdDriver " , category: " EtcdPluginDriver " )
2525 private static let maxKeys = PluginRowLimits . defaultMax
26- private static let maxOffset = 10_000
26+
2727
2828 private static let columns = [ " Key " , " Value " , " Version " , " ModRevision " , " CreateRevision " , " Lease " ]
2929 private static let columnTypeNames = [ " String " , " String " , " Int64 " , " Int64 " , " Int64 " , " String " ]
@@ -34,17 +34,36 @@ final class EtcdPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
3434
3535 var supportsTransactions : Bool { false }
3636
37+ // etcd has no transaction support — these are no-ops
3738 func beginTransaction( ) async throws { }
3839 func commitTransaction( ) async throws { }
3940 func rollbackTransaction( ) async throws { }
4041
4142 func quoteIdentifier( _ name: String ) -> String { name }
4243
44+ func escapeStringLiteral( _ value: String ) -> String { value }
45+
4346 func defaultExportQuery( table: String ) -> String ? {
4447 let prefix = resolvedPrefix ( for: table)
4548 return " get \( escapeArgument ( prefix) ) --prefix "
4649 }
4750
51+ func truncateTableStatements( table: String , cascade: Bool ) -> [ String ] ? {
52+ let prefix = resolvedPrefix ( for: table)
53+ if prefix. isEmpty {
54+ return [ " del \" \" --prefix " ]
55+ }
56+ return [ " del \( escapeArgument ( prefix) ) --prefix " ]
57+ }
58+
59+ func dropObjectStatement( name: String , type: String ) -> String ? {
60+ let prefix = resolvedPrefix ( for: name)
61+ if prefix. isEmpty {
62+ return " del \" \" --prefix "
63+ }
64+ return " del \( escapeArgument ( prefix) ) --prefix "
65+ }
66+
4867 init ( config: DriverConnectionConfig ) {
4968 self . config = config
5069 self . _rootPrefix = config. additionalFields [ " etcdKeyPrefix " ] ?? config. database
@@ -59,9 +78,8 @@ final class EtcdPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
5978 let status = try ? await client. endpointStatus ( )
6079 lock. withLock {
6180 _serverVersion = status? . version
81+ _httpClient = client
6282 }
63-
64- lock. withLock { _httpClient = client }
6583 }
6684
6785 func disconnect( ) {
@@ -280,7 +298,7 @@ final class EtcdPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
280298 }
281299
282300 func fetchViewDefinition( view: String , schema: String ? ) async throws -> String {
283- " "
301+ throw EtcdError . serverError ( " etcd does not support views " )
284302 }
285303
286304 func fetchTableMetadata( table: String , schema: String ? ) async throws -> PluginTableMetadata {
@@ -847,11 +865,12 @@ final class EtcdPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
847865 let response = try await client. rangeRequest ( req)
848866 var kvs = response. kvs ?? [ ]
849867
850- // Apply client-side filter if needed
868+ // Apply client-side filter if needed (checks both key and value)
851869 if needsClientFilter {
852870 kvs = kvs. filter { kv in
853871 let key = EtcdHttpClient . base64Decode ( kv. key)
854- return matchesFilter ( key: key, filterType: filterType, filterValue: filterValue)
872+ let value = kv. value. map { EtcdHttpClient . base64Decode ( $0) }
873+ return matchesFilter ( key: key, value: value, filterType: filterType, filterValue: filterValue)
855874 }
856875 }
857876
@@ -881,14 +900,16 @@ final class EtcdPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
881900 return Int ( response. count ?? " 0 " ) ?? 0
882901 }
883902
884- // Need to fetch keys and filter client-side
885- let req = EtcdRangeRequest ( key: b64Key, rangeEnd: b64RangeEnd, limit: Int64 ( Self . maxKeys) , keysOnly: true )
903+ // Need to fetch keys (and values for contains/startsWith filters) and filter client-side
904+ let needsValues = filterType == . contains || filterType == . startsWith
905+ let req = EtcdRangeRequest ( key: b64Key, rangeEnd: b64RangeEnd, limit: Int64 ( Self . maxKeys) , keysOnly: !needsValues)
886906 let response = try await client. rangeRequest ( req)
887907 let kvs = response. kvs ?? [ ]
888908
889909 return kvs. filter { kv in
890910 let key = EtcdHttpClient . base64Decode ( kv. key)
891- return matchesFilter ( key: key, filterType: filterType, filterValue: filterValue)
911+ let value = kv. value. map { EtcdHttpClient . base64Decode ( $0) }
912+ return matchesFilter ( key: key, value: value, filterType: filterType, filterValue: filterValue)
892913 } . count
893914 }
894915
@@ -916,7 +937,8 @@ final class EtcdPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
916937 return table
917938 }
918939 let root = _rootPrefix. hasSuffix ( " / " ) ? _rootPrefix : _rootPrefix + " / "
919- return root + table
940+ let cleanTable = table. hasPrefix ( " / " ) ? String ( table. dropFirst ( ) ) : table
941+ return root + cleanTable
920942 }
921943
922944 private func stripRootPrefix( _ key: String ) -> String {
@@ -928,14 +950,21 @@ final class EtcdPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
928950 return key
929951 }
930952
931- private func matchesFilter( key: String , filterType: EtcdFilterType , filterValue: String ) -> Bool {
953+ private func matchesFilter( key: String , value : String ? = nil , filterType: EtcdFilterType , filterValue: String ) -> Bool {
932954 switch filterType {
933955 case . none:
934956 return true
935957 case . contains:
936- return key. localizedCaseInsensitiveContains ( filterValue)
958+ if key. localizedCaseInsensitiveContains ( filterValue) {
959+ return true
960+ }
961+ return value? . localizedCaseInsensitiveContains ( filterValue) ?? false
937962 case . startsWith:
938- return key. lowercased ( ) . hasPrefix ( filterValue. lowercased ( ) )
963+ let lowerFilter = filterValue. lowercased ( )
964+ if key. lowercased ( ) . hasPrefix ( lowerFilter) {
965+ return true
966+ }
967+ return value? . lowercased ( ) . hasPrefix ( lowerFilter) ?? false
939968 case . endsWith:
940969 return key. lowercased ( ) . hasSuffix ( filterValue. lowercased ( ) )
941970 case . equals:
0 commit comments