Skip to content

Commit 07d1aec

Browse files
committed
type check upsert clause
1 parent 16e5444 commit 07d1aec

2 files changed

Lines changed: 73 additions & 1 deletion

File tree

Sources/Compiler/Sema/StmtTypeChecker.swift

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,10 @@ extension StmtTypeChecker {
594594
if !skipInputUnification {
595595
inferenceState.unify(inputType, with: resultColumns.type, at: insert.location)
596596
}
597+
598+
if let upsertClause = values.upsertClause {
599+
typeCheck(upsertClause: upsertClause, table: table)
600+
}
597601
} else {
598602
// TODO: Using 'DEFALUT VALUES' make sure all columns
599603
// TODO: actually have default values or null
@@ -608,6 +612,55 @@ extension StmtTypeChecker {
608612
return resultColumns
609613
}
610614

615+
mutating func typeCheck(upsertClause: UpsertClauseSyntax, table: Table) {
616+
if let conflictTarget = upsertClause.confictTarget {
617+
// Make sure all columns exist
618+
for column in conflictTarget.columns {
619+
guard let name = column.columnName,
620+
table.columns[name.value].isEmpty else { continue }
621+
diagnostics.add(.columnDoesNotExist(name))
622+
}
623+
}
624+
625+
// In new env since we are inserting excluded
626+
inNewEnvironment(extendCurrentEnv: true) { typeChecker in
627+
// Insert the table aliased to `excluded` to allow access
628+
// like `SET foo = excluded.foo`.
629+
let excluded = table.aliased(to: "excluded")
630+
typeChecker.importTable(excluded)
631+
632+
switch upsertClause.doAction {
633+
case .nothing:
634+
break
635+
case .updateSet(let sets, let `where`):
636+
for set in sets {
637+
let (_, name) = typeChecker.typeCheck(set.expr)
638+
639+
switch set.column {
640+
case .single(let column):
641+
// Single column being set, suggest the name for the param
642+
// if it is one.
643+
typeChecker.nameInferrer.suggest(name: column.value, for: name)
644+
645+
if table.columns[column.value].isEmpty {
646+
typeChecker.diagnostics.add(.columnDoesNotExist(column))
647+
}
648+
case .list(let columns):
649+
for column in columns {
650+
if table.columns[column.value].isEmpty {
651+
typeChecker.diagnostics.add(.columnDoesNotExist(column))
652+
}
653+
}
654+
}
655+
}
656+
657+
if let `where` {
658+
_ = typeChecker.typeCheck(`where`)
659+
}
660+
}
661+
}
662+
}
663+
611664
mutating func typeCheck(update: UpdateStmtSyntax) -> ResultColumns {
612665
typeCheck(with: update.with)
613666

@@ -1079,7 +1132,9 @@ extension StmtTypeChecker {
10791132
) {
10801133
// Insert real name not alias. These are used later for observation tracking
10811134
// so an alias is no good since it will always be the actual table name.
1082-
usedTableNames.insert(table.name.name)
1135+
if table.name.schema == .main {
1136+
usedTableNames.insert(table.name.name)
1137+
}
10831138

10841139
// Table is always accessible by it's name even if aliased
10851140
env.import(

Tests/CompilerTests/Compiler/CompileInsert.sql

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,20 @@ INSERT INTO user (id, name, description) VALUES (?, ?, ?);
120120
-- CHECK: TABLES
121121
-- CHECK: user
122122
INSERT INTO user (name, id) VALUES (?, ?);
123+
124+
-- CHECK: SIGNATURE
125+
-- CHECK: PARAMETERS
126+
-- CHECK: PARAMETER
127+
-- CHECK: TYPE INTEGER?
128+
-- CHECK: INDEX 1
129+
-- CHECK: NAME id
130+
-- CHECK: PARAMETER
131+
-- CHECK: TYPE TEXT?
132+
-- CHECK: INDEX 2
133+
-- CHECK: NAME name
134+
-- CHECK: TABLES
135+
-- CHECK: user
136+
INSERT INTO user (id, name) VALUES (?, ?)
137+
ON CONFLICT (id) DO UPDATE
138+
SET name = excluded.name
139+
WHERE excluded.name = 'bob';

0 commit comments

Comments
 (0)