Skip to content

Commit 0e32cd9

Browse files
committed
Add checks for duplicated column names on insert/update.
1 parent 24f5c5a commit 0e32cd9

File tree

4 files changed

+52
-15
lines changed

4 files changed

+52
-15
lines changed

src/Rezoom.SQL.Compiler/Error.fs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ let cannotCollateType typeName =
149149
sprintf "SQ063: A column of type ``%O`` cannot have a collation applied" typeName
150150
let cannotAlterPrimaryKeyColumn columnName =
151151
sprintf "SQ064: Cannot alter the column ``%O`` because it is part of the table's primary key" columnName
152+
let insertDuplicateColumn columnName =
153+
sprintf "SQ065: The column ``%O`` is specified multiple times in the insert statement" columnName
154+
let updateDuplicateColumn columnName =
155+
sprintf "SQ066: The column ``%O`` is specified multiple times in the update statement" columnName
152156

153157
let tableNameNotSuitableForPG =
154158
"SQ069: Table name is not suitable for PG (maybe you thought you were writing R?)"

src/Rezoom.SQL.Compiler/TypeChecker.fs

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -721,12 +721,16 @@ type private TypeChecker(cxt : ITypeInferenceContext, scope : InferredSelectScop
721721
failAt insert.Columns.[0].Source (Error.insertMissingColumns missingColumns)
722722
| _ ->
723723
failAt insert.InsertInto.Source Error.insertIntoNonTable
724-
{ With = withClause
725-
Or = insert.Or
726-
InsertInto = table
727-
Columns = columns // we *must* specify these because our order might not match DB's
728-
Data = checker.Select(insert.Data, SelfQueryShape.Known(knownShape))
729-
}
724+
match columns |> tryFindFirstDuplicateBy (fun c -> c.Value) with
725+
| None ->
726+
{ With = withClause
727+
Or = insert.Or
728+
InsertInto = table
729+
Columns = columns // we *must* specify these because our order might not match DB's
730+
Data = checker.Select(insert.Data, SelfQueryShape.Known(knownShape))
731+
}
732+
| Some duplicate ->
733+
failAt duplicate.Source (Error.insertDuplicateColumn duplicate.Value)
730734

731735
member this.Update(update : UpdateStmt) =
732736
let checker, withClause =
@@ -750,14 +754,18 @@ type private TypeChecker(cxt : ITypeInferenceContext, scope : InferredSelectScop
750754
| _ ->
751755
failAt name.Source <| Error.noSuchColumnToSet updateTable name.Value
752756
|]
753-
{ With = withClause
754-
UpdateTable = updateTable
755-
Or = update.Or
756-
Set = setColumns
757-
Where = Option.map checker.Expr update.Where
758-
OrderBy = Option.map (rmap checker.OrderingTerm) update.OrderBy
759-
Limit = Option.map checker.Limit update.Limit
760-
}
757+
match setColumns |> tryFindFirstDuplicateBy (fun (name, _) -> name.Value) with
758+
| None ->
759+
{ With = withClause
760+
UpdateTable = updateTable
761+
Or = update.Or
762+
Set = setColumns
763+
Where = Option.map checker.Expr update.Where
764+
OrderBy = Option.map (rmap checker.OrderingTerm) update.OrderBy
765+
Limit = Option.map checker.Limit update.Limit
766+
}
767+
| Some (name, _) ->
768+
failAt name.Source (Error.updateDuplicateColumn name.Value)
761769

762770
member this.Stmt(stmt : Stmt) =
763771
match stmt with

src/Rezoom.SQL.Compiler/Utilities.fs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,4 +215,14 @@ type StatefulBuilder() =
215215
member inline this.For(xs, body) : State<_, _> = State.forLoop xs body
216216
member inline this.Using(disposable, body) : State<_, _> = State.using disposable body
217217

218-
let stateful = StatefulBuilder()
218+
let stateful = StatefulBuilder()
219+
220+
let tryFindFirstDuplicateBy (selector : _ -> _) (xs : _ seq) =
221+
let set = HashSet()
222+
use enumer = xs.GetEnumerator()
223+
let mutable dup = None
224+
while enumer.MoveNext() && dup.IsNone do
225+
let x = enumer.Current
226+
if not (set.Add(selector x)) then
227+
dup <- Some x
228+
dup

src/Rezoom.SQL.Test/TestTypeErrors.fs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,19 @@ let ``ambiguous columns`` () =
130130
from users u
131131
join usergroupmaps ugm on ugm.userid = u.id
132132
join groups g on g.id = ugm.groupid
133+
"""
134+
135+
[<Test>]
136+
let ``duplicate column insert`` () =
137+
expectError (Error.insertDuplicateColumn "Email")
138+
"""
139+
insert into Users(Name, Email, Email) values ('', '', '');
140+
"""
141+
142+
[<Test>]
143+
let ``duplicate column update`` () =
144+
expectError (Error.updateDuplicateColumn "Name")
145+
"""
146+
update Users set Name = 'foo', Email = 'bar', Name = 'car'
147+
where Id = 1
133148
"""

0 commit comments

Comments
 (0)