Skip to content

Commit

Permalink
Implement CRUD for Postgres and MySQL
Browse files Browse the repository at this point in the history
Renamed some stuff like "State" to make it less likely to clash with a column name
Added ability to rertieve and clear pending changes from a data context
  • Loading branch information
pezipink committed May 17, 2014
1 parent 1bb3cd0 commit eb776dc
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 34 deletions.
6 changes: 3 additions & 3 deletions src/SQLProvider/Providers.MsSqlServer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ type internal MSSqlServerProvider() =
// initially supporting update/create/delete of single entities, no hierarchies yet
entities
|> List.iter(fun e ->
match e.State with
match e._State with
| Created ->
let cmd = createInsertCommand e
Common.QueryEvents.PublishSqlQuery cmd.CommandText
Expand All @@ -512,12 +512,12 @@ type internal MSSqlServerProvider() =
// this is because non-identity columns will have been set
// manually and in that case scope_identity would bring back 0 "" or whatever
| None -> e.SetColumnSilent(pkLookup.[e.Table.FullName], id)
e.State <- Unchanged
e._State <- Unchanged
| Modified fields ->
let cmd = createUpdateCommand e fields
Common.QueryEvents.PublishSqlQuery cmd.CommandText
cmd.ExecuteNonQuery() |> ignore
e.State <- Unchanged
e._State <- Unchanged
| Deleted ->
let cmd = createDeleteCommand e
Common.QueryEvents.PublishSqlQuery cmd.CommandText
Expand Down
123 changes: 122 additions & 1 deletion src/SQLProvider/Providers.MySql.fs
Original file line number Diff line number Diff line change
Expand Up @@ -356,4 +356,125 @@ type internal MySqlProvider(resolutionPath) as this =
(sql,parameters)

member this.ProcessUpdates(con, entities) =
failwith "The MySQL type provider does not currently support CRUD operations."
let sb = Text.StringBuilder()
let (~~) (t:string) = sb.Append t |> ignore

// ensure columns have been loaded
entities |> List.map(fun e -> e.Table)
|> Seq.distinct
|> Seq.iter(fun t -> (this :> ISqlProvider).GetColumns(con,t) |> ignore )

con.Open()
let createInsertCommand (entity:SqlEntity) =
let cmd = (this :> ISqlProvider).CreateCommand(con,"")
cmd.Connection <- con
let pk = pkLookup.[entity.Table.FullName]
let columnNames, values =
(([],0),entity.ColumnValues)
||> Seq.fold(fun (out,i) (k,v) ->
let name = sprintf "@param%i" i
let p = (this :> ISqlProvider).CreateCommandParameter(name,v,None)
(k,p)::out,i+1)
|> fun (x,_)-> x
|> List.rev
|> List.toArray
|> Array.unzip

sb.Clear() |> ignore
~~(sprintf "INSERT INTO %s (%s) VALUES (%s); SELECT LAST_INSERT_ID();"
(entity.Table.FullName.Replace("[","`").Replace("]","`"))
(String.Join(",",columnNames))
(String.Join(",",values |> Array.map(fun p -> p.ParameterName))))

values |> Array.iter (cmd.Parameters.Add >> ignore)
cmd.CommandText <- sb.ToString()
cmd

let createUpdateCommand (entity:SqlEntity) changedColumns =
let cmd = (this :> ISqlProvider).CreateCommand(con,"")
cmd.Connection <- con
let pk = pkLookup.[entity.Table.FullName]
sb.Clear() |> ignore

if changedColumns |> List.exists ((=)pk) then failwith "Error - you cannot change the primary key of an entity."

let pkValue =
match entity.GetColumnOption<obj> pk with
| Some v -> v
| None -> failwith "Error - you cannot update an entity that does not have a primary key."

let data =
(([],0),changedColumns)
||> List.fold(fun (out,i) col ->
let name = sprintf "@param%i" i
let p =
match entity.GetColumnOption<obj> col with
| Some v -> (this :> ISqlProvider).CreateCommandParameter(name,v,None)
| None -> (this :> ISqlProvider).CreateCommandParameter(name,DBNull.Value, None)
(col,p)::out,i+1)
|> fun (x,_)-> x
|> List.rev
|> List.toArray


let pkParam = (this :> ISqlProvider).CreateCommandParameter("@pk", pkValue, None)

~~(sprintf "UPDATE %s SET %s WHERE %s = @pk;"
(entity.Table.FullName.Replace("[","`").Replace("]","`"))
(String.Join(",", data |> Array.map(fun (c,p) -> sprintf "%s = %s" c p.ParameterName ) ))
pk)

data |> Array.map snd |> Array.iter (cmd.Parameters.Add >> ignore)
cmd.Parameters.Add pkParam |> ignore
cmd.CommandText <- sb.ToString()
cmd

let createDeleteCommand (entity:SqlEntity) =
let cmd = (this :> ISqlProvider).CreateCommand(con,"")
cmd.Connection <- con
sb.Clear() |> ignore
let pk = pkLookup.[entity.Table.FullName]
sb.Clear() |> ignore
let pkValue =
match entity.GetColumnOption<obj> pk with
| Some v -> v
| None -> failwith "Error - you cannot delete an entity that does not have a primary key."
let p = (this :> ISqlProvider).CreateCommandParameter("@id",pkValue,None)
cmd.Parameters.Add(p) |> ignore
~~(sprintf "DELETE FROM %s WHERE %s = @id" (entity.Table.FullName.Replace("[","`").Replace("]","`")) pk )
cmd.CommandText <- sb.ToString()
cmd

use scope = new Transactions.TransactionScope()
try

if con.State <> ConnectionState.Open then con.Open()
// initially supporting update/create/delete of single entities, no hierarchies yet
entities
|> List.iter(fun e ->
match e._State with
| Created ->
let cmd = createInsertCommand e
Common.QueryEvents.PublishSqlQuery cmd.CommandText
let id = cmd.ExecuteScalar()
match e.GetColumnOption pkLookup.[e.Table.FullName] with
| Some v -> () // if the primary key exists, do nothing
// this is because non-identity columns will have been set
// manually and in that case scope_identity would bring back 0 "" or whatever
| None -> e.SetColumnSilent(pkLookup.[e.Table.FullName], id)
e._State <- Unchanged
| Modified fields ->
let cmd = createUpdateCommand e fields
Common.QueryEvents.PublishSqlQuery cmd.CommandText
cmd.ExecuteNonQuery() |> ignore
e._State <- Unchanged
| Deleted ->
let cmd = createDeleteCommand e
Common.QueryEvents.PublishSqlQuery cmd.CommandText
cmd.ExecuteNonQuery() |> ignore
// remove the pk to prevent this attempting to be used again
e.SetColumnOptionSilent(pkLookup.[e.Table.FullName], None)
| Unchanged -> failwith "Unchanged entity encountered in update list - this should not be possible!")
scope.Complete()
finally
con.Close()
6 changes: 3 additions & 3 deletions src/SQLProvider/Providers.Oracle.fs
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ type internal OracleProvider(resolutionPath, owner) =
// initially supporting update/create/delete of single entities, no hierarchies yet
entities
|> List.iter(fun e ->
match e.State with
match e._State with
| Created ->
let cmd = createInsertCommand e
Common.QueryEvents.PublishSqlQuery cmd.CommandText
Expand All @@ -512,12 +512,12 @@ type internal OracleProvider(resolutionPath, owner) =
// this is because non-identity columns will have been set
// manually and in that case scope_identity would bring back 0 "" or whatever
| None -> e.SetColumnSilent(primaryKeyCache.[e.Table.Name].Column, id)
e.State <- Unchanged
e._State <- Unchanged
| Modified fields ->
let cmd = createUpdateCommand e fields
Common.QueryEvents.PublishSqlQuery cmd.CommandText
cmd.ExecuteNonQuery() |> ignore
e.State <- Unchanged
e._State <- Unchanged
| Deleted ->
let cmd = createDeleteCommand e
Common.QueryEvents.PublishSqlQuery cmd.CommandText
Expand Down
129 changes: 127 additions & 2 deletions src/SQLProvider/Providers.Postgresql.fs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ type internal PostgresqlProvider(resolutionPath) as this =
com.Parameters.Add p |> ignore
let p = (this:>ISqlProvider).CreateCommandParameter("@table",table.Name,None)
com.Parameters.Add p |> ignore
if con.State <> ConnectionState.Open then con.Open()
use reader = com.ExecuteReader()
let columns =
[ while reader.Read() do
Expand All @@ -211,6 +212,7 @@ type internal PostgresqlProvider(resolutionPath) as this =
yield col
| _ -> ()]
columnLookup.Add(table.FullName,columns)
con.Close()
columns
member __.GetRelationships(con,table) =
match relationshipLookup.TryGetValue(table.FullName) with
Expand Down Expand Up @@ -240,7 +242,7 @@ type internal PostgresqlProvider(resolutionPath) as this =
AND KCU2.CONSTRAINT_SCHEMA = RC.UNIQUE_CONSTRAINT_SCHEMA
AND KCU2.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME
AND KCU2.ORDINAL_POSITION = KCU1.ORDINAL_POSITION "

if con.State <> ConnectionState.Open then con.Open()
use reader = executeSql con (sprintf "%s WHERE KCU2.TABLE_NAME = '%s'" baseQuery table.Name )
let children =
[ while reader.Read() do
Expand All @@ -253,6 +255,7 @@ type internal PostgresqlProvider(resolutionPath) as this =
yield { Name = reader.GetString(0); PrimaryTable=toSchema (reader.GetString(9)) (reader.GetString(5)); PrimaryKey=reader.GetString(6)
ForeignTable=toSchema (reader.GetString(8)) (reader.GetString(1)); ForeignKey=reader.GetString(2) } ]
relationshipLookup.Add(table.FullName,(children,parents))
con.Close()
(children,parents)

/// Have not attempted stored procs yet
Expand Down Expand Up @@ -412,4 +415,126 @@ type internal PostgresqlProvider(resolutionPath) as this =
(sql,parameters)

member this.ProcessUpdates(con, entities) =
failwith "The PostgreSQL type provider does not currently support CRUD operations."
let sb = Text.StringBuilder()
let (~~) (t:string) = sb.Append t |> ignore

// ensure columns have been loaded
entities |> List.map(fun e -> e.Table)
|> Seq.distinct
|> Seq.iter(fun t -> (this :> ISqlProvider).GetColumns(con,t) |> ignore )

con.Open()
let createInsertCommand (entity:SqlEntity) =
let cmd = (this :> ISqlProvider).CreateCommand(con,"")
cmd.Connection <- con
let pk = pkLookup.[entity.Table.FullName]
let columnNames, values =
(([],0),entity.ColumnValues)
||> Seq.fold(fun (out,i) (k,v) ->
let name = sprintf "@param%i" i
let p = (this :> ISqlProvider).CreateCommandParameter(name,v,None)
(k,p)::out,i+1)
|> fun (x,_)-> x
|> List.rev
|> List.toArray
|> Array.unzip

sb.Clear() |> ignore
~~(sprintf "INSERT INTO %s (%s) VALUES (%s) RETURNING %s;"
(entity.Table.FullName.Replace("[","\"").Replace("]","\""))
(String.Join(",",columnNames))
(String.Join(",",values |> Array.map(fun p -> p.ParameterName)))
pk)

values |> Array.iter (cmd.Parameters.Add >> ignore)
cmd.CommandText <- sb.ToString()
cmd

let createUpdateCommand (entity:SqlEntity) changedColumns =
let cmd = (this :> ISqlProvider).CreateCommand(con,"")
cmd.Connection <- con
let pk = pkLookup.[entity.Table.FullName]
sb.Clear() |> ignore

if changedColumns |> List.exists ((=)pk) then failwith "Error - you cannot change the primary key of an entity."

let pkValue =
match entity.GetColumnOption<obj> pk with
| Some v -> v
| None -> failwith "Error - you cannot update an entity that does not have a primary key."

let data =
(([],0),changedColumns)
||> List.fold(fun (out,i) col ->
let name = sprintf "@param%i" i
let p =
match entity.GetColumnOption<obj> col with
| Some v -> (this :> ISqlProvider).CreateCommandParameter(name,v,None)
| None -> (this :> ISqlProvider).CreateCommandParameter(name,DBNull.Value, None)
(col,p)::out,i+1)
|> fun (x,_)-> x
|> List.rev
|> List.toArray


let pkParam = (this :> ISqlProvider).CreateCommandParameter("@pk", pkValue, None)

~~(sprintf "UPDATE %s SET %s WHERE %s = @pk;"
(entity.Table.FullName.Replace("[","\"").Replace("]","\""))
(String.Join(",", data |> Array.map(fun (c,p) -> sprintf "%s = %s" c p.ParameterName ) ))
pk)

data |> Array.map snd |> Array.iter (cmd.Parameters.Add >> ignore)
cmd.Parameters.Add pkParam |> ignore
cmd.CommandText <- sb.ToString()
cmd

let createDeleteCommand (entity:SqlEntity) =
let cmd = (this :> ISqlProvider).CreateCommand(con,"")
cmd.Connection <- con
sb.Clear() |> ignore
let pk = pkLookup.[entity.Table.FullName]
sb.Clear() |> ignore
let pkValue =
match entity.GetColumnOption<obj> pk with
| Some v -> v
| None -> failwith "Error - you cannot delete an entity that does not have a primary key."
let p = (this :> ISqlProvider).CreateCommandParameter("@id",pkValue,None)
cmd.Parameters.Add(p) |> ignore
~~(sprintf "DELETE FROM %s WHERE %s = @id" (entity.Table.FullName.Replace("[","\"").Replace("]","\"")) pk )
cmd.CommandText <- sb.ToString()
cmd

use scope = new Transactions.TransactionScope()
try

if con.State <> ConnectionState.Open then con.Open()
// initially supporting update/create/delete of single entities, no hierarchies yet
entities
|> List.iter(fun e ->
match e._State with
| Created ->
let cmd = createInsertCommand e
Common.QueryEvents.PublishSqlQuery cmd.CommandText
let id = cmd.ExecuteScalar()
match e.GetColumnOption pkLookup.[e.Table.FullName] with
| Some v -> () // if the primary key exists, do nothing
// this is because non-identity columns will have been set
// manually and in that case scope_identity would bring back 0 "" or whatever
| None -> e.SetColumnSilent(pkLookup.[e.Table.FullName], id)
e._State <- Unchanged
| Modified fields ->
let cmd = createUpdateCommand e fields
Common.QueryEvents.PublishSqlQuery cmd.CommandText
cmd.ExecuteNonQuery() |> ignore
e._State <- Unchanged
| Deleted ->
let cmd = createDeleteCommand e
Common.QueryEvents.PublishSqlQuery cmd.CommandText
cmd.ExecuteNonQuery() |> ignore
// remove the pk to prevent this attempting to be used again
e.SetColumnOptionSilent(pkLookup.[e.Table.FullName], None)
| Unchanged -> failwith "Unchanged entity encountered in update list - this should not be possible!")
scope.Complete()
finally
con.Close()

0 comments on commit eb776dc

Please sign in to comment.