-
Notifications
You must be signed in to change notification settings - Fork 121
[Local Catalog] Update schema with many-to-many images #16222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ba19aab
1b311fc
a8b474e
2566193
fe8bda6
262de6e
08ba3a6
887104e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,6 +10,7 @@ struct V001InitialSchema { | |||||||||||
| try createSiteTable(db) | ||||||||||||
| try createProductTable(db) | ||||||||||||
| try createProductAttributeTable(db) | ||||||||||||
| try createImageTable(db) | ||||||||||||
| try createProductImageTable(db) | ||||||||||||
| try createProductVariationTable(db) | ||||||||||||
| try createProductVariationAttributeTable(db) | ||||||||||||
|
|
@@ -74,23 +75,39 @@ struct V001InitialSchema { | |||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private static func createImageTable(_ db: Database) throws { | ||||||||||||
| // Single image table shared by products and variations | ||||||||||||
| try db.create(table: "image") { imageTable in | ||||||||||||
| imageTable.column("id", .integer).notNull() | ||||||||||||
| imageTable.primaryKey(["siteID", "id"]) // SiteID column created by belongsTo relationship | ||||||||||||
| imageTable.belongsTo("site", onDelete: .cascade) | ||||||||||||
|
|
||||||||||||
| imageTable.column("dateCreated", .datetime).notNull() | ||||||||||||
| imageTable.column("dateModified", .datetime) | ||||||||||||
|
|
||||||||||||
| imageTable.column("src", .text).notNull() | ||||||||||||
| imageTable.column("name", .text) | ||||||||||||
| imageTable.column("alt", .text) | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private static func createProductImageTable(_ db: Database) throws { | ||||||||||||
| // Join table for many-to-many relationship between products and images | ||||||||||||
| try db.create(table: "productImage") { productImageTable in | ||||||||||||
| productImageTable.column("siteID", .integer).notNull() | ||||||||||||
| productImageTable.column("id", .integer).notNull() | ||||||||||||
| productImageTable.primaryKey(["siteID", "id"]) | ||||||||||||
| productImageTable.column("productID", .integer).notNull() | ||||||||||||
| productImageTable.column("imageID", .integer).notNull() | ||||||||||||
| productImageTable.primaryKey(["siteID", "productID", "imageID"]) | ||||||||||||
|
|
||||||||||||
| productImageTable.foreignKey(["siteID", "productID"], | ||||||||||||
| references: "product", | ||||||||||||
| columns: ["siteID", "id"], | ||||||||||||
| onDelete: .cascade) | ||||||||||||
|
|
||||||||||||
| productImageTable.column("dateCreated", .datetime).notNull() | ||||||||||||
| productImageTable.column("dateModified", .datetime) | ||||||||||||
|
|
||||||||||||
| productImageTable.column("src", .text).notNull() | ||||||||||||
| productImageTable.column("name", .text) | ||||||||||||
| productImageTable.column("alt", .text) | ||||||||||||
| productImageTable.foreignKey(["siteID", "imageID"], | ||||||||||||
| references: "image", | ||||||||||||
| columns: ["siteID", "id"], | ||||||||||||
| onDelete: .cascade) | ||||||||||||
| } | ||||||||||||
|
||||||||||||
| } | |
| } | |
| // Add composite indexes to improve join/filter performance | |
| try db.create(index: "productImage_siteID_productID", on: "productImage", columns: ["siteID", "productID"]) | |
| try db.create(index: "productImage_siteID_imageID", on: "productImage", columns: ["siteID", "imageID"]) |
Copilot
AI
Oct 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to products, add indexes on (siteID, productVariationID) and (siteID, imageID) to optimize queries traversing variation↔image relationships.
| } | |
| } | |
| // Add indexes to optimize queries traversing variation↔image relationships | |
| try db.create(indexOn: "productVariationImage", columns: ["siteID", "productVariationID"]) | |
| try db.create(indexOn: "productVariationImage", columns: ["siteID", "imageID"]) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import Foundation | ||
| import GRDB | ||
|
|
||
| // periphery:ignore | ||
| public struct PersistedImage: Codable { | ||
| public let siteID: Int64 | ||
| public let id: Int64 | ||
| public let dateCreated: Date | ||
| public let dateModified: Date? | ||
| public let src: String | ||
| public let name: String? | ||
| public let alt: String? | ||
|
|
||
| public init(siteID: Int64, | ||
| id: Int64, | ||
| dateCreated: Date, | ||
| dateModified: Date?, | ||
| src: String, | ||
| name: String?, | ||
| alt: String?) { | ||
| self.siteID = siteID | ||
| self.id = id | ||
| self.dateCreated = dateCreated | ||
| self.dateModified = dateModified | ||
| self.src = src | ||
| self.name = name | ||
| self.alt = alt | ||
| } | ||
| } | ||
|
|
||
| // periphery:ignore - TODO: remove ignore when populating database | ||
| extension PersistedImage: FetchableRecord, PersistableRecord { | ||
| public static var databaseTableName: String { "image" } | ||
|
|
||
| public static var primaryKey: [String] { [CodingKeys.siteID.stringValue, CodingKeys.id.stringValue] } | ||
|
|
||
| public enum Columns { | ||
| public static let siteID = Column(CodingKeys.siteID) | ||
| public static let id = Column(CodingKeys.id) | ||
| public static let dateCreated = Column(CodingKeys.dateCreated) | ||
| public static let dateModified = Column(CodingKeys.dateModified) | ||
| public static let src = Column(CodingKeys.src) | ||
| public static let name = Column(CodingKeys.name) | ||
| public static let alt = Column(CodingKeys.alt) | ||
| } | ||
| } | ||
|
|
||
|
|
||
| // periphery:ignore - TODO: remove ignore when populating database | ||
| extension PersistedImage { | ||
| enum CodingKeys: String, CodingKey { | ||
| case siteID | ||
| case id | ||
| case dateCreated | ||
| case dateModified | ||
| case src | ||
| case name | ||
| case alt | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,64 +2,48 @@ import Foundation | |
| import GRDB | ||
|
|
||
| // periphery:ignore - TODO: remove ignore when populating database | ||
| /// Join table linking products to images (many-to-many relationship) | ||
|
Comment on lines
4
to
+5
|
||
| public struct PersistedProductImage: Codable { | ||
| public let siteID: Int64 | ||
| public let id: Int64 | ||
| public let productID: Int64 | ||
| public let dateCreated: Date | ||
| public let dateModified: Date? | ||
| public let src: String | ||
| public let name: String? | ||
| public let alt: String? | ||
| public let imageID: Int64 | ||
|
|
||
| public init(siteID: Int64, | ||
| id: Int64, | ||
| productID: Int64, | ||
| dateCreated: Date, | ||
| dateModified: Date?, | ||
| src: String, | ||
| name: String?, | ||
| alt: String?) { | ||
| imageID: Int64) { | ||
| self.siteID = siteID | ||
| self.id = id | ||
| self.productID = productID | ||
| self.dateCreated = dateCreated | ||
| self.dateModified = dateModified | ||
| self.src = src | ||
| self.name = name | ||
| self.alt = alt | ||
| self.imageID = imageID | ||
| } | ||
| } | ||
|
|
||
| // periphery:ignore - TODO: remove ignore when populating database | ||
| extension PersistedProductImage: FetchableRecord, PersistableRecord { | ||
| public static var databaseTableName: String { "productImage" } | ||
|
|
||
| public static var primaryKey: [String] { [CodingKeys.siteID.stringValue, CodingKeys.id.stringValue] } | ||
| public static var primaryKey: [String] { | ||
| [CodingKeys.siteID.stringValue, CodingKeys.productID.stringValue, CodingKeys.imageID.stringValue] | ||
| } | ||
|
|
||
| public enum Columns { | ||
| public static let siteID = Column(CodingKeys.siteID) | ||
| public static let id = Column(CodingKeys.id) | ||
| public static let productID = Column(CodingKeys.productID) | ||
| public static let dateCreated = Column(CodingKeys.dateCreated) | ||
| public static let dateModified = Column(CodingKeys.dateModified) | ||
| public static let src = Column(CodingKeys.src) | ||
| public static let name = Column(CodingKeys.name) | ||
| public static let alt = Column(CodingKeys.alt) | ||
| public static let imageID = Column(CodingKeys.imageID) | ||
| } | ||
|
|
||
| // Association to the actual image | ||
| public static let image = belongsTo(PersistedImage.self, | ||
| using: ForeignKey([CodingKeys.siteID.stringValue, | ||
| CodingKeys.imageID.stringValue], | ||
| to: PersistedImage.primaryKey)) | ||
| } | ||
|
|
||
|
|
||
| // periphery:ignore - TODO: remove ignore when populating database | ||
| extension PersistedProductImage { | ||
| enum CodingKeys: String, CodingKey { | ||
| case siteID | ||
| case id | ||
| case productID | ||
| case dateCreated | ||
| case dateModified | ||
| case src | ||
| case name | ||
| case alt | ||
| case imageID | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,64 +2,48 @@ import Foundation | |
| import GRDB | ||
|
|
||
| // periphery:ignore - TODO: remove ignore when populating database | ||
| /// Join table linking product variations to images (many-to-many relationship) | ||
|
Comment on lines
4
to
+5
|
||
| public struct PersistedProductVariationImage: Codable { | ||
| public let siteID: Int64 | ||
| public let id: Int64 | ||
| public let productVariationID: Int64 | ||
| public let dateCreated: Date | ||
| public let dateModified: Date? | ||
| public let src: String | ||
| public let name: String? | ||
| public let alt: String? | ||
| public let imageID: Int64 | ||
|
|
||
| public init(siteID: Int64, | ||
| id: Int64, | ||
| productVariationID: Int64, | ||
| dateCreated: Date, | ||
| dateModified: Date?, | ||
| src: String, | ||
| name: String?, | ||
| alt: String?) { | ||
| imageID: Int64) { | ||
| self.siteID = siteID | ||
| self.id = id | ||
| self.productVariationID = productVariationID | ||
| self.dateCreated = dateCreated | ||
| self.dateModified = dateModified | ||
| self.src = src | ||
| self.name = name | ||
| self.alt = alt | ||
| self.imageID = imageID | ||
| } | ||
| } | ||
|
|
||
| // periphery:ignore - TODO: remove ignore when populating database | ||
| extension PersistedProductVariationImage: FetchableRecord, PersistableRecord { | ||
| public static var databaseTableName: String { "productVariationImage" } | ||
|
|
||
| public static var primaryKey: [String] { [CodingKeys.siteID.stringValue, CodingKeys.id.stringValue] } | ||
| public static var primaryKey: [String] { | ||
| [CodingKeys.siteID.stringValue, CodingKeys.productVariationID.stringValue, CodingKeys.imageID.stringValue] | ||
| } | ||
|
|
||
| public enum Columns { | ||
| public static let siteID = Column(CodingKeys.siteID) | ||
| public static let id = Column(CodingKeys.id) | ||
| public static let productVariationID = Column(CodingKeys.productVariationID) | ||
| public static let dateCreated = Column(CodingKeys.dateCreated) | ||
| public static let dateModified = Column(CodingKeys.dateModified) | ||
| public static let src = Column(CodingKeys.src) | ||
| public static let name = Column(CodingKeys.name) | ||
| public static let alt = Column(CodingKeys.alt) | ||
| public static let imageID = Column(CodingKeys.imageID) | ||
| } | ||
|
|
||
| // Association to the actual image | ||
| public static let image = belongsTo(PersistedImage.self, | ||
| using: ForeignKey([CodingKeys.siteID.stringValue, | ||
| CodingKeys.imageID.stringValue], | ||
| to: PersistedImage.primaryKey)) | ||
| } | ||
|
|
||
|
|
||
| // periphery:ignore - TODO: remove ignore when populating database | ||
| extension PersistedProductVariationImage { | ||
| enum CodingKeys: String, CodingKey { | ||
| case siteID | ||
| case id | ||
| case productVariationID | ||
| case dateCreated | ||
| case dateModified | ||
| case src | ||
| case name | ||
| case alt | ||
| case imageID | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import Foundation | ||
| import Storage | ||
|
|
||
| // MARK: - PersistedImage Conversions | ||
| public extension PersistedImage { | ||
| /// Create a PersistedImage from a ProductImage | ||
| static func make(from productImage: ProductImage, siteID: Int64) -> PersistedImage { | ||
| return PersistedImage( | ||
| siteID: siteID, | ||
| id: productImage.imageID, | ||
| dateCreated: productImage.dateCreated, | ||
| dateModified: productImage.dateModified, | ||
| src: productImage.src, | ||
| name: productImage.name, | ||
| alt: productImage.alt | ||
| ) | ||
| } | ||
|
|
||
| func toProductImage() -> ProductImage { | ||
| return ProductImage( | ||
| imageID: id, | ||
| dateCreated: dateCreated, | ||
| dateModified: dateModified, | ||
| src: src, | ||
| name: name, | ||
| alt: alt | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] For clarity and to avoid potential migration-order pitfalls, consider declaring belongsTo("site") (which defines siteID) before setting the composite primary key that references siteID. Reordering these lines makes the dependency explicit and easier to reason about.