Skip to content

Commit 14bdab6

Browse files
committed
Handle optional embeddings
1 parent 38624e9 commit 14bdab6

16 files changed

Lines changed: 375 additions & 60 deletions

Sources/Compiler/Environment.swift

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ struct Environment {
9494
/// in `rank`. Those can be stored here so the table
9595
/// can remain intact.
9696
let additionalColumns: Columns?
97+
/// Whether or not this table exists optionally in the env.
98+
/// The `table`s columns have already been adjusted to be
99+
/// optional. This is just for extra metadata
100+
let isOptional: Bool
97101
}
98102

99103
struct Diff {
@@ -117,8 +121,8 @@ struct Environment {
117121
}
118122

119123
/// All table that have been imported into the environment
120-
var allImportedTables: [Table] {
121-
return importedTables.map(\.value.table)
124+
var allImportedTables: [ImportedTable] {
125+
return importedTables.map(\.value)
122126
}
123127

124128
func hasColumn(named: Substring) -> Bool {
@@ -182,7 +186,8 @@ struct Environment {
182186
) {
183187
let importedTable = ImportedTable(
184188
table: isOptional ? table.mapTypes { $0.coerceToOptional() } : table,
185-
additionalColumns: table.kind == .fts5 ? ["rank": Column(type: .real)] : nil
189+
additionalColumns: table.kind == .fts5 ? ["rank": Column(type: .real)] : nil,
190+
isOptional: isOptional
186191
)
187192

188193
if let alias {
@@ -223,8 +228,8 @@ struct Environment {
223228
}
224229

225230
/// Resolves the table for the given name and schema
226-
func resolve(table: Substring, schema: Substring?) -> LookupResult<Table> {
227-
resolveImported(table: table, schema: schema).map(\.table)
231+
func resolve(table: Substring, schema: Substring?) -> LookupResult<ImportedTable> {
232+
resolveImported(table: table, schema: schema)
228233
}
229234

230235
/// Will import all values imported in the `environment` vs `self`

Sources/Compiler/Gen/Language.swift

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ extension Language {
6666
) {
6767
let tables = Dictionary(schema.tables.map { ($0.key.name, model(for: $0.value)) }, uniquingKeysWith: { $1 })
6868
let queries = queries.map { ($0.map { "\($0)Queries" }, $1.map { query(for: $0, tables: tables) }) }
69-
return (Array(tables.values), queries)
69+
return (tables.values.sorted{ $0.name < $1.name }, queries)
7070
}
7171

7272
private func query(
@@ -109,7 +109,7 @@ extension Language {
109109
outputCardinality: statement.outputCardinality,
110110
sourceSql: sql,
111111
isReadOnly: statement.isReadOnly,
112-
usedTableNames: statement.usedTableNames
112+
usedTableNames: statement.usedTableNames.sorted()
113113
)
114114
}
115115

@@ -129,7 +129,12 @@ extension Language {
129129
isArray: type.isRow
130130
)
131131
},
132-
isTable: true
132+
isTable: true,
133+
nonOptionalIndices: table.columns.enumerated()
134+
.compactMap { (index, value) in
135+
guard !value.value.type.isOptional else { return nil }
136+
return index
137+
}
133138
)
134139
}
135140

@@ -169,10 +174,11 @@ extension Language {
169174
isArray: parameter.type.isRow
170175
)
171176
},
172-
isTable: false
177+
isTable: false,
178+
nonOptionalIndices: []
173179
)
174180

175-
return .model(model)
181+
return .model(model, isOptional: false)
176182
}
177183

178184
private func outputTypeIfNeeded(
@@ -187,7 +193,7 @@ extension Language {
187193
let tableName = firstResultColumns.table,
188194
let table = tables[tableName]
189195
{
190-
return .model(table)
196+
return .model(table, isOptional: false)
191197
}
192198

193199
// Make sure there is at least one column else return void
@@ -213,7 +219,7 @@ extension Language {
213219
let name = tableName.description
214220
fields[name] = GeneratedField(
215221
name: name,
216-
type: .model(table),
222+
type: .model(table, isOptional: chunk.isTableOptional),
217223
isArray: false
218224
)
219225
} else {
@@ -232,10 +238,11 @@ extension Language {
232238
}
233239
}
234240
},
235-
isTable: false
241+
isTable: false,
242+
nonOptionalIndices: []
236243
)
237244

238-
return .model(model)
245+
return .model(model, isOptional: false)
239246
}
240247
}
241248

@@ -257,6 +264,7 @@ public struct GeneratedModel {
257264
let fields: OrderedDictionary<String, GeneratedField>
258265
/// Whether or not this was generated for a table
259266
let isTable: Bool
267+
let nonOptionalIndices: [Int]
260268
}
261269

