80 lines
3.5 KiB
Swift
80 lines
3.5 KiB
Swift
//
|
|
// Copyright 2019 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
import LibSignalClient
|
|
import SignalCoreKit
|
|
|
|
public class SignalRecipientFinder {
|
|
public init() {}
|
|
|
|
public func signalRecipientForServiceId(_ serviceId: ServiceId?, tx: SDSAnyReadTransaction) -> SignalRecipient? {
|
|
guard let serviceId else { return nil }
|
|
let serviceIdColumn: SignalRecipient.CodingKeys
|
|
switch serviceId.kind {
|
|
case .aci:
|
|
serviceIdColumn = .aciString
|
|
case .pni:
|
|
serviceIdColumn = .pni
|
|
}
|
|
let sql = "SELECT * FROM \(SignalRecipient.databaseTableName) WHERE \(signalRecipientColumn: serviceIdColumn) = ?"
|
|
return SignalRecipient.anyFetch(sql: sql, arguments: [serviceId.serviceIdUppercaseString], transaction: tx)
|
|
}
|
|
|
|
public func signalRecipientForPhoneNumber(_ phoneNumber: String?, tx: SDSAnyReadTransaction) -> SignalRecipient? {
|
|
guard let phoneNumber = phoneNumber else { return nil }
|
|
let sql = "SELECT * FROM \(SignalRecipient.databaseTableName) WHERE \(signalRecipientColumn: .phoneNumber) = ?"
|
|
return SignalRecipient.anyFetch(sql: sql, arguments: [phoneNumber], transaction: tx)
|
|
}
|
|
|
|
public func signalRecipient(for address: SignalServiceAddress, tx: SDSAnyReadTransaction) -> SignalRecipient? {
|
|
if let recipient = signalRecipientForServiceId(address.serviceId, tx: tx) {
|
|
return recipient
|
|
} else if let recipient = signalRecipientForPhoneNumber(address.phoneNumber, tx: tx) {
|
|
return recipient
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
public func signalRecipients(for addresses: [SignalServiceAddress], tx: SDSAnyReadTransaction) -> [SignalRecipient] {
|
|
let phoneNumbersToLookUp = addresses.compactMap { $0.phoneNumber }
|
|
let aciStringsToLookUp = addresses.compactMap { ($0.serviceId as? Aci)?.serviceIdUppercaseString }
|
|
let pniStringsToLookUp = addresses.compactMap { ($0.serviceId as? Pni)?.serviceIdUppercaseString }
|
|
|
|
func orClause(column: SignalRecipient.CodingKeys, values: [String]) -> String? {
|
|
if values.isEmpty {
|
|
return nil
|
|
}
|
|
let wrappedValues = values.lazy.map { "'\($0)'" }.joined(separator: ",")
|
|
return "\(signalRecipientColumn: column) IN (\(wrappedValues))"
|
|
}
|
|
|
|
// A SQL query for "Col1 IN (v1, v2, v3)" will use an index that's
|
|
// available. A SQL query for "Col1 IN ()" will *not* use an index and will
|
|
// fall back to a full table scan. If you have a series of OR clauses that
|
|
// are all indexed and any of them includes "IN ()", the entire query will
|
|
// bypass all indexes. Therefore, we omit OR clauses that won't return any
|
|
// matches, and we return no results if we're left without any OR clauses.
|
|
let orClauses: [String] = [
|
|
orClause(column: .phoneNumber, values: phoneNumbersToLookUp),
|
|
orClause(column: .aciString, values: aciStringsToLookUp),
|
|
orClause(column: .pni, values: pniStringsToLookUp),
|
|
].compacted()
|
|
|
|
if orClauses.isEmpty {
|
|
return []
|
|
}
|
|
|
|
let sql = "SELECT * FROM \(SignalRecipient.databaseTableName) WHERE \(orClauses.joined(separator: " OR "))"
|
|
|
|
var result = [SignalRecipient]()
|
|
SignalRecipient.anyEnumerate(transaction: tx, sql: sql, arguments: []) { signalRecipient, _ in
|
|
result.append(signalRecipient)
|
|
}
|
|
return result
|
|
}
|
|
}
|