Skip to content

Commit 35b08c8

Browse files
committed
Working with columns as DuplicateDictionary
1 parent 91b40b2 commit 35b08c8

6 files changed

Lines changed: 104 additions & 42 deletions

File tree

Sources/Compiler/Columns.swift

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

88
import OrderedCollections
99

10-
public typealias Columns = OrderedDictionary<Substring, Type>
10+
public typealias Columns = DuplicateDictionary<Substring, Type>
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()
16-
.reduce(into: [:]) { c, v in c["column\(v.offset + 1)"] = v.element }
16+
.reduce(into: [:]) { c, v in
17+
c.append(v.element, for: "column\(v.offset + 1)")
18+
}
1719
}
1820
}

Sources/Compiler/Environment.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,13 @@ extension Environment {
174174
/// Imports the table into the environment.
175175
/// If `isOptional` is true all columns types will be
176176
/// forced to their optional value.
177+
/// If `qualifiedAccessOnly` is true the columns will only be
178+
/// available via qualified access with at least the table
179+
/// name specified
177180
mutating func `import`(
178181
table: Table,
179-
isOptional: Bool
182+
isOptional: Bool,
183+
qualifiedAccessOnly: Bool = false
180184
) {
181185
let importedTable = ImportedTable(
182186
table: isOptional ? table.mapTypes { $0.coerceToOptional() } : table,
@@ -189,6 +193,9 @@ extension Environment {
189193
// in `MATCH` and `IN` statements.
190194
insert(detached: table.name.name, type: table.type, isOptional: false)
191195

196+
// Don't insert columns into detached if they required qualified access
197+
guard !qualifiedAccessOnly else { return }
198+
192199
for (column, type) in table.columns {
193200
insert(detached: column, type: type, isOptional: isOptional)
194201
}
@@ -267,11 +274,13 @@ extension Environment {
267274
in importedTable: ImportedTable,
268275
forceAmbiguous: Bool = false
269276
) -> LookupResult<Type> {
270-
if let column = importedTable.table.columns[column] {
271-
return LookupResult(column, isAmbiguous: forceAmbiguous)
277+
let entries = importedTable.table.columns[column]
278+
279+
if let column = entries.first {
280+
return LookupResult(column, isAmbiguous: forceAmbiguous || entries.count > 1)
272281
}
273282

274-
guard let column = importedTable.additionalColumns?[column] else {
283+
guard let column = importedTable.additionalColumns?[column].first else {
275284
// If the table does not have it the parent won't either
276285
return .columnDoesNotExist(column)
277286
}

Sources/Compiler/Sema/Statement.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public struct ResultColumns: Sendable {
153153

154154
return chunks.reduce(into: [:]) { result, chunk in
155155
for column in chunk.columns {
156-
result[column.key] = column.value
156+
result.append(column.value, for: column.key)
157157
}
158158
}
159159
}

Sources/Compiler/Sema/StmtTypeChecker.swift

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ extension StmtTypeChecker: StmtSyntaxVisitor {
277277
let types = resultColumns.values
278278
let minCount = min(stmt.columnNames.count, types.count)
279279
columns = (0..<minCount).reduce(into: [:]) { columns, index in
280-
columns[stmt.columnNames[index].value] = types[index]
280+
columns.append(types[index], for: stmt.columnNames[index].value)
281281
}
282282
}
283283

@@ -345,17 +345,17 @@ extension StmtTypeChecker: StmtSyntaxVisitor {
345345

346346
switch stmt.action {
347347
case .delete:
348-
importTable(table, as: "old")
348+
importTable(table, as: "old", qualifiedAccessOnly: true)
349349
case .insert:
350-
importTable(table, as: "new")
350+
importTable(table, as: "new", qualifiedAccessOnly: true)
351351
case let .update(columns):
352-
importTable(table, as: "new")
353-
importTable(table, as: "old")
352+
importTable(table, as: "new", qualifiedAccessOnly: true)
353+
importTable(table, as: "old", qualifiedAccessOnly: true)
354354

355355
// Make sure all columns in the update statement actually exist
356356
if let columns {
357357
for column in columns {
358-
if table.columns[column.value] == nil {
358+
if !table.columns.contains(key: column.value) {
359359
diagnostics.add(.columnDoesNotExist(column))
360360
}
361361
}
@@ -502,7 +502,7 @@ extension StmtTypeChecker {
502502
if let columns = insert.columns {
503503
var columnTypes: [Type] = []
504504
for column in columns {
505-
guard let def = table.columns[column.value] else {
505+
guard let def = table.columns[column.value].first else {
506506
diagnostics.add(.columnDoesNotExist(column))
507507
columnTypes.append(.error)
508508
continue
@@ -552,7 +552,7 @@ extension StmtTypeChecker {
552552
case let .single(column):
553553
nameInferrer.suggest(name: column.value, for: valueName)
554554

555-
guard let column = table.columns[column.value] else {
555+
guard let column = table.columns[column.value].first else {
556556
diagnostics.add(.columnDoesNotExist(column))
557557
return .empty
558558
}
@@ -616,7 +616,7 @@ extension StmtTypeChecker {
616616
var columns: [Type] = []
617617

618618
for name in names {
619-
if let column = table.columns[name.value] {
619+
if let column = table.columns[name.value].first {
620620
columns.append(column)
621621
} else {
622622
diagnostics.add(.columnDoesNotExist(name))
@@ -640,10 +640,9 @@ extension StmtTypeChecker {
640640

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

643-
resultColumns[name] = type
643+
resultColumns.append(type, for: name)
644644
case .all:
645-
// TODO: See TODO on `Columns` typealias
646-
resultColumns.merge(sourceTable.columns, uniquingKeysWith: { $1 })
645+
resultColumns.append(contentsOf: sourceTable.columns)
647646
}
648647
}
649648

@@ -678,7 +677,7 @@ extension StmtTypeChecker {
678677
}
679678

680679
columns = (0 ..< min(columnTypes.count, cte.columns.count))
681-
.reduce(into: [:]) { $0[cte.columns[$1].value] = columnTypes[$1] }
680+
.reduce(into: [:]) { $0.append(columnTypes[$1], for: cte.columns[$1].value) }
682681
}
683682

684683
return Table(
@@ -796,7 +795,7 @@ extension StmtTypeChecker {
796795
/// selected all columns from foo and bar so the table structs can be embded
797796
/// within the output type.
798797
private mutating func typeCheck(resultColumns: [ResultColumnSyntax]) -> ResultColumns {
799-
var columns: OrderedDictionary<Substring, Type> = [:]
798+
var columns: Columns = [:]
800799
var table: Substring?
801800
// Each chunk is either a list of specifically listed columns
802801
// or a select all of a table.
@@ -819,7 +818,7 @@ extension StmtTypeChecker {
819818
let (type, names) = typeCheck(expr)
820819
let name = alias?.identifier.value ?? names.proposedName ?? "column\(offset + 1)"
821820

822-
columns[name] = type
821+
columns.append(type, for: name)
823822
nameInferrer.suggest(name: name, for: names)
824823

825824
// We selected a single column, so clear out the table
@@ -960,17 +959,28 @@ extension StmtTypeChecker {
960959
private mutating func importTable(
961960
_ table: Table,
962961
as alias: Substring? = nil,
963-
isOptional: Bool = false
962+
isOptional: Bool = false,
963+
qualifiedAccessOnly: Bool = false
964964
) {
965965
// Insert real name not alias. These are used later for observation tracking
966966
// so an alias is no good since it will always be the actual table name.
967967
usedTableNames.insert(table.name.name)
968968

969969
// Table is always accessible by it's name even if aliased
970-
env.import(table: table, isOptional: isOptional)
970+
env.import(
971+
table: table,
972+
isOptional: isOptional,
973+
qualifiedAccessOnly: qualifiedAccessOnly
974+
)
971975

976+
// Aliases can only ever be used qualifed since the original
977+
// columns are already inserted
972978
if let alias {
973-
env.import(table: table.aliased(to: alias), isOptional: isOptional)
979+
env.import(
980+
table: table.aliased(to: alias),
981+
isOptional: isOptional,
982+
qualifiedAccessOnly: true
983+
)
974984
}
975985
}
976986

@@ -994,11 +1004,12 @@ extension StmtTypeChecker {
9941004
case let .columns(columnsDefs, constraints, options):
9951005
var columns: Columns = [:]
9961006
for (name, def) in columnsDefs {
997-
columns[name.value] = typeFor(
1007+
let type = typeFor(
9981008
column: def,
9991009
tableColumns: columns,
10001010
tableName: createTable.name.value
10011011
)
1012+
columns.append(type, for: name.value)
10021013
}
10031014

10041015
validateTableConstraints(
@@ -1053,17 +1064,16 @@ extension StmtTypeChecker {
10531064
tableName = QualifiedName(name: newTableName.value, schema: tableName.schema)
10541065
table.name = tableName
10551066
case let .renameColumn(oldName, newName):
1056-
table.columns = table.columns.reduce(into: [:]) { newColumns, column in
1057-
newColumns[column.key == oldName.value ? newName.value : column.key] = column.value
1058-
}
1067+
table.columns.rename(oldName.value, to: newName.value)
10591068
case let .addColumn(column):
1060-
table.columns[column.name.value] = typeFor(
1069+
let newType = typeFor(
10611070
column: column,
10621071
tableColumns: table.columns,
10631072
tableName: table.name.name
10641073
)
1074+
table.columns.append(newType, for: column.name.value)
10651075
case let .dropColumn(column):
1066-
table.columns[column.value] = nil
1076+
table.columns = Columns(table.columns.filter { $0.key != column.value })
10671077
}
10681078

10691079
schema[tableName] = table
@@ -1127,13 +1137,13 @@ extension StmtTypeChecker {
11271137
// declared for so if its this table and this column then ignore it.
11281138
guard column.name.value != foreignColumn.value else { continue }
11291139

1130-
if tableColumns[foreignColumn.value] == nil {
1140+
if !tableColumns.contains(key: foreignColumn.value) {
11311141
diagnostics.add(.columnDoesNotExist(foreignColumn))
11321142
}
11331143
}
11341144
} else if let table = schema[QualifiedName(name: fk.foreignTable.value, schema: .main)] {
11351145
for foreignColumn in fk.foreignColumns {
1136-
if table.columns[foreignColumn.value] == nil {
1146+
if !table.columns.contains(key: foreignColumn.value) {
11371147
diagnostics.add(.columnDoesNotExist(foreignColumn))
11381148
}
11391149
}
@@ -1179,10 +1189,10 @@ extension StmtTypeChecker {
11791189
tableColumns: Columns
11801190
) {
11811191
for foreignColumn in fk.foreignColumns {
1182-
// Column constraints can oddly reference themselves
1192+
// Column constraints can reference themselves
11831193
guard column.name.value != foreignColumn.value else { continue }
11841194

1185-
if tableColumns[foreignColumn.value] == nil {
1195+
if !tableColumns.contains(key: foreignColumn.value) {
11861196
diagnostics.add(.columnDoesNotExist(foreignColumn))
11871197
}
11881198
}
@@ -1237,7 +1247,7 @@ extension StmtTypeChecker {
12371247
for column in constraint.0 {
12381248
guard let name = column.columnName else { continue }
12391249

1240-
if columns[name.value] == nil {
1250+
if !columns.contains(key: name.value) {
12411251
diagnostics.add(.columnDoesNotExist(name))
12421252
} else {
12431253
columnNames.append(name.value)
@@ -1263,7 +1273,7 @@ extension StmtTypeChecker {
12631273
case .foreignKey(let fkColumns, let fkClause):
12641274
// Make sure listed columns exist
12651275
for column in fkColumns {
1266-
guard columns[column.value] == nil else { continue }
1276+
guard !columns.contains(key: column.value) else { continue }
12671277
diagnostics.add(.columnDoesNotExist(column))
12681278
}
12691279

@@ -1277,7 +1287,7 @@ extension StmtTypeChecker {
12771287

12781288
// Make sure referenced columns exist
12791289
for column in fkClause.foreignColumns {
1280-
guard foreignTable.columns[column.value] == nil else { continue }
1290+
guard !foreignTable.columns.contains(key: column.value) else { continue }
12811291
diagnostics.add(.columnDoesNotExist(column))
12821292
}
12831293
case .primaryKey, .unique:
@@ -1298,9 +1308,11 @@ extension StmtTypeChecker {
12981308
continue
12991309
}
13001310

1301-
columns[name.value] = notNull != nil
1311+
let type: Type = notNull != nil
13021312
? .nominal(typeName)
13031313
: .optional(.nominal(typeName))
1314+
1315+
columns.append(type, for: name.value)
13041316
case .fts5Option:
13051317
break // Nothing to do, maybe validate these in the future
13061318
case .unknown:

Sources/Compiler/Utils/DuplicateDictionary.swift

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ public struct DuplicateDictionary<Key: Hashable, Value> {
2929

3030
/// Tuples cannot be equatable...
3131
public struct _Element {
32-
@usableFromInline let key: Key
33-
@usableFromInline let value: Value
32+
@usableFromInline var key: Key
33+
@usableFromInline var value: Value
3434

3535
@usableFromInline
3636
@inline(__always)
@@ -119,6 +119,7 @@ public struct DuplicateDictionary<Key: Hashable, Value> {
119119
var currentIndex = 0
120120

121121
public mutating func next() -> Element? {
122+
defer { currentIndex += 1 }
122123
switch positions {
123124
case .empty:
124125
return nil
@@ -145,6 +146,16 @@ public struct DuplicateDictionary<Key: Hashable, Value> {
145146
self.positions = positions
146147
}
147148

149+
public init<S: Sequence>(
150+
_ sequence: S
151+
) where S.Element == Element {
152+
self = .init()
153+
154+
for (k, v) in sequence {
155+
append(v, for: k)
156+
}
157+
}
158+
148159
/// Appends the value for the given key
149160
@inline(__always)
150161
public mutating func append(_ value: Value, for key: Key) {
@@ -182,6 +193,28 @@ public struct DuplicateDictionary<Key: Hashable, Value> {
182193
return p != .empty
183194
}
184195

196+
/// Renames all values under the `key` to the `newKey`
197+
public mutating func rename(_ key: Key, to newKey: Key) {
198+
guard let positions = positions[key] else { return }
199+
200+
for index in positions {
201+
_values[index].key = newKey
202+
}
203+
204+
// Insert positions for new key and remove old one.
205+
self.positions[newKey] = positions
206+
self.positions[key] = nil
207+
}
208+
209+
/// Updates all values for the given `key` to be the `newValue`
210+
public mutating func updateAll(_ key: Key, to newValue: Value) {
211+
guard let positions = positions[key] else { return }
212+
213+
for index in positions {
214+
_values[index].value = newValue
215+
}
216+
}
217+
185218
/// Map over the values and transform them into a new dictionary
186219
/// with the same keys but with the transformed values.
187220
public func mapValues<T>(
@@ -334,3 +367,9 @@ extension DuplicateDictionary: Sendable where Key: Sendable, Value: Sendable {}
334367
extension DuplicateDictionary._Element: Sendable where Key: Sendable, Value: Sendable {}
335368
extension DuplicateDictionary._Element: Equatable where Key: Equatable, Value: Equatable {}
336369
extension DuplicateDictionary: Equatable where Key: Equatable, Value: Equatable {}
370+
371+
extension DuplicateDictionary: CustomReflectable {
372+
public var customMirror: Mirror {
373+
Mirror(self, unlabeledChildren: self.map(\.self), displayStyle: .dictionary)
374+
}
375+
}

Tests/CompilerTests/CompilerTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class CompilerTests: XCTestCase {
3131
}
3232

3333
func testCreateTable() throws {
34-
try checkSchema(compile: "CompileCreateTable")
34+
try checkSchema(compile: "CompileCreateTable", dump: true)
3535
}
3636

3737
func testDropTable() throws {

0 commit comments

Comments
 (0)