262270
public struct GeneratedField {
@@ -289,7 +297,7 @@ public struct GeneratedQuery {
289297
let outputCardinality: Cardinality
290298
let sourceSql: String
291299
let isReadOnly: Bool
292-
let usedTableNames: Set<Substring>
300+
let usedTableNames: [Substring]
293301
}
294302

295303
public enum BuiltinOrGenerated: CustomStringConvertible {
@@ -299,21 +307,14 @@ public enum BuiltinOrGenerated: CustomStringConvertible {
299307
/// type rather than just having `UUID` always go to `TEXT`
300308
/// when some users may want a `BLOB`.
301309
case builtin(String, isArray: Bool, encodedAs: String?)
302-
case model(GeneratedModel)
310+
case model(GeneratedModel, isOptional: Bool)
303311

304312
public var description: String {
305313
switch self {
306314
case let .builtin(builtin, isArray, _):
307315
isArray ? "[\(builtin)]" : builtin
308-
case let .model(model):
309-
model.name
310-
}
311-
}
312-
313-
public func namespaced(to namespace: String) -> String {
314-
switch self {
315-
case let .model(model) where !model.isTable: "\(namespace).\(self)"
316-
default: description
316+
case let .model(model, isOptional):
317+
isOptional ? "\(model.name)?" : model.name
317318
}
318319
}
319320
}

Sources/Compiler/Gen/SwiftLanguage.swift

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public struct SwiftLanguage: Language {
105105
for query in allQueries {
106106
typeAlias(for: query)
107107

108-
if let input = query.input, case let .model(model) = input {
108+
if let input = query.input, case let .model(model, _) = input {
109109
inputExtension(for: query, input: model)
110110
}
111111
}
@@ -321,7 +321,7 @@ public struct SwiftLanguage: Language {
321321
switch input {
322322
case let .builtin(_, isArray, encodedAs):
323323
bind(field: nil, encodeToType: encodedAs, isArray: isArray)
324-
case .model(let model):
324+
case .model(let model, _):
325325
for field in model.fields.values {
326326
bind(field: field.name, encodeToType: field.encodedAsType, isArray: field.isArray)
327327
}
@@ -359,9 +359,9 @@ public struct SwiftLanguage: Language {
359359
for model: GeneratedModel,
360360
isOutput: Bool
361361
) {
362-
let dynamicLookupTables = model.fields.values.compactMap { value -> (String, GeneratedModel)? in
363-
guard case let .model(model) = value.type else { return nil }
364-
return (value.name, model)
362+
let dynamicLookupTables = model.fields.values.compactMap { value -> (String, GeneratedModel, Bool)? in
363+
guard case let .model(model, isOptional) = value.type else { return nil }
364+
return (value.name, model, isOptional)
365365
}
366366

367367
let addDynamicLookup = isOutput && !dynamicLookupTables.isEmpty && model.fields.count > 1
@@ -391,15 +391,31 @@ public struct SwiftLanguage: Language {
391391
}
392392

393393
if isOutput {
394+
writer.blankLine()
395+
396+
writer.write(line: "static var nonOptionalIndices: [Int32] { [")
397+
for (position, index) in model.nonOptionalIndices.positional() {
398+
writer.write(index.description)
399+
400+
if !position.isLast {
401+
writer.write(", ")
402+
}
403+
}
404+
writer.write("] }")
405+
394406
writer.blankLine()
395407
rowDecodableInit(for: model)
396408
writer.blankLine()
397409
memberWiseInit(for: model)
398410
}
399411

400412
if addDynamicLookup {
401-
for (fieldName, table) in dynamicLookupTables {
402-
dynamicMemberLookup(fieldName: fieldName, typeName: table.name)
413+
for (fieldName, table, isOptional) in dynamicLookupTables {
414+
dynamicMemberLookup(
415+
fieldName: fieldName,
416+
typeName: table.name,
417+
isOptional: isOptional
418+
)
403419
}
404420
}
405421

@@ -409,30 +425,37 @@ public struct SwiftLanguage: Language {
409425
}
410426

411427
private func modelsFor(query: GeneratedQuery) {
412-
if case let .model(model) = query.input, !model.isTable {
428+
if case let .model(model, _) = query.input, !model.isTable {
413429
declaration(for: model, isOutput: false)
414430
}
415431

416-
if case let .model(model) = query.output, !model.isTable {
432+
if case let .model(model, _) = query.output, !model.isTable {
417433
declaration(for: model, isOutput: true)
418434
}
419435
}
420436

421437
private func dynamicMemberLookup(
422438
fieldName: String,
423-
typeName: String
439+
typeName: String,
440+
isOptional: Bool
424441
) {
425442
writer.newline()
426443
writer.write(line: "subscript<Value>(dynamicMember dynamicMember: ")
427-
writer.write("KeyPath<", typeName, ", Value>) -> Value ")
444+
writer.write("KeyPath<", typeName, ", Value>) -> Value")
445+
if isOptional {
446+
writer.write("?")
447+
}
448+
writer.write(" ")
428449
writer.braces {
429-
writer.write(line: "self.", fieldName, "[keyPath: dynamicMember]")
450+
writer.write(line: "self.", fieldName)
451+
if isOptional {
452+
writer.write("?")
453+
}
454+
writer.write("[keyPath: dynamicMember]")
430455
}
431456
}
432457

433-
private func rowDecodableInit(
434-
for model: GeneratedModel
435-
) {
458+
private func rowDecodableInit(for model: GeneratedModel) {
436459
// Initializer signature
437460
writer.write(line: "init(")
438461
writer.indent()
@@ -464,9 +487,13 @@ public struct SwiftLanguage: Language {
464487
}
465488

466489
index += 1
467-
case .model(let model):
468-
// Model initializer
469-
writer.write(field.type.description, "(row: row, startingAt: start + ", index.description, ")")
490+
case let .model(model, isOptional):
491+
if isOptional {
492+
let type = field.type.description.replacingOccurrences(of: "?", with: "")
493+
writer.write(type, "(row: row, optionallyAt: start + ", index.description, ")")
494+
} else {
495+
writer.write(field.type.description, "(row: row, startingAt: start + ", index.description, ")")
496+
}
470497
index += model.fields.count
471498
}
472499
}

Sources/Compiler/Sema/CardinalityInferrer.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ extension CardinalityInferrer: StmtSyntaxVisitor {
5858
return .many
5959
}
6060

61-
return cadinalityForFilter(whereExpr, for: table)
61+
return cadinalityForFilter(whereExpr, for: table.table)
6262
}
6363

6464
mutating func visit(_ stmt: SelectStmtSyntax) -> Cardinality {
@@ -99,7 +99,7 @@ extension CardinalityInferrer: StmtSyntaxVisitor {
9999

100100
// If they had filtering on all primary keys we can assume a single
101101
// result will be returned.
102-
return cadinalityForFilter(filter, for: t)
102+
return cadinalityForFilter(filter, for: t.table)
103103
}
104104
case let .values(values):
105105
// VALUES (1, 2), (3, 4)
@@ -128,7 +128,7 @@ extension CardinalityInferrer: StmtSyntaxVisitor {
128128
return .many
129129
}
130130

131-
return cadinalityForFilter(filter, for: table)
131+
return cadinalityForFilter(filter, for: table.table)
132132
}
133133

134134
mutating func visit(_ stmt: QueryDefinitionStmtSyntax) -> Cardinality {

Sources/Compiler/Sema/ExprTypeChecker.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ extension ExprTypeChecker: ExprSyntaxVisitor {
145145
name: tableName.value
146146
) else { return inferenceState.errorType(for: expr) }
147147

148-
return table.type
148+
return table.table.type
149149
} else {
150150
return .row(.fixed(env.allColumnTypes))
151151
}

Sources/Compiler/Sema/Statement.swift

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,14 +119,31 @@ public struct ResultColumns: Sendable {
119119
/// due to selecting from many columns from many tables
120120
/// this will be `nil`.
121121
public let table: Substring?
122+
/// If the table is joined in with an OUTER join or something
123+
/// similar that does not require a match it is optional
124+
public let isTableOptional: Bool
125+
126+
public init(
127+
columns: Columns,
128+
table: Substring?,
129+
isTableOptional: Bool = false
130+
) {
131+
self.columns = columns
132+
self.table = table
133+
self.isTableOptional = isTableOptional
134+
}
122135
}
123136

124137
public init(chunks: [Chunk]) {
125138
self.chunks = chunks
126139
}
127140

128-
public init(columns: Columns, table: Substring?) {
129-
self.chunks = [Chunk(columns: columns, table: table)]
141+
public init(
142+
columns: Columns,
143+
table: Substring?,
144+
isTableOptional: Bool = false
145+
) {
146+
self.chunks = [Chunk(columns: columns, table: table, isTableOptional: isTableOptional)]
130147
}
131148

132149
/// The columns as a row type.
@@ -161,7 +178,11 @@ public struct ResultColumns: Sendable {
161178
public func mapTypes(_ transform: (Type) -> Type) -> ResultColumns {
162179
return ResultColumns(
163180
chunks: chunks.map { chunk in
164-
Chunk(columns: chunk.columns.mapValues{ $0.mapType(transform) }, table: chunk.table)
181+
Chunk(
182+
columns: chunk.columns.mapValues{ $0.mapType(transform) },
183+
table: chunk.table,
184+
isTableOptional: chunk.isTableOptional
185+
)
165186
}
166187
)
167188
}

0 commit comments

Comments
 (0)