From 71c89be5460f09032a6030540655f1f3d9ad1a08 Mon Sep 17 00:00:00 2001 From: Lucas Abijmil Date: Wed, 19 Feb 2025 19:44:12 +0100 Subject: [PATCH 1/9] Add RealTme filter --- .../Realtime/RealtimeChannel+AsyncAwait.swift | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/Sources/Realtime/RealtimeChannel+AsyncAwait.swift b/Sources/Realtime/RealtimeChannel+AsyncAwait.swift index c8a51c3e..48482da2 100644 --- a/Sources/Realtime/RealtimeChannel+AsyncAwait.swift +++ b/Sources/Realtime/RealtimeChannel+AsyncAwait.swift @@ -6,6 +6,36 @@ // import Foundation +import PostgREST + +public enum RealtimeChannelV2Filter { + case eq(column: String, value: any URLQueryRepresentable) + case neq(column: String, value: any URLQueryRepresentable) + case gt(column: String, value: any URLQueryRepresentable) + case gte(column: String, value: any URLQueryRepresentable) + case lt(column: String, value: any URLQueryRepresentable) + case lte(column: String, value: any URLQueryRepresentable) + case `in`(column: String, value: [any URLQueryRepresentable]) + + var value: String { + switch self { + case .eq(let column, let value): + return "\(column)=eq.\(value.queryValue)" + case .neq(let column, let value): + return "\(column)=neq.\(value.queryValue)" + case .gt(let column, let value): + return "\(column)=gt.\(value.queryValue)" + case .gte(let column, let value): + return "\(column)=gte.\(value.queryValue)" + case .lt(let column, let value): + return "\(column)=lt.\(value.queryValue)" + case .lte(let column, let value): + return "\(column)=lte.\(value.queryValue)" + case .in(let column, let value): + return "\(column)=in.(\(value.map(\.queryValue).joined(separator: ",")))" + } + } +} extension RealtimeChannelV2 { /// Listen for clients joining / leaving the channel using presences. @@ -24,6 +54,22 @@ extension RealtimeChannelV2 { } /// Listen for postgres changes in a channel. + public func postgresChange( + _: InsertAction.Type, + schema: String = "public", + table: String? = nil, + filter: RealtimeChannelV2Filter? = nil + ) -> AsyncStream { + postgresChange(event: .insert, schema: schema, table: table, filter: filter?.value) + .compactErase() + } + + /// Listen for postgres changes in a channel. + @available( + *, + deprecated, + message: "Use the new filter syntax instead." + ) public func postgresChange( _: InsertAction.Type, schema: String = "public", @@ -35,6 +81,22 @@ extension RealtimeChannelV2 { } /// Listen for postgres changes in a channel. + public func postgresChange( + _: UpdateAction.Type, + schema: String = "public", + table: String? = nil, + filter: RealtimeChannelV2Filter? = nil + ) -> AsyncStream { + postgresChange(event: .update, schema: schema, table: table, filter: filter?.value) + .compactErase() + } + + /// Listen for postgres changes in a channel. + @available( + *, + deprecated, + message: "Use the new filter syntax instead." + ) public func postgresChange( _: UpdateAction.Type, schema: String = "public", @@ -46,6 +108,22 @@ extension RealtimeChannelV2 { } /// Listen for postgres changes in a channel. + public func postgresChange( + _: DeleteAction.Type, + schema: String = "public", + table: String? = nil, + filter: RealtimeChannelV2Filter? = nil + ) -> AsyncStream { + postgresChange(event: .delete, schema: schema, table: table, filter: filter?.value) + .compactErase() + } + + /// Listen for postgres changes in a channel. + @available( + *, + deprecated, + message: "Use the new filter syntax instead." + ) public func postgresChange( _: DeleteAction.Type, schema: String = "public", @@ -57,6 +135,21 @@ extension RealtimeChannelV2 { } /// Listen for postgres changes in a channel. + public func postgresChange( + _: AnyAction.Type, + schema: String = "public", + table: String? = nil, + filter: RealtimeChannelV2Filter? = nil + ) -> AsyncStream { + postgresChange(event: .all, schema: schema, table: table, filter: filter?.value) + } + + /// Listen for postgres changes in a channel. + @available( + *, + deprecated, + message: "Use the new filter syntax instead." + ) public func postgresChange( _: AnyAction.Type, schema: String = "public", From 1051d899eaebe8daff22734dc6f65250b6ab0e8c Mon Sep 17 00:00:00 2001 From: Lucas Abijmil Date: Wed, 19 Feb 2025 19:57:20 +0100 Subject: [PATCH 2/9] Fix typo --- .../Realtime/RealtimeChannel+AsyncAwait.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/Realtime/RealtimeChannel+AsyncAwait.swift b/Sources/Realtime/RealtimeChannel+AsyncAwait.swift index 48482da2..18c70f44 100644 --- a/Sources/Realtime/RealtimeChannel+AsyncAwait.swift +++ b/Sources/Realtime/RealtimeChannel+AsyncAwait.swift @@ -15,24 +15,24 @@ public enum RealtimeChannelV2Filter { case gte(column: String, value: any URLQueryRepresentable) case lt(column: String, value: any URLQueryRepresentable) case lte(column: String, value: any URLQueryRepresentable) - case `in`(column: String, value: [any URLQueryRepresentable]) + case `in`(column: String, values: [any URLQueryRepresentable]) var value: String { switch self { - case .eq(let column, let value): + case let .eq(column, value): return "\(column)=eq.\(value.queryValue)" - case .neq(let column, let value): + case let .neq(column, value): return "\(column)=neq.\(value.queryValue)" - case .gt(let column, let value): + case let .gt(column, value): return "\(column)=gt.\(value.queryValue)" - case .gte(let column, let value): + case let .gte(column, value): return "\(column)=gte.\(value.queryValue)" - case .lt(let column, let value): + case let .lt(column, value): return "\(column)=lt.\(value.queryValue)" - case .lte(let column, let value): + case let .lte(column, value): return "\(column)=lte.\(value.queryValue)" - case .in(let column, let value): - return "\(column)=in.(\(value.map(\.queryValue).joined(separator: ",")))" + case let .in(column, values): + return "\(column)=in.(\(values.map(\.queryValue).joined(separator: ",")))" } } } From b53f3e7e73252c328bf288f577ff4717ae77f286 Mon Sep 17 00:00:00 2001 From: Lucas Abijmil Date: Wed, 19 Feb 2025 23:32:23 +0100 Subject: [PATCH 3/9] RealtimeFilterValue & RealtimeFilter --- .../Realtime/RealtimeChannel+AsyncAwait.swift | 82 ++++++++++++++----- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/Sources/Realtime/RealtimeChannel+AsyncAwait.swift b/Sources/Realtime/RealtimeChannel+AsyncAwait.swift index 18c70f44..b38dfc4a 100644 --- a/Sources/Realtime/RealtimeChannel+AsyncAwait.swift +++ b/Sources/Realtime/RealtimeChannel+AsyncAwait.swift @@ -6,33 +6,71 @@ // import Foundation -import PostgREST -public enum RealtimeChannelV2Filter { - case eq(column: String, value: any URLQueryRepresentable) - case neq(column: String, value: any URLQueryRepresentable) - case gt(column: String, value: any URLQueryRepresentable) - case gte(column: String, value: any URLQueryRepresentable) - case lt(column: String, value: any URLQueryRepresentable) - case lte(column: String, value: any URLQueryRepresentable) - case `in`(column: String, values: [any URLQueryRepresentable]) +/// A value that can be used to filter Realtime changes in a channel. +public protocol RealtimeFilterValue { + var rawValue: String { get } +} + +extension String: RealtimeFilterValue { + public var rawValue: String { self } +} + +extension Int: RealtimeFilterValue { + public var rawValue: String { "\(self)" } +} + +extension Double: RealtimeFilterValue { + public var rawValue: String { "\(self)" } +} + +extension Bool: RealtimeFilterValue { + public var rawValue: String { "\(self)" } +} + +extension UUID: RealtimeFilterValue { + public var rawValue: String { uuidString } +} + +extension Date: RealtimeFilterValue { + public var rawValue: String { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + return formatter.string(from: self) + } +} + +extension Array: RealtimeFilterValue where Element: RealtimeFilterValue { + public var rawValue: String { + map(\.rawValue).joined(separator: ",") + } +} + +public enum RealtimeFilter { + case eq(_ column: String, value: any RealtimeFilterValue) + case neq(_ column: String, value: any RealtimeFilterValue) + case gt(_ column: String, value: any RealtimeFilterValue) + case gte(_ column: String, value: any RealtimeFilterValue) + case lt(_ column: String, value: any RealtimeFilterValue) + case lte(_ column: String, value: any RealtimeFilterValue) + case `in`(_ column: String, values: [any RealtimeFilterValue]) var value: String { switch self { case let .eq(column, value): - return "\(column)=eq.\(value.queryValue)" + return "\(column)=eq.\(value.rawValue)" case let .neq(column, value): - return "\(column)=neq.\(value.queryValue)" + return "\(column)=neq.\(value.rawValue)" case let .gt(column, value): - return "\(column)=gt.\(value.queryValue)" + return "\(column)=gt.\(value.rawValue)" case let .gte(column, value): - return "\(column)=gte.\(value.queryValue)" + return "\(column)=gte.\(value.rawValue)" case let .lt(column, value): - return "\(column)=lt.\(value.queryValue)" + return "\(column)=lt.\(value.rawValue)" case let .lte(column, value): - return "\(column)=lte.\(value.queryValue)" + return "\(column)=lte.\(value.rawValue)" case let .in(column, values): - return "\(column)=in.(\(values.map(\.queryValue).joined(separator: ",")))" + return "\(column)=in.(\(values.map(\.rawValue)))" } } } @@ -58,7 +96,7 @@ extension RealtimeChannelV2 { _: InsertAction.Type, schema: String = "public", table: String? = nil, - filter: RealtimeChannelV2Filter? = nil + filter: RealtimeFilter? = nil ) -> AsyncStream { postgresChange(event: .insert, schema: schema, table: table, filter: filter?.value) .compactErase() @@ -70,6 +108,7 @@ extension RealtimeChannelV2 { deprecated, message: "Use the new filter syntax instead." ) + @_disfavoredOverload public func postgresChange( _: InsertAction.Type, schema: String = "public", @@ -85,7 +124,7 @@ extension RealtimeChannelV2 { _: UpdateAction.Type, schema: String = "public", table: String? = nil, - filter: RealtimeChannelV2Filter? = nil + filter: RealtimeFilter? = nil ) -> AsyncStream { postgresChange(event: .update, schema: schema, table: table, filter: filter?.value) .compactErase() @@ -97,6 +136,7 @@ extension RealtimeChannelV2 { deprecated, message: "Use the new filter syntax instead." ) + @_disfavoredOverload public func postgresChange( _: UpdateAction.Type, schema: String = "public", @@ -112,7 +152,7 @@ extension RealtimeChannelV2 { _: DeleteAction.Type, schema: String = "public", table: String? = nil, - filter: RealtimeChannelV2Filter? = nil + filter: RealtimeFilter? = nil ) -> AsyncStream { postgresChange(event: .delete, schema: schema, table: table, filter: filter?.value) .compactErase() @@ -124,6 +164,7 @@ extension RealtimeChannelV2 { deprecated, message: "Use the new filter syntax instead." ) + @_disfavoredOverload public func postgresChange( _: DeleteAction.Type, schema: String = "public", @@ -139,7 +180,7 @@ extension RealtimeChannelV2 { _: AnyAction.Type, schema: String = "public", table: String? = nil, - filter: RealtimeChannelV2Filter? = nil + filter: RealtimeFilter? = nil ) -> AsyncStream { postgresChange(event: .all, schema: schema, table: table, filter: filter?.value) } @@ -150,6 +191,7 @@ extension RealtimeChannelV2 { deprecated, message: "Use the new filter syntax instead." ) + @_disfavoredOverload public func postgresChange( _: AnyAction.Type, schema: String = "public", From 9ff51dd4f9eb3580382e7029dadf8f8a1ac1c07d Mon Sep 17 00:00:00 2001 From: Lucas Abijmil Date: Wed, 19 Feb 2025 23:39:26 +0100 Subject: [PATCH 4/9] Create proper files for FilerValue & Filter --- .../Realtime/RealtimeChannel+AsyncAwait.swift | 39 ---------------- Sources/Realtime/RealtimeFilter.swift | 28 +++++++++++ Sources/Realtime/RealtimeFilterValue.swift | 46 +++++++++++++++++++ .../RealtimeFilterValueTests.swift | 0 4 files changed, 74 insertions(+), 39 deletions(-) create mode 100644 Sources/Realtime/RealtimeFilter.swift create mode 100644 Sources/Realtime/RealtimeFilterValue.swift create mode 100644 Tests/RealtimeTests/RealtimeFilterValueTests.swift diff --git a/Sources/Realtime/RealtimeChannel+AsyncAwait.swift b/Sources/Realtime/RealtimeChannel+AsyncAwait.swift index b38dfc4a..136bb481 100644 --- a/Sources/Realtime/RealtimeChannel+AsyncAwait.swift +++ b/Sources/Realtime/RealtimeChannel+AsyncAwait.swift @@ -7,45 +7,6 @@ import Foundation -/// A value that can be used to filter Realtime changes in a channel. -public protocol RealtimeFilterValue { - var rawValue: String { get } -} - -extension String: RealtimeFilterValue { - public var rawValue: String { self } -} - -extension Int: RealtimeFilterValue { - public var rawValue: String { "\(self)" } -} - -extension Double: RealtimeFilterValue { - public var rawValue: String { "\(self)" } -} - -extension Bool: RealtimeFilterValue { - public var rawValue: String { "\(self)" } -} - -extension UUID: RealtimeFilterValue { - public var rawValue: String { uuidString } -} - -extension Date: RealtimeFilterValue { - public var rawValue: String { - let formatter = ISO8601DateFormatter() - formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] - return formatter.string(from: self) - } -} - -extension Array: RealtimeFilterValue where Element: RealtimeFilterValue { - public var rawValue: String { - map(\.rawValue).joined(separator: ",") - } -} - public enum RealtimeFilter { case eq(_ column: String, value: any RealtimeFilterValue) case neq(_ column: String, value: any RealtimeFilterValue) diff --git a/Sources/Realtime/RealtimeFilter.swift b/Sources/Realtime/RealtimeFilter.swift new file mode 100644 index 00000000..8ca66bfb --- /dev/null +++ b/Sources/Realtime/RealtimeFilter.swift @@ -0,0 +1,28 @@ +public enum RealtimeFilter { + case eq(_ column: String, value: any RealtimeFilterValue) + case neq(_ column: String, value: any RealtimeFilterValue) + case gt(_ column: String, value: any RealtimeFilterValue) + case gte(_ column: String, value: any RealtimeFilterValue) + case lt(_ column: String, value: any RealtimeFilterValue) + case lte(_ column: String, value: any RealtimeFilterValue) + case `in`(_ column: String, values: [any RealtimeFilterValue]) + + var value: String { + switch self { + case let .eq(column, value): + return "\(column)=eq.\(value.rawValue)" + case let .neq(column, value): + return "\(column)=neq.\(value.rawValue)" + case let .gt(column, value): + return "\(column)=gt.\(value.rawValue)" + case let .gte(column, value): + return "\(column)=gte.\(value.rawValue)" + case let .lt(column, value): + return "\(column)=lt.\(value.rawValue)" + case let .lte(column, value): + return "\(column)=lte.\(value.rawValue)" + case let .in(column, values): + return "\(column)=in.(\(values.map(\.rawValue)))" + } + } +} \ No newline at end of file diff --git a/Sources/Realtime/RealtimeFilterValue.swift b/Sources/Realtime/RealtimeFilterValue.swift new file mode 100644 index 00000000..d3458c1b --- /dev/null +++ b/Sources/Realtime/RealtimeFilterValue.swift @@ -0,0 +1,46 @@ +// +// RealtimeFilterValue.swift +// Supabase +// +// Created by Lucas Abijmil on 19/02/2025. +// + + +/// A value that can be used to filter Realtime changes in a channel. +public protocol RealtimeFilterValue { + var rawValue: String { get } +} + +extension String: RealtimeFilterValue { + public var rawValue: String { self } +} + +extension Int: RealtimeFilterValue { + public var rawValue: String { "\(self)" } +} + +extension Double: RealtimeFilterValue { + public var rawValue: String { "\(self)" } +} + +extension Bool: RealtimeFilterValue { + public var rawValue: String { "\(self)" } +} + +extension UUID: RealtimeFilterValue { + public var rawValue: String { uuidString } +} + +extension Date: RealtimeFilterValue { + public var rawValue: String { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + return formatter.string(from: self) + } +} + +extension Array: RealtimeFilterValue where Element: RealtimeFilterValue { + public var rawValue: String { + map(\.rawValue).joined(separator: ",") + } +} \ No newline at end of file diff --git a/Tests/RealtimeTests/RealtimeFilterValueTests.swift b/Tests/RealtimeTests/RealtimeFilterValueTests.swift new file mode 100644 index 00000000..e69de29b From 069d5954e4081ddc21c7274cb90a156aa9f6e6d4 Mon Sep 17 00:00:00 2001 From: Lucas Abijmil Date: Wed, 19 Feb 2025 23:42:27 +0100 Subject: [PATCH 5/9] Add tests --- .../Realtime/RealtimeChannel+AsyncAwait.swift | 29 ------------------- Sources/Realtime/RealtimeFilterValue.swift | 3 +- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/Sources/Realtime/RealtimeChannel+AsyncAwait.swift b/Sources/Realtime/RealtimeChannel+AsyncAwait.swift index 136bb481..809585fe 100644 --- a/Sources/Realtime/RealtimeChannel+AsyncAwait.swift +++ b/Sources/Realtime/RealtimeChannel+AsyncAwait.swift @@ -7,35 +7,6 @@ import Foundation -public enum RealtimeFilter { - case eq(_ column: String, value: any RealtimeFilterValue) - case neq(_ column: String, value: any RealtimeFilterValue) - case gt(_ column: String, value: any RealtimeFilterValue) - case gte(_ column: String, value: any RealtimeFilterValue) - case lt(_ column: String, value: any RealtimeFilterValue) - case lte(_ column: String, value: any RealtimeFilterValue) - case `in`(_ column: String, values: [any RealtimeFilterValue]) - - var value: String { - switch self { - case let .eq(column, value): - return "\(column)=eq.\(value.rawValue)" - case let .neq(column, value): - return "\(column)=neq.\(value.rawValue)" - case let .gt(column, value): - return "\(column)=gt.\(value.rawValue)" - case let .gte(column, value): - return "\(column)=gte.\(value.rawValue)" - case let .lt(column, value): - return "\(column)=lt.\(value.rawValue)" - case let .lte(column, value): - return "\(column)=lte.\(value.rawValue)" - case let .in(column, values): - return "\(column)=in.(\(values.map(\.rawValue)))" - } - } -} - extension RealtimeChannelV2 { /// Listen for clients joining / leaving the channel using presences. public func presenceChange() -> AsyncStream { diff --git a/Sources/Realtime/RealtimeFilterValue.swift b/Sources/Realtime/RealtimeFilterValue.swift index d3458c1b..81c4e902 100644 --- a/Sources/Realtime/RealtimeFilterValue.swift +++ b/Sources/Realtime/RealtimeFilterValue.swift @@ -5,6 +5,7 @@ // Created by Lucas Abijmil on 19/02/2025. // +import Foundation /// A value that can be used to filter Realtime changes in a channel. public protocol RealtimeFilterValue { @@ -43,4 +44,4 @@ extension Array: RealtimeFilterValue where Element: RealtimeFilterValue { public var rawValue: String { map(\.rawValue).joined(separator: ",") } -} \ No newline at end of file +} From 3c76a22e05c82b7bd51a68abbfe4c1b72621a141 Mon Sep 17 00:00:00 2001 From: Lucas Abijmil Date: Wed, 19 Feb 2025 23:46:57 +0100 Subject: [PATCH 6/9] Clean up --- Sources/Realtime/RealtimeFilter.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Sources/Realtime/RealtimeFilter.swift b/Sources/Realtime/RealtimeFilter.swift index 8ca66bfb..55a89d46 100644 --- a/Sources/Realtime/RealtimeFilter.swift +++ b/Sources/Realtime/RealtimeFilter.swift @@ -1,3 +1,11 @@ +// +// RealtimeFilter.swift +// Supabase +// +// Created by Lucas Abijmil on 19/02/2025. +// + +/// A filter that can be used in Realtime. public enum RealtimeFilter { case eq(_ column: String, value: any RealtimeFilterValue) case neq(_ column: String, value: any RealtimeFilterValue) @@ -25,4 +33,4 @@ public enum RealtimeFilter { return "\(column)=in.(\(values.map(\.rawValue)))" } } -} \ No newline at end of file +} From 7f3887bb833ba593f483b41e4e0e46df33b9e788 Mon Sep 17 00:00:00 2001 From: Lucas Abijmil Date: Wed, 19 Feb 2025 23:49:40 +0100 Subject: [PATCH 7/9] Add tests --- .../RealtimeFilterValueTests.swift | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Tests/RealtimeTests/RealtimeFilterValueTests.swift b/Tests/RealtimeTests/RealtimeFilterValueTests.swift index e69de29b..5645a957 100644 --- a/Tests/RealtimeTests/RealtimeFilterValueTests.swift +++ b/Tests/RealtimeTests/RealtimeFilterValueTests.swift @@ -0,0 +1,28 @@ +// +// RealtimeFilterValueTests.swift +// Supabase +// +// Created by Lucas Abijmil on 19/02/2025. +// + +import XCTest +@testable import Realtime + +final class RealtimeFilterValueTests: XCTestCase { + func testUUID() { + XCTAssertEqual( + UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!.rawValue, + "E621E1F8-C36C-495A-93FC-0C247A3E6E5F") + } + + func testDate() { + XCTAssertEqual( + Date(timeIntervalSince1970: 1_737_465_985).rawValue, + "2025-01-21T13:26:25.000Z" + ) + } + + func testArray() { + XCTAssertEqual(["a", "b", "c"].rawValue, "a,b,c") + } +} From 879a2c491086a95dcb99dcf5502aa387ea585746 Mon Sep 17 00:00:00 2001 From: Lucas Abijmil Date: Thu, 20 Feb 2025 15:12:33 +0100 Subject: [PATCH 8/9] Renaming --- ...ter.swift => RealtimePostgresFilter.swift} | 18 +++++++-------- ...wift => RealtimePostgresFilterValue.swift} | 22 +++++++------------ 2 files changed, 17 insertions(+), 23 deletions(-) rename Sources/Realtime/{RealtimeFilter.swift => RealtimePostgresFilter.swift} (56%) rename Sources/Realtime/{RealtimeFilterValue.swift => RealtimePostgresFilterValue.swift} (57%) diff --git a/Sources/Realtime/RealtimeFilter.swift b/Sources/Realtime/RealtimePostgresFilter.swift similarity index 56% rename from Sources/Realtime/RealtimeFilter.swift rename to Sources/Realtime/RealtimePostgresFilter.swift index 55a89d46..928cd9d9 100644 --- a/Sources/Realtime/RealtimeFilter.swift +++ b/Sources/Realtime/RealtimePostgresFilter.swift @@ -1,19 +1,19 @@ // -// RealtimeFilter.swift +// RealtimePostgresFilter.swift // Supabase // // Created by Lucas Abijmil on 19/02/2025. // /// A filter that can be used in Realtime. -public enum RealtimeFilter { - case eq(_ column: String, value: any RealtimeFilterValue) - case neq(_ column: String, value: any RealtimeFilterValue) - case gt(_ column: String, value: any RealtimeFilterValue) - case gte(_ column: String, value: any RealtimeFilterValue) - case lt(_ column: String, value: any RealtimeFilterValue) - case lte(_ column: String, value: any RealtimeFilterValue) - case `in`(_ column: String, values: [any RealtimeFilterValue]) +public enum RealtimePostgresFilter { + case eq(_ column: String, value: any RealtimePostgresFilterValue) + case neq(_ column: String, value: any RealtimePostgresFilterValue) + case gt(_ column: String, value: any RealtimePostgresFilterValue) + case gte(_ column: String, value: any RealtimePostgresFilterValue) + case lt(_ column: String, value: any RealtimePostgresFilterValue) + case lte(_ column: String, value: any RealtimePostgresFilterValue) + case `in`(_ column: String, values: [any RealtimePostgresFilterValue]) var value: String { switch self { diff --git a/Sources/Realtime/RealtimeFilterValue.swift b/Sources/Realtime/RealtimePostgresFilterValue.swift similarity index 57% rename from Sources/Realtime/RealtimeFilterValue.swift rename to Sources/Realtime/RealtimePostgresFilterValue.swift index 81c4e902..259e4c72 100644 --- a/Sources/Realtime/RealtimeFilterValue.swift +++ b/Sources/Realtime/RealtimePostgresFilterValue.swift @@ -1,5 +1,5 @@ // -// RealtimeFilterValue.swift +// RealtimePostgresFilterValue.swift // Supabase // // Created by Lucas Abijmil on 19/02/2025. @@ -8,40 +8,34 @@ import Foundation /// A value that can be used to filter Realtime changes in a channel. -public protocol RealtimeFilterValue { +public protocol RealtimePostgresFilterValue { var rawValue: String { get } } -extension String: RealtimeFilterValue { +extension String: RealtimePostgresFilterValue { public var rawValue: String { self } } -extension Int: RealtimeFilterValue { +extension Int: RealtimePostgresFilterValue { public var rawValue: String { "\(self)" } } -extension Double: RealtimeFilterValue { +extension Double: RealtimePostgresFilterValue { public var rawValue: String { "\(self)" } } -extension Bool: RealtimeFilterValue { +extension Bool: RealtimePostgresFilterValue { public var rawValue: String { "\(self)" } } -extension UUID: RealtimeFilterValue { +extension UUID: RealtimePostgresFilterValue { public var rawValue: String { uuidString } } -extension Date: RealtimeFilterValue { +extension Date: RealtimePostgresFilterValue { public var rawValue: String { let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] return formatter.string(from: self) } } - -extension Array: RealtimeFilterValue where Element: RealtimeFilterValue { - public var rawValue: String { - map(\.rawValue).joined(separator: ",") - } -} From f074ac204f9f932e6677b6496abbffba537962aa Mon Sep 17 00:00:00 2001 From: Lucas Abijmil Date: Thu, 20 Feb 2025 15:18:36 +0100 Subject: [PATCH 9/9] Update Tests --- .../Realtime/RealtimeChannel+AsyncAwait.swift | 8 +-- Sources/Realtime/RealtimePostgresFilter.swift | 2 +- .../RealtimePostgresFilterTests.swift | 68 +++++++++++++++++++ ...=> RealtimePostgresFilterValueTests.swift} | 8 +-- 4 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 Tests/RealtimeTests/RealtimePostgresFilterTests.swift rename Tests/RealtimeTests/{RealtimeFilterValueTests.swift => RealtimePostgresFilterValueTests.swift} (71%) diff --git a/Sources/Realtime/RealtimeChannel+AsyncAwait.swift b/Sources/Realtime/RealtimeChannel+AsyncAwait.swift index 809585fe..8a12a4d9 100644 --- a/Sources/Realtime/RealtimeChannel+AsyncAwait.swift +++ b/Sources/Realtime/RealtimeChannel+AsyncAwait.swift @@ -28,7 +28,7 @@ extension RealtimeChannelV2 { _: InsertAction.Type, schema: String = "public", table: String? = nil, - filter: RealtimeFilter? = nil + filter: RealtimePostgresFilter? = nil ) -> AsyncStream { postgresChange(event: .insert, schema: schema, table: table, filter: filter?.value) .compactErase() @@ -56,7 +56,7 @@ extension RealtimeChannelV2 { _: UpdateAction.Type, schema: String = "public", table: String? = nil, - filter: RealtimeFilter? = nil + filter: RealtimePostgresFilter? = nil ) -> AsyncStream { postgresChange(event: .update, schema: schema, table: table, filter: filter?.value) .compactErase() @@ -84,7 +84,7 @@ extension RealtimeChannelV2 { _: DeleteAction.Type, schema: String = "public", table: String? = nil, - filter: RealtimeFilter? = nil + filter: RealtimePostgresFilter? = nil ) -> AsyncStream { postgresChange(event: .delete, schema: schema, table: table, filter: filter?.value) .compactErase() @@ -112,7 +112,7 @@ extension RealtimeChannelV2 { _: AnyAction.Type, schema: String = "public", table: String? = nil, - filter: RealtimeFilter? = nil + filter: RealtimePostgresFilter? = nil ) -> AsyncStream { postgresChange(event: .all, schema: schema, table: table, filter: filter?.value) } diff --git a/Sources/Realtime/RealtimePostgresFilter.swift b/Sources/Realtime/RealtimePostgresFilter.swift index 928cd9d9..8b8c7790 100644 --- a/Sources/Realtime/RealtimePostgresFilter.swift +++ b/Sources/Realtime/RealtimePostgresFilter.swift @@ -30,7 +30,7 @@ public enum RealtimePostgresFilter { case let .lte(column, value): return "\(column)=lte.\(value.rawValue)" case let .in(column, values): - return "\(column)=in.(\(values.map(\.rawValue)))" + return "\(column)=in.(\(values.map(\.rawValue).joined(separator: ",")))" } } } diff --git a/Tests/RealtimeTests/RealtimePostgresFilterTests.swift b/Tests/RealtimeTests/RealtimePostgresFilterTests.swift new file mode 100644 index 00000000..78b5e8e9 --- /dev/null +++ b/Tests/RealtimeTests/RealtimePostgresFilterTests.swift @@ -0,0 +1,68 @@ +// +// RealtimePostgresFilterTests.swift +// Supabase +// +// Created by Lucas Abijmil on 20/02/2025. +// + +import XCTest +@testable import Realtime + +final class RealtimePostgresFilterTests: XCTestCase { + + func testEq() { + let value = "value" + let column = "column" + let filter: RealtimePostgresFilter = .eq(column, value: value) + + XCTAssertEqual(filter.value, "column=eq.value") + } + + func testNeq() { + let value = "value" + let column = "column" + let filter: RealtimePostgresFilter = .neq(column, value: value) + + XCTAssertEqual(filter.value, "column=neq.value") + } + + func testGt() { + let value = "value" + let column = "column" + let filter: RealtimePostgresFilter = .gt(column, value: value) + + XCTAssertEqual(filter.value, "column=gt.value") + } + + func testGte() { + let value = "value" + let column = "column" + let filter: RealtimePostgresFilter = .gte(column, value: value) + + XCTAssertEqual(filter.value, "column=gte.value") + } + + func testLt() { + let value = "value" + let column = "column" + let filter: RealtimePostgresFilter = .lt(column, value: value) + + XCTAssertEqual(filter.value, "column=lt.value") + } + + func testLte() { + let value = "value" + let column = "column" + let filter: RealtimePostgresFilter = .lte(column, value: value) + + XCTAssertEqual(filter.value, "column=lte.value") + } + + func testIn() { + let values = ["value1", "value2"] + let column = "column" + let filter: RealtimePostgresFilter = .in(column, values: values) + + XCTAssertEqual(filter.value, "column=in.(value1,value2)") + } +} diff --git a/Tests/RealtimeTests/RealtimeFilterValueTests.swift b/Tests/RealtimeTests/RealtimePostgresFilterValueTests.swift similarity index 71% rename from Tests/RealtimeTests/RealtimeFilterValueTests.swift rename to Tests/RealtimeTests/RealtimePostgresFilterValueTests.swift index 5645a957..a931cb1f 100644 --- a/Tests/RealtimeTests/RealtimeFilterValueTests.swift +++ b/Tests/RealtimeTests/RealtimePostgresFilterValueTests.swift @@ -1,5 +1,5 @@ // -// RealtimeFilterValueTests.swift +// RealtimePostgresFilterValueTests.swift // Supabase // // Created by Lucas Abijmil on 19/02/2025. @@ -8,7 +8,7 @@ import XCTest @testable import Realtime -final class RealtimeFilterValueTests: XCTestCase { +final class RealtimePostgresFilterValueTests: XCTestCase { func testUUID() { XCTAssertEqual( UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!.rawValue, @@ -21,8 +21,4 @@ final class RealtimeFilterValueTests: XCTestCase { "2025-01-21T13:26:25.000Z" ) } - - func testArray() { - XCTAssertEqual(["a", "b", "c"].rawValue, "a,b,c") - } }