// // main.swift // PGSQLDataStore // // Created by Duncan Groenewald on 1/9/2026. // import Foundation import SwiftData import PostgresNIO import Logging // ========================================== // 1. DATA MODEL SCHEMA // ========================================== @Model final class ProductRecord { @Attribute(.unique) var id: UUID = UUID() var sku: String = "" var stockQuantity: Int = 0 init(id: UUID = UUID(), sku: String, stockQuantity: Int) { self.id = id self.sku = sku self.stockQuantity = stockQuantity } // enum CodingKeys: String, CodingKey { // case id, sku, stockQuantity // } // // required init(from decoder: any Decoder) throws { // let container = try decoder.container(keyedBy: CodingKeys.self) // self.id = try container.decode(UUID.self, forKey: .id) // self.sku = try container.decode(String.self, forKey: .sku) // self.stockQuantity = try container.decode(Int.self, forKey: .stockQuantity) // } // // func encode(to encoder: any Encoder) throws { // var container = encoder.container(keyedBy: CodingKeys.self) // try container.encode(id, forKey: .id) // try container.encode(sku, forKey: .sku) // try container.encode(stockQuantity, forKey: .stockQuantity) // } } @Model final class SupplierRecord { @Attribute(.unique) var id: UUID = UUID() @Attribute(.unique) var name: String = "" init(id: UUID = UUID(), name: String) { self.id = id self.name = name } // enum CodingKeys: String, CodingKey { // case id, name // } // // required init(from decoder: any Decoder) throws { // let container = try decoder.container(keyedBy: CodingKeys.self) // self.id = try container.decode(UUID.self, forKey: .id) // self.name = try container.decode(String.self, forKey: .name) // } // // func encode(to encoder: any Encoder) throws { // var container = encoder.container(keyedBy: CodingKeys.self) // try container.encode(id, forKey: .id) // try container.encode(name, forKey: .name) // } } enum AppError: Error { case runtimeError(String) } enum PostgresSnapshotPayload: Codable { case product(id: UUID, sku: String, stockQuantity: Int) case supplier(id: UUID, name: String) } struct PostgresSnapshot: DataStoreSnapshot { // Required protocol property identifier var persistentIdentifier: PersistentIdentifier // Poly-morphic storage payload var payload: PostgresSnapshotPayload // Requirement A: SwiftData extracts model state through this lifecycle initializer init(from backingData: any BackingData, relatedBackingDatas: inout [PersistentIdentifier : any BackingData]) { self.persistentIdentifier = backingData.persistentModelID! let entityName = persistentIdentifier.entityName switch entityName { case "ProductRecord": if let productBacking = backingData as? (any BackingData) { let id = productBacking.getValue(forKey: \ProductRecord.id) let sku = productBacking.getValue(forKey: \ProductRecord.sku) let stock = productBacking.getValue(forKey: \ProductRecord.stockQuantity) self.payload = .product(id: id, sku: sku, stockQuantity: stock) } else { self.payload = .product(id: UUID(), sku: "UNKNOWN", stockQuantity: 0) } case "SupplierRecord": if let supplierBacking = backingData as? (any BackingData) { let id = supplierBacking.getValue(forKey: \SupplierRecord.id) let name = supplierBacking.getValue(forKey: \SupplierRecord.name) self.payload = .supplier(id: id, name: name) } else { self.payload = .supplier(id: UUID(), name: "UNKNOWN") } default: fatalError("Unsupported entity schema variant: \(entityName)") } } // Requirement B: Identifiers copier used during transaction insertions and cache remappings func copy(persistentIdentifier: PersistentIdentifier, remappedIdentifiers: [PersistentIdentifier : PersistentIdentifier]? = nil) -> PostgresSnapshot { return PostgresSnapshot(persistentIdentifier: persistentIdentifier, payload: self.payload) } // Custom initializers utilized for building records dynamically from Postgres rows init(persistentIdentifier: PersistentIdentifier, payload: PostgresSnapshotPayload) { self.persistentIdentifier = persistentIdentifier self.payload = payload } } // // ========================================== // 3. POSTGRESNIO STORES CONFIGURATION // ========================================== struct PostgresStoreConfiguration: DataStoreConfiguration { typealias Store = PostgresSwiftDataStore var name: String var schema: Schema? var connectionOptions: PostgresConnection.Configuration } extension PostgresStoreConfiguration: Equatable, Hashable { static func == (lhs: PostgresStoreConfiguration, rhs: PostgresStoreConfiguration) -> Bool { return lhs.name == rhs.name } func hash(into hasher: inout Hasher) { hasher.combine(name) } } // ========================================== // 4. CUSTOM COMPLIANT DATASTORE ENGINE // ========================================== final class PostgresSwiftDataStore: DataStore { let schema: Schema = Schema([ProductRecord.self, SupplierRecord.self], version: Schema.Version(1, 0, 0)) typealias Configuration = PostgresStoreConfiguration typealias Snapshot = PostgresSnapshot let configuration: PostgresStoreConfiguration var connection: PostgresConnection? = nil let logger = Logger(label: "com.app.postgres.datastore") var identifier: String { "PostgresSwiftDataStore-\(configuration.name)" } init(_ configuration: PostgresStoreConfiguration, migrationPlan: (any SchemaMigrationPlan.Type)?) throws { self.configuration = configuration let semaphore = DispatchSemaphore(value: 0) Task { do { // Bootstrapping PostgresNIO wire framework connection self.connection = try await PostgresConnection.connect( on: MultiThreadedEventLoopGroup.singleton.any(), configuration: configuration.connectionOptions, id: 1, logger: self.logger ) } catch { } semaphore.signal() } semaphore.wait() } deinit { self.close() } func close() { let semaphore = DispatchSemaphore(value: 0) Task { try? await self.connection?.close() semaphore.signal() } semaphore.wait() } // Saves changes by parsing individual items inside the save request bundle func save(_ request: DataStoreSaveChangesRequest) throws -> DataStoreSaveChangesResult { guard let connection = self.connection else { throw AppError.runtimeError("No connection to database") } let semaphore = DispatchSemaphore(value: 0) var remappedIdentifiers: [PersistentIdentifier: PersistentIdentifier] = [:] Task { var remappedIdentifiersX: [PersistentIdentifier: PersistentIdentifier] = [:] do { // Process Insertions for snapshot in request.inserted { let entityName = snapshot.persistentIdentifier.entityName switch snapshot.payload { case .product(let id, let sku, let stockQuantity): let pId = try PersistentIdentifier.identifier(for: identifier, entityName: entityName, primaryKey: id) remappedIdentifiersX[snapshot.persistentIdentifier] = pId try await connection.query( "INSERT INTO products (id, sku, stockquantity) VALUES (\(id), \(sku), \(stockQuantity));", logger: logger ) case .supplier(let id, let name): let pId = try PersistentIdentifier.identifier(for: identifier, entityName: entityName, primaryKey: id) remappedIdentifiersX[snapshot.persistentIdentifier] = pId try await connection.query( "INSERT INTO suppliers (id, name) VALUES (\(id), \(name));", logger: logger ) } } // Process Modifications (Updates) for snapshot in request.updated { switch snapshot.payload { case .product(let id, let sku, let stockQuantity): try await connection.query( "UPDATE products SET sku = \(sku), stockquantity = \(stockQuantity) WHERE id = \(id);", logger: logger ) case .supplier(let id, let name): try await connection.query( "UPDATE suppliers SET name = \(name) WHERE id = \(id);", logger: logger ) } } // Process Record Deletions for snapshot in request.deleted { switch snapshot.payload { case .product(let id, _, _): try await connection.query("DELETE FROM products WHERE id = \(id);", logger: logger) case .supplier(let id, _): try await connection.query("DELETE FROM suppliers WHERE id = \(id);", logger: logger) } } } catch { logger.error("Failed executing batch change block: \(error)") } remappedIdentifiers = remappedIdentifiersX semaphore.signal() } semaphore.wait() return DataStoreSaveChangesResult(for: self.identifier, remappedIdentifiers: remappedIdentifiers) } // Reads out records and hydrates fresh snapshot frames directly func fetch(_ request: DataStoreFetchRequest) throws -> DataStoreFetchResult where T : PersistentModel { guard let connection = self.connection else { throw AppError.runtimeError("No connection to database") } var collectedSnapshots: [PostgresSnapshot] = [] let semaphore = DispatchSemaphore(value: 0) // Target validation checks based on the incoming context model type requested let modelName = String(describing: T.self) Task { do { if modelName == "ProductRecord" { let rows = try await connection.query( "SELECT id, sku, stockquantity FROM products;", logger: logger ) // Decode rows directly into tuples via PostgresNIO for try await (dbID, dbSku, dbStock) in rows.decode((UUID, String, Int).self) { let persistentID = try PersistentIdentifier.identifier(for: identifier, entityName: modelName, primaryKey: dbID) let snapshot = PostgresSnapshot( persistentIdentifier: persistentID, payload: .product(id: dbID, sku: dbSku, stockQuantity: dbStock) ) collectedSnapshots.append(snapshot) } } else if modelName == "SupplierRecord" { let rows = try await connection.query( "SELECT id, name FROM suppliers;", logger: logger ) // Decode rows directly into tuples via PostgresNIO for try await (dbID, dbName) in rows.decode((UUID, String).self) { let persistentID = try PersistentIdentifier.identifier(for: identifier, entityName: modelName, primaryKey: dbID) let snapshot = PostgresSnapshot( persistentIdentifier: persistentID, payload: .supplier(id: dbID, name: dbName) ) collectedSnapshots.append(snapshot) } } } catch { logger.error("Data tracking stream operation caught error: \(error)") } semaphore.signal() } semaphore.wait() return DataStoreFetchResult(descriptor: request.descriptor, fetchedSnapshots: collectedSnapshots) } } struct Credentials { var username: String var password: String } // ========================================== // 5. RUNTIME INITIALIZER // ========================================== struct InventoryApp { static let hostname: String = "localhost" static let username: String = "swiftdatastore" static let databasename: String = "swiftdatastore" static let port: Int = 5432 static let password: String = "swiftdatastore" static func main() async { print("🚀 Initializing Protocol-Compliant PostgresNIO + SwiftData Core Context...") let credentials = Credentials(username: username, password: password) var tlsConfiguration = TLSConfiguration.makeClientConfiguration() tlsConfiguration.certificateVerification = .none let config = PostgresClient.Configuration( host: hostname, port: port, username: username, password: credentials.password, database: databasename, tls: .prefer(tlsConfiguration) ) let storeConfig = PostgresStoreConfiguration( name: "ProductionInventory", schema: Schema([SupplierRecord.self, ProductRecord.self]), connectionOptions: try! createPgConConfiguration() ) do { // Instantiate our updated custom store natively into the model container let customContainer = try ModelContainer(for: SupplierRecord.self, ProductRecord.self, configurations: storeConfig) let context = ModelContext(customContainer) // --- INSERTS TESTING --- // print("💾 Saving a new record into SwiftData context...") // let newSupplier = SupplierRecord(name: "Supplier 1") // context.insert(newSupplier) // try context.save() // // let newSupplier2 = SupplierRecord(name: "Supplier 2") // context.insert(newSupplier2) // try context.save() // // let newProduct = ProductRecord(sku: "IPHONE-16-PRO", stockQuantity: 20) // context.insert(newProduct) // try context.save() // let newProduct2 = ProductRecord(sku: "IPHONE-15-PRO", stockQuantity: 50) // context.insert(newProduct2) // try context.save() // --- FETCHES TESTING --- print("🔍 Requesting collection from PostgreSQL target tables...") let fetchDescriptor1 = FetchDescriptor() let suppliers = try context.fetch(fetchDescriptor1) let fetchDescriptor2 = FetchDescriptor() let products = try context.fetch(fetchDescriptor2) for item in products { //print("📦 Mapped Record -> SKU: \(item.sku) | Stock: \(item.stockQuantity) | Identifier: \(item.id.uuidString)") print("📦 Mapped Record -> Identifier: \(item.id.uuidString) | SKU: \(item.sku) | Stock: \(item.stockQuantity)") } } catch { print("🛑 Fatal exception caught in pipeline execution: \(error)") } } static func createPgConConfiguration() throws -> PostgresConnection.Configuration { let credentials = Credentials(username: username, password: password) var tlsConfiguration = TLSConfiguration.makeClientConfiguration() tlsConfiguration.certificateVerification = .none let sslContext = try! NIOSSLContext(configuration: tlsConfiguration) //var tlsConfiguration = TLSConfiguration.makeClientConfiguration() let config = PostgresConnection.Configuration( host: hostname, port: port, username: username, password: credentials.password, database: databasename, tls: .prefer(sslContext) ) return config } } await InventoryApp.main() /* DATABASE SCHEMA */ /* CREATE TABLE swiftdatastore.products ( id UUID PRIMARY KEY NOT NULL DEFAULT (gen_random_uuid()), tstamp timestamp with time zone DeFAULT CURRENT_TIMESTAMP, sku text NOT NULL, stockQuantity int DEFAULT 0, UNIQUE (sku) ); CREATE TABLE swiftdatastore.suppliers ( id UUID PRIMARY KEY NOT NULL DEFAULT (gen_random_uuid()), tstamp timestamp with time zone DeFAULT CURRENT_TIMESTAMP, name text NOT NULL, UNIQUE (name) ); */ // ========================================== //// 2. CUSTOM SNAPSHOT //// ========================================== //struct ProductPostgresSnapshot: DataStoreSnapshot { // // Required protocol property identifier // var persistentIdentifier: PersistentIdentifier // // // Strict column fields matching PostgreSQL definitions // var id: UUID // var sku: String // var stockQuantity: Int // // // Requirement A: Lifecycle initializer used by SwiftData to pull out internal model states // init(from backingData: any BackingData, relatedBackingDatas: inout [PersistentIdentifier : any BackingData]) { // // Recover or safely establish the identity block // self.persistentIdentifier = backingData.persistentModelID! // // // Dynamically extract model values from the type-erased BackingData container // if let userBacking = backingData as? (any BackingData) { // self.id = userBacking.getValue(forKey: \ProductRecord.id) // self.sku = userBacking.getValue(forKey: \ProductRecord.sku) // self.stockQuantity = userBacking.getValue(forKey: \ProductRecord.stockQuantity) // } else { // // Safe fallbacks for edge-case corruption payloads // self.id = UUID() // self.sku = "UNKNOWN" // self.stockQuantity = 0 // } // } // // // Requirement B: Identifiers copier used during transaction insertions and cache remappings // func copy(persistentIdentifier: PersistentIdentifier, remappedIdentifiers: [PersistentIdentifier : PersistentIdentifier]? = nil) -> ProductPostgresSnapshot { // return ProductPostgresSnapshot( // persistentIdentifier: persistentIdentifier, // id: self.id, // sku: self.sku, // stockQuantity: self.stockQuantity // ) // } // enum CodingKeys: String, CodingKey { // case persistentIdentifier, id, sku, stockQuantity // } // init(from decoder: any Decoder) throws { // let container = try decoder.container(keyedBy: CodingKeys.self) // self.persistentIdentifier = try container.decode(PersistentIdentifier.self, forKey: .persistentIdentifier) // self.id = try container.decode(UUID.self, forKey: .id) // self.sku = try container.decode(String.self, forKey: .sku) // self.stockQuantity = try container.decode(Int.self, forKey: .stockQuantity) // } // // func encode(to encoder: any Encoder) throws { // var container = encoder.container(keyedBy: CodingKeys.self) // try container.encode(persistentIdentifier, forKey: .persistentIdentifier) // try container.encode(id, forKey: .id) // try container.encode(sku, forKey: .sku) // try container.encode(stockQuantity, forKey: .stockQuantity) // } // // // Custom driver initializer utilized for building records dynamically from PostgresNIO rows // init(persistentIdentifier: PersistentIdentifier, id: UUID, sku: String, stockQuantity: Int) { // self.persistentIdentifier = persistentIdentifier // self.id = id // self.sku = sku // self.stockQuantity = stockQuantity // } //}