-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Add FilteredProductsBuilder, FiltersSQLRawFactory for build raw sql - Fix filtered products, available filters behavior
- Loading branch information
Showing
6 changed files
with
315 additions
and
113 deletions.
There are no files selected for viewing
73 changes: 73 additions & 0 deletions
73
Sources/App/Controllers/Products/FilteredProductsBuilder.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// | ||
// FilteredProductsBuilder.swift | ||
// | ||
// | ||
// Created by Artem Mayer on 10.09.2024. | ||
// | ||
|
||
import Foundation | ||
import FluentSQL | ||
|
||
final class FilteredProductsBuilder { | ||
|
||
private var categoriesWhereCondition: String = "" | ||
private var filtersWhereCondition: String = "" | ||
private var filtersHavingCondition: SQLQueryString = "" | ||
|
||
@discardableResult | ||
func setCategoriesWhereCondition(for categoriesIDs: [UUID]?) -> FilteredProductsBuilder { | ||
guard let categoriesIDs else { return self } | ||
let categoriesList = categoriesIDs.map({ "'\($0)'" }).joined(separator: ",") | ||
categoriesWhereCondition = "CP.CATEGORY_ID IN (\(categoriesList))" | ||
return self | ||
} | ||
|
||
@discardableResult | ||
func setFiltersWhereCondition(for filters: [String: [String]]) -> FilteredProductsBuilder { | ||
let whereCondition = filters.reduce("") { partialResult, pair in | ||
guard !pair.value.isEmpty else { return partialResult } | ||
|
||
let values = pair.value.map({ "'\($0)'" }).joined(separator: ",") | ||
if partialResult.isEmpty { | ||
return "(P_PROPERTY.CODE = '\(pair.key)' AND P_VALUE.VALUE IN (\(values))" | ||
} else { | ||
return partialResult + "\n\tOR P_PROPERTY.CODE = '\(pair.key)' AND P_VALUE.VALUE IN (\(values))" | ||
} | ||
} | ||
|
||
if !whereCondition.isEmpty { | ||
filtersWhereCondition = "\(whereCondition)\n)" | ||
filtersHavingCondition = "HAVING COUNT(DISTINCT P_VALUE.VALUE) = \(bind: filters.count)" | ||
} | ||
|
||
return self | ||
} | ||
|
||
func build() -> SQLQueryString? { | ||
let whereCondition: SQLQueryString | ||
|
||
if !categoriesWhereCondition.isEmpty, !filtersWhereCondition.isEmpty { | ||
whereCondition = "\(unsafeRaw: categoriesWhereCondition)\n\tAND \(unsafeRaw: filtersWhereCondition)" | ||
} else if !filtersWhereCondition.isEmpty { | ||
whereCondition = "\(unsafeRaw: filtersWhereCondition)" | ||
} else { | ||
return nil | ||
} | ||
|
||
return """ | ||
SELECT DISTINCT P.* | ||
FROM | ||
PRODUCTS P | ||
JOIN PRODUCT_VARIANTS PV ON P.ID = PV.PRODUCT_ID | ||
JOIN "product_variants+property_values" PVPV ON PV.ID = PVPV.PRODUCT_VARIANT_ID | ||
JOIN PROPERTY_VALUES P_VALUE ON PVPV.PROPERTY_VALUE_ID = P_VALUE.ID | ||
JOIN PRODUCT_PROPERTIES P_PROPERTY ON P_VALUE.PRODUCT_PROPERTY_ID = P_PROPERTY.ID | ||
JOIN "categories+products" CP ON CP.PRODUCT_ID = P.ID | ||
WHERE | ||
\(whereCondition) | ||
GROUP BY P.ID | ||
\(filtersHavingCondition) | ||
""" | ||
} | ||
|
||
} |
102 changes: 102 additions & 0 deletions
102
Sources/App/Controllers/Products/FiltersSQLRawFactory.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
// | ||
// FiltersSQLRawFactory.swift | ||
// | ||
// | ||
// Created by Artem Mayer on 10.09.2024. | ||
// | ||
|
||
import Foundation | ||
import FluentSQL | ||
|
||
final class FiltersSQLRawFactory { | ||
|
||
private init() {} | ||
|
||
static func makeAvailableFiltersSQL(for categoriesIDs: [UUID], filters: [String: [String]]) -> SQLQueryString { | ||
""" | ||
\(getFilteredProducts(for: categoriesIDs, filters: filters)) | ||
\(getAvailableFilters(for: Array(filters.keys))) | ||
""" | ||
} | ||
|
||
private static func getFilteredProducts(for categoriesIDs: [UUID], filters: [String: [String]]) -> SQLQueryString { | ||
let codeValueFiltersSQL: SQLQueryString = filters.map { | ||
let values = $0.value.map({ "'\($0)'" }).joined(separator: ",") | ||
return "(P_PROPERTY.CODE='\(unsafeRaw: $0.key)' AND P_VALUE.VALUE IN (\(unsafeRaw: values)))" | ||
}.joined(separator: "\n\t\t\tOR ") | ||
|
||
return """ | ||
WITH FILTERED_PRODUCTS AS ( | ||
SELECT DISTINCT P.ID | ||
FROM | ||
PRODUCTS P | ||
JOIN PRODUCT_VARIANTS PV ON P.ID = PV.PRODUCT_ID | ||
JOIN "product_variants+property_values" PVPV ON PV.ID = PVPV.PRODUCT_VARIANT_ID | ||
JOIN PROPERTY_VALUES P_VALUE ON PVPV.PROPERTY_VALUE_ID = P_VALUE.ID | ||
JOIN PRODUCT_PROPERTIES P_PROPERTY ON P_VALUE.PRODUCT_PROPERTY_ID = P_PROPERTY.ID | ||
JOIN "categories+products" CP ON CP.PRODUCT_ID = P.ID | ||
WHERE | ||
\(getWhereCategoriesCondition(for: categoriesIDs)) | ||
AND ( | ||
\(codeValueFiltersSQL) | ||
) | ||
GROUP BY P.ID | ||
HAVING COUNT(DISTINCT P_VALUE.VALUE) >= \(bind: filters.count - 1) | ||
) | ||
""" | ||
} | ||
|
||
private static func getAvailableFilters(for filtersCodes: [String]) -> SQLQueryString { | ||
var selectedFiltersCodes: [String] = [] | ||
|
||
var resultSQL = filtersCodes.map { key -> String in | ||
selectedFiltersCodes.append(key) | ||
return getSelectFilterTypeSQL(for: key, excludeKeys: nil) | ||
}.joined(separator: "\nUNION ALL\n") | ||
|
||
resultSQL += "\nUNION ALL\n\(getSelectFilterTypeSQL(for: nil, excludeKeys: selectedFiltersCodes))" | ||
|
||
return "\(unsafeRaw: resultSQL)" | ||
} | ||
|
||
/// - WARNING: Either `key` or `excludeKeys` must be not nil | ||
private static func getSelectFilterTypeSQL(for key: String?, excludeKeys: [String]?) -> String { | ||
let whereCondition: String | ||
|
||
if let key { | ||
whereCondition = "WHERE P_PROPERTY.CODE = '\(key)'" | ||
} else if let excludeKeys { | ||
whereCondition = excludeKeys.reduce("") { initialResult, selectedKey -> String in | ||
if initialResult.isEmpty { | ||
return "WHERE P_PROPERTY.CODE != '\(selectedKey)'" | ||
} else { | ||
return initialResult + " AND P_PROPERTY.CODE != '\(selectedKey)'" | ||
} | ||
} | ||
} else { | ||
fatalError("keys or excludeKeys must be not nil!") | ||
} | ||
|
||
return """ | ||
SELECT | ||
P_PROPERTY.CODE AS property_code, | ||
P_PROPERTY.NAME AS property_name, | ||
P_VALUE.VALUE AS property_value, | ||
COUNT(DISTINCT P.ID) AS count | ||
FROM | ||
PRODUCTS P | ||
JOIN PRODUCT_VARIANTS PV ON P.ID = PV.PRODUCT_ID | ||
JOIN "product_variants+property_values" PVPV ON PV.ID = PVPV.PRODUCT_VARIANT_ID | ||
JOIN PROPERTY_VALUES P_VALUE ON PVPV.PROPERTY_VALUE_ID = P_VALUE.ID | ||
JOIN PRODUCT_PROPERTIES P_PROPERTY ON P_VALUE.PRODUCT_PROPERTY_ID = P_PROPERTY.ID | ||
JOIN FILTERED_PRODUCTS FP ON P.ID = FP.ID | ||
\(whereCondition) | ||
GROUP BY P_PROPERTY.CODE, P_PROPERTY.NAME, P_VALUE.VALUE | ||
""" | ||
} | ||
|
||
private static func getWhereCategoriesCondition(for categoriesIDs: [UUID]) -> SQLQueryString { | ||
"CP.CATEGORY_ID IN (\(unsafeRaw: categoriesIDs.map { "'\($0.uuidString)'" }.joined(separator: ",")))" | ||
} | ||
|
||
} |
Oops, something went wrong.