Skip to content

Commit 447c308

Browse files
committed
Track generated columns
1 parent 36d4c14 commit 447c308

13 files changed

Lines changed: 88 additions & 39 deletions

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ let package = Package(
1616
products: [
1717
.library(name: "Otter", targets: ["Otter"]),
1818
.library(name: "Compiler", targets: ["Compiler"]),
19-
.executable(name: "otter", targets: ["OtterCLI"]),
19+
.executable(name: "OtterCLI", targets: ["OtterCLI"]),
2020
],
2121
dependencies: [
2222
.package(url: "https://github.com/apple/swift-syntax.git", from: "601.0.1"),

Sources/Compiler/Columns.swift

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,47 @@
77

88
import OrderedCollections
99

10-
public typealias Columns = DuplicateDictionary<Substring, Type>
10+
public typealias Columns = DuplicateDictionary<Substring, Column>
1111

1212
extension Columns {
1313
/// Initializes the columns with their default names that SQLite gives to them.
1414
init(withDefaultNames types: [Type]) {
1515
self = types.enumerated()
1616
.reduce(into: [:]) { c, v in
17-
c.append(v.element, for: "column\(v.offset + 1)")
17+
let column = Column(type: v.element, isGenerated: false)
18+
c.append(column, for: "column\(v.offset + 1)")
1819
}
1920
}
2021
}
22+
23+
public struct Column: Equatable, Sendable {
24+
public let type: Type
25+
public let isGenerated: Bool
26+
27+
public init(type: Type, isGenerated: Bool = false) {
28+
self.type = type
29+
self.isGenerated = isGenerated
30+
}
31+
32+
public var isRequired: Bool {
33+
guard !isGenerated else { return false }
34+
return !type.isOptional
35+
}
36+
37+
public func mapType(_ transform: (Type) -> Type) -> Column {
38+
return Column(
39+
type: transform(type),
40+
isGenerated: isGenerated
41+
)
42+
}
43+
}
44+
45+
extension Column: CustomStringConvertible {
46+
public var description: String {
47+
if isGenerated {
48+
return "\(type) generated"
49+
} else {
50+
return type.description
51+
}
52+
}
53+
}

Sources/Compiler/Environment.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ struct Environment {
182182
) {
183183
let importedTable = ImportedTable(
184184
table: isOptional ? table.mapTypes { $0.coerceToOptional() } : table,
185-
additionalColumns: table.kind == .fts5 ? ["rank": .real] : nil
185+
additionalColumns: table.kind == .fts5 ? ["rank": Column(type: .real)] : nil
186186
)
187187

188188
if let alias {
@@ -199,21 +199,21 @@ struct Environment {
199199
// Don't insert columns into detached if they required qualified access
200200
guard !qualifiedAccessOnly else { return }
201201

202-
for (column, type) in table.columns {
203-
insert(detached: column, type: type, isOptional: isOptional)
202+
for (name, column) in table.columns {
203+
insert(detached: name, type: column.type, isOptional: isOptional)
204204
}
205205

206206
if let additionalColumns = importedTable.additionalColumns {
207-
for (column, type) in additionalColumns {
208-
insert(detached: column, type: type, isOptional: isOptional)
207+
for (name, column) in additionalColumns {
208+
insert(detached: name, type: column.type, isOptional: isOptional)
209209
}
210210
}
211211
}
212212

213213
/// Imports all columns into the environment
214214
mutating func `import`(columns: Columns) {
215-
for (column, type) in columns {
216-
insert(detached: column, type: type, isOptional: false)
215+
for (name, column) in columns {
216+
insert(detached: name, type: column.type, isOptional: false)
217217
}
218218
}
219219

@@ -314,15 +314,15 @@ struct Environment {
314314
let entries = importedTable.table.columns[column]
315315

316316
if let column = entries.first {
317-
return LookupResult(column, isAmbiguous: forceAmbiguous || entries.count > 1)
317+
return LookupResult(column.type, isAmbiguous: forceAmbiguous || entries.count > 1)
318318
}
319319

320320
guard let column = importedTable.additionalColumns?[column].first else {
321321
// If the table does not have it the parent won't either
322322
return .columnDoesNotExist(column)
323323
}
324324

325-
return LookupResult(column, isAmbiguous: forceAmbiguous)
325+
return LookupResult(column.type, isAmbiguous: forceAmbiguous)
326326
}
327327

328328
/// Inserts a type into the map of detached values.

Sources/Compiler/Gen/Language.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ extension Language {
118118
name: table.name.name.capitalizedFirst,
119119
fields: table.columns.reduce(into: [:]) { fields, column in
120120
let name = column.key.description
121-
let type = column.value
121+
let type = column.value.type
122122
fields[name] = GeneratedField(
123123
name: name,
124124
type: .builtin(
@@ -191,7 +191,7 @@ extension Language {
191191
}
192192

193193
// Make sure there is at least one column else return void
194-
guard let firstColumn = firstResultColumns.columns.values.first else {
194+
guard let firstColumn = firstResultColumns.columns.values.first?.type else {
195195
return nil
196196
}
197197

@@ -219,7 +219,7 @@ extension Language {
219219
} else {
220220
for column in chunk.columns {
221221
let name = column.key.description
222-
let type = column.value
222+
let type = column.value.type
223223
fields[name] = GeneratedField(
224224
name: name,
225225
type: .builtin(

Sources/Compiler/Sema/ExprTypeChecker.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ struct ExprTypeChecker {
6363
pragmas: pragmas
6464
)
6565
let signature = typeChecker.signature(for: select)
66-
let type: Type = .row(.fixed(signature.output.allColumns.map(\.value)))
66+
let type: Type = .row(.fixed(signature.output.allColumns.map(\.value.type)))
6767
// Make sure to update our inference state
6868
inferenceState = typeChecker.inferenceState
6969
// Using typeCheckers `allDiagnostics` would include diags

Sources/Compiler/Sema/Statement.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public struct ResultColumns: Sendable {
131131

132132
/// The columns as a row type.
133133
public var type: Type {
134-
return .row(.fixed(allColumns.map(\.value)))
134+
return .row(.fixed(allColumns.map(\.value.type)))
135135
}
136136

137137
/// Whether or not there are any columns returned
@@ -161,7 +161,7 @@ public struct ResultColumns: Sendable {
161161
public func mapTypes(_ transform: (Type) -> Type) -> ResultColumns {
162162
return ResultColumns(
163163
chunks: chunks.map { chunk in
164-
Chunk(columns: chunk.columns.mapValues(transform), table: chunk.table)
164+
Chunk(columns: chunk.columns.mapValues{ $0.mapType(transform) }, table: chunk.table)
165165
}
166166
)
167167
}

Sources/Compiler/Sema/StmtTypeChecker.swift

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ extension StmtTypeChecker {
469469
// to say this stays for now but I dont like it.
470470
inNewEnvironment(extendCurrentEnv: true) { typeChecker in
471471
for column in resultColumns.allColumns where !typeChecker.env.hasColumn(named: column.key) {
472-
typeChecker.env.import(column: column.key, with: column.value)
472+
typeChecker.env.import(column: column.key, with: column.value.type)
473473
}
474474

475475
for term in select.orderBy {
@@ -524,7 +524,7 @@ extension StmtTypeChecker {
524524
let type = inferenceState.solution(for: type)
525525
inferenceState.unify(
526526
type,
527-
with: inferenceState.solution(for: secondColumns[index]),
527+
with: inferenceState.solution(for: secondColumns[index].type),
528528
at: location
529529
)
530530
index += 1
@@ -555,7 +555,7 @@ extension StmtTypeChecker {
555555
continue
556556
}
557557

558-
columnTypes.append(def)
558+
columnTypes.append(def.type)
559559
}
560560
inputType = .row(.fixed(columnTypes))
561561
} else {
@@ -604,7 +604,7 @@ extension StmtTypeChecker {
604604
return .empty
605605
}
606606

607-
inferenceState.unify(column, with: valueType, at: set.location)
607+
inferenceState.unify(column.type, with: valueType, at: set.location)
608608
// SET (column1, column2) = (value1, value2)
609609
case let .list(columnNames):
610610
// TODO: Names will not be inferred here. Names only handles
@@ -664,7 +664,7 @@ extension StmtTypeChecker {
664664

665665
for name in names {
666666
if let column = table.columns[name.value].first {
667-
columns.append(column)
667+
columns.append(column.type)
668668
} else {
669669
diagnostics.add(.columnDoesNotExist(name))
670670
columns.append(.error)
@@ -687,7 +687,7 @@ extension StmtTypeChecker {
687687

688688
let name = alias?.identifier.value ?? names.proposedName ?? "column\(offset + 1)"
689689

690-
resultColumns.append(type, for: name)
690+
resultColumns.append(Column(type: type), for: name)
691691
case .all:
692692
resultColumns.append(contentsOf: sourceTable.columns)
693693
}
@@ -726,7 +726,8 @@ extension StmtTypeChecker {
726726
recursiveCte = Table(
727727
name: cteName,
728728
columns: cte.columns.reduce(into: [:]) { columns, name in
729-
columns.append(inferenceState.freshTyVar(for: name), for: name.value)
729+
let type = inferenceState.freshTyVar(for: name)
730+
columns.append(Column(type: type), for: name.value)
730731
},
731732
kind: .cte
732733
)
@@ -888,7 +889,7 @@ extension StmtTypeChecker {
888889
let (type, names) = typeCheck(expr)
889890
let name = alias?.identifier.value ?? names.proposedName ?? "column\(offset + 1)"
890891

891-
columns.append(type, for: name)
892+
columns.append(Column(type: type), for: name)
892893
nameInferrer.suggest(name: name, for: names)
893894

894895
// We selected a single column, so clear out the table
@@ -1085,7 +1086,9 @@ extension StmtTypeChecker {
10851086
tableColumns: columns,
10861087
tableName: createTable.name.value
10871088
)
1088-
columns.append(type, for: name.value)
1089+
let isGenerated = def.constraints.contains { $0.isGenerated }
1090+
let column = Column(type: type, isGenerated: isGenerated)
1091+
columns.append(column, for: name.value)
10891092
}
10901093

10911094
validateTableConstraints(
@@ -1148,7 +1151,9 @@ extension StmtTypeChecker {
11481151
tableColumns: table.columns,
11491152
tableName: table.name.name
11501153
)
1151-
table.columns.append(newType, for: column.name.value)
1154+
let isGenerated = column.constraints.contains { $0.isGenerated }
1155+
let newColumn = Column(type: newType, isGenerated: isGenerated)
1156+
table.columns.append(newColumn, for: column.name.value)
11521157
case let .dropColumn(column):
11531158
table.columns = Columns(table.columns.filter { $0.key != column.value })
11541159
}
@@ -1389,7 +1394,7 @@ extension StmtTypeChecker {
13891394
? .nominal(typeName)
13901395
: .optional(.nominal(typeName))
13911396

1392-
columns.append(type, for: name.value)
1397+
columns.append(Column(type: type), for: name.value)
13931398
case .fts5Option:
13941399
break // Nothing to do, maybe validate these in the future
13951400
case .unknown:

Sources/Compiler/Syntax/ColumnConstraintSyntax.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,11 @@ struct ColumnConstraintSyntax: Syntax {
4040
default: return false
4141
}
4242
}
43+
44+
var isGenerated: Bool {
45+
switch kind {
46+
case .generated: return true
47+
default: return false
48+
}
49+
}
4350
}

Sources/Compiler/Table.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public struct Table: Sendable, Equatable {
3737
}
3838

3939
var type: Type {
40-
return .row(.fixed(columns.map(\.value)))
40+
return .row(.fixed(columns.map(\.value.type)))
4141
}
4242

4343
/// A table to be returned incase of an error in type checking
@@ -62,7 +62,7 @@ public struct Table: Sendable, Equatable {
6262
/// transformations needed
6363
func mapTypes(_ transform: (Type) -> Type) -> Table {
6464
var copy = self
65-
copy.columns = columns.mapValues(transform)
65+
copy.columns = columns.mapValues { $0.mapType(transform) }
6666
return copy
6767
}
6868
}

Tests/CompilerTests/Check/Check.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ struct CheckEmitter {
287287
is Float, is Double, is String, is Any.Type, is IdentifierSyntax,
288288
is LiteralExprSyntax, is TableOptionsSyntax, is TypeNameSyntax, is BindParameterSyntax,
289289
is OperatorSyntax, is OrderSyntax, is Type, is AliasSyntax, is ColumnExprSyntax.Column,
290-
is QualifiedName: true
290+
is QualifiedName, is Column: true
291291
case _ as Substring:
292292
true
293293
default: false

0 commit comments

Comments
 (0)