diff --git a/BaasRuleCodable.swift b/BaasRuleCodable.swift new file mode 100644 index 0000000000..163560d3dd --- /dev/null +++ b/BaasRuleCodable.swift @@ -0,0 +1,1019 @@ +import RealmSwift + +class _ReverseDecoder : Decoder { + func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { + return KeyedDecodingContainer(Self.KeyedContainer(storage: storage, + codingPath: codingPath, + allKeys: [])) + } + + func unkeyedContainer() throws -> UnkeyedDecodingContainer { + Self.UnkeyedContainer.init(storage: storage, + codingPath: codingPath, + currentIndex: 0) + } + + func singleValueContainer() throws -> SingleValueDecodingContainer { + SingleValueContainer(storage: storage, codingPath: codingPath) + } + + var codingPath: [CodingKey] = [] + + var userInfo: [CodingUserInfoKey : Any] = [:] + + var storage: RLMObjectSchema + + init(codingPath: [CodingKey], + userInfo: [CodingUserInfoKey : Any], + className: String) throws { + self.codingPath = codingPath + self.storage = RLMObjectSchema(className: className, + objectClass: NSNull.self, + properties: []) + } + + class SingleValueContainer : SingleValueDecodingContainer { + var storage: RLMObjectSchema + var codingPath: [CodingKey] = [] + + init(storage: RLMObjectSchema, codingPath: [CodingKey]) { + self.storage = storage + self.codingPath = codingPath + } + private func unwrap(_ value: Any?) throws -> T { + guard let value = value else { + throw DecodingError.valueNotFound(T.self, .init(codingPath: codingPath, + debugDescription: "")) + } + guard let value = value as? T else { + throw DecodingError.typeMismatch(T.self, .init(codingPath: codingPath, + debugDescription: "")) + } + return value + } + private func unwrapObject(_ value: Any?) throws -> [String : Any] { + guard let value = value else { + throw DecodingError.valueNotFound([String: Any].self, .init(codingPath: codingPath, + debugDescription: "")) + } + guard let value = value as? [String: Any] else { + throw DecodingError.typeMismatch([String: Any].self, .init(codingPath: codingPath, + debugDescription: "")) + } + return value + } + + func decodeNil() -> Bool { + return storage == nil + } + + func decode(_ type: Bool.Type) throws -> Bool { + fatalError() + } + + func decode(_ type: String.Type) throws -> String { + return "" + } + + func decode(_ type: Double.Type) throws -> Double { + guard let stringValue = try unwrapObject(self.storage)["$numberDouble"] as? String else { + throw DecodingError.typeMismatch([String: Any].self, .init(codingPath: codingPath, + debugDescription: "")) + } + guard let doubleValue = Double(stringValue) else { + throw DecodingError.typeMismatch([String: Any].self, .init(codingPath: codingPath, + debugDescription: "")) + } + return doubleValue + } + + func decode(_ type: Float.Type) throws -> Float { + guard let stringValue = try unwrapObject(self.storage)["$numberDouble"] as? String else { + throw DecodingError.typeMismatch([String: Any].self, .init(codingPath: codingPath, + debugDescription: "")) + } + guard let floatValue = Float(stringValue) else { + throw DecodingError.typeMismatch([String: Any].self, .init(codingPath: codingPath, + debugDescription: "")) + } + return floatValue + } + + func decode(_ type: Int.Type) throws -> Int { + guard let stringValue = try unwrapObject(self.storage)["$numberInt"] as? String else { + throw DecodingError.typeMismatch([String: Any].self, .init(codingPath: codingPath, + debugDescription: "")) + } + guard let intValue = Int(stringValue) else { + throw DecodingError.typeMismatch([String: Any].self, .init(codingPath: codingPath, + debugDescription: "")) + } + return intValue + } + + func decode(_ type: Int8.Type) throws -> Int8 { + fatalError() + } + + func decode(_ type: Int16.Type) throws -> Int16 { + fatalError() + } + + func decode(_ type: Int32.Type) throws -> Int32 { + fatalError() + } + + func decode(_ type: Int64.Type) throws -> Int64 { + fatalError() + } + + func decode(_ type: UInt.Type) throws -> UInt { + fatalError() + } + + func decode(_ type: UInt8.Type) throws -> UInt8 { + fatalError() + } + + func decode(_ type: UInt16.Type) throws -> UInt16 { + fatalError() + } + + func decode(_ type: UInt32.Type) throws -> UInt32 { + fatalError() + } + + func decode(_ type: UInt64.Type) throws -> UInt64 { + fatalError() + } + + func decode(_ type: T.Type) throws -> T where T : Decodable { + fatalError() +// switch type { +// case is any ExtJSONLiteral.Type: +// return self.storage as! T +// case let type as any ExtJSONObjectRepresentable.Type: +// return try type.init(extJSONValue: self.storage as! Dictionary) as! T +// default: +// let decoder = try _ExtJSONDecoder(storage: storage as! [String: Any], codingPath: codingPath, userInfo: [:], container: nil) +// return try T(from: decoder) +// } + } + } + + class KeyedContainer : KeyedDecodingContainerProtocol + where Key: CodingKey { + var storage: RLMObjectSchema + + var codingPath: [CodingKey] + var allKeys: [Key] + + init(storage: RLMObjectSchema, codingPath: [CodingKey], allKeys: [CodingKey]) { + self.storage = storage + self.codingPath = codingPath + self.allKeys = allKeys as! [Key] + } + + func contains(_ key: Key) -> Bool { + true + } + + var isOpt = false + func decodeNil(forKey key: Key) throws -> Bool { + isOpt = true + return false +// storage.index(forKey: key.stringValue) == nil || +// storage[key.stringValue] as? NSObject == NSNull() + } + + func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { + fatalError() +// storage[key.stringValue] as! Bool + } + + func decode(_ type: String.Type, forKey key: Key) throws -> String { + defer { isOpt = false } + storage.properties.append(RLMProperty(name: key.stringValue, + type: .string, + objectClassName: nil, + linkOriginPropertyName: nil, + indexed: false, + optional: isOpt)) +// storage.storage.schema.properties[key.stringValue] = Rule.Schema.Property(bsonType: bsonType(.string), +// items: nil) + return "" + } + + func decode(_ type: Double.Type, forKey key: Key) throws -> Double { + storage.properties.append(RLMProperty(name: key.stringValue, + type: .double, + objectClassName: nil, + linkOriginPropertyName: nil, + indexed: false, + optional: false)) +// storage.storage.schema.properties[key.stringValue] = Rule.Schema.Property(bsonType: bsonType(.double), +// items: nil) + return 0 + } + + func decode(_ type: Float.Type, forKey key: Key) throws -> Float { + storage.properties.append(RLMProperty(name: key.stringValue, + type: .float, + objectClassName: nil, + linkOriginPropertyName: nil, + indexed: false, + optional: false)) + return 0 + } + + func decode(_ type: Int.Type, forKey key: Key) throws -> Int { + defer { isOpt = false } + storage.properties.append(RLMProperty(name: key.stringValue, + type: .int, + objectClassName: nil, + linkOriginPropertyName: nil, + indexed: false, + optional: isOpt)) + return 0 + } + + func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { + fatalError() + } + + func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { + fatalError() + } + + func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { + fatalError() + } + + func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { + fatalError() + } + + func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { + fatalError() + } + + func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { + fatalError() + } + + func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { + fatalError() + } + + func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { + fatalError() + } + + func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { + fatalError() + } + + func decode(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable { + switch type { + case is ObjectId.Type: + storage.properties.append(RLMProperty(name: key.stringValue, + type: .objectId, + objectClassName: nil, + linkOriginPropertyName: nil, + indexed: false, + optional: false)) + return ObjectId.generate() as! T + case is Data.Type: + storage.properties.append(RLMProperty(name: key.stringValue, + type: .data, + objectClassName: nil, + linkOriginPropertyName: nil, + indexed: false, + optional: false)) + return Data() as! T + case is any Collection.Type: + let property = RLMProperty(name: key.stringValue, + type: .any, + objectClassName: nil, + linkOriginPropertyName: nil, + indexed: false, + optional: type is any OptionalProtocol.Type) + property.array = true + storage.properties.append(property) + default: + let property = RLMProperty(name: key.stringValue, + type: .any, + objectClassName: String(describing: type), + linkOriginPropertyName: nil, + indexed: false, + optional: type is any OptionalProtocol.Type) + property.dictionary = true + storage.properties.append(property) + } + return try ReverseDecoder().decode(type).0 +// switch type { +// case is ObjectId.Type: +// guard let value = storage[key.stringValue] as? [String: String] else { +// throw DecodingError.typeMismatch(ObjectId.self, .init(codingPath: codingPath, debugDescription: "")) +// } +// return try ObjectId(string: value["$oid"]!) as! T +// case is Data.Type: +// guard let value = storage[key.stringValue] as? [String: [String: String]], +// let value = value["$binary"], +// let value = value["base64"] else { +// throw DecodingError.typeMismatch(ObjectId.self, .init(codingPath: codingPath, debugDescription: "")) +// } +// return Data(base64Encoded: value)! as! T +// case is any Collection.Type: +// let decoder = try _ExtJSONDecoder(storage: storage[key.stringValue] as! [Any], codingPath: codingPath, userInfo: [:], container: nil) +// return try T(from: decoder) +// case is any ExtJSONLiteral.Type: +// return self.storage[key.stringValue] as! T +// case let type as any ExtJSONObjectRepresentable.Type: +// print("doing stuff") +// return try type.init(extJSONValue: self.storage[key.stringValue] as! Dictionary) as! T +// default: +// let decoder = try _ExtJSONDecoder(storage: storage[key.stringValue] as! [String: Any], codingPath: codingPath, userInfo: [:], container: nil) +// return try T(from: decoder) +// } + } + + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + fatalError() + } + + func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { + fatalError() + } + + func superDecoder() throws -> Decoder { + fatalError() + } + + func superDecoder(forKey key: Key) throws -> Decoder { + fatalError() + } + + var data: Data { + fatalError() + } + } + + class UnkeyedContainer : UnkeyedDecodingContainer { + init(storage: RLMObjectSchema, codingPath: [CodingKey], currentIndex: Int) { + self.storage = storage + self.codingPath = codingPath + self.currentIndex = currentIndex + } + func decode(_ type: String.Type) throws -> String { + fatalError() + } + + func decode(_ type: Double.Type) throws -> Double { + fatalError() + } + + func decode(_ type: Float.Type) throws -> Float { + fatalError() + } + + func decode(_ type: Int.Type) throws -> Int { + fatalError() + } + + func decode(_ type: Int8.Type) throws -> Int8 { + fatalError() + } + + func decode(_ type: Int16.Type) throws -> Int16 { + fatalError() + } + + func decode(_ type: Int32.Type) throws -> Int32 { + fatalError() + } + + func decode(_ type: Int64.Type) throws -> Int64 { + fatalError() + } + + func decode(_ type: UInt.Type) throws -> UInt { + fatalError() + } + + func decode(_ type: UInt8.Type) throws -> UInt8 { + fatalError() + } + + func decode(_ type: UInt16.Type) throws -> UInt16 { + fatalError() + } + + func decode(_ type: UInt32.Type) throws -> UInt32 { + fatalError() + } + + func decode(_ type: UInt64.Type) throws -> UInt64 { + fatalError() + } + + func decode(_ type: T.Type) throws -> T where T : Decodable { + defer { + self.currentIndex += 1 + } + fatalError() +// switch type { +// case is any ExtJSONLiteral.Type: +// return self.storage[self.currentIndex] as! T +// case let type as any ExtJSONObjectRepresentable.Type: +// print("doing stuff") +// return try type.init(extJSONValue: self.storage[self.currentIndex] as! Dictionary) as! T +// default: +// let decoder = try _ExtJSONDecoder(storage: self.storage[self.currentIndex] as! Dictionary, codingPath: codingPath, userInfo: [:], container: nil) +// return try T.init(from: decoder) +// } + } + + var storage: RLMObjectSchema + + func decode(_ type: Bool.Type) throws -> Bool { + fatalError() + } + + typealias StorageType = [Any] + + var data: Data { + fatalError() + } + + var codingPath: [CodingKey] + + var count: Int? { + return 0 + } + + var isAtEnd: Bool { + true + } + + var currentIndex: Int + + func decodeNil() throws -> Bool { + fatalError() + } + + func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + fatalError() + } + + func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { + fatalError() + } + + func superDecoder() throws -> Decoder { + fatalError() + } + } +} + +final private class ReverseDecoder { + public init() { + } + + /** + A dictionary you use to customize the encoding process + by providing contextual information. + */ + public var userInfo: [CodingUserInfoKey : Any] = [:] + + /** + Returns a MessagePack-encoded representation of the value you supply. + + - Parameters: + - value: The value to encode as MessagePack. + - Throws: `EncodingError.invalidValue(_:_:)` + if the value can't be encoded as a MessagePack object. + */ + public func decode(_ type: T.Type) throws -> (T, RLMObjectSchema) where T : Decodable { + let decoder = try _ReverseDecoder(codingPath: [], userInfo: self.userInfo, className: String(describing: type)) +// _id: .int(0), +// title: String(describing: type), +// metadata: .init(dataSource: serviceName, database: database, collection: collection)) + let t = try T(from: decoder) + return (t, decoder.storage) +// switch try JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed) { +// case let value as [String: Any]: +// let decoder = try _ReverseDecoder(codingPath: [], +// userInfo: self.userInfo) +// decoder.userInfo = self.userInfo +// return try T(from: decoder) +// default: +// throw DecodingError.valueNotFound(T.self, .init(codingPath: [], +// debugDescription: "Invalid input")) +// } + + } +} +//// MARK: Encoder +class _BaasRuleEncoder { + var codingPath: [CodingKey] = [] + + var userInfo: [CodingUserInfoKey : Any] = [:] + + var container: [String: Rule] = [:] +} +// +final class BaasRuleEncoder { + public init() {} + + /** + A dictionary you use to customize the encoding process + by providing contextual information. + */ + public var userInfo: [CodingUserInfoKey : Any] = [:] + + /** + Returns a MessagePack-encoded representation of the value you supply. + + - Parameters: + - value: The value to encode as MessagePack. + - Throws: `EncodingError.invalidValue(_:_:)` + if the value can't be encoded as a MessagePack object. + */ + public func encode(_ type: T.Type) throws -> RLMObjectSchema where T : Codable { + let encoder = _BaasRuleEncoder() + encoder.userInfo = self.userInfo + let reverseDecoder = try ReverseDecoder().decode(type) + let schema = reverseDecoder.1 + schema.primaryKeyProperty = schema.properties.first { + $0.name == "_id" + } ?? RLMProperty(name: "_id", + type: .objectId, + objectClassName: nil, + linkOriginPropertyName: nil, + indexed: false, + optional: false) +// try value.encode(to: encoder) + return schema + } +} +// +//protocol _BaasRuleContainer { +//// var storage: [String: Any] { get } +// associatedtype StorageType +// var storage: StorageType { get } +// var data: Data { get throws } +//} +// +//extension _BaasRuleEncoder { +// final class SingleValueContainer { +// var storage: Any? +// +// fileprivate var canEncodeNewValue = true +// fileprivate func checkCanEncode(value: Any?) throws { +// guard self.canEncodeNewValue else { +// let context = EncodingError.Context(codingPath: self.codingPath, debugDescription: "Attempt to encode value through single value container when previously value already encoded.") +// throw EncodingError.invalidValue(value as Any, context) +// } +// } +// +// var codingPath: [CodingKey] +// var userInfo: [CodingUserInfoKey: Any] +// +// init(codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) { +// self.codingPath = codingPath +// self.userInfo = userInfo +// } +// +// var data: Data { +// get throws { +// try JSONSerialization.data(withJSONObject: storage) +// } +// } +// } +//} +// +//extension _BaasRuleEncoder.SingleValueContainer: SingleValueEncodingContainer { +// func encodeNil() throws { +// try checkCanEncode(value: nil) +// defer { self.canEncodeNewValue = false } +// +// self.storage = NSNull() +// } +// +// func encode(_ value: Bool) throws { +// try checkCanEncode(value: nil) +// defer { self.canEncodeNewValue = false } +// +// self.storage = value +// } +// +// func encode(_ value: String) throws { +// try checkCanEncode(value: value) +// defer { self.canEncodeNewValue = false } +// +// self.storage = value +// } +// +// func encode(_ value: Double) throws { +// try checkCanEncode(value: value) +// defer { self.canEncodeNewValue = false } +// +// self.storage = [ +// "$numberDouble": value.description +// ] +// } +// +// func encode(_ value: Float) throws { +// try checkCanEncode(value: value) +// defer { self.canEncodeNewValue = false } +// +// fatalError() +// } +// +// func encode(_ value: T) throws where T : BinaryInteger & Encodable { +// try checkCanEncode(value: value) +// defer { self.canEncodeNewValue = false } +// +// switch value { +// case let value as Int: +// storage = [ +// "$numberInt": value.description +// ] +// case let value as Int64: +// storage = [ +// "$numberLong": value.description +// ] +// default: +// throw EncodingError.invalidValue(value, +// EncodingError.Context(codingPath: codingPath, debugDescription: "Invalid BinaryInteger type.")) +// } +// } +// +// func encode(_ value: Int8) throws { +// try checkCanEncode(value: value) +// defer { self.canEncodeNewValue = false } +// +// fatalError() +// } +// +// func encode(_ value: Int16) throws { +// try checkCanEncode(value: value) +// defer { self.canEncodeNewValue = false } +// +// fatalError() +// } +// +// func encode(_ value: Int32) throws { +// try checkCanEncode(value: value) +// defer { self.canEncodeNewValue = false } +// +// fatalError() +// } +// +// func encode(_ value: Int64) throws { +// try checkCanEncode(value: value) +// defer { self.canEncodeNewValue = false } +// self.storage = [ +// "$numberLong": value.description +// ] +// } +// +// func encode(_ value: UInt8) throws { +// try checkCanEncode(value: value) +// defer { self.canEncodeNewValue = false } +// fatalError() +// } +// +// func encode(_ value: UInt16) throws { +// try checkCanEncode(value: value) +// defer { self.canEncodeNewValue = false } +// fatalError() +// } +// +// func encode(_ value: UInt32) throws { +// try checkCanEncode(value: value) +// defer { self.canEncodeNewValue = false } +// fatalError() +// } +// +// func encode(_ value: UInt64) throws { +// try checkCanEncode(value: value) +// defer { self.canEncodeNewValue = false } +// fatalError() +// } +// +// func encode(_ value: Date) throws { +// try checkCanEncode(value: value) +// defer { self.canEncodeNewValue = false } +// self.storage = [ +// "$date": ["$numberLong": Int64(value.timeIntervalSince1970)] +// ] +// } +// +// func encode(_ value: Data) throws { +// try checkCanEncode(value: value) +// defer { self.canEncodeNewValue = false } +// self.storage = [ +// "$binary": ["base64": value.base64EncodedString()] +// ] +// } +// +// func encode(_ value: Decimal128) throws { +// try checkCanEncode(value: value) +// defer { self.canEncodeNewValue = false } +// self.storage = [ +// "$numberDecimal": value.stringValue +// ] +// } +// +// func encode(_ value: ObjectId) throws { +// try checkCanEncode(value: value) +// defer { self.canEncodeNewValue = false } +// self.storage = [ +// "$oid": value.stringValue +// ] +// } +// +// func encode(_ value: T) throws where T : Encodable { +// try checkCanEncode(value: value) +// defer { self.canEncodeNewValue = false } +// let encoder = _ExtJSONEncoder() +// switch value { +//// case let value as ObjectId: +//// self.storage = [ +//// "$oid": value.stringValue +//// ] +// case let value as any ExtJSONObjectRepresentable: +// self.storage = value.extJSONValue +// case let value as Data: +// try encode(value) +// default: +// try value.encode(to: encoder) +// self.storage = encoder.container?.storage +// } +// } +//} +// +//extension _ExtJSONEncoder: Encoder { +// fileprivate func assertCanCreateContainer() { +// precondition(self.container == nil) +// } +// +// func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { +// assertCanCreateContainer() +// +// let container = KeyedContainer(codingPath: self.codingPath, userInfo: self.userInfo) +// self.container = container +// +// return KeyedEncodingContainer(container) +// } +// +// func unkeyedContainer() -> UnkeyedEncodingContainer { +// assertCanCreateContainer() +// +// let container = UnkeyedContainer(codingPath: self.codingPath, userInfo: self.userInfo) +// self.container = container +// +// return container +// } +// +// func singleValueContainer() -> SingleValueEncodingContainer { +// assertCanCreateContainer() +// +// let container = SingleValueContainer(codingPath: self.codingPath, userInfo: self.userInfo) +// self.container = container +// +// return container +// } +//} +// +//// MARK: KeyedContainer +//extension _ExtJSONEncoder { +// final class KeyedContainer : _ExtJSONContainer where Key: CodingKey { +// var storage: [String: any _ExtJSONContainer] = [:] +// +// var codingPath: [CodingKey] +// var userInfo: [CodingUserInfoKey: Any] +// +// func nestedCodingPath(forKey key: CodingKey) -> [CodingKey] { +// return self.codingPath + [key] +// } +// +// init(codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) { +// self.codingPath = codingPath +// self.userInfo = userInfo +// } +// var data: Data { +// get throws { +// try JSONSerialization.data(withJSONObject: storage.storage) +// } +// } +// } +//} +// +//extension _ExtJSONEncoder.KeyedContainer: KeyedEncodingContainerProtocol { +// func encodeNil(forKey key: Key) throws { +// var container = self.nestedSingleValueContainer(forKey: key) +// try container.encodeNil() +// } +// +// func encode(_ value: T, forKey key: Key) throws where T : Encodable { +// let container = _ExtJSONEncoder.SingleValueContainer(codingPath: self.nestedCodingPath(forKey: key), +// userInfo: self.userInfo) +// try container.encode(value) +// self.storage[key.stringValue] = container +// } +// +// private func nestedSingleValueContainer(forKey key: Key) -> SingleValueEncodingContainer { +// let container = _ExtJSONEncoder.SingleValueContainer(codingPath: self.nestedCodingPath(forKey: key), +// userInfo: self.userInfo) +// +// return container +// } +// +// func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { +// let container = _ExtJSONEncoder.UnkeyedContainer(codingPath: self.nestedCodingPath(forKey: key), userInfo: self.userInfo) +// self.storage[key.stringValue] = container +// +// return container +// } +// +// func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { +// let container = _ExtJSONEncoder.KeyedContainer(codingPath: self.nestedCodingPath(forKey: key), userInfo: self.userInfo) +// self.storage[key.stringValue] = container +// +// return KeyedEncodingContainer(container) +// } +// +// func superEncoder() -> Encoder { +// fatalError("Unimplemented") // FIXME +// } +// +// func superEncoder(forKey key: Key) -> Encoder { +// fatalError("Unimplemented") // FIXME +// } +//} +// +//struct AnyCodingKey: CodingKey, Equatable { +// var stringValue: String +// var intValue: Int? +// +// init?(stringValue: String) { +// self.stringValue = stringValue +// self.intValue = nil +// } +// +// init?(intValue: Int) { +// self.stringValue = "\(intValue)" +// self.intValue = intValue +// } +// +// init(_ base: Key) where Key : CodingKey { +// if let intValue = base.intValue { +// self.init(intValue: intValue)! +// } else { +// self.init(stringValue: base.stringValue)! +// } +// } +//} +// +//extension AnyCodingKey: Hashable { +// var hashValue: Int { +// return self.intValue?.hashValue ?? self.stringValue.hashValue +// } +//} +// +//extension Array : _ExtJSONContainer where Element == any _ExtJSONContainer { +// var data: Data { +// get throws { +// fatalError() +// } +// } +// +// var storage: [Any] { +// self.map { +// if let value = $0.storage as? any _ExtJSONContainer { +// value.storage +// } else { +// $0.storage +// } +// } +// } +//} +//extension Dictionary : _ExtJSONContainer where Key == String, Value == any _ExtJSONContainer { +// var data: Data { +// get throws { +// fatalError() +// } +// } +// +// var storage: [String: Any] { +// self.reduce(into: [String: Any](), { +// if let value = $1.value.storage as? any _ExtJSONContainer { +// $0[$1.key] = value.storage +// } else { +// $0[$1.key] = $1.value.storage +// } +// }) +// } +//} +// +//extension _ExtJSONEncoder { +// final class UnkeyedContainer : _ExtJSONContainer { +// var storage: [any _ExtJSONContainer] = [] +// +// var count: Int { +// return storage.count +// } +// +// var codingPath: [CodingKey] +// +// var nestedCodingPath: [CodingKey] { +// return self.codingPath + [AnyCodingKey(intValue: self.count)!] +// } +// +// var userInfo: [CodingUserInfoKey: Any] +// +// init(codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) { +// self.codingPath = codingPath +// self.userInfo = userInfo +// } +// var data: Data { +// get throws { +// try JSONSerialization.data(withJSONObject: storage.storage) +// } +// } +// } +//} +// +//extension _ExtJSONEncoder.UnkeyedContainer: UnkeyedEncodingContainer { +// func encodeNil() throws { +// var container = self.nestedSingleValueContainer() +// try container.encodeNil() +// } +// +// func encode(_ value: T) throws where T : Encodable { +// var container = self.nestedSingleValueContainer() +// try container.encode(value) +// } +// +// private func nestedSingleValueContainer() -> SingleValueEncodingContainer { +// let container = _ExtJSONEncoder.SingleValueContainer(codingPath: self.nestedCodingPath, userInfo: self.userInfo) +// self.storage.append(container) +// +// return container +// } +// +// func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer where NestedKey : CodingKey { +// let container = _ExtJSONEncoder.KeyedContainer(codingPath: self.nestedCodingPath, +// userInfo: self.userInfo) +// self.storage.append(container) +// +// return KeyedEncodingContainer(container) +// } +// +// func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { +// let container = _ExtJSONEncoder.UnkeyedContainer(codingPath: self.nestedCodingPath, +// userInfo: self.userInfo) +// self.storage.append(container) +// +// return container +// } +// +// func superEncoder() -> Encoder { +// fatalError("Unimplemented") // FIXME +// } +//} +// +// +////extension _MessagePackEncoder.KeyedContainer: _ExtJSONEncodingContainer { +//// var data: Data { +//// var data = Data() +//// +//// let length = storage.count +//// if let uint16 = UInt16(exactly: length) { +//// if length <= 15 { +//// data.append(0x80 + UInt8(length)) +//// } else { +//// data.append(0xde) +//// data.append(contentsOf: uint16.bytes) +//// } +//// } else if let uint32 = UInt32(exactly: length) { +//// data.append(0xdf) +//// data.append(contentsOf: uint32.bytes) +//// } else { +//// fatalError() +//// } +//// +//// for (key, container) in self.storage { +//// let keyContainer = _MessagePackEncoder.SingleValueContainer(codingPath: self.codingPath, userInfo: self.userInfo) +//// try! keyContainer.encode(key.stringValue) +//// data.append(keyContainer.data) +//// +//// data.append(container.data) +//// } +//// +//// return data +//// } +////} diff --git a/Realm.xcodeproj/project.pbxproj b/Realm.xcodeproj/project.pbxproj index 9fa54df89b..2d0558d272 100644 --- a/Realm.xcodeproj/project.pbxproj +++ b/Realm.xcodeproj/project.pbxproj @@ -242,7 +242,6 @@ 531F9570279070A900E497F1 /* RLMServerTestObjects.m in Sources */ = {isa = PBXBuildFile; fileRef = 531F956E279070A800E497F1 /* RLMServerTestObjects.m */; }; 532E916F24AA533A003FD9DB /* TimeoutProxyServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 532E916E24AA533A003FD9DB /* TimeoutProxyServer.swift */; }; 5346B5112B8FB6A4001CADFE /* ExtJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5346B50F2B8FB6A4001CADFE /* ExtJSON.swift */; }; - 5346B5122B8FB6A4001CADFE /* ExtJSONCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5346B5102B8FB6A4001CADFE /* ExtJSONCodable.swift */; }; 5346B5142B8FB6A9001CADFE /* MongoDataAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5346B5132B8FB6A9001CADFE /* MongoDataAccess.swift */; }; 5346B5162B8FB6C4001CADFE /* SwiftMongoAccessTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5346B5152B8FB6C4001CADFE /* SwiftMongoAccessTests.swift */; }; 5346B5192B8FB6E9001CADFE /* bson-corpus.rst in Resources */ = {isa = PBXBuildFile; fileRef = 5346B5172B8FB6E9001CADFE /* bson-corpus.rst */; }; @@ -254,10 +253,13 @@ 53626AAF25D31CAC00D9515D /* Objects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53626AAE25D31CAC00D9515D /* Objects.swift */; }; 53626AB025D31CAC00D9515D /* Objects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53626AAE25D31CAC00D9515D /* Objects.swift */; }; 537130C824A9E417001FDBBC /* RealmServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537130C724A9E417001FDBBC /* RealmServer.swift */; }; + 538ADD452B9CC54000ADE639 /* ExtJSONCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538ADD442B9CC54000ADE639 /* ExtJSONCodable.swift */; }; + 539A9A392B98D15200AE0E9D /* BaasRuleCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539A9A382B98D15200AE0E9D /* BaasRuleCodable.swift */; }; 53A34E3625CDA0AC00698930 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53A34E3325CDA0AC00698930 /* LaunchScreen.storyboard */; }; 53A34E3725CDA0AC00698930 /* SwiftUITestHostApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A34E3425CDA0AC00698930 /* SwiftUITestHostApp.swift */; }; 53CCC6C4257EC8A300A8FC50 /* RLMApp_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 53CCC6C3257EC8A300A8FC50 /* RLMApp_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 53CCC6E8257EC8C400A8FC50 /* RLMUser_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 53CCC6E7257EC8C300A8FC50 /* RLMUser_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 53D7CBB52B9A22D100399257 /* MongoQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53D7CBB42B9A22D100399257 /* MongoQuery.swift */; }; 5B77EACE1DCC5614006AB51D /* ObjectiveCSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B77EACD1DCC5614006AB51D /* ObjectiveCSupport.swift */; }; 5D03FB1F1E0DAFBA007D53EA /* PredicateUtilTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5D03FB1E1E0DAFBA007D53EA /* PredicateUtilTests.mm */; }; 5D128F2A1BE984E5001F4FBF /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5D659ED91BE04556006515A0 /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -820,7 +822,6 @@ 532E916E24AA533A003FD9DB /* TimeoutProxyServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TimeoutProxyServer.swift; path = Realm/ObjectServerTests/TimeoutProxyServer.swift; sourceTree = ""; }; 533489DD26E0F9510085EEE1 /* RLMChildProcessEnvironment.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RLMChildProcessEnvironment.h; path = Realm/TestUtils/include/RLMChildProcessEnvironment.h; sourceTree = ""; }; 5346B50F2B8FB6A4001CADFE /* ExtJSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtJSON.swift; sourceTree = ""; }; - 5346B5102B8FB6A4001CADFE /* ExtJSONCodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtJSONCodable.swift; sourceTree = ""; }; 5346B5132B8FB6A9001CADFE /* MongoDataAccess.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MongoDataAccess.swift; sourceTree = ""; }; 5346B5152B8FB6C4001CADFE /* SwiftMongoAccessTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftMongoAccessTests.swift; path = Realm/ObjectServerTests/SwiftMongoAccessTests.swift; sourceTree = ""; }; 5346B5172B8FB6E9001CADFE /* bson-corpus.rst */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "bson-corpus.rst"; sourceTree = ""; }; @@ -833,11 +834,14 @@ 53626AAE25D31CAC00D9515D /* Objects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Objects.swift; sourceTree = ""; }; 536B7C0B24A4C223006B535D /* dependencies.list */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dependencies.list; sourceTree = ""; }; 537130C724A9E417001FDBBC /* RealmServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RealmServer.swift; path = Realm/ObjectServerTests/RealmServer.swift; sourceTree = ""; }; + 538ADD442B9CC54000ADE639 /* ExtJSONCodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtJSONCodable.swift; sourceTree = ""; }; + 539A9A382B98D15200AE0E9D /* BaasRuleCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaasRuleCodable.swift; sourceTree = ""; }; 53A34E3325CDA0AC00698930 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 53A34E3425CDA0AC00698930 /* SwiftUITestHostApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftUITestHostApp.swift; sourceTree = ""; }; 53A34E3525CDA0AC00698930 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53CCC6C3257EC8A300A8FC50 /* RLMApp_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMApp_Private.h; sourceTree = ""; }; 53CCC6E7257EC8C300A8FC50 /* RLMUser_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMUser_Private.h; sourceTree = ""; }; + 53D7CBB42B9A22D100399257 /* MongoQuery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MongoQuery.swift; sourceTree = ""; }; 53E308C927905794002A8D91 /* RLMSyncSubscription_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSyncSubscription_Private.h; sourceTree = ""; }; 53E308CA27905794002A8D91 /* RLMSyncSubscription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RLMSyncSubscription.h; sourceTree = ""; }; 53E308CB27905794002A8D91 /* RLMSyncSubscription_Private.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RLMSyncSubscription_Private.hpp; sourceTree = ""; }; @@ -1185,6 +1189,7 @@ 1AA5AE9A1D98A1B000ED8C27 /* Object-Server-Tests-Bridging-Header.h */, 3F73BC871E3A876600FE80B6 /* ObjectServerTests-Info.plist */, 537130C724A9E417001FDBBC /* RealmServer.swift */, + 539A9A382B98D15200AE0E9D /* BaasRuleCodable.swift */, 1AF64DD01DA304A90081EB15 /* RLMUser+ObjectServerTests.h */, 1AF64DD11DA304A90081EB15 /* RLMUser+ObjectServerTests.mm */, CF330BBC24E57D5F00F07EE2 /* RLMWatchTestUtility.h */, @@ -1297,6 +1302,17 @@ path = SwiftUITestHostUITests; sourceTree = ""; }; + 538ADD432B9CC52600ADE639 /* MongoDataAccess */ = { + isa = PBXGroup; + children = ( + 538ADD442B9CC54000ADE639 /* ExtJSONCodable.swift */, + 5346B50F2B8FB6A4001CADFE /* ExtJSON.swift */, + 5346B5132B8FB6A9001CADFE /* MongoDataAccess.swift */, + 53D7CBB42B9A22D100399257 /* MongoQuery.swift */, + ); + path = MongoDataAccess; + sourceTree = ""; + }; 53A34E3225CDA0AC00698930 /* SwiftUITestHost */ = { isa = PBXGroup; children = ( @@ -1350,6 +1366,7 @@ 5D660FCD1BE98C560021E04F /* RealmSwift */ = { isa = PBXGroup; children = ( + 538ADD432B9CC52600ADE639 /* MongoDataAccess */, 3F48201C26307CE2005B40E8 /* Impl */, 5D660FE31BE98D670021E04F /* Aliases.swift */, 681EE33A25EE8E1400A9DEC5 /* AnyRealmValue.swift */, @@ -1361,9 +1378,6 @@ 3FB6ABD82416A27000E318C2 /* Decimal128.swift */, 3FC3F9162419B63100E27322 /* EmbeddedObject.swift */, 29B7FDF51C0DA6560023224E /* Error.swift */, - 5346B50F2B8FB6A4001CADFE /* ExtJSON.swift */, - 5346B5132B8FB6A9001CADFE /* MongoDataAccess.swift */, - 5346B5102B8FB6A4001CADFE /* ExtJSONCodable.swift */, 3F7BCEB42834377E00EB6E16 /* Events.swift */, AC7825B82ACD90BE007ABA4B /* Geospatial.swift */, 5D1534B71CCFF545008976D7 /* LinkingObjects.swift */, @@ -2551,7 +2565,6 @@ 681EE33B25EE8E1400A9DEC5 /* AnyRealmValue.swift in Sources */, CFB4313A243DF87100471C18 /* App.swift in Sources */, AC81360F287F21350029F15E /* AsymmetricObject.swift in Sources */, - 5346B5122B8FB6A4001CADFE /* ExtJSONCodable.swift in Sources */, 3FE267D7264308680030F83C /* BasicTypes.swift in Sources */, 4996EA9E2465BB8A003A1F51 /* BSON.swift in Sources */, 3FE267D5264308680030F83C /* CollectionAccess.swift in Sources */, @@ -2580,6 +2593,7 @@ 5B77EACE1DCC5614006AB51D /* ObjectiveCSupport.swift in Sources */, 5D660FF51BE98D670021E04F /* ObjectSchema.swift in Sources */, 5D660FF61BE98D670021E04F /* Optional.swift in Sources */, + 538ADD452B9CC54000ADE639 /* ExtJSONCodable.swift in Sources */, 3FE267D8264308680030F83C /* Persistable.swift in Sources */, 3F149CCB2668112A00111D65 /* PersistedProperty.swift in Sources */, 3F857A49276A507200F9B9B1 /* Projection.swift in Sources */, @@ -2594,6 +2608,7 @@ CFE9CE3326555BBD00BF96D6 /* RealmKeyedCollection.swift in Sources */, CF44461E26121C6800BAFDB4 /* RealmProperty.swift in Sources */, 5D660FFB1BE98D670021E04F /* Results.swift in Sources */, + 53D7CBB52B9A22D100399257 /* MongoQuery.swift in Sources */, 68A7B91D2543538B00C703BC /* RLMSupport.swift in Sources */, 5D660FFC1BE98D670021E04F /* Schema.swift in Sources */, 3FE267DA264308680030F83C /* SchemaDiscovery.swift in Sources */, @@ -2737,6 +2752,7 @@ 1AA5AE981D989BE400ED8C27 /* SwiftSyncTestCase.swift in Sources */, AC8846B72687BC4100DF4A65 /* SwiftUIServerTests.swift in Sources */, 3F558C8822C29A03002F0F30 /* TestUtils.mm in Sources */, + 539A9A392B98D15200AE0E9D /* BaasRuleCodable.swift in Sources */, 3FAF2D4229577100002EAC93 /* TestUtils.swift in Sources */, 532E916F24AA533A003FD9DB /* TimeoutProxyServer.swift in Sources */, CFB674A324EEE9CB00FBF0B8 /* WatchTestUtility.swift in Sources */, @@ -3083,6 +3099,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 5D659E761BE03E0D006515A0 /* Realm.xcconfig */; buildSettings = { + OTHER_SWIFT_FLAGS = "-Xfrontend -enable-actor-data-race-checks -module-name RealmSwift -package-name realmPkg"; }; name = Debug; }; @@ -3097,6 +3114,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 5D660FBD1BE98BEF0021E04F /* RealmSwift.xcconfig */; buildSettings = { + OTHER_SWIFT_FLAGS = "-Xfrontend -enable-actor-data-race-checks -module-name RealmSwift -package-name realmPkg"; }; name = Debug; }; @@ -3117,7 +3135,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - OTHER_SWIFT_FLAGS = "$(inherited) -D BUILDING_REALM_SWIFT_TESTS -D DEBUG"; + OTHER_SWIFT_FLAGS = "-Xfrontend -enable-actor-data-race-checks -package-name realmPkg -D BUILDING_REALM_SWIFT_TESTS"; }; name = Debug; }; diff --git a/Realm/ObjectServerTests/RealmServer.swift b/Realm/ObjectServerTests/RealmServer.swift index 219ed57cdd..7407b593bf 100644 --- a/Realm/ObjectServerTests/RealmServer.swift +++ b/Realm/ObjectServerTests/RealmServer.swift @@ -31,7 +31,7 @@ import RealmSyncTestSupport extension URLSession { fileprivate func resultDataTask(with request: URLRequest, _ completionHandler: @Sendable @escaping (Result) -> Void) { - URLSession(configuration: .default, delegate: nil, delegateQueue: OperationQueue()).dataTask(with: request) { (data, response, error) in + URLSession.shared.dataTask(with: request) { (data, response, error) in if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode >= 200 && httpResponse.statusCode < 300, let data = data { @@ -65,7 +65,7 @@ extension URLSession { } } -private func bsonType(_ type: PropertyType) -> String { +internal func bsonType(_ type: PropertyType) -> String { switch type { case .UUID: return "uuid" case .any: return "mixed" @@ -142,6 +142,85 @@ extension Optional: Json where Wrapped: Json {} typealias Json = Any #endif +func stitchRule(for type: T.Type, + _ partitionKeyType: String?, + id: String? = nil, + appId: String) -> [String: Json] { + fatalError() +// var stitchProperties: [String: Json] = [:] +// +// // We only add a partition property for pbs +// if let partitionKeyType = partitionKeyType { +// stitchProperties["realm_id"] = [ +// "bsonType": "\(partitionKeyType)" +// ] +// } +// +// var relationships: [String: Json] = [:] +// +// let mirror = Mirror(reflecting: type) +// // First pass we only add the properties to the schema as we can't add +// // links until the targets of the links exist. +// let pk = primaryKeyProperty! +// stitchProperties[pk.columnName] = pk.stitchRule(self) +// for property in properties { +// if property.type != .object { +// stitchProperties[property.columnName] = property.stitchRule(self) +// } else if id != nil { +// stitchProperties[property.columnName] = property.stitchRule(self) +// relationships[property.columnName] = [ +// "ref": "#/relationship/mongodb1/test_data/\(property.objectClassName!) \(appId)", +// "foreign_key": "_id", +// "is_list": property.isArray || property.isSet || property.isMap +// ] +// } +// } +// +// return [ +// "_id": id as Json, +// "schema": [ +// "properties": stitchProperties, +// // The server currently only supports non-optional collections +// // but requires them to be marked as optional +// "required": properties.compactMap { $0.isOptional || $0.type == .any || $0.isArray || $0.isMap || $0.isSet ? nil : $0.columnName }, +// "title": "\(className)" +// ], +// "metadata": [ +// "data_source": "mongodb1", +// "database": "test_data", +// "collection": "\(className) \(appId)" +// ], +// "relationships": relationships +// ] +} + +struct Rule: Codable { + struct Schema: Codable { + struct Property: Codable { + let bsonType: String + let items: [Property]? + } + var properties: [String: Property] + let required: [String] + let title: String + } + struct Metadata: Codable { + let dataSource: String + let database: String + let collection: String + } + struct Relationship: Codable { + let ref: String + let foreignKey: String + let isList: Bool + } + + let _id: AnyBSONKey + var schema: Schema + let metadata: Metadata + var relationships: [String: Relationship] +} + private extension ObjectSchema { func stitchRule(_ partitionKeyType: String?, id: String? = nil, appId: String) -> [String: Json] { var stitchProperties: [String: Json] = [:] @@ -164,6 +243,9 @@ private extension ObjectSchema { stitchProperties[property.columnName] = property.stitchRule(self) } else if id != nil { stitchProperties[property.columnName] = property.stitchRule(self) + guard property.objectClassName.map(RLMSchema.shared().schema(forClassName:))?.map(\.isEmbedded).map({ !$0 }) ?? false else { + continue + } relationships[property.columnName] = [ "ref": "#/relationship/mongodb1/test_data/\(property.objectClassName!) \(appId)", "foreign_key": "_id", @@ -198,7 +280,7 @@ struct AdminProfile: Codable { case groupId = "group_id" } - let groupId: String + let groupId: String? } let roles: [Role] @@ -303,15 +385,17 @@ class AdminSession { ] if let data = data { do { - request.httpBody = try JSONSerialization.data(withJSONObject: data) + if let data = data as? Encodable { + request.httpBody = try JSONEncoder().encode(data) + } else { + request.httpBody = try JSONSerialization.data(withJSONObject: data) + } } catch { completionHandler(.failure(error)) } } - URLSession(configuration: URLSessionConfiguration.default, - delegate: nil, delegateQueue: OperationQueue()) - .resultDataTask(with: request) { result in + URLSession.shared.resultDataTask(with: request) { result in completionHandler(result.flatMap { data in Result { data.count > 0 ? try JSONSerialization.jsonObject(with: data) : nil @@ -361,6 +445,10 @@ class AdminSession { request(httpMethod: "POST", data: data, completionHandler: completionHandler) } + func post(on group: DispatchGroup, _ data: D, + _ completionHandler: @escaping Completion) where D: Encodable { + request(on: group, httpMethod: "POST", data: data, completionHandler) + } func post(on group: DispatchGroup, _ data: [String: Json], _ completionHandler: @escaping Completion) { request(on: group, httpMethod: "POST", data: data, completionHandler) @@ -462,8 +550,10 @@ class Admin { } } .flatMap { (accessToken: String) -> Result in - self.userProfile(accessToken: accessToken).map { - AdminSession(accessToken: accessToken, groupId: $0.roles[0].groupId) + self.userProfile(accessToken: accessToken).flatMap { + $0.roles.compactMap(\.groupId).first.flatMap { + .success(AdminSession(accessToken: accessToken, groupId: $0)) + } ?? .failure(URLError(.badServerResponse)) } } .get() @@ -537,8 +627,8 @@ public class RealmServer: NSObject { } do { - try launchMongoProcess() - try launchServerProcess() +// try launchMongoProcess() +// try launchServerProcess() self.session = try Admin().login() try makeUserAdmin() } catch { @@ -549,31 +639,36 @@ public class RealmServer: NSObject { /// Lazy teardown for exit only. private lazy var tearDown: () = { - serverProcess.terminate() - - let mongo = RealmServer.binDir.appendingPathComponent("mongo").path - - // step down the replica set - let rsStepDownProcess = Process() - rsStepDownProcess.launchPath = mongo - rsStepDownProcess.arguments = [ - "admin", - "--port", "26000", - "--eval", "'db.adminCommand({replSetStepDown: 0, secondaryCatchUpPeriodSecs: 0, force: true})'"] - try? rsStepDownProcess.run() - rsStepDownProcess.waitUntilExit() - - // step down the replica set - let mongoShutdownProcess = Process() - mongoShutdownProcess.launchPath = mongo - mongoShutdownProcess.arguments = [ - "admin", - "--port", "26000", - "--eval", "'db.shutdownServer({force: true})'"] - try? mongoShutdownProcess.run() - mongoShutdownProcess.waitUntilExit() - - mongoProcess.terminate() + if serverProcess.isRunning { + serverProcess.terminate() + } + + if mongoProcess.isRunning { + let mongo = RealmServer.binDir.appendingPathComponent("mongo").path + + // step down the replica set + let rsStepDownProcess = Process() + rsStepDownProcess.launchPath = mongo + rsStepDownProcess.arguments = [ + "admin", + "--port", "26000", + "--eval", "'db.adminCommand({replSetStepDown: 0, secondaryCatchUpPeriodSecs: 0, force: true})'"] + try? rsStepDownProcess.run() + rsStepDownProcess.waitUntilExit() + + // step down the replica set + let mongoShutdownProcess = Process() + mongoShutdownProcess.launchPath = mongo + mongoShutdownProcess.arguments = [ + "admin", + "--port", "26000", + "--eval", "'db.shutdownServer({force: true})'"] + try? mongoShutdownProcess.run() + mongoShutdownProcess.waitUntilExit() + + + mongoProcess.terminate() + } try? FileManager().removeItem(at: tempDir) }() @@ -760,8 +855,14 @@ public class RealmServer: NSObject { public typealias AppId = String - /// Create a new server app func createApp(syncMode: SyncMode, types: [ObjectBase.Type], persistent: Bool) throws -> AppId { + try createApp(syncMode: syncMode, + schema: types.map { ObjectiveCSupport.convert(object: $0.sharedSchema()!) }, + persistent: persistent) + } + /// Create a new server app + func createApp(syncMode: SyncMode, schema: [ObjectSchema], + persistent: Bool) throws -> AppId { let session = try XCTUnwrap(session) let info = try session.apps.post(["name": "test"]).get() @@ -840,7 +941,7 @@ public class RealmServer: NSObject { throw URLError(.badServerResponse) } - let schema = types.map { ObjectiveCSupport.convert(object: $0.sharedSchema()!) } +// let schema = types.map { ObjectiveCSupport.convert(object: $0.sharedSchema()!) } let partitionKeyType: String? if case .pbs(let bsonType) = syncMode { @@ -1005,10 +1106,29 @@ public class RealmServer: NSObject { return clientAppId } + public func createApp(fields: [String], + types: [Codable.Type], + persistent: Bool = false) throws -> AppId { + return try createApp(syncMode: .flx(fields), schema: types.compactMap { try? BaasRuleEncoder().encode($0) + }.map { ObjectiveCSupport.convert(object: $0) }, persistent: persistent) + } + @objc public func createApp(fields: [String], types: [ObjectBase.Type], persistent: Bool = false) throws -> AppId { return try createApp(syncMode: .flx(fields), types: types, persistent: persistent) } + public func createApp(fields: [String], + types: repeat (each Ts).Type, + persistent: Bool = false) throws -> AppId where repeat each Ts: Codable & ObjectBase { + var schema = [ObjectSchema]() + func createSchema(type: T.Type, schema: inout [ObjectSchema]) + where T: Codable & ObjectBase { + schema.append(ObjectiveCSupport.convert(object: type.sharedSchema()!)) + } + repeat createSchema(type: each types, schema: &schema) + return try createApp(syncMode: .flx(fields), schema: schema, persistent: persistent) + } + @objc public func createApp(partitionKeyType: String = "string", types: [ObjectBase.Type], persistent: Bool = false) throws -> AppId { return try createApp(syncMode: .pbs(partitionKeyType), types: types, persistent: persistent) } diff --git a/Realm/ObjectServerTests/SwiftMongoAccessTests.swift b/Realm/ObjectServerTests/SwiftMongoAccessTests.swift index 90b9d9f8de..0d2be0c227 100644 --- a/Realm/ObjectServerTests/SwiftMongoAccessTests.swift +++ b/Realm/ObjectServerTests/SwiftMongoAccessTests.swift @@ -1,4 +1,5 @@ import Foundation +import RealmSwift #if canImport(RealmTestSupport) import RealmSwiftSyncTestSupport import RealmSyncTestSupport @@ -6,6 +7,7 @@ import RealmTestSupport import RealmSwiftTestSupport #endif +// MARK: Models struct Person : Codable, Equatable { struct Address : Codable, Equatable { let city: String @@ -16,14 +18,109 @@ struct Person : Codable, Equatable { let address: Address } +@objc(AllTypesObject) private class AllTypesObject : Object, Codable { + @objc(Subdocument) class Subdocument : EmbeddedObject, Codable { + @Persisted var strCol: String + + convenience init(strCol: String) { + self.init() + self.strCol = strCol + } + } + @Persisted(primaryKey: true) var _id: ObjectId + + @Persisted var strCol: String + @Persisted var intCol: Int + @Persisted var boolCol: Bool + @Persisted var binaryCol: Data + @Persisted var dateCol: Date + @Persisted var subdocumentCol: Subdocument? + + @Persisted var arrayStrCol: List + @Persisted var arrayIntCol: List + @Persisted var arrayBinaryCol: List + @Persisted var arrayDateCol: List + @Persisted var arraySubdocumentCol: List + + @Persisted var optStrCol: String? + @Persisted var optIntCol: Int? + @Persisted var optBinaryCol: Data? + @Persisted var optDateCol: Date? + + convenience init(_id: ObjectId, strCol: String, intCol: Int, boolCol: Bool, binaryCol: Data, dateCol: Date, subdocumentCol: Subdocument? = nil, arrayStrCol: List, arrayIntCol: List, arrayBinaryCol: List, arrayDateCol: List, arraySubdocumentCol: List, optStrCol: String? = nil, optIntCol: Int? = nil, optBinaryCol: Data? = nil, optDateCol: Date? = nil) { + self.init() + self._id = _id + self.strCol = strCol + self.intCol = intCol + self.boolCol = boolCol + self.binaryCol = binaryCol + self.dateCol = dateCol + self.subdocumentCol = subdocumentCol + self.arrayStrCol = arrayStrCol + self.arrayIntCol = arrayIntCol + self.arrayBinaryCol = arrayBinaryCol + self.arrayDateCol = arrayDateCol + self.arraySubdocumentCol = arraySubdocumentCol + self.optStrCol = optStrCol + self.optIntCol = optIntCol + self.optBinaryCol = optBinaryCol + self.optDateCol = optDateCol + } +} + +extension AllTypesObject { + func compare(to other: AllTypesObject) { + XCTAssertEqual(strCol, other.strCol) + XCTAssertEqual(self.intCol, other.intCol) + XCTAssertEqual(self.boolCol, other.boolCol) + XCTAssertEqual(self.binaryCol, other.binaryCol) + XCTAssertEqual(self.dateCol.timeIntervalSince1970, + other.dateCol.timeIntervalSince1970, + accuracy: 0.1) + XCTAssertEqual(self.subdocumentCol?.strCol, other.subdocumentCol?.strCol) + XCTAssertEqual(self.arrayStrCol, other.arrayStrCol) + XCTAssertEqual(self.arrayIntCol, other.arrayIntCol) + XCTAssertEqual(self.arrayBinaryCol, other.arrayBinaryCol) + XCTAssertEqual(self.arrayDateCol, other.arrayDateCol) + XCTAssertEqual(self.arraySubdocumentCol.map(\.strCol), + other.arraySubdocumentCol.map(\.strCol)) + XCTAssertEqual(self.optStrCol, other.optStrCol) + XCTAssertEqual(self.optIntCol, other.optIntCol) + XCTAssertEqual(self.optBinaryCol, other.optBinaryCol) + XCTAssertEqual(self.optDateCol, other.optDateCol) + } +} + +// MARK: CRUD/Types tests class MongoDataAccessCollectionTests : SwiftSyncTestCase { + struct AllTypes : Codable, Equatable { + struct Subdocument : Codable, Equatable { + let strCol: String + } + let _id: ObjectId + + let strCol: String + let intCol: Int + let binaryCol: Data + + let arrayStrCol: [String] + let arrayIntCol: [Int] + + let optStrCol: String? + let optIntCol: Int? + + let subdocument: Subdocument + } + func testMongoCollection() async throws { - let user = try await self.app.login(credentials: .anonymous) +// try BaasRuleEncoder().encode(Person.self) + let app = self.app(id: try RealmServer.shared.createApp(fields: ["_id"], types: [Person.self])) + let user = try await app.login(credentials: .anonymous) // let app = App(id: "car-wsney") // let user = try await app.login(credentials: .anonymous) - let collection = user.mongoClient("mongodb-atlas") - .database(named: "car") - .collection(named: "persons", type: Person.self) + let collection = user.mongoClient("mongodb1") + .database(named: "test_data") + .collection(named: "Person", type: Person.self) _ = try await collection.deleteMany() let person = Person(name: "Jason", age: 32, address: Person.Address(city: "Austin", state: "TX")) // let try await collection.findOne() @@ -37,123 +134,388 @@ class MongoDataAccessCollectionTests : SwiftSyncTestCase { XCTAssertEqual(foundPerson.address.city, "Austin") XCTAssertEqual(foundPerson.address.state, "TX") let deleteCount = try await collection.deleteOne() - XCTAssertEqual(deleteCount, 1) + XCTAssertEqual(deleteCount.deletedCount, 1) let nilPerson = try await collection.findOne() XCTAssertNil(nilPerson) } -////// -// @MainActor func testMongoCollectionFilters() async throws { -// let app = App(id: "application-1-iyczo") -// let user = try await app.login(credentials: .anonymous) -// let realm = try await Realm(configuration: user.flexibleSyncConfiguration()) -// let subscriptions = realm.subscriptions -// try await subscriptions.update { -// subscriptions.append( -// RealmPerson.self, -// RealmDog.self -// ) -// } -// let dog = RealmDog(owner: nil, name: "Murphy", age: 2) -// let person = RealmPerson(name: "Jason", -// age: 32, -// address: RealmAddress(city: "Marlboro", state: "NJ"), -// dogs: [dog]) -// dog.owner = person -// -// try realm.write { -// realm.add(person) -// } -// try await realm.syncSession?.waitForUpload() -// try await realm.syncSession?.waitForDownload() -// let collection = user.mongoClient("mongodb-atlas") -// .database(named: "static-queries") -// .collection(named: "RealmPerson", type: RealmPerson.self) -// -// guard let foundPerson = try await collection.findOne([ -// "_id": person._id -// ]) else { -// return XCTFail() -// } -// -// XCTAssertEqual(person._id, -// foundPerson._id) -// XCTAssertEqual(person.name, -// foundPerson.name) -// XCTAssertEqual(person.age, -// foundPerson.age) -// XCTAssertEqual(person.address?.city, -// foundPerson.address?.city) -// XCTAssertEqual(person.address?.state, -// foundPerson.address?.state) -//// XCTAssertEqual(person.dog?.name, -//// foundPerson.dog?.name) -//// XCTAssertEqual(person.dog?.age, -//// foundPerson.dog?.age) -// -//// try? realm.write { -//// realm.deleteAll() -//// } -//// try await realm.syncSession?.waitForUpload() -//// try await realm.syncSession?.waitForDownload() -//// let dogCollection = user.mongoClient("mongodb-atlas") -//// .database(named: "car") -//// .collection(named: "Dog", type: Dog.self) -//// let id1 = try await collection -//// .insertOne(Person(name: "John", -//// age: 32, -//// address: Address(city: "Austin", state: "TX"))) -//// XCTAssertNotNil(id1.objectIdValue) -//// let id2 = try await collection -//// .insertOne(Person(name: "Jane", -//// age: 29, -//// address: Address(city: "Austin", state: "TX"))) -//// XCTAssertNotNil(id2.objectIdValue) -//// var count = try await collection.count() -//// XCTAssertEqual(count, 2) -//// -//// // MARK: $lt query -//// guard let meghna = try await collection.findOne({ person in -//// person.age < 30 -//// }) else { -//// return XCTFail() -//// } -//// XCTAssertNotNil(meghna) -//// XCTAssertEqual(meghna.name, "Jane") -//// XCTAssertEqual(meghna.age, 29) -//// -//// // MARK: $gt query -//// guard let jason = try await collection.findOne({ person in -//// person.age > 30 -//// }) else { -//// return XCTFail() -//// } -//// XCTAssertNotNil(jason) -//// XCTAssertEqual(jason.name, "John") -//// XCTAssertEqual(jason.age, 32) -//// -//// // MARK: nested query $eq -//// count = try await collection.find(filter: { person in -//// person.address.city == "Austin" -//// }).count -//// XCTAssertEqual(count, 2) -//// count = try await collection.find(filter: { person in -//// person.address.city == "NYC" -//// }).count -//// XCTAssertEqual(count, 0) -//// -//// count = try await collection.deleteOne { -//// $0.age > 30 -//// } -//// XCTAssertEqual(count, 1) -//// -//// count = try await collection.count() -//// XCTAssertEqual(count, 1) -//// -//// count = try await collection.deleteOne { -//// $0.age < 30 -//// } -//// XCTAssertEqual(count, 1) -//// _ = try await collection.deleteMany() -// } -//} + + func testAllTypes() async throws { + let app = self.app(id: try RealmServer.shared.createApp(fields: ["_id"], + types: [AllTypes.self])) + let user = try await app.login(credentials: .anonymous) + let collection = user.mongoClient("mongodb1") + .database(named: "test_data") + .collection(named: "AllTypes", type: AllTypes.self) + _ = try await collection.deleteMany() + + let oid = ObjectId.generate() + let allTypes = AllTypes(_id: oid, + strCol: "foo", + intCol: 42, + binaryCol: Data([1, 2, 3]), + arrayStrCol: ["bar", "baz"], + arrayIntCol: [1, 2], + optStrCol: nil, + optIntCol: nil, + subdocument: .init(strCol: "qux")) + + _ = try await collection.insertOne(allTypes) + + guard let foundPerson = try await collection.findOne() else { + return XCTFail("Could not find inserted Person") + } + XCTAssertEqual(foundPerson.strCol, "foo") + XCTAssertEqual(foundPerson.intCol, 42) + XCTAssertEqual(foundPerson.binaryCol, Data([1, 2, 3])) + XCTAssertEqual(foundPerson.arrayStrCol, ["bar", "baz"]) + XCTAssertEqual(foundPerson.arrayIntCol, [1, 2]) + XCTAssertEqual(foundPerson.subdocument, AllTypes.Subdocument(strCol: "qux")) + + let deleteCount = try await collection.deleteOne() + XCTAssertEqual(deleteCount.deletedCount, 1) + let nilPerson = try await collection.findOne() + XCTAssertNil(nilPerson) + } + + func testAllTypesObject() async throws { + let app = self.app(id: try RealmServer.shared.createApp(fields: ["_id"], + types: [AllTypes.self])) + let user = try await app.login(credentials: .anonymous) + let collection = user.mongoClient("mongodb1") + .database(named: "test_data") + .collection(named: "AllTypes", type: AllTypesObject.self) + let oid = ObjectId.generate() + + _ = try await collection.deleteMany() + let allTypes = AllTypesObject(_id: oid, + strCol: "foo", + intCol: 42, + boolCol: true, + binaryCol: Data([1, 2, 3]), + dateCol: Date(), + subdocumentCol: .init(strCol: "qux"), + arrayStrCol: ["bar", "baz"], + arrayIntCol: [1, 2], + arrayBinaryCol: [Data([1,2,3]),Data([4,5,6])], + arrayDateCol: [.distantPast, .distantFuture], + arraySubdocumentCol: [.init(strCol: "meep"), + .init(strCol: "moop")], + optStrCol: nil, + optIntCol: nil, + optDateCol: nil) + + _ = try await collection.insertOne(allTypes) + guard var foundPerson = try await collection.findOne() else { + return XCTFail("Could not find inserted Person") + } + XCTAssertEqual(foundPerson.strCol, "foo") + XCTAssertEqual(foundPerson.intCol, 42) + XCTAssertEqual(foundPerson.binaryCol, Data([1, 2, 3])) + XCTAssertEqual(foundPerson.arrayStrCol, ["bar", "baz"]) + XCTAssertEqual(foundPerson.arrayIntCol, [1, 2]) + XCTAssertEqual(foundPerson.arrayBinaryCol, [Data([1,2,3]),Data([4,5,6])]) + XCTAssertEqual(foundPerson.arrayDateCol, [.distantPast, .distantFuture]) + XCTAssertEqual(foundPerson.subdocumentCol?.strCol, AllTypes.Subdocument(strCol: "qux").strCol) + + let objects = try await collection.find { + $0._id == oid && $0.intCol == 42 + } + foundPerson = objects[0] + XCTAssertEqual(foundPerson.strCol, "foo") + XCTAssertEqual(foundPerson.optStrCol, nil) + XCTAssertEqual(foundPerson.intCol, 42) + XCTAssertEqual(foundPerson.binaryCol, Data([1, 2, 3])) + XCTAssertEqual(foundPerson.arrayStrCol, ["bar", "baz"]) + XCTAssertEqual(foundPerson.arrayIntCol, [1, 2]) + XCTAssertEqual(foundPerson.subdocumentCol?.strCol, AllTypes.Subdocument(strCol: "qux").strCol) + func buildQuery(_ query: (Query) -> Query) -> [String: Any] { + fatalError() + } + var count = try await collection.find { + $0.intCol == 41 + }.count + XCTAssertEqual(count, 0) + let deleteCount = try await collection.deleteOne() + XCTAssertEqual(deleteCount.deletedCount, 1) + count = try await collection.find { + $0.optStrCol == nil && $0.intCol > 42 + }.count + XCTAssertEqual(count, 0) + let nilPerson = try await collection.findOne() + XCTAssertNil(nilPerson) + } + + // MARK: Query Tests + override var appId: String { + Self.appId + } + static var appId: String! + override class func setUp() { + super.setUp() + Self.appId = try! RealmServer.shared.createApp(fields: ["_id"], + types: AllTypesObject.self) + + } + private var collection: MongoTypedCollection! + override func setUp() async throws { + try await super.setUp() + let app = self.app(id: Self.appId) + let user = try await app.login(credentials: .anonymous) + self.collection = user.mongoClient("mongodb1") + .database(named: "test_data") + .collection(named: "AllTypesObject", type: AllTypesObject.self) + _ = try await collection.deleteMany() + } + + override func tearDown() { + + } + override class func tearDown() { + try? RealmServer.shared.deleteApp(appId) + } + + fileprivate var defaultAllTypesObject: AllTypesObject { + AllTypesObject(_id: .generate(), + strCol: "foo", + intCol: 42, + boolCol: true, + binaryCol: Data([1, 2, 3]), + dateCol: Date(), + subdocumentCol: .init(strCol: "qux"), + arrayStrCol: ["bar", "baz"], + arrayIntCol: [1, 2], + arrayBinaryCol: [Data([1,2,3]),Data([4,5,6])], + arrayDateCol: [.distantPast, .distantFuture], + arraySubdocumentCol: [.init(strCol: "meep"), + .init(strCol: "moop")], + optStrCol: nil, + optIntCol: nil, + optDateCol: nil) + } + + // MARK: FindOne + func testFindOne() async throws { + let defaultAllTypesObject = self.defaultAllTypesObject + var foundDocument = try await collection.findOne() + XCTAssertNil(foundDocument) + let insertedId = try await self.collection.insertOne(defaultAllTypesObject).insertedId + XCTAssertEqual(insertedId, .objectId(defaultAllTypesObject._id)) + foundDocument = try await self.collection.findOne() + foundDocument?.compare(to: defaultAllTypesObject) ?? XCTFail("Could not find document.") + foundDocument = try await self.collection.findOne(["_id": defaultAllTypesObject._id]) + foundDocument?.compare(to: defaultAllTypesObject) ?? XCTFail("Could not find document.") + foundDocument = try await self.collection.findOne({ $0._id == defaultAllTypesObject._id }) + foundDocument?.compare(to: defaultAllTypesObject) ?? XCTFail("Could not find document.") + foundDocument = try await self.collection.findOne { $0._id == .generate() } + XCTAssertNil(foundDocument) + } + + // MARK: Update + func testUpdateOne() async throws { + let defaultAllTypesObject = self.defaultAllTypesObject + let default2 = self.defaultAllTypesObject + let value = try await self.collection.insertMany([defaultAllTypesObject, + default2]) + XCTAssertEqual(value.insertedIds.count, 2) + defaultAllTypesObject.intCol = 84 + var update = try await self.collection.updateOne(filter: { + $0.intCol == self.defaultAllTypesObject.intCol + }, update: defaultAllTypesObject) + XCTAssertEqual(update.matchedCount, 1) + XCTAssertEqual(update.modifiedCount, 1) + XCTAssertEqual(update.upsertedId, nil) + default2.intCol = -42 + default2.strCol = randomString(32) + default2._id = .generate() + update = try await self.collection.updateOne(filter: { + $0.intCol == -42 && $0._id == default2._id + }, update: default2, upsert: true) + XCTAssertEqual(update.matchedCount, 0) + XCTAssertEqual(update.modifiedCount, 0) + XCTAssertEqual(update.upsertedId, .objectId(default2._id)) + } + + func testUpdateMany() async throws { + let allTypesObject = self.defaultAllTypesObject + let allTypesObject2 = AllTypesObject(_id: .generate(), + strCol: "fee", + intCol: 64, + boolCol: false, + binaryCol: .init([60, 57, 100]), + dateCol: .now, arrayStrCol: ["fi", "fo", "fum"], + arrayIntCol: [98, 11, 7], + arrayBinaryCol: [.init([47, 89, 108])], + arrayDateCol: [ + .distantFuture, .distantPast + ], arraySubdocumentCol: [ + .init(strCol: "dum") + ]) + _ = try await self.collection.insertMany([allTypesObject, allTypesObject2]) + allTypesObject.intCol = 84 + var update = try await self.collection.updateMany(filter: { + $0.intCol == self.defaultAllTypesObject.intCol + }, update: allTypesObject) + XCTAssertEqual(update.matchedCount, 1) + update = try await self.collection.updateMany(filter: { + $0.strCol == self.defaultAllTypesObject.strCol + }, update: allTypesObject) + XCTAssertEqual(update.matchedCount, 1) + } + // MARK: $eq + func testFind_QueryEq() async throws { + let defaultAllTypesObject = self.defaultAllTypesObject + _ = try await self.collection.insertOne(defaultAllTypesObject) + try await XCTAssertEqualAsync(await self.collection.count { + $0.intCol == self.defaultAllTypesObject.intCol + }, 1) + try await XCTAssertEqualAsync(await self.collection.count { + $0.intCol == self.defaultAllTypesObject.intCol + 1 + }, 0) + try await XCTAssertEqualAsync(await self.collection.count { + $0.boolCol + }, 1) + try await XCTAssertEqualAsync(await self.collection.count { + !$0.boolCol + }, 0) + } + + // MARK: $ne + func testFind_QueryNe() async throws { + let defaultAllTypesObject = self.defaultAllTypesObject + _ = try await self.collection.insertOne(defaultAllTypesObject) + try await XCTAssertEqualAsync(await self.collection.count { + $0.intCol != self.defaultAllTypesObject.intCol + }, 0) + try await XCTAssertEqualAsync(await self.collection.count { + $0.intCol != self.defaultAllTypesObject.intCol + 1 + }, 1) + } + + // MARK: $not + func testFind_QueryNot() async throws { + let defaultAllTypesObject = self.defaultAllTypesObject + _ = try await self.collection.insertOne(defaultAllTypesObject) + try await XCTAssertEqualAsync(await self.collection.count { + !($0.intCol == self.defaultAllTypesObject.intCol) + }, 0) + try await XCTAssertEqualAsync(await self.collection.count { + !($0.intCol == self.defaultAllTypesObject.intCol + 1) + }, 1) + } + + // MARK: $gt + func testFind_QueryGt() async throws { + let defaultAllTypesObject = self.defaultAllTypesObject + _ = try await self.collection.insertOne(defaultAllTypesObject) + var count = try await self.collection.count { + $0.dateCol > Date.distantPast + } + XCTAssertEqual(count, 1) + count = try await self.collection.count { + $0.dateCol > Date.distantFuture + } + XCTAssertEqual(count, 0) + await XCTAssertEqualAsync(try await self.collection.count { + $0.intCol > 24 + }, 1) + await XCTAssertEqualAsync(try await self.collection.count { + $0.intCol > 42 + }, 0) + } + + // MARK: $gte + func testFind_QueryGte() async throws { + let defaultAllTypesObject = self.defaultAllTypesObject + _ = try await self.collection.insertOne(defaultAllTypesObject) + var count = try await self.collection.count { + $0.dateCol >= Date.distantPast + } + XCTAssertEqual(count, 1) + count = try await self.collection.count { + $0.dateCol >= Date.distantFuture + } + XCTAssertEqual(count, 0) + await XCTAssertEqualAsync(try await self.collection.count { + $0.intCol >= 24 + }, 1) + await XCTAssertEqualAsync(try await self.collection.count { + $0.intCol >= 43 + }, 0) + } + + // MARK: BETWEEN + func testFind_QueryBetween() async throws { + let defaultAllTypesObject = self.defaultAllTypesObject + _ = try await self.collection.insertOne(defaultAllTypesObject) + var count = try await self.collection.count { + $0.dateCol >= Date.distantPast + } + XCTAssertEqual(count, 1) + count = try await self.collection.count { + $0.dateCol >= Date.distantFuture + } + XCTAssertEqual(count, 0) + await XCTAssertEqualAsync(try await self.collection.count { + $0.intCol.contains(41..<43) + }, 1) + await XCTAssertEqualAsync(try await self.collection.count { + $0.intCol.contains(43..<45) + }, 0) + } + + // MARK: subdocument + func testFind_QuerySubdocument() async throws { + let defaultAllTypesObject = self.defaultAllTypesObject + _ = try await self.collection.insertOne(defaultAllTypesObject) + var count = try await self.collection.find { + $0.subdocumentCol.strCol.contains("q") + }.count + XCTAssertEqual(count, 1) + count = try await self.collection.find { + $0.subdocumentCol.strCol.contains("z") + }.count + XCTAssertEqual(count, 0) + count = try await self.collection.find { + $0.subdocumentCol.strCol == "qux" + }.count + XCTAssertEqual(count, 1) + count = try await self.collection.find { + $0.subdocumentCol.strCol != "qux" + }.count + XCTAssertEqual(count, 0) + } + + // MARK: $in + func testFind_QueryArrayIn() async throws { + let defaultAllTypesObject = self.defaultAllTypesObject + _ = try await self.collection.insertOne(defaultAllTypesObject) + var count = Int(try await self.collection.count { + $0.arrayIntCol.containsAny(in: [1, 3]) + }) + XCTAssertEqual(count, 1) + count = try await self.collection.find { + $0.arrayIntCol.containsAny(in: [4, 3]) + }.count + XCTAssertEqual(count, 0) + count = try await self.collection.find { + $0.arrayIntCol.contains(1) + }.count + XCTAssertEqual(count, 1) + count = try await self.collection.find { + $0.arrayIntCol.contains(3) + }.count + XCTAssertEqual(count, 0) + } +} + +@_unsafeInheritExecutor +func XCTAssertEqualAsync(_ lhs: @autoclosure () async throws -> T, + _ rhs: @autoclosure () async throws -> T) async where T: Equatable { + do { + let lhsValue = try await lhs() + let rhsValue = try await rhs() + XCTAssertEqual(lhsValue, rhsValue) + } catch { + XCTFail(error.localizedDescription) + } } diff --git a/Realm/ObjectServerTests/setup_baas.rb b/Realm/ObjectServerTests/setup_baas.rb index 95fb2ad7cd..4a0eaeb819 100644 --- a/Realm/ObjectServerTests/setup_baas.rb +++ b/Realm/ObjectServerTests/setup_baas.rb @@ -156,18 +156,20 @@ def setup_stitch puts `ls -l #{stitch_dir}/etc/transpiler/bin` exports << "export GOROOT=\"#{go_root}\"" - exports << "export PATH=\"$GOROOT/bin:$PATH\"" +# exports << "export PATH=\"$PATH:$GOROOT/bin:$PATH\"" exports << "export STITCH_PATH=\"#{stitch_dir}\"" - exports << "export PATH=\"$PATH:$STITCH_PATH/etc/transpiler/bin\"" +# exports << "export PATH=\"$PATH:$STITCH_PATH/etc/transpiler/bin\"" exports << "export DYLD_LIBRARY_PATH='#{LIB_DIR}'" exports << "export GOPRIVATE=\"github.com/10gen/*\"" - + exports << "export GOPATH=\"${stitch_dir}\"" + exports << "export GO111MODULE=off" + +# puts "GOROOT=#{go_root}" puts 'build create_user binary' puts `#{exports.join(' && ')} && \ - cd '#{stitch_dir}' && \ - #{go_root}/bin/go build -o create_user cmd/auth/user.go && + #{go_root}/bin/go build -o create_user #{stitch_dir}/cmd/auth/user.go && \ cp -c create_user '#{BIN_DIR}'` puts 'create_user binary built' @@ -175,8 +177,7 @@ def setup_stitch puts 'building server binary' puts `#{exports.join(' && ')} && \ - cd '#{stitch_dir}' && \ - #{go_root}/bin/go build -o stitch_server cmd/server/main.go + #{go_root}/bin/go build -o stitch_server #{stitch_dir}/cmd/server/main.go && \ cp -c stitch_server '#{BIN_DIR}'` puts 'server binary built' diff --git a/RealmSwift/ExtJSON.swift b/RealmSwift/ExtJSON.swift deleted file mode 100644 index c10ea0b2c6..0000000000 --- a/RealmSwift/ExtJSON.swift +++ /dev/null @@ -1,299 +0,0 @@ -import Foundation - -extension Optional: ExtJSON { -} -extension Optional: ExtJSONLiteral, ExpressibleByExtJSONLiteral -where Wrapped: ExpressibleByExtJSONLiteral { - public typealias ExtJSONValue = Optional - - public init(extJSONValue value: Optional) throws { - if let value = value { - self = try Wrapped(extJSONValue: value) - } else { - self = nil - } - } - public var extJSONValue: Optional { - if let wrapped = self { - return wrapped.extJSONValue - } else { - return nil - } - } -} - -// MARK: Int -extension Int: ExtJSONObjectRepresentable { - public typealias ExtJSONValue = ExtJSONDocument - - public init(extJSONValue value: ExtJSONDocument) throws { - guard let numberLong = value["$numberInt"] as? String, - let number = Int(numberLong) else { - throw JSONError.missingKey(key: "$numberInt") - } - self = number - } - - public var extJSONValue: ExtJSONDocument { - [ - "$numberInt": String(self) - ] - } -} - -// MARK: Int64 -extension Int64: ExtJSONObjectRepresentable { - public init(extJSONValue value: ExtJSONDocument) throws { - guard let numberLong = value["$numberLong"] as? String, - let number = Int64(numberLong) else { - throw JSONError.missingKey(key: "$numberLong") - } - self = number - } - - public var extJSONValue: ExtJSONDocument { - [ - "$numberLong": String(self) - ] - } -} - -// MARK: ObjectId -extension ObjectId: ExtJSONObjectRepresentable { - public convenience init(extJSONValue value: ExtJSONDocument) throws { - guard let oid = value["$oid"] as? String else { - throw JSONError.missingKey(key: "$oid") - } - try self.init(string: oid) - } - public var extJSONValue: ExtJSONDocument { - [ - "$oid": self.stringValue - ] - } -} - -// MARK: Double -extension Double: ExtJSONObjectRepresentable { - public init(extJSONValue value: ExtJSONDocument) throws { - guard let double = value["$numberDouble"] as? String else { - throw JSONError.missingKey(key: "$numberDouble") - } - self.init(double)! - } - - public var extJSONValue: ExtJSONDocument { - [ - "$numberDouble": String(self) - ] - } -} - -// MARK: Date -extension Date: ExtJSONObjectRepresentable { - public init(extJSONValue value: ExtJSONDocument) throws { - guard let date = value["$date"] as? NSDictionary, - let epochString = date["$numberLong"] as? String, - let epoch = Int64(epochString) else { - throw JSONError.missingKey(key: "$date") - } - self.init(timeIntervalSince1970: TimeInterval(epoch)/1_000) - } - - public var extJSONValue: ExtJSONDocument { - [ - "$date": [ - "$numberLong": String(Int64(self.timeIntervalSince1970 * 1_000)) - ] - ] - } -} - -// MARK: Data -extension Data: ExtJSONObjectRepresentable { - public typealias ExtJSONValue = [String: Any] - - public init(extJSONValue value: ExtJSONValue) throws { - guard let value = value["$binary"] as? [String: String], - let value = value["base64"], - let data = Data(base64Encoded: value) else { - throw JSONError.missingKey(key: "base64") - } - self = data - } - - public var extJSONValue: ExtJSONValue { - [ - "$binary": ["base64": self.base64EncodedString()] - ] - } -} - -extension Decimal128: ExtJSONObjectRepresentable { - public typealias ExtJSONValue = [String: Any] -// @BSONCodable public struct ExtJSONValue { -// @BSONCodable(key: "numberDecimal") let numberDecimal: String -// } - public convenience init(extJSONValue value: ExtJSONValue) throws { - fatalError() -// try self.init(string: value.numberDecimal) - } - - public var extJSONValue: ExtJSONValue { - fatalError() -// ExtJSONValue(numberDecimal: self.stringValue) - } -} - -//extension NSRegularExpression : ExtJSONObjectRepresentable { -// public init(extJSONValue value: Dictionary) throws { -// guard let regExDictionary = value["$regularExpression"] as? [String: String], -// let pattern = regExDictionary["pattern"], -// let options = regExDictionary["options"] -// else { -// throw JSONError.missingKey(key: "base64") -// } -// try self.init(pattern: pattern) -// } -// -// public var extJSONValue: Dictionary { -// [ -// "$regularExpression": ["pattern": "\(self.pattern)"] -// ] -// } -//} - -@available(macOS 13.0, *) -extension Regex: ExtJSONObjectRepresentable { - public init(extJSONValue value: Dictionary) throws { - guard let regExDictionary = value["$regularExpression"] as? [String: String], - let pattern = regExDictionary["pattern"], - let options = regExDictionary["options"] - else { - throw JSONError.missingKey(key: "base64") - } - try self.init(pattern) - } - - public var extJSONValue: Dictionary { - [ - "$regularExpression": ["pattern": "\(self.regex)"] - ] - } -} - -public protocol _ExtJSON { - associatedtype ExtJSONValue: _ExtJSON - init(extJSONValue value: ExtJSONValue) throws - var extJSONValue: ExtJSONValue { get } -} - -// MARK: Literal Conformance -public protocol _ExtJSONLiteral : _ExtJSON where ExtJSONValue == Self { -} -extension _ExtJSONLiteral { -} -extension String: _ExtJSONLiteral { -} -extension Bool: _ExtJSONLiteral { -} -// MARK: Array Conformance -//public protocol ExtJSONArrayRepresentable : MutableCollection, _ExtJSON where Element: _ExtJSON { -// init() -// associatedtype ExtJSONElement -// init(extJSONValue value: [ExtJSONElement]) throws -// var extJSONValue: [ExtJSONElement] { get } -//} -//public protocol ExtJSONObjectArrayRepresentable : ExtJSONArrayRepresentable -// where Element: ExtJSONObjectRepresentable { -// -//} -//public protocol ExtJSONLiteralArrayRepresentable : ExtJSONArrayRepresentable -// where Element: _ExtJSONLiteral { -// init(extJSONValue value: [Element]) throws -// var extJSONValue: [Element] { get } -//} -// -//extension ExtJSONObjectArrayRepresentable where Self: MutableCollection { -// init(extJSONValue value: [Element]) throws {} -// var extJSONValue: [Element] { -// [] -// } -//} -//extension Array: ExtJSONArrayRepresentable, _ExtJSON where Element: _ExtJSON { -// public init(extJSONValue value: [Element.ExtJSONValue]) throws { -// self.init() -// try self.append(contentsOf: value.map(Element.init)) -// } -// public var extJSONValue: [Element.ExtJSONValue] { -// self.map(\.extJSONValue) -// } -//} -//extension Array : ExtJSONObjectArrayRepresentable where Element: ExtJSONObjectRepresentable { -//} -//extension Array : ExtJSONLiteralArrayRepresentable where Element: _ExtJSONLiteral { -//} -//extension List: ExtJSONArrayRepresentable, _ExtJSON where Element: _ExtJSON { -// public typealias ExtJSONValue = [Element.ExtJSONValue] -// public convenience init(extJSONValue value: [Element.ExtJSONValue]) throws { -// self.init() -// try self.append(objectsIn: value.map(Element.init)) -// } -// -// public var extJSONValue: [Element.ExtJSONValue] { -// self.map(\.extJSONValue) -// } -//} -// -//// MARK: KeyValuePairs Conformance -//protocol ExtJSONDictionaryRepresentable : _ExtJSON {} -//extension Dictionary: ExtJSONDictionaryRepresentable, _ExtJSON where Key == String, Value: _ExtJSON { -//} -// -public protocol ExtJSONObjectRepresentable : ExpressibleByExtJSONLiteral where ExtJSONValue == [String: Any] { -} - -//extension MutableSet: ExtJSONArrayRepresentable where Element: ExtJSON {} -//extension MaxKey: ExtJSONObjectRepresentable { -// public required init(extJSONValue value: Dictionary) throws { -// -// } -// public var extJSONValue: Dictionary { -// <#code#> -// } -// -// -//} -//extension MinKey: ExtJSONObjectRepresentable { -// -//} -/* - expressibleByExtJsonLiteral - ––––––– - extjsonliterals - ––––––– - string - bool - array of any extjson - document of any extjson - - extjsonobjectrepresentable - ––––––– - int - oid - double - data - date - - extjsoncollection - –––––– - array of extended extjsonrep - dictionary of extended extjsonrep - */ - - -public enum JSONError : Error { - case missingKey(key: String) - case invalidType(_ type: String) -} - diff --git a/RealmSwift/ExtJSONCodable.swift b/RealmSwift/ExtJSONCodable.swift deleted file mode 100644 index 013a00a79f..0000000000 --- a/RealmSwift/ExtJSONCodable.swift +++ /dev/null @@ -1,942 +0,0 @@ -import Foundation - -// MARK: Decoder -class _ExtJSONDecoder : Decoder { - func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { - guard let storage = storage as? [String: Any] else { - fatalError() - } - return KeyedDecodingContainer(Self.KeyedContainer(storage: storage, - codingPath: codingPath, - allKeys: [])) - } - - func unkeyedContainer() throws -> UnkeyedDecodingContainer { - Self.UnkeyedContainer.init(storage: storage as! [Any], codingPath: codingPath, currentIndex: 0) - } - - func singleValueContainer() throws -> SingleValueDecodingContainer { - SingleValueContainer(storage: storage, codingPath: codingPath) - } - - var codingPath: [CodingKey] = [] - - var userInfo: [CodingUserInfoKey : Any] = [:] - - var container: (any _ExtJSONContainer)? - - var storage: Any - init(storage: Any, codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any], container: (any _ExtJSONContainer)?) throws { - self.codingPath = codingPath - self.userInfo = userInfo - self.container = container - self.storage = storage - } - class SingleValueContainer : SingleValueDecodingContainer { - var storage: Any? - var codingPath: [CodingKey] = [] - - init(storage: Any? = nil, codingPath: [CodingKey]) { - self.storage = storage - self.codingPath = codingPath - } - private func unwrap(_ value: Any?) throws -> T { - guard let value = value else { - throw DecodingError.valueNotFound(T.self, .init(codingPath: codingPath, - debugDescription: "")) - } - guard let value = value as? T else { - throw DecodingError.typeMismatch(T.self, .init(codingPath: codingPath, - debugDescription: "")) - } - return value - } - private func unwrapObject(_ value: Any?) throws -> [String : Any] { - guard let value = value else { - throw DecodingError.valueNotFound([String: Any].self, .init(codingPath: codingPath, - debugDescription: "")) - } - guard let value = value as? [String: Any] else { - throw DecodingError.typeMismatch([String: Any].self, .init(codingPath: codingPath, - debugDescription: "")) - } - return value - } - - func decodeNil() -> Bool { - return storage == nil - } - - func decode(_ type: Bool.Type) throws -> Bool { - try unwrap(self.storage) - } - - func decode(_ type: String.Type) throws -> String { - try unwrap(self.storage) - } - - func decode(_ type: Double.Type) throws -> Double { - guard let stringValue = try unwrapObject(self.storage)["$numberDouble"] as? String else { - throw DecodingError.typeMismatch([String: Any].self, .init(codingPath: codingPath, - debugDescription: "")) - } - guard let doubleValue = Double(stringValue) else { - throw DecodingError.typeMismatch([String: Any].self, .init(codingPath: codingPath, - debugDescription: "")) - } - return doubleValue - } - - func decode(_ type: Float.Type) throws -> Float { - guard let stringValue = try unwrapObject(self.storage)["$numberDouble"] as? String else { - throw DecodingError.typeMismatch([String: Any].self, .init(codingPath: codingPath, - debugDescription: "")) - } - guard let floatValue = Float(stringValue) else { - throw DecodingError.typeMismatch([String: Any].self, .init(codingPath: codingPath, - debugDescription: "")) - } - return floatValue - } - - func decode(_ type: Int.Type) throws -> Int { - guard let stringValue = try unwrapObject(self.storage)["$numberInt"] as? String else { - throw DecodingError.typeMismatch([String: Any].self, .init(codingPath: codingPath, - debugDescription: "")) - } - guard let intValue = Int(stringValue) else { - throw DecodingError.typeMismatch([String: Any].self, .init(codingPath: codingPath, - debugDescription: "")) - } - return intValue - } - - func decode(_ type: Int8.Type) throws -> Int8 { - fatalError() - } - - func decode(_ type: Int16.Type) throws -> Int16 { - fatalError() - } - - func decode(_ type: Int32.Type) throws -> Int32 { - fatalError() - } - - func decode(_ type: Int64.Type) throws -> Int64 { - fatalError() - } - - func decode(_ type: UInt.Type) throws -> UInt { - fatalError() - } - - func decode(_ type: UInt8.Type) throws -> UInt8 { - fatalError() - } - - func decode(_ type: UInt16.Type) throws -> UInt16 { - fatalError() - } - - func decode(_ type: UInt32.Type) throws -> UInt32 { - fatalError() - } - - func decode(_ type: UInt64.Type) throws -> UInt64 { - fatalError() - } - - func decode(_ type: T.Type) throws -> T where T : Decodable { - switch type { - case is any ExtJSONLiteral.Type: - return self.storage as! T - case let type as any ExtJSONObjectRepresentable.Type: - return try type.init(extJSONValue: self.storage as! Dictionary) as! T - default: - let decoder = try _ExtJSONDecoder(storage: storage as! [String: Any], codingPath: codingPath, userInfo: [:], container: nil) - return try T(from: decoder) - } - } - } - - class KeyedContainer : _ExtJSONContainer, KeyedDecodingContainerProtocol - where Key: CodingKey { - var storage: [String: Any] - - var codingPath: [CodingKey] - var allKeys: [Key] - - init(storage: [String: Any], codingPath: [CodingKey], allKeys: [CodingKey]) { - self.storage = storage - self.codingPath = codingPath - self.allKeys = allKeys as! [Key] - } - - func contains(_ key: Key) -> Bool { - storage.index(forKey: key.stringValue) != nil - } - - func decodeNil(forKey key: Key) throws -> Bool { - storage.index(forKey: key.stringValue) == nil || - storage[key.stringValue] as? NSObject == NSNull() - } - - func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { - storage[key.stringValue] as! Bool - } - - func decode(_ type: String.Type, forKey key: Key) throws -> String { - storage[key.stringValue] as! String - } - - func decode(_ type: Double.Type, forKey key: Key) throws -> Double { - guard let value = storage[key.stringValue] as? [String: String] else { - throw DecodingError.typeMismatch(Int.self, .init(codingPath: codingPath, debugDescription: "")) - } - return Double(value["$numberDouble"]!)! - } - - func decode(_ type: Float.Type, forKey key: Key) throws -> Float { - fatalError() - } - - func decode(_ type: Int.Type, forKey key: Key) throws -> Int { - guard let value = storage[key.stringValue] as? [String: String] else { - throw DecodingError.typeMismatch(Int.self, .init(codingPath: codingPath, debugDescription: "")) - } - return Int(value["$numberInt"]!)! - } - - func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { - fatalError() - } - - func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { - fatalError() - } - - func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { - fatalError() - } - - func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { - guard let value = storage[key.stringValue] as? [String: String] else { - throw DecodingError.typeMismatch(Int.self, .init(codingPath: codingPath, debugDescription: "")) - } - return Int64(value["$numberLong"]!)! - } - - func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { - fatalError() - } - - func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { - fatalError() - } - - func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { - fatalError() - } - - func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { - fatalError() - } - - func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { - fatalError() - } - - func decode(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable { - switch type { - case is ObjectId.Type: - guard let value = storage[key.stringValue] as? [String: String] else { - throw DecodingError.typeMismatch(ObjectId.self, .init(codingPath: codingPath, debugDescription: "")) - } - return try ObjectId(string: value["$oid"]!) as! T - case is Data.Type: - guard let value = storage[key.stringValue] as? [String: [String: String]], - let value = value["$binary"], - let value = value["base64"] else { - throw DecodingError.typeMismatch(ObjectId.self, .init(codingPath: codingPath, debugDescription: "")) - } - return Data(base64Encoded: value)! as! T - case is any Collection.Type: - let decoder = try _ExtJSONDecoder(storage: storage[key.stringValue] as! [Any], codingPath: codingPath, userInfo: [:], container: nil) - return try T(from: decoder) - case is any ExtJSONLiteral.Type: - return self.storage[key.stringValue] as! T - case let type as any ExtJSONObjectRepresentable.Type: - print("doing stuff") - return try type.init(extJSONValue: self.storage[key.stringValue] as! Dictionary) as! T - default: - let decoder = try _ExtJSONDecoder(storage: storage[key.stringValue] as! [String: Any], codingPath: codingPath, userInfo: [:], container: nil) - return try T(from: decoder) - } - } - - func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - fatalError() - } - - func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { - fatalError() - } - - func superDecoder() throws -> Decoder { - fatalError() - } - - func superDecoder(forKey key: Key) throws -> Decoder { - fatalError() - } - - var data: Data { - fatalError() - } - } - - class UnkeyedContainer : _ExtJSONContainer, UnkeyedDecodingContainer { - init(storage: [Any], codingPath: [CodingKey], currentIndex: Int) { - self.storage = storage - self.codingPath = codingPath - self.currentIndex = currentIndex - } - func decode(_ type: String.Type) throws -> String { - fatalError() - } - - func decode(_ type: Double.Type) throws -> Double { - fatalError() - } - - func decode(_ type: Float.Type) throws -> Float { - fatalError() - } - - func decode(_ type: Int.Type) throws -> Int { - fatalError() - } - - func decode(_ type: Int8.Type) throws -> Int8 { - fatalError() - } - - func decode(_ type: Int16.Type) throws -> Int16 { - fatalError() - } - - func decode(_ type: Int32.Type) throws -> Int32 { - fatalError() - } - - func decode(_ type: Int64.Type) throws -> Int64 { - fatalError() - } - - func decode(_ type: UInt.Type) throws -> UInt { - fatalError() - } - - func decode(_ type: UInt8.Type) throws -> UInt8 { - fatalError() - } - - func decode(_ type: UInt16.Type) throws -> UInt16 { - fatalError() - } - - func decode(_ type: UInt32.Type) throws -> UInt32 { - fatalError() - } - - func decode(_ type: UInt64.Type) throws -> UInt64 { - fatalError() - } - - func decode(_ type: T.Type) throws -> T where T : Decodable { - defer { - self.currentIndex += 1 - } - switch type { - case is any ExtJSONLiteral.Type: - return self.storage[self.currentIndex] as! T - case let type as any ExtJSONObjectRepresentable.Type: - print("doing stuff") - return try type.init(extJSONValue: self.storage[self.currentIndex] as! Dictionary) as! T - default: - let decoder = try _ExtJSONDecoder(storage: self.storage[self.currentIndex] as! Dictionary, codingPath: codingPath, userInfo: [:], container: nil) - return try T.init(from: decoder) - } - } - - var storage: [Any] - - func decode(_ type: Bool.Type) throws -> Bool { - fatalError() - } - - typealias StorageType = [Any] - - var data: Data { - fatalError() - } - - var codingPath: [CodingKey] - - var count: Int? { - storage.count - } - - var isAtEnd: Bool { - storage.count <= self.currentIndex - } - - var currentIndex: Int - - func decodeNil() throws -> Bool { - fatalError() - } - - func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - fatalError() - } - - func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { - fatalError() - } - - func superDecoder() throws -> Decoder { - fatalError() - } - } -} - -final public class ExtJSONDecoder { - public init() { - } - - /** - A dictionary you use to customize the encoding process - by providing contextual information. - */ - public var userInfo: [CodingUserInfoKey : Any] = [:] - - /** - Returns a MessagePack-encoded representation of the value you supply. - - - Parameters: - - value: The value to encode as MessagePack. - - Throws: `EncodingError.invalidValue(_:_:)` - if the value can't be encoded as a MessagePack object. - */ - public func decode(_ type: T.Type, from data: Data) throws -> T where T : Decodable { - switch try JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed) { - case let value as [String: Any]: - let decoder = try _ExtJSONDecoder(storage: value, - codingPath: [], - userInfo: self.userInfo, - container: nil) - decoder.userInfo = self.userInfo - return try T(from: decoder) - default: - throw DecodingError.valueNotFound(T.self, .init(codingPath: [], - debugDescription: "Invalid input")) - } - - } -} -// MARK: Encoder -class _ExtJSONEncoder { - var codingPath: [CodingKey] = [] - - var userInfo: [CodingUserInfoKey : Any] = [:] - - var container: (any _ExtJSONContainer)? -} - -final public class ExtJSONEncoder { - public init() {} - - /** - A dictionary you use to customize the encoding process - by providing contextual information. - */ - public var userInfo: [CodingUserInfoKey : Any] = [:] - - /** - Returns a MessagePack-encoded representation of the value you supply. - - - Parameters: - - value: The value to encode as MessagePack. - - Throws: `EncodingError.invalidValue(_:_:)` - if the value can't be encoded as a MessagePack object. - */ - public func encode(_ value: T) throws -> Data where T : Encodable { - let encoder = _ExtJSONEncoder() - encoder.userInfo = self.userInfo - try value.encode(to: encoder) - - return try encoder.container!.data - } -} - -protocol _ExtJSONContainer { -// var storage: [String: Any] { get } - associatedtype StorageType - var storage: StorageType { get } - var data: Data { get throws } -} - -extension _ExtJSONEncoder { - final class SingleValueContainer : _ExtJSONContainer { - var storage: Any? - - fileprivate var canEncodeNewValue = true - fileprivate func checkCanEncode(value: Any?) throws { - guard self.canEncodeNewValue else { - let context = EncodingError.Context(codingPath: self.codingPath, debugDescription: "Attempt to encode value through single value container when previously value already encoded.") - throw EncodingError.invalidValue(value as Any, context) - } - } - - var codingPath: [CodingKey] - var userInfo: [CodingUserInfoKey: Any] - - init(codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) { - self.codingPath = codingPath - self.userInfo = userInfo - } - - var data: Data { - get throws { - try JSONSerialization.data(withJSONObject: storage) - } - } - } -} - -extension _ExtJSONEncoder.SingleValueContainer: SingleValueEncodingContainer { - func encodeNil() throws { - try checkCanEncode(value: nil) - defer { self.canEncodeNewValue = false } - - self.storage = NSNull() - } - - func encode(_ value: Bool) throws { - try checkCanEncode(value: nil) - defer { self.canEncodeNewValue = false } - - self.storage = value - } - - func encode(_ value: String) throws { - try checkCanEncode(value: value) - defer { self.canEncodeNewValue = false } - - self.storage = value - } - - func encode(_ value: Double) throws { - try checkCanEncode(value: value) - defer { self.canEncodeNewValue = false } - - self.storage = [ - "$numberDouble": value.description - ] - } - - func encode(_ value: Float) throws { - try checkCanEncode(value: value) - defer { self.canEncodeNewValue = false } - - fatalError() - } - - func encode(_ value: T) throws where T : BinaryInteger & Encodable { - try checkCanEncode(value: value) - defer { self.canEncodeNewValue = false } - - switch value { - case let value as Int: - storage = [ - "$numberInt": value.description - ] - case let value as Int64: - storage = [ - "$numberLong": value.description - ] - default: - throw EncodingError.invalidValue(value, - EncodingError.Context(codingPath: codingPath, debugDescription: "Invalid BinaryInteger type.")) - } - } - - func encode(_ value: Int8) throws { - try checkCanEncode(value: value) - defer { self.canEncodeNewValue = false } - - fatalError() - } - - func encode(_ value: Int16) throws { - try checkCanEncode(value: value) - defer { self.canEncodeNewValue = false } - - fatalError() - } - - func encode(_ value: Int32) throws { - try checkCanEncode(value: value) - defer { self.canEncodeNewValue = false } - - fatalError() - } - - func encode(_ value: Int64) throws { - try checkCanEncode(value: value) - defer { self.canEncodeNewValue = false } - self.storage = [ - "$numberLong": value.description - ] - } - - func encode(_ value: UInt8) throws { - try checkCanEncode(value: value) - defer { self.canEncodeNewValue = false } - fatalError() - } - - func encode(_ value: UInt16) throws { - try checkCanEncode(value: value) - defer { self.canEncodeNewValue = false } - fatalError() - } - - func encode(_ value: UInt32) throws { - try checkCanEncode(value: value) - defer { self.canEncodeNewValue = false } - fatalError() - } - - func encode(_ value: UInt64) throws { - try checkCanEncode(value: value) - defer { self.canEncodeNewValue = false } - fatalError() - } - - func encode(_ value: Date) throws { - try checkCanEncode(value: value) - defer { self.canEncodeNewValue = false } - self.storage = [ - "$date": ["$numberLong": Int64(value.timeIntervalSince1970)] - ] - } - - func encode(_ value: Data) throws { - try checkCanEncode(value: value) - defer { self.canEncodeNewValue = false } - self.storage = [ - "$binary": ["base64": value.base64EncodedString()] - ] - } - - func encode(_ value: Decimal128) throws { - try checkCanEncode(value: value) - defer { self.canEncodeNewValue = false } - self.storage = [ - "$numberDecimal": value.stringValue - ] - } - - func encode(_ value: ObjectId) throws { - try checkCanEncode(value: value) - defer { self.canEncodeNewValue = false } - self.storage = [ - "$oid": value.stringValue - ] - } - - func encode(_ value: T) throws where T : Encodable { - try checkCanEncode(value: value) - defer { self.canEncodeNewValue = false } - let encoder = _ExtJSONEncoder() - switch value { -// case let value as ObjectId: -// self.storage = [ -// "$oid": value.stringValue -// ] - case let value as any ExtJSONObjectRepresentable: - self.storage = value.extJSONValue - case let value as Data: - try encode(value) - default: - try value.encode(to: encoder) - self.storage = encoder.container?.storage - } - } -} - -extension _ExtJSONEncoder: Encoder { - fileprivate func assertCanCreateContainer() { - precondition(self.container == nil) - } - - func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { - assertCanCreateContainer() - - let container = KeyedContainer(codingPath: self.codingPath, userInfo: self.userInfo) - self.container = container - - return KeyedEncodingContainer(container) - } - - func unkeyedContainer() -> UnkeyedEncodingContainer { - assertCanCreateContainer() - - let container = UnkeyedContainer(codingPath: self.codingPath, userInfo: self.userInfo) - self.container = container - - return container - } - - func singleValueContainer() -> SingleValueEncodingContainer { - assertCanCreateContainer() - - let container = SingleValueContainer(codingPath: self.codingPath, userInfo: self.userInfo) - self.container = container - - return container - } -} - -// MARK: KeyedContainer -extension _ExtJSONEncoder { - final class KeyedContainer : _ExtJSONContainer where Key: CodingKey { - var storage: [String: any _ExtJSONContainer] = [:] - - var codingPath: [CodingKey] - var userInfo: [CodingUserInfoKey: Any] - - func nestedCodingPath(forKey key: CodingKey) -> [CodingKey] { - return self.codingPath + [key] - } - - init(codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) { - self.codingPath = codingPath - self.userInfo = userInfo - } - var data: Data { - get throws { - try JSONSerialization.data(withJSONObject: storage.storage) - } - } - } -} - -extension _ExtJSONEncoder.KeyedContainer: KeyedEncodingContainerProtocol { - func encodeNil(forKey key: Key) throws { - var container = self.nestedSingleValueContainer(forKey: key) - try container.encodeNil() - } - - func encode(_ value: T, forKey key: Key) throws where T : Encodable { - let container = _ExtJSONEncoder.SingleValueContainer(codingPath: self.nestedCodingPath(forKey: key), - userInfo: self.userInfo) - try container.encode(value) - self.storage[key.stringValue] = container - } - - private func nestedSingleValueContainer(forKey key: Key) -> SingleValueEncodingContainer { - let container = _ExtJSONEncoder.SingleValueContainer(codingPath: self.nestedCodingPath(forKey: key), - userInfo: self.userInfo) - - return container - } - - func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { - let container = _ExtJSONEncoder.UnkeyedContainer(codingPath: self.nestedCodingPath(forKey: key), userInfo: self.userInfo) - self.storage[key.stringValue] = container - - return container - } - - func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { - let container = _ExtJSONEncoder.KeyedContainer(codingPath: self.nestedCodingPath(forKey: key), userInfo: self.userInfo) - self.storage[key.stringValue] = container - - return KeyedEncodingContainer(container) - } - - func superEncoder() -> Encoder { - fatalError("Unimplemented") // FIXME - } - - func superEncoder(forKey key: Key) -> Encoder { - fatalError("Unimplemented") // FIXME - } -} - -struct AnyCodingKey: CodingKey, Equatable { - var stringValue: String - var intValue: Int? - - init?(stringValue: String) { - self.stringValue = stringValue - self.intValue = nil - } - - init?(intValue: Int) { - self.stringValue = "\(intValue)" - self.intValue = intValue - } - - init(_ base: Key) where Key : CodingKey { - if let intValue = base.intValue { - self.init(intValue: intValue)! - } else { - self.init(stringValue: base.stringValue)! - } - } -} - -extension AnyCodingKey: Hashable { - var hashValue: Int { - return self.intValue?.hashValue ?? self.stringValue.hashValue - } -} - -extension Array : _ExtJSONContainer where Element == any _ExtJSONContainer { - var data: Data { - get throws { - fatalError() - } - } - - var storage: [Any] { - self.map { - if let value = $0.storage as? any _ExtJSONContainer { - value.storage - } else { - $0.storage - } - } - } -} -extension Dictionary : _ExtJSONContainer where Key == String, Value == any _ExtJSONContainer { - var data: Data { - get throws { - fatalError() - } - } - - var storage: [String: Any] { - self.reduce(into: [String: Any](), { - if let value = $1.value.storage as? any _ExtJSONContainer { - $0[$1.key] = value.storage - } else { - $0[$1.key] = $1.value.storage - } - }) - } -} - -extension _ExtJSONEncoder { - final class UnkeyedContainer : _ExtJSONContainer { - var storage: [any _ExtJSONContainer] = [] - - var count: Int { - return storage.count - } - - var codingPath: [CodingKey] - - var nestedCodingPath: [CodingKey] { - return self.codingPath + [AnyCodingKey(intValue: self.count)!] - } - - var userInfo: [CodingUserInfoKey: Any] - - init(codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) { - self.codingPath = codingPath - self.userInfo = userInfo - } - var data: Data { - get throws { - try JSONSerialization.data(withJSONObject: storage.storage) - } - } - } -} - -extension _ExtJSONEncoder.UnkeyedContainer: UnkeyedEncodingContainer { - func encodeNil() throws { - var container = self.nestedSingleValueContainer() - try container.encodeNil() - } - - func encode(_ value: T) throws where T : Encodable { - var container = self.nestedSingleValueContainer() - try container.encode(value) - } - - private func nestedSingleValueContainer() -> SingleValueEncodingContainer { - let container = _ExtJSONEncoder.SingleValueContainer(codingPath: self.nestedCodingPath, userInfo: self.userInfo) - self.storage.append(container) - - return container - } - - func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer where NestedKey : CodingKey { - let container = _ExtJSONEncoder.KeyedContainer(codingPath: self.nestedCodingPath, - userInfo: self.userInfo) - self.storage.append(container) - - return KeyedEncodingContainer(container) - } - - func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { - let container = _ExtJSONEncoder.UnkeyedContainer(codingPath: self.nestedCodingPath, - userInfo: self.userInfo) - self.storage.append(container) - - return container - } - - func superEncoder() -> Encoder { - fatalError("Unimplemented") // FIXME - } -} - - -//extension _MessagePackEncoder.KeyedContainer: _ExtJSONEncodingContainer { -// var data: Data { -// var data = Data() -// -// let length = storage.count -// if let uint16 = UInt16(exactly: length) { -// if length <= 15 { -// data.append(0x80 + UInt8(length)) -// } else { -// data.append(0xde) -// data.append(contentsOf: uint16.bytes) -// } -// } else if let uint32 = UInt32(exactly: length) { -// data.append(0xdf) -// data.append(contentsOf: uint32.bytes) -// } else { -// fatalError() -// } -// -// for (key, container) in self.storage { -// let keyContainer = _MessagePackEncoder.SingleValueContainer(codingPath: self.codingPath, userInfo: self.userInfo) -// try! keyContainer.encode(key.stringValue) -// data.append(keyContainer.data) -// -// data.append(container.data) -// } -// -// return data -// } -//} diff --git a/RealmSwift/MongoDataAccess.swift b/RealmSwift/MongoDataAccess.swift deleted file mode 100644 index 5e96230e88..0000000000 --- a/RealmSwift/MongoDataAccess.swift +++ /dev/null @@ -1,1117 +0,0 @@ -import Foundation -import Realm -import RealmSwift -import Realm.Private - -public protocol BSONFilter { - var documentRef: DocumentRef { get set } - init() - mutating func encode() -> RawDocument -} - -public protocol ExtJSON { -} - -public protocol ExpressibleByExtJSONLiteral : ExtJSON { - associatedtype ExtJSONValue: ExtJSONLiteral - init(extJSONValue value: ExtJSONValue) throws - var extJSONValue: ExtJSONValue { get } -} - -/** - String - Int - Data - Date - Bool - Double - Long - Optionals - Any - Dictionary - Array - */ -public protocol ExtJSONLiteral : ExpressibleByExtJSONLiteral { -} - -let extJSONTypes: [any ExtJSONObjectRepresentable.Type] = [ - Int.self, - Int64.self, - Double.self, - ObjectId.self, - Date.self -] - -extension ExpressibleByExtJSONLiteral { - public static func from(any extJSONValue: any ExpressibleByExtJSONLiteral) throws -> T { - if self is any ExpressibleByExtJSONLiteral { - if let extJSONValue = extJSONValue as? ExtJSONDocument { - for type in extJSONTypes { - do { - return try type.init(extJSONValue: extJSONValue) as! T - } catch { - } - } - } - - return extJSONValue as! T - } else { - return try Self.init(extJSONValue: extJSONValue as! ExtJSONValue) as! T - } - } -// public static func from(any extJSONValue: any ExpressibleByExtJSONLiteral) throws -> Self { -// try Self.init(extJSONValue: extJSONValue as! Self.ExtJSONValue) -// } -} - -extension ExtJSON { -} - -extension ExtJSONDocument { -// public subscript(_ key: String) -> T where T: ExtJSONLiteral { -// get throws { -// try T.init(extJSONValue: self[key] as! Dictionary) -// } -// } - - public subscript(extJSONKey key: String) -> T? { - get throws { - self[key] as? T - } - } - public subscript(extJSONKey key: String) -> T? where T: OptionalProtocol { - get throws { - if let value = self[key] { - return value as? T - } else { - return nil - } - } - } - public subscript(extJSONKey key: String) -> T where T: ExtJSONQueryRepresentable { - get throws { - try T.init(extJSONValue: self[key] as! ExtJSONDocument as! T.ExtJSONValue) - } - } - public subscript(extJSONKey key: String) -> Array? where T: ExtJSONQueryRepresentable { - get throws { - return try (self[key] as! Array<[String: Any]>).map(T.init) - } - } - public subscript(extJSONKey key: String) -> Any? { - get throws { - return self[key] - } - } - // Special case for Any type -// public subscript(key: String) -> Dictionary { -// get throws { -// (self[key] as! Dictionary).reduce(into: Dictionary()) { partialResult, element in -// partialResult[element.key] = element.value -// } -// } -// } -} - -extension String: ExtJSONLiteral { - public init(extJSONValue value: Self) throws { - self = value - } - public var extJSONValue: Self { - self - } -} - -extension Bool: ExtJSONLiteral { - public init(extJSONValue value: Self) throws { - self = value - } - public var extJSONValue: Self { - self - } -} - -extension NSNull: ExtJSON { -} - -public struct ExtJSONSerialization { - private init() {} - public static func deserialize(literal: Any) throws -> T { - try deserialize(literal: literal as Any?) as! T - } - public static func deserialize(literal: Any) throws -> T where T: ExtJSONObjectRepresentable { -// let value: ExtJSONDocument = try deserialize(literal: literal) - return try T.init(extJSONValue: literal as! ExtJSONDocument) - } - public static func deserialize(literal: Any?) throws -> T? { - try deserialize(literal: literal) as? T - } - public static func read(from document: ExtJSONDocument, - for key: String) throws -> T where T: ExtJSONLiteral { - return document[key] as! T - } - public static func read(from document: ExtJSONDocument, - for key: String) throws -> T where T: ExtJSONObjectRepresentable { - return document[key] as! T - } - public static func read(from document: ExtJSONDocument, - for key: String) throws -> T where T: ExtJSONQueryRepresentable { - try T.init(extJSONValue: document[key] as! ExtJSONDocument) - } - public static func read(from document: ExtJSONDocument, - for key: String) throws -> T? where T: ExtJSONQueryRepresentable { - try? T.init(extJSONValue: document[key] as! ExtJSONDocument) - } - public static func read(from document: ExtJSONDocument, - for key: String) throws -> [T] where T: ExtJSONQueryRepresentable { - return try (document[key] as! Array<[String: Any]>).map(T.init) - } -// public static func read(from document: ExtJSONDocument, -// for key: String) throws -> C where C: ExtJSONArrayRepresentable, C.Element: ExtJSONQueryRepresentable { -// var c = C.init() -// let parsed = (document[key] as! Array<[String: Any]>) -// for i in 0.. Any? { - switch literal { - case let literal as String: return literal - case let literal as Bool: return literal - case let literal as NSDictionary: - for type in extJSONTypes { - do { - return try type.init(extJSONValue: literal as! Dictionary) - } catch { - } - } - return try literal.reduce(into: ExtJSONDocument()) { partialResult, element in - partialResult[element.key as! String] = try deserialize(literal: element.value) - } - case let literal as NSArray: - return try literal.map { - try deserialize(literal: $0) - } - case let literal as [String : any ExtJSON]: - return try literal.reduce(into: ExtJSONDocument()) { partialResult, element in - partialResult[element.key] = try deserialize(literal: element.value) - } - case nil: - fallthrough - case is NSNull: - return Optional.none - case let opt as Optional: - return opt ?? nil - case let literal as Int: return literal - case let literal as Double: return literal - case let literal as any OptionalProtocol: - return literal - default: throw JSONError.invalidType("\(type(of: literal))") - } - } - public static func serialize(literal: Any?) throws -> Any? { - switch literal { - case let literal as String: return literal - case let literal as Bool: return literal - case let literal as any ExtJSONObjectRepresentable: return literal.extJSONValue - case let literal as NSDictionary: - return try literal.reduce(into: ExtJSONDocument()) { partialResult, element in - partialResult[element.key as! String] = try serialize(literal: element.value) - } - case let literal as NSArray: - return try literal.map { - try serialize(literal: $0) - } - case let literal as [String : Any]: - return try literal.reduce(into: ExtJSONDocument()) { partialResult, element in - partialResult[element.key] = try serialize(literal: element.value) - } - case nil: - fallthrough - case is NSNull: - return Optional.none - case let literal as any OptionalProtocol: - return literal - default: fatalError() - } - } - - private static func deserialize(literal: NSDictionary) throws -> Any { - try literal.reduce(into: ExtJSONDocument()) { partialResult, element in - partialResult[element.key as! String] = try deserialize(literal: element.value) - } - } - // anyL -> anyL: - // t: literal -> t: literal - // t: obj -> t: object - - public static func extJSONObject(with data: Data) throws -> Any { - try deserialize(literal: try JSONSerialization.jsonObject(with: data)) - } - public static func extJSONObject(with data: Data) throws -> T where T : ExtJSONLiteral { - try deserialize(literal: try JSONSerialization.jsonObject(with: data) as! T.ExtJSONValue) - } - public static func extJSONObject(with data: Data) throws -> T where T: ExtJSONObjectRepresentable { - let value = try JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed) - return try T(extJSONValue: deserialize(literal: value) as! ExtJSONDocument) - } - public static func data(with extJSONObject: ExtJSONDocument) throws -> Data { - try JSONSerialization.data(withJSONObject: extJSONObject.reduce(into: NSMutableDictionary()) { partialResult, element in - partialResult[element.key] = try serialize(literal: element.value) - }) - } - public static func data(with extJSONObject: Any) throws -> Data { - switch extJSONObject { - case let object as [String : Encodable]: - try JSONSerialization.data(withJSONObject: object.reduce(into: [String: Any](), { - let encoder = _ExtJSONEncoder() - try $1.value.encode(to: encoder) - $0[$1.key] = encoder.container?.storage - })) -// try JSONSerialization.data(withJSONObject: object.reduce(into: NSMutableDictionary()) { partialResult, element in -// partialResult[element.key] = try serialize(literal: element.value) -// }) - case let array as NSArray: - try JSONSerialization.data(withJSONObject: array.map { element in - try serialize(literal: element) - }) - default: throw JSONError.missingKey(key: "") - } - } - public static func data(with extJSONObject: T) throws -> Data where T: Encodable { - try ExtJSONEncoder().encode(extJSONObject) - } -} - -//extension Dictionary: ExpressibleByExtJSONLiteral { -// public typealias ExtJSONValue = Self -// -// public init(extJSONValue value: Self) throws { -// self = value -// } -// public var extJSONValue: Self { -// self -// } -//} -extension Dictionary: ExtJSON { -} -extension Dictionary: ExtJSONLiteral, ExpressibleByExtJSONLiteral { - public typealias ExtJSONValue = Self - - public init(extJSONValue value: Self) throws { - self = value - } - public var extJSONValue: Self { - self - } -} -public typealias ExtJSONDocument = Dictionary - -protocol _ExtJSONSequence : Collection where Element == any ExpressibleByExtJSONLiteral { - -} -extension _ExtJSONSequence { - public init(extJSONValue value: Self) throws { - self = value - } - public var extJSONValue: Self { - self - } -} -// MARK: Array Conformance -extension Array: ExtJSON { -} - -extension RealmSwift.List: ExtJSONLiteral, ExpressibleByExtJSONLiteral, ExtJSON -where Element: ExpressibleByExtJSONLiteral { - public typealias ExtJSONValue = List - - public convenience init(extJSONValue value: List) throws { - self.init(collection: value._rlmCollection) - } - public var extJSONValue: List { - self - } -} -public typealias ExtJSONArray = Array -public protocol RawDocumentKey : RawRepresentable, CaseIterable where RawValue == String { -} - -public protocol KeyPathIterable { - static var keyPaths: [PartialKeyPath : String] { get } -} - -public protocol ExtJSONQueryRepresentable : KeyPathIterable, ExtJSONObjectRepresentable { -} - -struct Person: Codable { - let name: String - let age: Int -} - -public typealias RawDocument = [String : Any] -@dynamicMemberLookup public struct RawDocumentFilter : BSONFilter { - public init() { - documentRef = DocumentRef() - } - - public var documentRef: DocumentRef - - public func encode() -> RawDocument { - self.documentRef.document - } - - public subscript(dynamicMember member: String) -> BSONQuery { - return BSONQuery(identifier: member, documentRef: documentRef) - } -} - -public class DocumentRef { - public var document = RawDocument() - - public init() {} -} - -public protocol BSONQueryable { - associatedtype FieldType : ExpressibleByExtJSONLiteral - var documentRef: DocumentRef { get } - var key: String { get } -} - -@dynamicMemberLookup public struct Filter { - var documentRef: DocumentRef = .init() - - public subscript(dynamicMember member: KeyPath) -> BSONQuery { - guard let memberName = Value.keyPaths[member] else { - fatalError() - } - var query = BSONQuery(identifier: memberName, documentRef: documentRef) - query.documentRef = documentRef - return query - } -} - -@dynamicMemberLookup public struct BSONQuery : BSONQueryable { - public let identifier: String - public fileprivate(set) var documentRef: DocumentRef - public var key: String { - prefix.isEmpty ? identifier : "\(prefix).\(identifier)" - } - fileprivate var prefix: String = "" - - public init(identifier: String, documentRef: DocumentRef) { - self.identifier = identifier - self.documentRef = documentRef - } - - public static func ==(lhs: Self, rhs: FieldType) -> Self { - lhs.documentRef.document[lhs.key] = rhs - return lhs - } - - public subscript(dynamicMember member: KeyPath>) -> BSONQuery - where FieldType : KeyPathIterable { - guard let memberName = FieldType.keyPaths[member] else { - fatalError() - } - var query = BSONQuery(identifier: memberName, documentRef: documentRef) - query.documentRef = documentRef - return query - } -} - -public extension Collection where Element : ExpressibleByExtJSONLiteral { - func contains(_ element: BSONQuery) -> BSONQuery { - let raw: RawDocument = [ - "$in" : self.map({ $0 }) - ] - element.documentRef.document[element.key] = raw - return element - } -} - -public extension BSONQueryable where FieldType : Comparable { - static func >(lhs: Self, rhs: FieldType) -> Self { - lhs.documentRef.document[lhs.key] = [ - "$gt" : rhs - ] - return lhs - } - static func <(lhs: Self, rhs: FieldType) -> Self { - lhs.documentRef.document[lhs.key] = [ - "$lt" : rhs - ] - return lhs - } - static func >=(lhs: Self, rhs: FieldType) -> Self { - lhs.documentRef.document[lhs.key] = [ - "$gte" : rhs - ] - return lhs - } - static func <=(lhs: Self, rhs: FieldType) -> Self { - lhs.documentRef.document[lhs.key] = [ - "$lte" : rhs - ] - return lhs - } - static func ||(lhs: Self, rhs: @autoclosure (() -> any BSONQueryable)) -> Self { - let documentBeforeLogicalOr = lhs.documentRef.document - lhs.documentRef.document.removeAll() - let documentAfterLogicalOr = rhs().documentRef.document - lhs.documentRef.document.removeAll() - lhs.documentRef.document["$or"] = [ - documentBeforeLogicalOr, - documentAfterLogicalOr - ] - return lhs - } - static func &&(lhs: Self, - rhs: @autoclosure (() -> U)) -> Self { - let documentBeforeLogicalOr = lhs.documentRef.document - lhs.documentRef.document.removeAll() - let documentAfterLogicalOr = rhs().documentRef.document - lhs.documentRef.document.removeAll() - lhs.documentRef.document["$and"] = [documentBeforeLogicalOr, documentAfterLogicalOr] - return lhs - } -} - -public extension BSONQuery where FieldType : Collection { - func contains(_ element: FieldType.Element) { - - } - func contains(_ other: FieldType) { - - } -} -// -//public extension BSONQuery where FieldType == String { -// @available(macOS 13.0, *) -// func contains(_ regex: some RegexComponent) -> Self { -//// self.documentRef.document[self.key] = regex.regex -// return self -// } -//} - -public enum BSONError : Error { - case missingKey(String) - case invalidType(key: String) -} - -public struct QueryObject { -} - -enum AnyBSONKey : Codable { - case string(String) - case objectId(ObjectId) - case int(Int) - - func encode(to encoder: Encoder) throws { - switch self { - case .string(let a0): try a0.encode(to: encoder) - case .objectId(let a0): try a0.encode(to: encoder) - case .int(let a0): try a0.encode(to: encoder) - } - } - - init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - do { - self = .string(try container.decode(String.self)) - } catch { - } - do { - self = .int(try container.decode(Int.self)) - } catch { - } - self = .objectId(try container.decode(ObjectId.self)) - } -} - -public struct InsertOneResult : Codable { - let insertedId: AnyBSONKey -} - -public struct MongoTypedCollection { - fileprivate let mongoCollection: RLMMongoCollection - fileprivate let database: MongoDatabase - fileprivate init(mongoCollection: RLMMongoCollection, database: MongoDatabase) { - self.mongoCollection = mongoCollection - self.database = database - } - - struct Arguments : Codable { - let database: String - let collection: String - let document: T - } - /// Encodes the provided value to BSON and inserts it. If the value is missing an identifier, one will be - /// generated for it. - /// - Parameters: - /// - object: object A `T` value to insert. - /// - completion: The result of attempting to perform the insert. An Id will be returned for the inserted object on sucess - @preconcurrency - public func insertOne(_ object: T, - _ completion: @Sendable @escaping (Result) -> Void) -> Void { - let data: Data - do { - var document = RawDocument() - document["database"] = mongoCollection.databaseName - document["collection"] = mongoCollection.name - document["query"] = try ExtJSONEncoder().encode(object) - data = try ExtJSONEncoder().encode([Arguments(database: mongoCollection.databaseName, - collection: mongoCollection.name, - document: object)]) - } catch { - return completion(.failure(error)) - } - - mongoCollection.user.callFunctionNamed("insertOne", - arguments: String(data: data, encoding: .utf8)!, - serviceName: "mongodb-atlas", completionBlock: { data, error in - guard let data = data else { - guard let error = error else { - return completion(.failure(AppError(RLMAppError.Code.httpRequestFailed))) - } - return completion(.failure(error)) - } - do { - let object = try ExtJSONDecoder().decode(InsertOneResult.self, from: data.data(using: .utf8)!) - completion(.success(object)) - } catch { - return completion(.failure(error)) - } - }) - } - - @_unsafeInheritExecutor - @available(macOS 10.15, *) - public func insertOne(_ object: T) async throws -> Any { - try await withCheckedThrowingContinuation { continuation in - insertOne(object) { returnValue in - switch returnValue { - case .success(let value): - continuation.resume(returning: value) - case .failure(let error): - continuation.resume(throwing: error) - } - } - } - } -// -// /// Encodes the provided values to BSON and inserts them. If any values are missing identifiers, -// /// they will be generated. -// /// - Parameters: -// /// - documents: The `Document` values in a bson array to insert. -// /// - completion: The result of the insert, returns an array inserted document ids in order. -// @preconcurrency -// public func insertMany(_ objects: [T], -// _ completion: @Sendable @escaping (Result<[AnyBSON], Error>) -> Void) -> Void { -// let objects = objects.map { -// var document = Document() -// $0.encode(to: &document) -// return document -// } -// mongoCollection.insertMany(objects, completion) -// } -// // MARK: Find -// /// Finds the documents in this collection which match the provided filter. -// /// - Parameters: -// /// - filter: A `Document` as bson that should match the query. -// /// - options: `FindOptions` to use when executing the command. -// /// - completion: The resulting bson array of documents or error if one occurs -// @preconcurrency -// public func find(filter: T.Filter? = nil, -// options: FindOptions = FindOptions(), -// _ completion: @Sendable @escaping (Result<[T], Error>) -> Void) -> Void { -// var document = Document() -// if var filter = filter { -// document = filter.encode() -// } -// mongoCollection.find(filter: document, options: options, { result in -// do { -// completion(.success(try result.get().map { document in -// try T(from: document) -// })) -// } catch { -// completion(.failure(error)) -// } -// }) -// } -// -// public func find(options: FindOptions = FindOptions(), -// filter: ((inout T.Filter) -> any BSONQueryable)? = nil) async throws -> [T] { -// try await withCheckedThrowingContinuation { continuation in -// var query = T.Filter() -// _ = filter?(&query) -// find(filter: query, options: options) { returnValue in -// switch returnValue { -// case .success(let value): -// continuation.resume(returning: value) -// case .failure(let error): -// continuation.resume(throwing: error) -// } -// } -// } -// } -// - // MARK: FindOne - /// Returns one document from a collection or view which matches the - /// provided filter. If multiple documents satisfy the query, this method - /// returns the first document according to the query's sort order or natural - /// order. - /// - Parameters: - /// - filter: A `Document` as bson that should match the query. - /// - options: `FindOptions` to use when executing the command. - /// - completion: The resulting bson or error if one occurs -// @preconcurrency -// public func findOne(filter: T.Filter? = nil, -// options: FindOptions = FindOptions(), -// _ completion: @escaping @Sendable (Result) -> Void) -> Void { -// var document = RawDocument() -// if var filter = filter { -// document["query"] = filter.encode() -// } -// document["database"] = mongoCollection.databaseName -// document["collection"] = mongoCollection.name -// mongoCollection.user.callFunctionNamed("findOne", -// arguments: document.syntaxView.description, -// serviceName: mongoCollection.serviceName) { data, error in -// guard let data = data, -// let view = ExtJSON(extJSON: data).parse(database: self.database) as? ObjectSyntaxView else { -// guard let error = error else { -// return completion(.failure(AppError(RLMAppError.Code.httpRequestFailed))) -// } -// return completion(.failure(error)) -// } -// } -// } - - @dynamicMemberLookup struct Filter where T: Codable { - class Encoder : Swift.Encoder { - class _KeyedEncodingContainer: Swift.KeyedEncodingContainerProtocol where Key: CodingKey { - func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { - fatalError() - } - - func superEncoder() -> Swift.Encoder { - fatalError() - } - - func superEncoder(forKey key: Key) -> Swift.Encoder { - fatalError() - } - - var codingPath: [CodingKey] - init(codingPath: [CodingKey]) { - self.codingPath = codingPath - } - func encodeNil(forKey key: Key) throws { - fatalError() - } - - func encode(_ value: Bool, forKey key: Key) throws { - fatalError() - } - - func encode(_ value: String, forKey key: Key) throws { - fatalError() - } - - func encode(_ value: Double, forKey key: Key) throws { - fatalError() - } - - func encode(_ value: Float, forKey key: Key) throws { - fatalError() - } - - func encode(_ value: Int, forKey key: Key) throws { - fatalError() - } - - func encode(_ value: Int8, forKey key: Key) throws { - fatalError() - } - - func encode(_ value: Int16, forKey key: Key) throws { - fatalError() - } - - func encode(_ value: Int32, forKey key: Key) throws { - fatalError() - } - - func encode(_ value: Int64, forKey key: Key) throws { - fatalError() - } - - func encode(_ value: UInt, forKey key: Key) throws { - fatalError() - } - - func encode(_ value: UInt8, forKey key: Key) throws { - fatalError() - } - - func encode(_ value: UInt16, forKey key: Key) throws { - fatalError() - } - - func encode(_ value: UInt32, forKey key: Key) throws { - fatalError() - } - - func encode(_ value: UInt64, forKey key: Key) throws { - fatalError() - } - - func encode(_ value: T, forKey key: Key) throws where T : Encodable { - fatalError() - } - - func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { - fatalError() - } - } - var codingPath: [CodingKey] - - var userInfo: [CodingUserInfoKey : Any] - - init(codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) { - self.codingPath = codingPath - self.userInfo = userInfo - } - - func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { - fatalError() - } - - func unkeyedContainer() -> UnkeyedEncodingContainer { - fatalError() - } - - func singleValueContainer() -> SingleValueEncodingContainer { - fatalError() - } - } - subscript(dynamicMember member: KeyPath) -> V { - fatalError() - } - } - struct D : ExpressibleByDictionaryLiteral { - typealias Key = PartialKeyPath - - init(dictionaryLiteral elements: (PartialKeyPath, F)...) { - - } - } - func f(_ d: D) { - f([ - \.name: F.name - ]) - } - - @dynamicMemberLookup public struct F { - public static func greaterThan() -> Self { - fatalError() - } - - public static subscript(dynamicMember member: KeyPath) -> Self { - fatalError() - } - } -// public typealias Tuple = (KeyPath, F) -// public func findOne(options: FindOptions = FindOptions(), -// _ filter: T? = nil) async throws -> T? { -// withCheckedThrowingContinuation { continuation in -// findOne(nil) { -// -// } -// } -// } - @_unsafeInheritExecutor - @available(macOS 10.15, *) - public func findOne(options: FindOptions = FindOptions(), - _ filter: T? = nil) async throws -> T? { - let data: String = try await withCheckedThrowingContinuation { continuation in - var document = RawDocument() - if let filter = filter { -// var filterDocument = Filter() -// _ = filter(&filterDocument) - document["query"] = filter//filterDocument.documentRef.document - } - document["database"] = mongoCollection.databaseName - document["collection"] = mongoCollection.name - do { - try mongoCollection.user.callFunctionNamed( - "findOne", - arguments: String(data: ExtJSONSerialization.data(with: [document]), - encoding: .utf8)!, - serviceName: mongoCollection.serviceName) { data, error in - guard let data = data else { - guard let error = error else { - return continuation.resume(throwing: AppError(RLMAppError.Code.httpRequestFailed)) - } - return continuation.resume(throwing: error) - } - continuation.resume(returning: data) - } - } catch { - return continuation.resume(throwing: error) - } - } - do { - return try ExtJSONDecoder().decode(T.self, from: data.data(using: .utf8)!) - } catch { - return nil - } - } - -// @_unsafeInheritExecutor -// public func findOne(options: FindOptions = FindOptions(), -// _ filter: ((inout Filter) -> any BSONQueryable)? = nil) async throws -> T? where T: ObjectBase { -// let data: String = try await withCheckedThrowingContinuation { continuation in -// var document = RawDocument() -// if let filter = filter { -// var filterDocument = Filter() -// _ = filter(&filterDocument) -// -// guard let schema = T.sharedSchema() else { -// fatalError() -// } -// let namesAndClassNames: [(String, String?)] = schema.properties.filter { -// if let objectClassName = $0.objectClassName, let schema = RLMSchema.partialPrivateShared().schema(forClassName: objectClassName), !schema.isEmbedded { -// return true -// } -// return false -// }.map { ($0.name, $0.objectClassName) } -// document["pipeline"] = [ -// [ -// "$match": filterDocument.documentRef.document, -//// "$limit": 1 -// ] -// ] -// + namesAndClassNames.map { -// ["$lookup": [ -// "from": $0.1!, -// "localField": $0.0, -// "foreignField": "_id", -// "as": $0.0 -// ]] -// } -//// + namesAndClassNames.map { -//// ["$unwind": "$\($0.0)"]//[ -//// "path":"$\($0.0)", -////// "includeArrayIndex": "0", -//// "preserveNullAndEmptyArrays": true -//// ]] -//// } -// } -// print(document.syntaxView.description) -// document["database"] = mongoCollection.databaseName -// document["collection"] = mongoCollection.name -// mongoCollection.user.callFunctionNamed( -// "aggregate", -// arguments: [document].syntaxView.description, -// serviceName: mongoCollection.serviceName) { data, error in -// guard let data = data else { -// guard let error = error else { -// return continuation.resume(throwing: AppError(RLMAppError.Code.httpRequestFailed)) -// } -// return continuation.resume(throwing: error) -// } -// continuation.resume(returning: data) -// } -// } -// let view = await ExtJSON(extJSON: data).parse(database: self.database) -// guard let view = view as? RawArraySyntaxView else { -// return nil -// } -// return try T.SyntaxView(from: view[0] as! RawObjectSyntaxView).rawDocumentRepresentable -// } -// // -// // /// Runs an aggregation framework pipeline against this collection. -// // /// - Parameters: -// // /// - pipeline: A bson array made up of `Documents` containing the pipeline of aggregation operations to perform. -// // /// - completion: The resulting bson array of documents or error if one occurs -// // @preconcurrency -// // public func aggregate(pipeline: [Document], _ completion: @escaping MongoFindBlock) { -// // let bson = pipeline.map(ObjectiveCSupport.convert) -// // __aggregate(withPipeline: bson) { documents, error in -// // if let bson = documents?.map(ObjectiveCSupport.convert) { -// // completion(.success(bson)) -// // } else { -// // completion(.failure(error ?? Realm.Error.callFailed)) -// // } -// // } -// // } -// -// /// Counts the number of documents in this collection matching the provided filter. -// /// - Parameters: -// /// - filter: A `Document` as bson that should match the query. -// /// - limit: The max amount of documents to count -// /// - completion: Returns the count of the documents that matched the filter. -// @preconcurrency -// public func count(filter: T? = nil, -// limit: Int? = nil, -// _ completion: @escaping @Sendable (Result) -> Void) -> Void { -// var document = Document() -// if let filter = filter { -// filter.encode(to: &document) -// } -// mongoCollection.count(filter: document, limit: limit, completion) -// } -// -// @_unsafeInheritExecutor -// public func count(filter: T? = nil, -// limit: Int? = nil) async throws -> Int { -// try await withCheckedThrowingContinuation { continuation in -// count(filter: filter, limit: limit) { returnValue in -// switch returnValue { -// case .success(let value): -// continuation.resume(returning: value) -// case .failure(let error): -// continuation.resume(throwing: error) -// } -// } -// } -// } -// - /// Deletes a single matching document from the collection. - /// - Parameters: - /// - filter: A `Document` as bson that should match the query. - /// - completion: The result of performing the deletion. Returns the count of deleted objects - @preconcurrency - public func deleteOne(filter: T? = nil, - _ completion: @escaping @Sendable (Result) -> Void) -> Void { - do { - mongoCollection.deleteOneDocument(filter: [:], completion) - } catch { - - } - } - - @available(macOS 10.15, *) - @_unsafeInheritExecutor - public func deleteOne(filter: ((inout T) -> Bool)? = nil) async throws -> Int { - try await withCheckedThrowingContinuation { continuation in -// var query = T.Filter() -// _ = filter?(&query) - deleteOne(filter: nil) { returnValue in - switch returnValue { - case .success(let value): - continuation.resume(returning: value) - case .failure(let error): - continuation.resume(throwing: error) - } - } - } - } - - /// Deletes multiple documents - /// - Parameters: - /// - filter: Document representing the match criteria - /// - completion: The result of performing the deletion. Returns the count of the deletion - @preconcurrency - public func deleteMany(filter: T? = nil, - _ completion: @escaping @Sendable (Result) -> Void) -> Void { -// var document = Document() -// if var filter = filter { -// document = filter.encode() -// } - mongoCollection.deleteManyDocuments(filter: [:], completion) - } - - @_unsafeInheritExecutor - @available(macOS 10.15, *) - public func deleteMany(filter: T? = nil) async throws -> Int { - try await withCheckedThrowingContinuation { continuation in - deleteMany(filter: filter) { returnValue in - switch returnValue { - case .success(let value): - continuation.resume(returning: value) - case .failure(let error): - continuation.resume(throwing: error) - } - } - } - } -} - -extension MongoDatabase { -// fileprivate let mongoDatabase: RLMMongoDatabase - public func collection(named name: String, type: T.Type) -> MongoTypedCollection { - MongoTypedCollection(mongoCollection: self.collection(withName: name), - database: self) - } -} - -public extension MongoClient { -// func database(named name: String) -> MongoDatabase { -// MongoDatabase(mongoDatabase: self.database(named: name)) -// } -} - -//extension ExtJSON { -// func parse(database: MongoDatabase) async -> any SyntaxView { -// let ast = parse() -// if let ast = ast as? any ObjectSyntaxView { -//// for field in ast.fieldList.fields where field.value is DBRefSyntaxView { -//// guard let dbRefView = field.value as? DBRefSyntaxView else { -//// continue -//// } -//// guard let type = configuration.schema.first(where: { -//// "\($0)" == dbRefView.collectionName.string -//// }) else { -//// continue -//// } -//// -//// let collection = database.collection(named: dbRefView.collectionName.string, type: RawDocument.self) -//// await collection.findOne { -//// $0._id == dbRefView._id.rawDocumentRepresentable -//// } -//// } -// } -// return ast -// } -//} - -@attached(extension, conformances: ExtJSONQueryRepresentable, names: prefixed(_), arbitrary) -@attached(member, - names: named(init(extJSONValue:)), - prefixed(_), arbitrary, suffixed(Key)) -@available(swift 5.9) -public macro BSONCodable() = #externalMacro(module: "MongoDataAccessMacros", - type: "BSONCodableMacro") -//@attached(extension, conformances: ExtJSONQueryRepresentable, names: arbitrary) -//@attached(member, names: named(init(extJSONValue:)), named(rawDocument), arbitrary) -@attached(peer, names: suffixed(Key), prefixed(_)) -@available(swift 5.9) -public macro BSONCodable(key: String) = #externalMacro(module: "MongoDataAccessMacros", - type: "BSONCodableMacro") -@attached(peer, names: suffixed(Key), prefixed(_)) -@available(swift 5.9) -public macro BSONCodable(ignore: Bool) = #externalMacro(module: "MongoDataAccessMacros", - type: "BSONCodableMacro") - -@attached(peer) -@available(swift 5.9) -public macro DocumentKey(_ key: String? = nil) = #externalMacro(module: "MongoDataAccessMacros", - type: "RawDocumentQueryRepresentableMacro") - diff --git a/RealmSwift/MongoDataAccess/ExtJSON.swift b/RealmSwift/MongoDataAccess/ExtJSON.swift new file mode 100644 index 0000000000..c1cdf015eb --- /dev/null +++ b/RealmSwift/MongoDataAccess/ExtJSON.swift @@ -0,0 +1,227 @@ +import Foundation + +protocol ExtJSONBuiltin { + static func initialize(from value: Any) throws -> Self + var extJSONValue: any Codable { get } +} +protocol ExtJSONCodable: Codable, ExtJSONBuiltin { + associatedtype ExtJSONValue: Codable + init(from value: ExtJSONValue) throws + var extJSONValue: ExtJSONValue { get } +} +extension ExtJSONCodable { + static func initialize(from value: Any) throws -> Self { + try Self.init(from: value as! ExtJSONValue) + } + var extJSONValue: any Codable { self.extJSONValue } + var isLiteral: Bool { + false + } +} +extension ExtJSONCodable where ExtJSONValue == Self { + init(from value: Self) throws { + self = value//.array + } + var extJSONValue: Self { + self//ExtJSONValue(array: self) + } + var isLiteral: Bool { + true + } +} +protocol ExtJSONSingleValue: ExtJSONBuiltin { +} +typealias ExtJSONSingleValueCodable = ExtJSONSingleValue & ExtJSONCodable +protocol ExtJSONKeyedValue: ExtJSONBuiltin { +} +protocol ExtJSONKeyedCodable: ExtJSONKeyedValue, ExtJSONCodable { +} +protocol ExtJSONUnkeyedValue: ExtJSONBuiltin { +} +protocol ExtJSONUnkeyedCodable: ExtJSONUnkeyedValue, ExtJSONCodable, Collection +where Element: ExtJSONCodable { +} + +// MARK: Array +extension Array: ExtJSONUnkeyedCodable, + ExtJSONUnkeyedValue, + ExtJSONCodable, + ExtJSONBuiltin where Element: ExtJSONCodable { + typealias ExtJSONValue = Self +} +extension List: ExtJSONUnkeyedCodable, + ExtJSONUnkeyedValue, + ExtJSONCodable, + ExtJSONBuiltin where Element: ExtJSONCodable { + typealias ExtJSONValue = List +} + +// MARK: Optional +extension Optional: ExtJSONSingleValueCodable, ExtJSONBuiltin where Wrapped: ExtJSONCodable { + typealias ExtJSONValue = Self +} +extension NSNull: ExtJSONSingleValue, ExtJSONBuiltin { + static func initialize(from value: Any) throws -> Self { + fatalError() + } + var extJSONValue: Codable { + Optional.none + } +} +extension NSNumber: ExtJSONBuiltin { + static func initialize(from value: Any) -> Self { + fatalError() + } + + var extJSONValue: any Codable { + if CFGetTypeID(self) == CFBooleanGetTypeID() { + return self.boolValue + } + switch CFNumberGetType(self) { + case .charType: + return self.stringValue + case .intType, .nsIntegerType: + return self.intValue.extJSONValue + case .shortType, .sInt8Type, .sInt16Type, .sInt32Type: + return self.intValue.extJSONValue + case .longType, .sInt64Type: + return self.int64Value.extJSONValue + case .doubleType, .float32Type, .float64Type, .cgFloatType, .floatType: + return self.doubleValue.extJSONValue + default: fatalError() + } + } +} +// MARK: String +extension String: ExtJSONSingleValueCodable { + typealias ExtJSONValue = Self + init(from value: Self) throws { + self = value + } + var extJSONValue: Self { + self + } +} +extension NSString: ExtJSONSingleValue { + static func initialize(from value: Any) throws -> Self { + fatalError() + } + var extJSONValue: Codable { + (self as String).extJSONValue + } +} +// MARK: Bool +extension Bool: ExtJSONSingleValueCodable { + typealias ExtJSONValue = Self + init(from value: Self) throws { + self = value + } + var extJSONValue: Self { + self + } +} +// MARK: Int +extension Int: ExtJSONKeyedCodable { + struct ExtJSONValue: Codable { + enum CodingKeys: String, CodingKey { case numberInt = "$numberInt" } + let numberInt: String + } + init(from value: ExtJSONValue) throws { + guard let value = Int(value.numberInt) else { + throw DecodingError.valueNotFound(Self.self, DecodingError.Context(codingPath: [], + debugDescription: "")) + } + self = value + } + var extJSONValue: ExtJSONValue { + ExtJSONValue(numberInt: "\(self)") + } +} +// MARK: Int64 +extension Int64 : ExtJSONKeyedCodable { + struct ExtJSONValue: Codable { + enum CodingKeys: String, CodingKey { case numberLong = "$numberLong" } + let numberLong: String + } + init(from value: ExtJSONValue) throws { + guard let value = Int64(value.numberLong) else { + throw DecodingError.valueNotFound(Self.self, DecodingError.Context(codingPath: [], + debugDescription: "")) + } + self = value + } + var extJSONValue: ExtJSONValue { + ExtJSONValue(numberLong: "\(self)") + } +} +// MARK: ObjectId +extension ObjectId: ExtJSONKeyedCodable { + struct ExtJSONValue: Codable { + enum CodingKeys: String, CodingKey { case oid = "$oid" } + let oid: String + } + convenience init(from value: ExtJSONValue) throws { + try self.init(string: value.oid) + } + var extJSONValue: ExtJSONValue { + ExtJSONValue(oid: self.stringValue) + } +} +// MARK: Double +extension Double: ExtJSONKeyedCodable { + struct ExtJSONValue: Codable { + enum CodingKeys: String, CodingKey { case numberDouble = "$numberDouble" } + let numberDouble: String + } + init(from value: ExtJSONValue) throws { + self = Double(value.numberDouble)! + } + var extJSONValue: ExtJSONValue { + ExtJSONValue(numberDouble: String(self)) + } +} +// +// MARK: Date +extension Date : ExtJSONKeyedCodable { + struct ExtJSONValue: Codable { + enum CodingKeys: String, CodingKey { case date = "$date" } + let date: Int64 + } + init(from value: ExtJSONValue) throws { + self.init(timeIntervalSince1970: TimeInterval(value.date) / 1_000) + } + var extJSONValue: ExtJSONValue { + ExtJSONValue(date: Int64(self.timeIntervalSince1970 * 1_000.0)) + } +} +extension NSDate: ExtJSONKeyedValue { + static func initialize(from value: Any) throws -> Self { + fatalError() + } + var extJSONValue: Codable { + (self as Date).extJSONValue + } +} + +// MARK: Data +extension Data : ExtJSONKeyedCodable { + struct ExtJSONValue: Codable { + enum CodingKeys: String, CodingKey { case binary = "$binary" } + struct Binary: Codable { + let base64: String + let subType: String + } + let binary: Binary + } + + init(from value: ExtJSONValue) throws { + guard let data = Data(base64Encoded: value.binary.base64) else { + fatalError() + } + self = data + } + var extJSONValue: ExtJSONValue { + ExtJSONValue(binary: ExtJSONValue.Binary(base64: self.base64EncodedString(), + subType: "00")) + } +} diff --git a/RealmSwift/MongoDataAccess/ExtJSONCodable.swift b/RealmSwift/MongoDataAccess/ExtJSONCodable.swift new file mode 100644 index 0000000000..21a3e0f176 --- /dev/null +++ b/RealmSwift/MongoDataAccess/ExtJSONCodable.swift @@ -0,0 +1,605 @@ +import Foundation + +extension CodingUserInfoKey { + static var storage: CodingUserInfoKey { + CodingUserInfoKey(rawValue: "__realm_storage")! + } + static var superCoder: CodingUserInfoKey { + CodingUserInfoKey(rawValue: "__realm_super_coder")! + } +} + +// MARK: Decoder + +final public class ExtJSONDecoder { + public init() { + } + + /** + A dictionary you use to customize the encoding process + by providing contextual information. + */ + public var userInfo: [CodingUserInfoKey : Any] = [:] + + /** + Returns an ExtJSON-encoded representation of the value you supply. + + - Parameters: + - value: The value to encode as MessagePack. + - Throws: `EncodingError.invalidValue(_:_:)` + if the value can't be encoded as a MessagePack object. + */ + public func decode(_ type: T.Type, from data: Data) throws -> T where T : Decodable { + try T(from: _ExtJSONDecoder(storage: try JSONSerialization.jsonObject(with: data, + options: .fragmentsAllowed), + userInfo: self.userInfo)) + } +} + +private class _ExtJSONDecoder : Decoder { + class SingleValueContainer : _ExtJSONContainer { + var storage: Any? + var codingPath: [CodingKey] = [] + + init(storage: Any? = nil, codingPath: [CodingKey]) { + self.storage = storage + self.codingPath = codingPath + } + } + + class KeyedContainer : _ExtJSONContainer where Key: CodingKey { + let storage: [String: Any] + let codingPath: [CodingKey] + let userInfo: [CodingUserInfoKey: Any] + + init(storage: [String: Any], + codingPath: [CodingKey], + userInfo: [CodingUserInfoKey: Any]) { + self.storage = storage + self.codingPath = codingPath + self.userInfo = userInfo + } + } + + class UnkeyedContainer : _ExtJSONContainer { + var codingPath: [CodingKey] + + var count: Int? { + storage.count + } + + var isAtEnd: Bool { + storage.count <= self.currentIndex + } + + private(set) var currentIndex: Int + var storage: [Any?] + fileprivate let userInfo: [CodingUserInfoKey: Any] + + init(storage: [Any?], + codingPath: [CodingKey], + userInfo: [CodingUserInfoKey: Any]) { + self.storage = storage + self.codingPath = codingPath + self.currentIndex = 0 + self.userInfo = userInfo + } + } + + var codingPath: [CodingKey] = [] + var userInfo: [CodingUserInfoKey : Any] = [:] + var storage: Any? + + init(storage: Any?, + codingPath: [CodingKey] = [], + userInfo: [CodingUserInfoKey : Any] = [:]) { + self.codingPath = codingPath + self.userInfo = userInfo + self.userInfo[.storage] = storage + self.userInfo[.superCoder] = self + self.storage = storage + } + + func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { + guard let storage = storage as? [String: Any] else { + fatalError() + } + return KeyedDecodingContainer(Self.KeyedContainer(storage: storage, + codingPath: codingPath, + userInfo: userInfo)) + } + + func unkeyedContainer() throws -> UnkeyedDecodingContainer { + guard let storage = storage as? [Any] else { + fatalError() + } + return UnkeyedContainer(storage: storage, + codingPath: codingPath, + userInfo: userInfo) + } + + func singleValueContainer() throws -> SingleValueDecodingContainer { + SingleValueContainer(storage: storage, codingPath: codingPath) + } +} + +extension _ExtJSONDecoder.SingleValueContainer: SingleValueDecodingContainer { + func decodeNil() -> Bool { + storage == nil || storage is NSNull + } + + func decode(_ type: Bool.Type) throws -> Bool { + guard let storage = storage as? Bool else { + throw DecodingError.typeMismatch(Bool.self, DecodingError.Context(codingPath: codingPath, + debugDescription: "Type of \(storage ?? "null") was not Bool")) + } + return storage + } + + func decode(_ type: String.Type) throws -> String { + guard let storage = storage as? String else { + throw DecodingError.typeMismatch(String.self, DecodingError.Context(codingPath: codingPath, + debugDescription: "Type of \(storage ?? "null") was not String")) + } + return storage + } + + func decode(_ type: T.Type) throws -> T where T : Decodable { + let decoder = _ExtJSONDecoder(storage: storage, + codingPath: codingPath, + userInfo: [:]) + switch type { + case let type as any ExtJSONCodable.Type: + func decode(_ type: V.Type) throws -> V { + try V(from: V.ExtJSONValue(from: decoder)) + } + return try decode(type) as! T + default: + return try T(from: decoder) + } + } +} +// MARK: KeyedDecodingContainer +extension _ExtJSONDecoder.KeyedContainer: KeyedDecodingContainerProtocol { + var allKeys: [Key] { + storage.keys.compactMap(Key.init) + } + + func contains(_ key: Key) -> Bool { + storage.index(forKey: key.stringValue) != nil + } + + func decodeNil(forKey key: Key) throws -> Bool { + storage.index(forKey: key.stringValue) == nil || + storage[key.stringValue] as? NSObject == NSNull() + } + + func decode(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable { + try _ExtJSONDecoder.SingleValueContainer(storage: storage[key.stringValue], + codingPath: codingPath).decode(type) + } + + private func nestedCodingPath(forKey key: CodingKey) -> [CodingKey] { + return self.codingPath + [key] + } + + func nestedContainer(keyedBy type: NestedKey.Type, + forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + KeyedDecodingContainer(_ExtJSONDecoder.KeyedContainer(storage: self.storage[key.stringValue] as! [String: Any], + codingPath: nestedCodingPath(forKey: key), + userInfo: userInfo)) + } + + func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { + fatalError() + } + + func superDecoder() throws -> Decoder { + guard let decoder = self.userInfo[.superCoder] as? any Decoder else { + throw DecodingError.valueNotFound(Decoder.self, + DecodingError.Context(codingPath: codingPath, debugDescription: "Could not find reference to super decoder")) + } + return decoder + } + + func superDecoder(forKey key: Key) throws -> Decoder { + _ExtJSONDecoder(storage: storage[key.stringValue]) + } +} + +extension _ExtJSONDecoder.UnkeyedContainer: UnkeyedDecodingContainer { + func decode(_ type: T.Type) throws -> T where T : Decodable { + defer { + self.currentIndex += 1 + } + return try _ExtJSONDecoder.SingleValueContainer(storage: self.storage[self.currentIndex], + codingPath: codingPath).decode(type) + } + + + func decodeNil() throws -> Bool { + storage[self.currentIndex] == nil || storage[self.currentIndex] is NSNull + } + + func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + fatalError() + } + + func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { + fatalError() + } + + func superDecoder() throws -> Decoder { + guard let decoder = self.userInfo[.superCoder] as? any Decoder else { + throw DecodingError.valueNotFound(Decoder.self, + DecodingError.Context(codingPath: codingPath, debugDescription: "Could not find reference to super decoder")) + } + return decoder + } +} + +// MARK: - _ExtJSONEncoder +private class _ExtJSONEncoder { + var codingPath: [CodingKey] = [] + + var userInfo: [CodingUserInfoKey : Any] = [:] + + var container: (any _ExtJSONContainer)? + + init(codingPath: [CodingKey] = [], + userInfo: [CodingUserInfoKey : Any] = [:]) { + self.codingPath = codingPath + self.userInfo = userInfo + self.userInfo[.superCoder] = self + } +} + +final public class ExtJSONEncoder { + public init() {} + + /** + A dictionary you use to customize the encoding process + by providing contextual information. + */ + public var userInfo: [CodingUserInfoKey : Any] = [:] + + /** + Returns a MessagePack-encoded representation of the value you supply. + + - Parameters: + - value: The value to encode as MessagePack. + - Throws: `EncodingError.invalidValue(_:_:)` + if the value can't be encoded as a MessagePack object. + */ + public func encode(_ value: T) throws -> Data where T : Encodable { + let encoder = _ExtJSONEncoder() + try value.encode(to: encoder) + return try JSONSerialization.data(withJSONObject: encoder.container?.storage ?? NSNull()) + } + + /** + Returns a MessagePack-encoded representation of the value you supply. + + - Parameters: + - value: The value to encode as MessagePack. + - Throws: `EncodingError.invalidValue(_:_:)` + if the value can't be encoded as a MessagePack object. + */ + func encode(_ value: T) throws -> Any where T: ExtJSONBuiltin { + let encoder = _ExtJSONEncoder() + try value.extJSONValue.encode(to: encoder) + return encoder.container?.storage as Any + } +} + +public protocol _ExtJSONContainer { + associatedtype StorageType + var storage: StorageType { get } +} + +extension _ExtJSONEncoder { + final class SingleValueContainer : _ExtJSONContainer { + var storage: Any? + + fileprivate var canEncodeNewValue = true + fileprivate func checkCanEncode(value: Any?) throws { + guard self.canEncodeNewValue else { + let context = EncodingError.Context(codingPath: self.codingPath, debugDescription: "Attempt to encode value through single value container when previously value already encoded.") + throw EncodingError.invalidValue(value as Any, context) + } + } + + var codingPath: [CodingKey] + var userInfo: [CodingUserInfoKey: Any] + + init(codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) { + self.codingPath = codingPath + self.userInfo = userInfo + } + } +} +// MARK: SingleValueEncodingContainer +extension _ExtJSONEncoder.SingleValueContainer: SingleValueEncodingContainer { + func encodeNil() throws { + try checkCanEncode(value: nil) + defer { self.canEncodeNewValue = false } + + self.storage = NSNull() + } + + func encode(_ value: Bool) throws { + try checkCanEncode(value: nil) + defer { self.canEncodeNewValue = false } + + self.storage = value + } + + func encode(_ value: String) throws { + try checkCanEncode(value: value) + defer { self.canEncodeNewValue = false } + + self.storage = value + } + + func encode(_ value: T) throws where T : Encodable { + try checkCanEncode(value: value) + defer { self.canEncodeNewValue = false } + let encoder = _ExtJSONEncoder(codingPath: codingPath, userInfo: userInfo) + switch value { + case let value as any ExtJSONKeyedCodable: + try value.extJSONValue.encode(to: encoder) + case let value as any ExtJSONSingleValueCodable: + return storage = value + default: + try value.encode(to: encoder) + } + self.storage = encoder.container?.storage + } +} + +extension _ExtJSONEncoder: Encoder { + fileprivate func assertCanCreateContainer() { + precondition(self.container == nil) + } + + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { + assertCanCreateContainer() + + let container = KeyedContainer(codingPath: self.codingPath, + userInfo: self.userInfo) + self.container = container + + return KeyedEncodingContainer(container) + } + + func unkeyedContainer() -> UnkeyedEncodingContainer { + assertCanCreateContainer() + + let container = UnkeyedContainer(codingPath: self.codingPath, + userInfo: self.userInfo) + self.container = container + + return container + } + + func singleValueContainer() -> SingleValueEncodingContainer { + assertCanCreateContainer() + + let container = SingleValueContainer(codingPath: self.codingPath, userInfo: self.userInfo) + self.container = container + + return container + } +} + +extension _ExtJSONEncoder { + final class KeyedContainer : _ExtJSONContainer where Key: CodingKey { + var storage: [String: Any] = [:] + + var codingPath: [CodingKey] + var userInfo: [CodingUserInfoKey: Any] + + init(codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) { + self.codingPath = codingPath + self.userInfo = userInfo + } + } +} + +// MARK: KeyedEncodingContainer +extension _ExtJSONEncoder.KeyedContainer: KeyedEncodingContainerProtocol { + private func nestedCodingPath(forKey key: CodingKey) -> [CodingKey] { + return self.codingPath + [key] + } + + func encodeNil(forKey key: Key) throws { + var container = self.nestedSingleValueContainer(forKey: key) + try container.encodeNil() + self.storage[key.stringValue] = NSNull() + } + + func encode(_ value: T, forKey key: Key) throws where T : Encodable { + let container = _ExtJSONEncoder.SingleValueContainer(codingPath: self.nestedCodingPath(forKey: key), + userInfo: self.userInfo) + try container.encode(value) + self.storage[key.stringValue] = container.storage + } + + private func nestedSingleValueContainer(forKey key: Key) -> SingleValueEncodingContainer { + _ExtJSONEncoder.SingleValueContainer(codingPath: self.nestedCodingPath(forKey: key), + userInfo: self.userInfo) + } + + func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { + let container = _ExtJSONEncoder.UnkeyedContainer(codingPath: self.nestedCodingPath(forKey: key), + userInfo: self.userInfo) + self.storage[key.stringValue] = container.storage + + return container + } + + func nestedContainer(keyedBy keyType: NestedKey.Type, + forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { + let container = _ExtJSONEncoder.KeyedContainer(codingPath: self.nestedCodingPath(forKey: key), userInfo: self.userInfo) + self.storage[key.stringValue] = container.storage + + return KeyedEncodingContainer(container) + } + + func superEncoder() -> Encoder { + guard let encoder = self.userInfo[.superCoder] as? any Encoder else { + throwRealmException("Could not find reference to super encoder") + } + return encoder + } + + func superEncoder(forKey key: Key) -> Encoder { + fatalError("Unimplemented") // FIXME + } +} + +struct AnyCodingKey: CodingKey, Equatable { + var stringValue: String + var intValue: Int? + + init?(stringValue: String) { + self.stringValue = stringValue + self.intValue = nil + } + + init?(intValue: Int) { + self.stringValue = "\(intValue)" + self.intValue = intValue + } + + init(_ base: Key) where Key : CodingKey { + if let intValue = base.intValue { + self.init(intValue: intValue)! + } else { + self.init(stringValue: base.stringValue)! + } + } +} + +extension _ExtJSONEncoder { + fileprivate final class UnkeyedContainer : _ExtJSONContainer { + var storage: [Any?] = [] + + var codingPath: [CodingKey] + + var nestedCodingPath: [CodingKey] { + return self.codingPath + [AnyCodingKey(intValue: self.count)!] + } + + var userInfo: [CodingUserInfoKey: Any] + + init(codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) { + self.codingPath = codingPath + self.userInfo = userInfo + } + } +} + +// MARK: UnkeyedEncodingContainer +extension _ExtJSONEncoder.UnkeyedContainer: UnkeyedEncodingContainer { + var count: Int { + return storage.count + } + + func encodeNil() throws { + self.storage.append(nil) + } + + func encode(_ value: T) throws where T : Encodable { + let container = _ExtJSONEncoder.SingleValueContainer(codingPath: self.nestedCodingPath, + userInfo: self.userInfo) + try container.encode(value) + self.storage.append(container.storage) + } + + func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer where NestedKey : CodingKey { + let container = _ExtJSONEncoder.KeyedContainer(codingPath: self.nestedCodingPath, + userInfo: self.userInfo) + self.storage.append(container.storage) + + return KeyedEncodingContainer(container) + } + + func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { + let container = _ExtJSONEncoder.UnkeyedContainer(codingPath: self.nestedCodingPath, + userInfo: self.userInfo) + self.storage.append(container.storage) + + return container + } + + func superEncoder() -> Encoder { + fatalError("Unimplemented") // FIXME + } +} + +// MARK: ExtJSONSerialization +public struct ExtJSONSerialization { + private init() {} + + public static func extJSONObject(with data: Data) throws -> Any { + func decode(_ object: Any) throws -> Any? { + switch object { + case let (key, value) as (String, Any): + return (key, try decode(value)) + case let object as [String: Any]: + return try (object.map(decode) as? [(String, Any)]) + .map { + Dictionary(uniqueKeysWithValues: $0) + } + case let object as [Any]: + return try object.map(decode) + // case let object as (any _ExtJSON)?: + // return try object.map { try ExtJSONDecoder().decode(type(of: $0.extJSONValue), + // from: object) } + default: + guard let value = object as? Decodable else { + throw DecodingError.typeMismatch(type(of: object), + .init(codingPath: [], + debugDescription: String(describing: type(of: object)))) + } + return try type(of: value).init(from: _ExtJSONDecoder(storage: value)) + // return try ExtJSONDecoder().decode(type(of: value), from: value) + } + } + return try decode(JSONSerialization.jsonObject(with: data)) as Any + } + + public static func data(with extJSONObject: Any) throws -> Data { + func encode(_ object: Any) throws -> Any? { + switch object { + case let (key, value) as (String, Any?): + return (key, try value.map(encode) ?? NSNull()) + case let object as [String: Any]: + return try (object.map(encode) as? [(String, Any?)]) + .map { + Dictionary(uniqueKeysWithValues: $0) + } + case let object as [Any]: + return try object.map(encode) + case let object as (any ExtJSONBuiltin)?: + return try object.map { try ExtJSONEncoder().encode($0) } + default: + let encoder = _ExtJSONEncoder() + guard let value = object as? Encodable else { + throw DecodingError.typeMismatch(type(of: object), + .init(codingPath: [], + debugDescription: String(describing: type(of: object)))) + } + try value.encode(to: encoder) + return encoder.container?.storage + } + } + let encoded = try encode(extJSONObject) + return try JSONSerialization.data(withJSONObject: encoded as Any) + } + + public static func data(with extJSONObject: T) throws -> Data where T: Encodable { + try ExtJSONEncoder().encode(extJSONObject) + } +} diff --git a/RealmSwift/MongoDataAccess/MongoDataAccess.swift b/RealmSwift/MongoDataAccess/MongoDataAccess.swift new file mode 100644 index 0000000000..27ecef3d88 --- /dev/null +++ b/RealmSwift/MongoDataAccess/MongoDataAccess.swift @@ -0,0 +1,352 @@ +import Foundation +import Realm +import Realm.Private + +public enum AnyBSONKey: ExtJSONCodable, Equatable { + typealias ExtJSONValue = Self + + case string(String) + case objectId(ObjectId) + case int(Int) + + public func encode(to encoder: Encoder) throws { + switch self { + case .string(let a0): try a0.encode(to: encoder) + case .objectId(let a0): try a0.encode(to: encoder) + case .int(let a0): try a0.encode(to: encoder) + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + switch decoder.userInfo[.storage] { + case let value as String: self = .string(value) + case let value as [String: String]: + if value.keys.first == Int.ExtJSONValue.CodingKeys.numberInt.rawValue { + self = .int(try container.decode(Int.self)) + } else { + self = .objectId(try container.decode(ObjectId.self)) + } + default: + if let value = try? container.decode(String.self) { + self = .string(value) + } else if let value = try? container.decode(Int.self) { + self = .int(value) + } else { + self = .objectId(try container.decode(ObjectId.self)) + } + } + } +} + +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) +@usableFromInline +typealias Functor = (repeat (each Args), + @Sendable @escaping (Result) -> Void) -> Void + +protocol Resolvable { + associatedtype Success + associatedtype Failure: Error + + var result: Result { get } +} +extension Result: Resolvable { + var result: Result { return self } +} + +//@usableFromInline +public typealias Function = (repeat (each Args)) -> Void + + +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) +func withTypeCheckedThrowingContinuation(_ block: @escaping (repeat (each U), @escaping (V) -> Void) -> Void, + _ arguments: repeat (each U)) +async throws -> V.Success where V: Resolvable { + func curry(_ fn: @escaping (repeat each Args) -> Void, + arguments: repeat (each Args)) { + fn(repeat each arguments) + } + return try await withCheckedThrowingContinuation { continuation in + let rb = unsafeBitCast({ @Sendable (returnValue: Result) in + switch returnValue.result { + case .success(let value): + continuation.resume(returning: value) + case .failure(let error): + continuation.resume(throwing: error) + } + }, to: Function.self) + curry(block, arguments: repeat (each arguments) as (each U), rb) + } +} + +public struct InsertOneResult : Codable { + public let insertedId: AnyBSONKey +} + +public struct MongoTypedCollection { + fileprivate let mongoCollection: RLMMongoCollection + fileprivate let database: MongoDatabase + fileprivate init(mongoCollection: RLMMongoCollection, database: MongoDatabase) { + self.mongoCollection = mongoCollection + self.database = database + } + + private func call(function: String, + arguments: NSDictionary, + _ block: @Sendable @escaping (Result) -> Void) where Res: Codable { + do { + let arguments = NSMutableDictionary(dictionary: arguments) + arguments["database"] = database.name + arguments["collection"] = mongoCollection.name + try mongoCollection.user.callFunctionNamed( + function, + arguments: String(data: ExtJSONSerialization.data(with: [arguments]), + encoding: .utf8)!, + serviceName: mongoCollection.serviceName) { data, error in + guard let data = data?.data(using: .utf8) else { + guard let error = error else { + return block(.failure(AppError(RLMAppError.Code.httpRequestFailed))) + } + return block(.failure(error)) + } + do { + block(.success(try ExtJSONDecoder().decode(Res.self, from: data))) + } catch { + block(.failure(error)) + } + } + } catch { + block(.failure(error)) + } + } + + @available(macOS 10.15, *) + @_unsafeInheritExecutor + private func call(function: String, arguments: NSDictionary) async throws -> Res where Res: Codable { + try await withCheckedThrowingContinuation { continuation in + call(function: function, arguments: arguments, { + continuation.resume(with: $0) + }) + } + } + + struct Arguments : Codable { + let database: String + let collection: String + let document: T + } + + // MARK: InsertOne + /// Encodes the provided value to BSON and inserts it. If the value is missing an identifier, one will be + /// generated for it. + /// - Parameters: + /// - object: object A `T` value to insert. + /// - completion: The result of attempting to perform the insert. An Id will be returned for the inserted object on sucess + @preconcurrency + public func insertOne(_ object: T, + _ completion: @Sendable @escaping (Result) -> Void) { + call(function: "insertOne", arguments: ["document": object], completion) + } + + @_unsafeInheritExecutor + @available(macOS 10.15, *) + public func insertOne(_ object: T) async throws -> InsertOneResult { + try await withTypeCheckedThrowingContinuation(self.insertOne, object) + } + + // MARK: InsertMany + public struct InsertManyResult: Codable { + public let insertedIds: [AnyBSONKey] + } + + public func insertMany(_ documents: [T], + _ block: @Sendable @escaping (Result) -> Void) { + call(function: "insertMany", arguments: ["documents": documents], block) + } + @available(macOS 10.15, *) + @_unsafeInheritExecutor + public func insertMany(_ documents: [T]) async throws -> InsertManyResult { + try await withTypeCheckedThrowingContinuation(insertMany, documents) + } + + // MARK: FindOne + public func findOne(_ block: @Sendable @escaping (Result) -> Void) { + call(function: "findOne", arguments: [:], block) + } + public func findOne(_ filter: [String: Any], + options: FindOptions = FindOptions(), + _ block: @Sendable @escaping (Result) -> Void) { + call(function: "findOne", arguments: ["query": filter], block) + } + public func findOne(_ filter: T, + options: FindOptions = FindOptions(), + _ block: @Sendable @escaping (Result) -> Void) { + call(function: "findOne", arguments: ["query": filter], block) + } + + /// Returns one document from a collection or view which matches the + /// provided filter. If multiple documents satisfy the query, this method + /// returns the first document according to the query's sort order or natural + /// order. + /// - returns: A publisher that eventually return `Document` or `nil` if document wasn't found or `Error`. + @_unsafeInheritExecutor + @available(macOS 10.15, *) + public func findOne() async throws -> T? { + try await withTypeCheckedThrowingContinuation(findOne) + } + + /// Returns one document from a collection or view which matches the + /// provided filter. If multiple documents satisfy the query, this method + /// returns the first document according to the query's sort order or natural + /// order. + /// - parameter options: Options to apply to the query. + /// - parameter filter: A `Document` as bson that should match the query. + /// - returns: A publisher that eventually return `Document` or `nil` if document wasn't found or `Error`. + @_unsafeInheritExecutor + @available(macOS 10.15, *) + public func findOne(_ filter: [String: Any], + options: FindOptions = FindOptions()) async throws -> T? { + try await withTypeCheckedThrowingContinuation(findOne, filter, options) + } + @_unsafeInheritExecutor + @available(macOS 10.15, *) + public func findOne(_ filter: T, + options: FindOptions = FindOptions()) async throws -> T? { + try await withTypeCheckedThrowingContinuation(findOne, filter, options) + } + @_unsafeInheritExecutor + @available(macOS 10.15, *) + public func findOne(_ filter: ((Query) -> Query), + options: FindOptions = FindOptions()) async throws -> T? + where T: Object { + try await withTypeCheckedThrowingContinuation(findOne, + buildFilter(filter(Query()).node), + options) + } + + // MARK: Find + public func find(options: FindOptions = FindOptions(), + _ filter: T? = nil, + _ block: @Sendable @escaping (Result<[T], Error>) -> Void) { + call(function: "find", arguments: ["query": filter ?? [:]], block) + } + public func find(options: FindOptions = FindOptions(), + _ filter: [String: Any]? = nil, + _ block: @Sendable @escaping (Result<[T], Error>) -> Void) { + call(function: "find", arguments: [ + "query": filter ?? [:] + ], block) + } + @available(macOS 10.15, *) + @_unsafeInheritExecutor + public func find(options: FindOptions = FindOptions(), + where filter: ((Query) -> Query)? = nil) async throws -> [T] { + try await withTypeCheckedThrowingContinuation(find, + options, + filter.map { try buildFilter($0(Query()).node) } ?? [:]) + } + + // MARK: Count + @available(macOS 10.15, *) + @_unsafeInheritExecutor + public func count(where filter: (Query) -> Query) async throws -> Int64 { + try await call(function: "count", arguments: [ + "query": try buildFilter(filter(Query()).node), + ]) + } + + // MARK: Update + public struct UpdateResult: Codable { + + /// The number of documents that matched the filter. + public let matchedCount: Int + + /// The number of documents modified. + public let modifiedCount: Int + + /// The identifier of the inserted document if an upsert took place. + public let upsertedId: AnyBSONKey? + } + + @available(macOS 10.15, *) + @_unsafeInheritExecutor + public func updateOne(filter: ((Query) -> Query), + update: T, + upsert: Bool? = nil) async throws -> UpdateResult { + try await call(function: "updateOne", arguments: [ + "query": buildFilter(filter(Query()).node), + "update": update, + "upsert": upsert ?? false, + "database": mongoCollection.databaseName, + "collection": mongoCollection.name + ]) + } + + @available(macOS 10.15, *) + @_unsafeInheritExecutor + public func updateMany(filter: ((Query) -> Query), + update: T, + upsert: Bool? = nil) async throws -> UpdateResult { + try await call(function: "updateMany", arguments: [ + "query": buildFilter(filter(Query()).node), + "update": update, + "upsert": upsert ?? false, + "database": mongoCollection.databaseName, + "collection": mongoCollection.name + ]) + } + + // MARK: Delete + public struct Deleted : Codable { + public let deletedCount: Int + } + + /// Deletes a single matching document from the collection. + /// - Parameters: + /// - filter: A `Document` as bson that should match the query. + /// - completion: The result of performing the deletion. Returns the count of deleted objects + @preconcurrency + public func deleteOne(filter: T? = nil, + _ completion: @escaping @Sendable (Result) -> Void) -> Void { + call(function: "deleteOne", arguments: [ + "database": mongoCollection.databaseName, + "collection": mongoCollection.name, + "query": filter ?? [:] + ], completion) + } + + @available(macOS 10.15, *) + @_unsafeInheritExecutor + public func deleteOne(filter: T? = nil) async throws -> Deleted { + try await withTypeCheckedThrowingContinuation(deleteOne, + filter) + } + /// Deletes multiple documents + /// - Parameters: + /// - filter: Document representing the match criteria + /// - completion: The result of performing the deletion. Returns the count of the deletion + @preconcurrency + public func deleteMany(filter: T? = nil, + _ completion: @escaping @Sendable (Result) -> Void) -> Void { + call(function: "deleteMany", arguments: [ + "database": mongoCollection.databaseName, + "collection": mongoCollection.name, + "query": filter ?? [:] + ], completion) + } + + @_unsafeInheritExecutor + @available(macOS 10.15, watchOS 6.0, iOS 13.0, tvOS 13.0, *) + public func deleteMany(filter: T? = nil) async throws -> Deleted { + try await withTypeCheckedThrowingContinuation(deleteMany, + filter) + } +} + +extension MongoDatabase { + public func collection(named name: String, + type: T.Type) -> MongoTypedCollection { + MongoTypedCollection(mongoCollection: self.collection(withName: name), + database: self) + } +} diff --git a/RealmSwift/MongoDataAccess/MongoQuery.swift b/RealmSwift/MongoDataAccess/MongoQuery.swift new file mode 100644 index 0000000000..aa21d85c4b --- /dev/null +++ b/RealmSwift/MongoDataAccess/MongoQuery.swift @@ -0,0 +1,212 @@ +// +// MongoQuery.swift +// RealmSwift +// +// Created by Jason Flax on 3/7/24. +// Copyright © 2024 Realm. All rights reserved. +// + +import Foundation + +struct MongoQuery { + class Node { + var keyPath: String? + var node: Node? + } +} + +package func buildFilter(_ root: QueryNode, subqueryCount: Int = 0) throws -> [String: Any] { + var subqueryCounter = subqueryCount + func buildExpression(_ lhs: QueryNode, + _ op: String, + _ rhs: QueryNode, + prefix: String? = nil) { + + if case let .keyPath(_, lhsOptions) = lhs, + case let .keyPath(_, rhsOptions) = rhs, + lhsOptions.contains(.isCollection), rhsOptions.contains(.isCollection) { + throwRealmException("Comparing two collection columns is not permitted.") + } + fatalError() + } + + func buildCompoundExpression(_ lhs: QueryNode, + _ op: QueryNode.Operator, + _ rhs: QueryNode, + document: NSMutableDictionary) { + switch op { + case .and: + document["$and"] = [build(lhs, root: NSMutableDictionary()), + build(rhs, root: NSMutableDictionary())] + case .or: + document["$or"] = [build(lhs, root: NSMutableDictionary()), + build(rhs, root: NSMutableDictionary())] + default: + throwRealmException("Unsupported operator \(op) for compound query expression") + } + } + + func buildBetween(_ lowerBound: QueryNode, _ upperBound: QueryNode) { + // formatStr.append(" BETWEEN {") + // build(lowerBound) + // formatStr.append(", ") + // build(upperBound) + // formatStr.append("}") + } + + func buildBool(_ node: QueryNode, + document: NSMutableDictionary, + isNot: Bool = false) { + if case let .keyPath(kp, _) = node { + document[kp.joined(separator: ".")] = ["$eq": !isNot] + } + } + + func strOptions(_ options: StringOptions) -> String { + if options == [] { + return "" + } + return "[\(options.contains(.caseInsensitive) ? "c" : "")\(options.contains(.diacriticInsensitive) ? "d" : "")]" + } + + func build(_ node: QueryNode, root: NSMutableDictionary, isNewNode: Bool = false) -> NSDictionary { + switch node { + case .constant(let value): + fatalError() + // formatStr.append("%@") + // arguments.add(value ?? NSNull()) + case .keyPath(let kp, let options): + if isNewNode { + buildBool(node, document: root) + return root + } + fatalError() + // if options.contains(.requiresAny) { + // formatStr.append("ANY ") + // } + // formatStr.append(kp.joined(separator: ".")) + case .not(let child): + if case .keyPath = child, + isNewNode { + buildBool(child, document: root, isNot: true) + return root + } + let built = build(child, root: NSMutableDictionary()) + root[built.allKeys.first!] = ["$not": built.allValues.first] +// root["$not"] = build(child, root: NSMutableDictionary()) + case .comparison(operator: let op, let lhs, let rhs, let options): + func unwrapComparison(lhs: QueryNode, rhs: QueryNode) -> (String, Any?) { + if case let .keyPath(kp, _) = lhs, + case let .constant(value) = rhs { + guard !(kp.last?.starts(with: "@") ?? false) else { + throwRealmException("Aggregation operation \(kp.last ?? "") not currently supported in Query translator") + } + return (kp.joined(separator: "."), value) + } else if case let .keyPath(kp, _) = rhs, + case let .constant(value) = lhs { + guard !(kp.last?.starts(with: "@") ?? false) else { + fatalError("Aggregation operation \(kp.last ?? "") not currently supported in Query translator") + } + return (kp.joined(separator: "."), value) + } else { + throwRealmException("Could not read keyPath from comparison query") + } + } + switch op { + case .and, .or: + buildCompoundExpression(lhs, op, rhs, document: root) + case .contains: + let (kp, value) = unwrapComparison(lhs: lhs, rhs: rhs) + if let value = value as? String { + root[kp] = ["$regex": value] + } else { + fatalError() + } + case .in: + let (kp, value) = unwrapComparison(lhs: lhs, rhs: rhs) + if let value = value as? any Collection { + root[kp] = ["$in": value] + } else { + root[kp] = ["$elemMatch": ["$eq": value]] + } + default: + let (kp, value) = unwrapComparison(lhs: lhs, rhs: rhs) + switch op { + case .equal: + root[kp] = ["$eq": value] + case .notEqual: + guard case let .constant(value) = rhs else { + fatalError() + } + root[kp] = ["$ne": value] + case .lessThan: + guard case let .constant(value) = rhs else { + fatalError() + } + root[kp] = ["$lt": value] + case .lessThanEqual: + guard case let .constant(value) = rhs else { + fatalError() + } + root[kp] = ["$lte": value] + case .greaterThan: + guard case let .constant(value) = rhs else { + fatalError() + } + root[kp] = ["$gt": value] + case .greaterThanEqual: + guard case let .constant(value) = rhs else { + fatalError() + } + root[kp] = ["$gte": value] + case .contains: + root[kp] = [ + "$elemMatch": [ value ] + ] + case .in: + guard case let .constant(value) = rhs else { + fatalError() + } + if let value = value as? any Collection { + root[kp] = value.map { + [ + "$eq": $0 + ] + } + } else { + root[kp] = [ + "$elemMatch": [ + "$eq": value + ] + ] + } + default: + throwRealmException("Invalid operator \(op) for comparison query expression") + } + // buildExpression(lhs, "\(op.rawValue)\(strOptions(options))", rhs, prefix: prefix) + } + case .between(let lhs, let lowerBound, let upperBound): + guard case let .keyPath(kp, options) = lhs, + case .constant(let lowerBound) = lowerBound, + case .constant(let upperBound) = upperBound else { + throwRealmException("Invalid query BETWEEN: \(lhs)") + } + root[kp.joined(separator: ".")] = [ + "$gte": lowerBound, + "$lt": upperBound + ] + case .subqueryCount(_): + throwRealmException("Subqueries not supported") + case .mapSubscript(_, _): + fatalError() + // build(keyPath) + // formatStr.append("[%@]") + // arguments.add(key) + case .geoWithin(let keyPath, let value): + buildExpression(keyPath, QueryNode.Operator.in.rawValue, value, prefix: nil) + fatalError() + } + return root + } + return build(root, root: NSMutableDictionary(), isNewNode: true) as! [String: Any] +} diff --git a/RealmSwift/PersistedProperty.swift b/RealmSwift/PersistedProperty.swift index de5da25cdc..d1cb9ea8ef 100644 --- a/RealmSwift/PersistedProperty.swift +++ b/RealmSwift/PersistedProperty.swift @@ -95,6 +95,7 @@ import Realm.Private /// runtime errors. @propertyWrapper public struct Persisted { + internal typealias Value = Value private var storage: PropertyStorage /// :nodoc: @@ -263,6 +264,17 @@ public protocol OptionalCodingWrapper { init(wrappedValue: WrappedType) } +internal protocol _PersistedProperty { + associatedtype Value: _Persistable + static var valueType: Value.Type { + get + } +} +extension Persisted: _PersistedProperty { + static var valueType: Value.Type { + Value.self + } +} /// :nodoc: extension KeyedDecodingContainer { // This is used to override the default decoding behaviour for OptionalCodingWrapper to allow a value to avoid a missing key Error diff --git a/RealmSwift/Query.swift b/RealmSwift/Query.swift index f6af381289..3d38d17523 100644 --- a/RealmSwift/Query.swift +++ b/RealmSwift/Query.swift @@ -104,7 +104,7 @@ public struct StringOptions: OptionSet { public struct Query { /// This initaliser should be used from callers who require queries on primitive collections. /// - Parameter isPrimitive: True if performing a query on a primitive collection. - internal init(isPrimitive: Bool = false) { + package init(isPrimitive: Bool = false) { if isPrimitive { node = .keyPath(["self"], options: [.isCollection]) } else { @@ -112,7 +112,7 @@ public struct Query { } } - private let node: QueryNode + package let node: QueryNode /** The `Query` struct works by compounding `QueryNode`s together in a tree structure. @@ -867,8 +867,18 @@ extension Optional: _QueryBinary where Wrapped: _Persistable, Wrapped.PersistedT // MARK: QueryNode - -private indirect enum QueryNode { - enum Operator: String { +package indirect enum QueryNode { + package struct KeyPathOptions: OptionSet { + package let rawValue: Int8 + package init(rawValue: RawValue) { + self.rawValue = rawValue + } + + static let isCollection = KeyPathOptions(rawValue: 1) + static let requiresAny = KeyPathOptions(rawValue: 2) + } + + package enum Operator: String { case or = "||" case and = "&&" case equal = "==" @@ -897,6 +907,8 @@ private indirect enum QueryNode { case geoWithin(_ keyPath: QueryNode, _ value: QueryNode) } +private typealias KeyPathOptions = QueryNode.KeyPathOptions + private func buildPredicate(_ root: QueryNode, subqueryCount: Int = 0) -> (String, [Any]) { let formatStr = NSMutableString() let arguments = NSMutableArray() @@ -1009,15 +1021,6 @@ private func buildPredicate(_ root: QueryNode, subqueryCount: Int = 0) -> (Strin return (formatStr as String, (arguments as! [Any])) } -private struct KeyPathOptions: OptionSet { - let rawValue: Int8 - init(rawValue: RawValue) { - self.rawValue = rawValue - } - - static let isCollection = KeyPathOptions(rawValue: 1) - static let requiresAny = KeyPathOptions(rawValue: 2) -} private struct SubqueryRewriter { private var collectionName: String? diff --git a/RealmSwift/Tests/MongoDataAccessTests.swift b/RealmSwift/Tests/MongoDataAccessTests.swift index 4cb14b6027..afb36ba2dd 100644 --- a/RealmSwift/Tests/MongoDataAccessTests.swift +++ b/RealmSwift/Tests/MongoDataAccessTests.swift @@ -3,73 +3,52 @@ import XCTest import Realm import RealmSwift -// -//@BSONCodable struct AllTypesBSONObject { -// @BSONCodable struct AllTypesA : Equatable { -// @BSONCodable struct AllTypesB : Equatable { -// let intB: Int -// let stringB: String -// } -// let intA: Int -// let stringA: String -// let allTypesB: AllTypesB -// } -// let int: Int -// let intArray: [Int] -// let intOpt: Int? -// -// let string: String -// let bool: Bool -// let double: Double -//// let data: Data -// let long: Int64 -//// let decimal: Decimal128 -//// let uuid: UUID -// let object: AllTypesA -// let objectArray: [AllTypesA] -// let objectOpt: AllTypesA? -// var anyValue: Any -//} -// -//@BSONCodable final class RealmPerson : Object { -// @Persisted(primaryKey: true) var _id: ObjectId = .generate() -// @Persisted var name: String -// @Persisted var age: Int -// @Persisted var address: RealmAddress? -// @Persisted var dogs: List -//} -// -//@BSONCodable final class RealmAddress : EmbeddedObject { -// @Persisted var city: String -// @Persisted var state: String -//} -// -//@BSONCodable final class RealmDog : Object { -// @Persisted(primaryKey: true) var _id: ObjectId = .generate() -// @Persisted var owner: RealmPerson? -// @Persisted var name: String -// @Persisted var age: Int -//} -// -//@BSONCodable struct RegexTest { -// let a: Regex -//} -// -//extension RegexTest : Equatable { -// static func == (lhs: Self, -// rhs: Self) -> Bool { -//// lhs.a.regex._literalPattern -// return "\(lhs.a)" == "\(rhs.a)" -// } -//} -//// -////extension Regex : BSON { -//// public static func == (lhs: Regex, rhs: Regex) -> Bool { -//// "\(lhs)" == "\(rhs)" -//// } -////} -//// -// +class Subdocument : EmbeddedObject, Codable { + @Persisted var strCol: String + + convenience init(strCol: String) { + self.init() + self.strCol = strCol + } +} + +@objc(_AllTypesExtJSONObject) private class AllTypesObject : Object, Codable { + @Persisted var _id: ObjectId + + @Persisted var strCol: String + @Persisted var intCol: Int + @Persisted var binaryCol: Data + + @Persisted var arrayStrCol: List + @Persisted var arrayIntCol: List + + @Persisted var optStrCol: String? + @Persisted var optIntCol: Int? + + @Persisted var subdocument: Subdocument? + + convenience init(_id: ObjectId, + strCol: String, + intCol: Int, + binaryCol: Data, + arrayStrCol: List, + arrayIntCol: List, + optStrCol: String? = nil, + optIntCol: Int? = nil, + subdocument: Subdocument) { + self.init() + self._id = _id + self.strCol = strCol + self.intCol = intCol + self.binaryCol = binaryCol + self.arrayStrCol = arrayStrCol + self.arrayIntCol = arrayIntCol + self.optStrCol = optStrCol + self.optIntCol = optIntCol + self.subdocument = subdocument + } +} + class MongoDataAccessMacrosTests : XCTestCase { struct MultiTypeTest : Codable, Equatable { enum CodingKeys: String, CodingKey { @@ -218,11 +197,11 @@ class MongoDataAccessMacrosTests : XCTestCase { XCTAssertEqual(allTypes1.date.timeIntervalSince1970, allTypes2.date.timeIntervalSince1970, - accuracy: 0.001) + accuracy: 0.1) for (d1, d2) in zip(allTypes1.arrayDate.map(\.timeIntervalSince1970), allTypes2.arrayDate.map(\.timeIntervalSince1970)) { - XCTAssertEqual(d1, d2, accuracy: 0.001) + XCTAssertEqual(d1, d2, accuracy: 0.1) } // zip(allTypes1.arrayDate, allTypes2.arrayDate) // .forEach(XCTAssertEqual) @@ -348,233 +327,52 @@ class MongoDataAccessMacrosTests : XCTestCase { NullTest(a: nil) ]) } - // - // @BSONCodable struct MultiTypeTest : Equatable { - // let _id: ObjectId - // @BSONCodable(key: "String") let string: String - // @BSONCodable(key: "Int32") let int32: Int - // @BSONCodable(key: "Int64") let int64: Int64 - // @BSONCodable(key: "Double") let double: Double - // @BSONCodable(key: "Binary") let binary: Data - // - // @BSONCodable struct Subdocument : Equatable { - // let foo: String - // } - // @BSONCodable(key: "Subdocument") let subdocument: Subdocument - // @BSONCodable(key: "Array") let array: [Int] - //// @BSONCodable(key: "Timestamp") let timestamp: Date - // @BSONCodable(key: "DatetimeEpoch") let datetimeEpoch: Date - // @BSONCodable(key: "DatetimePositive") let datetimePositive: Date - // @BSONCodable(key: "DatetimeNegative") let datetimeNegative: Date - // @BSONCodable(key: "True") let `true`: Bool - // @BSONCodable(key: "False") let `false`: Bool - //// @BSONCodable(key: "MinKey") let minKey: MinKey - //// @BSONCodable(key: "MaxKey") let maxKey: MaxKey - // @BSONCodable(key: "Null") let null: Optional - // - // struct NonComformantType : Equatable {} - // @BSONCodable(ignore: true) let nonComformantType: NonComformantType = .init() - // @BSONCodable(ignore: true) let nonComformantTypeOptional: NonComformantType? - // } - //// {\"_id\": {\"$oid\": \"57e193d7a9cc81b4027498b5\"}, \"String\": \"string\", \"Int32\": {\"$numberInt\": \"42\"}, \"Int64\": {\"$numberLong\": \"42\"}, \"Double\": {\"$numberDouble\": \"-1.0\"}, \"Binary\": { \"$binary\" : {\"base64\": \"o0w498Or7cijeBSpkquNtg==\", \"subType\": \"03\"}}, \"BinaryUserDefined\": { \"$binary\" : {\"base64\": \"AQIDBAU=\", \"subType\": \"80\"}}, \"Code\": {\"$code\": \"function() {}\"}, \"CodeWithScope\": {\"$code\": \"function() {}\", \"$scope\": {}}, \"Subdocument\": {\"foo\": \"bar\"}, \"Array\": [{\"$numberInt\": \"1\"}, {\"$numberInt\": \"2\"}, {\"$numberInt\": \"3\"}, {\"$numberInt\": \"4\"}, {\"$numberInt\": \"5\"}], \"Timestamp\": {\"$timestamp\": {\"t\": 42, \"i\": 1}}, \"Regex\": {\"$regularExpression\": {\"pattern\": \"pattern\", \"options\": \"\"}}, \"DatetimeEpoch\": {\"$date\": {\"$numberLong\": \"0\"}}, \"DatetimePositive\": {\"$date\": {\"$numberLong\": \"2147483647\"}}, \"DatetimeNegative\": {\"$date\": {\"$numberLong\": \"-2147483648\"}}, \"True\": true, \"False\": false, \"DBRef\": {\"$ref\": \"collection\", \"$id\": {\"$oid\": \"57fd71e96e32ab4225b723fb\"}, \"$db\": \"database\"}, \"Minkey\": {\"$minKey\": 1}, \"Maxkey\": {\"$maxKey\": 1}, \"Null\": null}" - // func testMultiType() throws { - // try run(test: "multi-type", answers: [ - // MultiTypeTest(_id: .init("57e193d7a9cc81b4027498b5"), - // string: "string", - // int32: 42, - // int64: 42, - // double: -1.0, - // binary: Data(base64Encoded: "o0w498Or7cijeBSpkquNtg==")!, - // subdocument: MultiTypeTest.Subdocument(foo: "bar"), - // array: [1, 2, 3, 4, 5], - //// timestamp: Date(timeIntervalSince1970: 42), - // datetimeEpoch: Date(timeIntervalSince1970: 0), - // datetimePositive: Date(timeIntervalSince1970: 2147483647), - // datetimeNegative: Date(timeIntervalSince1970: -2147483648), - // true: true, - // false: false, - //// minKey: MinKey(), - //// maxKey: MaxKey(), - // null: nil, - // nonComformantTypeOptional: nil) - // ]) - // } - //// // TODO: Fix spacing in macro expansion - //// func testWithoutAnyCustomization() throws { - //// assertMacroExpansion( - //// """ - //// @BSONCodable struct Person { - //// let name: String - //// let age: Int - //// } - //// """, - //// expandedSource: - //// """ - //// struct Person { - //// let name: String - //// let age: Int - //// init(name: String, age: Int) { - //// self.name = name - //// self.age = age - //// } - //// init(from document: Document) throws { - //// guard let name = document["name"] else { - //// throw BSONError.missingKey("name") - //// } - //// guard let name: String = try name?.as() else { - //// throw BSONError.invalidType(key: "name") - //// } - //// self.name = name - //// guard let age = document["age"] else { - //// throw BSONError.missingKey("age") - //// } - //// guard let age: Int = try age?.as() else { - //// throw BSONError.invalidType(key: "age") - //// } - //// self.age = age - //// } - //// func encode(to document: inout Document) { - //// document["name"] = AnyBSON(name) - //// document["age"] = AnyBSON(age) - //// } - //// struct Filter : BSONFilter { - //// var documentRef = DocumentRef() - //// var name: BSONQuery - //// var age: BSONQuery - //// init() { - //// name = BSONQuery(identifier: "name", documentRef: documentRef) - //// age = BSONQuery(identifier: "age", documentRef: documentRef) - //// } - //// mutating func encode() -> Document { - //// return documentRef.document - //// } - //// } - //// } - //// extension Person: BSONCodable { - //// } - //// """, macros: ["BSONCodable" : BSONCodableMacro.self] - //// ) - //// } - ////} - //// - //// - ////extension MongoCollection { - //// subscript(keyPath: String) -> V { - //// Mirror(reflecting: self).descendant(keyPath) as! V - //// } - ////} - //// - ////@freestanding(declaration, names: arbitrary) - ////private macro mock(object: T, _ block: () -> ()) = - //// #externalMacro(module: "MongoDataAccessMacros", type: "MockMacro2") - //// - ////@objc class MongoDataAccessTests : XCTestCase { - //// func testFind() async throws { - //// let app = App(id: "test") - //// #mock(object: app) { - //// func login(withCredential: RLMCredentials, completion: RLMUserCompletionBlock) { - //// completion(class_createInstance(RLMUser.self, 0) as? RLMUser, nil) - //// } - //// } - //// let user = try await app.login(credentials: .anonymous) - //// let collection = user.mongoClient("mongodb-atlas") - //// .database(named: "my_app") - //// .collection(named: "persons", type: Person.self) - //// - //// let underlying: RLMMongoCollection = collection["mongoCollection"] - //// // find error - //// #mock(object: underlying) { - //// func findWhere(_ document: Dictionary, - //// options: RLMFindOptions, - //// completion: RLMMongoFindBlock) { - //// completion(nil, SyncError(_bridgedNSError: .init(domain: "MongoClient", code: 42))) - //// } - //// } - //// do { - //// _ = try await collection.find() - //// XCTFail() - //// } catch { - //// } - //// // find empty - //// #mock(object: underlying) { - //// func findWhere(_ document: Dictionary, - //// options: RLMFindOptions, - //// completion: RLMMongoFindBlock) { - //// completion([], nil) - //// } - //// } - //// var persons = try await collection.find() - //// XCTAssert(persons.isEmpty) - //// // find one - //// #mock(object: underlying) { - //// func findWhere(_ document: Dictionary, - //// options: RLMFindOptions, - //// completion: RLMMongoFindBlock) { - //// let document: Document = [ - //// "name": "Jason", - //// "age": 32, - //// "address": ["city": "Austin", "state": "TX"] - //// ] - //// completion([ObjectiveCSupport.convert(document)], nil) - //// } - //// } - //// persons = try await collection.find() - //// let person = Person(name: "Jason", age: 32, address: Address(city: "Austin", state: "TX")) - //// XCTAssertEqual(person.name, "Jason") - //// XCTAssertEqual(person.age, 32) - //// XCTAssertEqual(person.address.city, "Austin") - //// XCTAssertEqual(person.address.state, "TX") - //// XCTAssertEqual(persons.first, person) - //// // find many - //// #mock(object: underlying) { - //// func findWhere(_ document: Dictionary, - //// options: RLMFindOptions, - //// completion: RLMMongoFindBlock) { - //// XCTAssertEqual(ObjectiveCSupport.convert(document), [ - //// "$or": [ ["name": "Jason"], ["name" : "Lee"] ] - //// ]) - //// let document: [Document] = [ - //// [ - //// "name": "Jason", - //// "age": 32, - //// "address": ["city": "Austin", "state": "TX"] - //// ], - //// [ - //// "name": "Lee", - //// "age": 10, - //// "address": ["city": "Dublin", "state": "DUBLIN"] - //// ] - //// ] - //// completion(document.map(ObjectiveCSupport.convert), nil) - //// } - //// } - //// persons = try await collection.find { - //// $0.name == "Jason" || $0.name == "Lee" - //// } - //// let person2 = Person(name: "Lee", age: 10, address: Address(city: "Dublin", state: "DUBLIN")) - //// XCTAssertEqual(persons[0], person) - //// XCTAssertEqual(persons[1], person2) - //// } - //// - //// func testCodableGeneratedDecode() throws { - //// let person = try Person(from: ["name" : "Jason", "age": 32, "address": ["city": "Austin", "state": "TX"]]) - //// XCTAssertEqual(person.name, "Jason") - //// XCTAssertEqual(person.age, 32) - //// XCTAssertEqual(person.address.city, "Austin") - //// XCTAssertEqual(person.address.state, "TX") - //// } - //// - //// func testCodableGeneratedEncode() throws { - //// var document = Document() - //// let person = Person(name: "Jason", age: 32, address: Address(city: "Austin", state: "TX")) - //// - //// person.encode(to: &document) - //// XCTAssertEqual(document["name"], "Jason") - //// XCTAssertEqual(document["age"], 32) - //// XCTAssertEqual(document["address"]??.documentValue?["city"], "Austin") - //// XCTAssertEqual(document["address"]??.documentValue?["state"], "TX") - //// - //// XCTAssertEqual(try DocumentEncoder().encode(person), document) - //// } - //// + + // MARK: Filters + private static func compareQuery(_ query: (Query) -> Query, + _ rhs: NSDictionary) throws { + XCTAssertEqual( + try buildFilter(query(Query()).node, subqueryCount: 0), + rhs) + } + + func testFilters() throws { + try Self.compareQuery({ + $0.intCol > 42 + }, [ + "intCol": [ + "$gt": 42 + ] + ]) + try Self.compareQuery({ + $0.optStrCol == nil && $0.intCol > 42 + }, [ + "$and": [ + [ + "optStrCol": ["$eq": nil] + ], + [ + "intCol": [ + "$gt": 42 + ] + ] + ] + ]) + } + + func testSubdocumentFilters() throws { + try Self.compareQuery({ + $0.subdocument.strCol == "foo" + }, [ + "subdocument.strCol": ["$eq": "foo"] + ]) + } + + func testCollectionFilters() throws { + try Self.compareQuery({ + $0.arrayIntCol.containsAny(in: [1, 2, 3]) + }, [ + "arrayIntCol": [ "$in": [1, 2, 3] ] + ]) + } }