Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ nupkg
**/.idea/**/tasks.xml
**/.idea/shelf/*
**/.idea/dictionaries
.idea

# Sensitive or high-churn files
**/.idea/**/dataSources/
Expand Down
31 changes: 1 addition & 30 deletions src/SqlHydra/Domain.fs → src/SqlHydra.Domain/Domain.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type TypeMapping =
{
ClrType: string
DbType: DbType
ProviderDbType: string option
ColumnTypeAlias: string
ReaderMethod: string
}
Expand Down Expand Up @@ -74,33 +75,3 @@ type Config =
Filters: Filters
Readers: ReadersConfig option
}

open GlobExpressions

let applyFilters (filters: Filters) (tables: Table list) =
match filters with
| { Includes = []; Excludes = [] } ->
tables
| _ ->
let getPath tbl = $"{tbl.Schema}/{tbl.Name}"
let tablesByPath = tables |> List.map (fun t -> getPath t, t) |> Map.ofList
let paths = tablesByPath |> Map.toList |> List.map fst

let includePatterns = filters.Includes |> List.map Glob
let excludePatterns = filters.Excludes |> List.map Glob

let includedPaths =
includePatterns
|> List.collect (fun pattern -> paths |> List.filter pattern.IsMatch)
|> List.distinct
|> Set.ofList

let excludedPaths =
excludePatterns
|> List.collect (fun pattern -> paths |> List.filter pattern.IsMatch)
|> List.distinct
|> Set.ofList

let filteredPaths = includedPaths - excludedPaths
let filteredTables = filteredPaths |> Seq.map (fun path -> tablesByPath.[path]) |> Seq.toList
filteredTables
10 changes: 10 additions & 0 deletions src/SqlHydra.Domain/ProviderDbTypeAttribute.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module SqlHydra.ProviderDbTypeAttribute

open System

[<AttributeUsage(AttributeTargets.Property
||| AttributeTargets.Field)>]
type ProviderDbTypeAttribute(providerDbTypeName: string) =
inherit Attribute()

member this.ProviderDbTypeName = providerDbTypeName
14 changes: 14 additions & 0 deletions src/SqlHydra.Domain/SqlHydra.Domain.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<WarnOn>3390;$(WarnOn)</WarnOn>
</PropertyGroup>

<ItemGroup>
<Compile Include="Domain.fs" />
<Compile Include="ProviderDbTypeAttribute.fs" />
</ItemGroup>

</Project>
58 changes: 30 additions & 28 deletions src/SqlHydra.Npgsql/NpgsqlDataTypes.fs
Original file line number Diff line number Diff line change
@@ -1,55 +1,57 @@
module SqlHydra.Npgsql.NpgsqlDataTypes

open System.Data
open NpgsqlTypes
open SqlHydra.Domain

let private r : Npgsql.NpgsqlDataReader = null

/// A list of supported column type mappings
let supportedTypeMappings =
[
"boolean", "bool", DbType.Boolean, nameof r.GetBoolean
"smallint", "int16", DbType.Int16, nameof r.GetInt16
"integer", "int", DbType.Int32, nameof r.GetInt32
"bigint", "int64", DbType.Int64, nameof r.GetInt64
"real", "double", DbType.Double, nameof r.GetDouble
"double precision", "double", DbType.Double, nameof r.GetDouble
"numeric", "decimal", DbType.Decimal, nameof r.GetDecimal
"money", "decimal", DbType.Decimal, nameof r.GetDecimal
"text", "string", DbType.String, nameof r.GetString
"character varying", "string", DbType.String, nameof r.GetString
"character", "string", DbType.String, nameof r.GetString
"citext", "string", DbType.String, nameof r.GetString
"json", "string", DbType.String, nameof r.GetString
"jsonb", "string", DbType.String, nameof r.GetString
"xml", "string", DbType.String, nameof r.GetString
"boolean", "bool", DbType.Boolean, None, nameof r.GetBoolean
"smallint", "int16", DbType.Int16, None, nameof r.GetInt16
"integer", "int", DbType.Int32, None, nameof r.GetInt32
"bigint", "int64", DbType.Int64, None, nameof r.GetInt64
"real", "double", DbType.Double, None, nameof r.GetDouble
"double precision", "double", DbType.Double, None, nameof r.GetDouble
"numeric", "decimal", DbType.Decimal, None, nameof r.GetDecimal
"money", "decimal", DbType.Decimal, None, nameof r.GetDecimal
"text", "string", DbType.String, None, nameof r.GetString
"character varying", "string", DbType.String, None, nameof r.GetString
"character", "string", DbType.String, None, nameof r.GetString
"citext", "string", DbType.String, None, nameof r.GetString
"json", "string", DbType.String, Some (nameof NpgsqlDbType.Json), nameof r.GetString
"jsonb", "string", DbType.String, Some (nameof NpgsqlDbType.Jsonb), nameof r.GetString
"xml", "string", DbType.String, None, nameof r.GetString
// skipped unsupported types
"bit(1)", "bool", DbType.Boolean, nameof r.GetBoolean
"bit(1)", "bool", DbType.Boolean, None, nameof r.GetBoolean
// skipped unsupported types
"uuid", "System.Guid", DbType.Guid, nameof r.GetGuid
"uuid", "System.Guid", DbType.Guid, None, nameof r.GetGuid
// skipped unsupported types
"date", "System.DateTime", DbType.DateTime, nameof r.GetDateTime
"interval", "System.TimeSpan", DbType.Time, nameof r.GetTimeSpan
"timestamp without time zone", "System.DateTime", DbType.DateTime, nameof r.GetDateTime
"timestamp with time zone", "System.DateTime", DbType.DateTime, nameof r.GetDateTime
"time without time zone", "System.TimeSpan", DbType.Time, nameof r.GetTimeSpan
"time with time zone", "System.DateTime", DbType.DateTime, nameof r.GetDateTime
"bytea", "byte[]", DbType.Binary, nameof r.GetValue
"date", "System.DateTime", DbType.DateTime, None, nameof r.GetDateTime
"interval", "System.TimeSpan", DbType.Time, None, nameof r.GetTimeSpan
"timestamp without time zone", "System.DateTime", DbType.DateTime, None, nameof r.GetDateTime
"timestamp with time zone", "System.DateTime", DbType.DateTime, None, nameof r.GetDateTime
"time without time zone", "System.TimeSpan", DbType.Time, None, nameof r.GetTimeSpan
"time with time zone", "System.DateTime", DbType.DateTime, None, nameof r.GetDateTime
"bytea", "byte[]", DbType.Binary, None, nameof r.GetValue
// skipped unsupported types
"name", "string", DbType.String, nameof r.GetString
"(internal) char", "char", DbType.String, nameof r.GetChar
"name", "string", DbType.String, None, nameof r.GetString
"(internal) char", "char", DbType.String, None, nameof r.GetChar
// skipped unsupported types
]

let typeMappingsByName =
supportedTypeMappings
|> List.map (fun (columnTypeAlias, clrType, dbType, readerMethod) ->
|> List.map (fun (columnTypeAlias, clrType, dbType, providerDbType, readerMethod) ->
columnTypeAlias,
{
TypeMapping.ColumnTypeAlias = columnTypeAlias
TypeMapping.ClrType = clrType
TypeMapping.DbType = dbType
TypeMapping.ReaderMethod = readerMethod
TypeMapping.ProviderDbType = providerDbType
}
)
|> Map.ofList
Expand All @@ -59,7 +61,7 @@ let tryFindTypeMapping (providerTypeName: string) =

let primitiveTypeReaders =
supportedTypeMappings
|> List.map(fun (_, clrType, _, readerMethod) ->
|> List.map(fun (_, clrType, _, _, readerMethod) ->
{ PrimitiveTypeReader.ClrType = clrType; PrimitiveTypeReader.ReaderMethod = readerMethod }
)
|> List.distinctBy (fun ptr -> ptr.ClrType)
2 changes: 1 addition & 1 deletion src/SqlHydra.Npgsql/NpgsqlSchemaProvider.fs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module SqlHydra.Npgsql.NpgsqlSchemaProvider

open System.Data
open Npgsql
open NpgsqlTypes
open SqlHydra.Domain

let getSchema (cfg: Config) : Schema =
Expand Down
24 changes: 19 additions & 5 deletions src/SqlHydra.Query/Kata.fs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
namespace SqlHydra.Query

open System.Reflection
open SqlHydra.ProviderDbTypeAttribute
open SqlKata
open System.Collections.Generic
open System
Expand All @@ -25,6 +27,12 @@ module FQ =
| Some schema -> $"{schema}.{tbl.Name}"
| None -> tbl.Name

type QueryParameter =
{
Value: obj
ProviderDbType: string option
}

type InsertQuerySpec<'T, 'Identity> =
{
Table: string
Expand Down Expand Up @@ -80,28 +88,34 @@ module private KataUtils =
| null -> box System.DBNull.Value
| o -> o

let getProviderDbTypeName (p: PropertyInfo) =
let attrs = p.GetCustomAttributes(true)
(attrs
|> Seq.choose (function | :? ProviderDbTypeAttribute as attr -> Some attr.ProviderDbTypeName | _ -> None))
|> Seq.tryHead

let fromUpdate (spec: UpdateQuerySpec<'T>) =
let kvps =
match spec.Entity, spec.SetValues with
| Some entity, [] ->
match spec.Fields with
| [] ->
FSharp.Reflection.FSharpType.GetRecordFields(typeof<'T>)
|> Array.map (fun p -> p.Name, p.GetValue(entity))
|> Array.map (fun p -> p.Name, { Value = p.GetValue(entity) |> boxValueOrOption; ProviderDbType = getProviderDbTypeName p } :> obj)

| fields ->
let included = fields |> Set.ofList
FSharp.Reflection.FSharpType.GetRecordFields(typeof<'T>)
|> Array.filter (fun p -> included.Contains(p.Name))
|> Array.map (fun p -> p.Name, p.GetValue(entity))
|> Array.map (fun p -> p.Name, { Value = p.GetValue(entity) |> boxValueOrOption; ProviderDbType = getProviderDbTypeName p } :> obj)

| Some _, _ -> failwith "Cannot have both `entity` and `set` operations in an `update` expression."
| None, [] -> failwith "Either an `entity` or `set` operations must be present in an `update` expression."
| None, setValues -> setValues |> List.toArray

let preparedKvps =
kvps
|> Seq.map (fun (key,value) -> key, boxValueOrOption value)
|> Seq.map (fun (key,value) -> key, value)
|> dict
|> Seq.map id

Expand Down Expand Up @@ -129,7 +143,7 @@ module private KataUtils =
| [ entity ] ->
let keyValuePairs =
includedProperties
|> Array.map (fun p -> KeyValuePair(p.Name, p.GetValue(entity) |> boxValueOrOption))
|> Array.map (fun p -> KeyValuePair(p.Name, { Value = p.GetValue(entity) |> boxValueOrOption; ProviderDbType = getProviderDbTypeName p } :> obj))
|> Array.toList
Query(spec.Table).AsInsert(keyValuePairs, returnId = spec.IdentityField.IsSome)

Expand All @@ -141,7 +155,7 @@ module private KataUtils =
entities
|> List.map (fun entity ->
includedProperties
|> Array.map (fun p -> p.GetValue(entity) |> boxValueOrOption)
|> Array.map (fun p -> { Value = p.GetValue(entity) |> boxValueOrOption; ProviderDbType = getProviderDbTypeName p } :> obj)
|> Array.toSeq
)
Query(spec.Table).AsInsert(columns, rowsValues)
Expand Down
24 changes: 22 additions & 2 deletions src/SqlHydra.Query/QueryContext.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,21 @@ open SqlKata

/// Contains methods that compile and read a query.
type QueryContext(conn: DbConnection, compiler: SqlKata.Compilers.Compiler) =

let setProviderDbType (param: DbParameter) (propertyName: string) (providerDbType: string) =
let property = param.GetType().GetProperty(propertyName)
let dbTypeSetter = property.GetSetMethod()

let value = System.Enum.Parse(property.PropertyType, providerDbType)
dbTypeSetter.Invoke(param, [|value|]) |> ignore

let setParameterDbType (param: DbParameter) (qp: QueryParameter) =
match qp.ProviderDbType, compiler with
| Some type', :? SqlKata.Compilers.PostgresCompiler ->
setProviderDbType param "NpgsqlDbType" type'
| Some type', :? SqlKata.Compilers.SqlServerCompiler ->
setProviderDbType param "SqlDbType" type'
| _ -> ()

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to add a handler here for SQL Server as well:

| Some type', :? SqlKata.Compilers.SqlServerCompiler when type'.TypeName = "SqlDbType" ->
            let property = param.GetType().GetProperty("SqlDbType")
 ...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had TypeName and TypeValue in the attribute before, but I figured you meant to just have a string there so I stored only the enum value of the provider type without the type name (like NpgsqlDbType.Json became just Json). So now I can't really do the check on type'.TypeName = "SqlDbType". What are you thoughts on this? Do we need to store the full type like NpgsqlDbType.Json or SqlDbType.Whatever?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What you have in NpgsqlDataTypes.fs and the attribute looks perfect. I think that it will be safe enough to assume that if they are using a given SqlKata compiler that they should be passing in types with the appropriate attributes.

So this should work:

    let setParameterDbType (param: DbParameter) (qp: QueryParameter) =
      match qp.ProviderDbType, compiler with
      | Some dbType, :? SqlKata.Compilers.PostgresCompiler ->
          let property = param.GetType().GetProperty("NpgsqlDbType")
          let dbTypeSetter = property.GetSetMethod()            
          let value = System.Enum.Parse(property.PropertyType, dbType)
          dbTypeSetter.Invoke(param, [|value|]) |> ignore

      | Some dbType, :? SqlKata.Compilers.SqlServerCompiler ->
          let property = param.GetType().GetProperty("SqlDbType")
          let dbTypeSetter = property.GetSetMethod()            
          let value = System.Enum.Parse(property.PropertyType, dbType)
          dbTypeSetter.Invoke(param, [|value|]) |> ignore
      | _ -> ()

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the code now looks almost as yours, just with a helper function to hide the reflection ugliness 🙂

interface System.IDisposable with
member this.Dispose() =
conn.Dispose()
Expand Down Expand Up @@ -44,7 +58,13 @@ type QueryContext(conn: DbConnection, compiler: SqlKata.Compilers.Compiler) =
for kvp in compiledQuery.NamedBindings do
let p = cmd.CreateParameter()
p.ParameterName <- kvp.Key
p.Value <- kvp.Value

match kvp.Value with
| :? QueryParameter as qp ->
do setParameterDbType p qp
p.Value <- qp.Value
| _ ->
p.Value <- kvp.Value
cmd.Parameters.Add(p) |> ignore
cmd

Expand Down
5 changes: 5 additions & 0 deletions src/SqlHydra.Query/SqlHydra.Query.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<Authors>Jordan Marr</Authors>
<PackageTags>F# fsharp data database orm sql</PackageTags>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
<PackageReleaseNotes>
SqlHydra.Query:
Added support for multiple inserts via the `entities` operation.
Expand All @@ -32,4 +33,8 @@
<PackageReference Include="SqlKata" Version="2.3.7" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\SqlHydra.Domain\SqlHydra.Domain.fsproj" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions src/SqlHydra.SqlServer/SqlHydra.SqlServer.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\SqlHydra.Domain\SqlHydra.Domain.fsproj" />
<ProjectReference Include="..\SqlHydra\SqlHydra.fsproj">
<ReferenceOutputAssembly>true</ReferenceOutputAssembly>
<IncludeAssets>SqlHydra.dll</IncludeAssets>
Expand Down
1 change: 1 addition & 0 deletions src/SqlHydra.SqlServer/SqlServerDataTypes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ let typeMappingsByName =
TypeMapping.ClrType = clrType
TypeMapping.DbType = dbType
TypeMapping.ReaderMethod = readerMethod
TypeMapping.ProviderDbType = None
}
)
|> Map.ofList
Expand Down
2 changes: 1 addition & 1 deletion src/SqlHydra.SqlServer/SqlServerSchemaProvider.fs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ let getSchema (cfg: Config) : Schema =
|> Seq.choose (fun col ->
SqlServerDataTypes.tryFindTypeMapping(col.ProviderTypeName)
|> Option.map (fun typeMapping ->
{
{
Column.Name = col.ColumnName
Column.IsNullable = col.IsNullable
Column.TypeMapping = typeMapping
Expand Down
1 change: 1 addition & 0 deletions src/SqlHydra.Sqlite/SqlHydra.Sqlite.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\SqlHydra.Domain\SqlHydra.Domain.fsproj" />
<ProjectReference Include="..\SqlHydra\SqlHydra.fsproj">
<ReferenceOutputAssembly>true</ReferenceOutputAssembly>
<IncludeAssets>SqlHydra.dll</IncludeAssets>
Expand Down
1 change: 1 addition & 0 deletions src/SqlHydra.Sqlite/SqliteDataTypes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ let typeMappingsByName =
TypeMapping.ClrType = clrType
TypeMapping.DbType = dbType
TypeMapping.ReaderMethod = readerMethod
TypeMapping.ProviderDbType = None
}
)
|> Map.ofList
Expand Down
20 changes: 20 additions & 0 deletions src/SqlHydra.sln
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "SqlHydra.Npgsql", "SqlHydra
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Build", "Build\Build.fsproj", "{1D3ACD32-25B4-40BA-BFE8-6292F3F1A6B4}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SqlHydra.Domain", "SqlHydra.Domain\SqlHydra.Domain.fsproj", "{958CEE47-CC2A-4CA2-BFE3-53653CB39604}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -180,6 +182,24 @@ Global
{1D3ACD32-25B4-40BA-BFE8-6292F3F1A6B4}.Release|x64.Build.0 = Release|Any CPU
{1D3ACD32-25B4-40BA-BFE8-6292F3F1A6B4}.Release|x86.ActiveCfg = Release|Any CPU
{1D3ACD32-25B4-40BA-BFE8-6292F3F1A6B4}.Release|x86.Build.0 = Release|Any CPU
{958CEE47-CC2A-4CA2-BFE3-53653CB39604}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{958CEE47-CC2A-4CA2-BFE3-53653CB39604}.Debug|Any CPU.Build.0 = Debug|Any CPU
{958CEE47-CC2A-4CA2-BFE3-53653CB39604}.Debug|x64.ActiveCfg = Debug|Any CPU
{958CEE47-CC2A-4CA2-BFE3-53653CB39604}.Debug|x64.Build.0 = Debug|Any CPU
{958CEE47-CC2A-4CA2-BFE3-53653CB39604}.Debug|x86.ActiveCfg = Debug|Any CPU
{958CEE47-CC2A-4CA2-BFE3-53653CB39604}.Debug|x86.Build.0 = Debug|Any CPU
{958CEE47-CC2A-4CA2-BFE3-53653CB39604}.DebugLocal|Any CPU.ActiveCfg = Debug|Any CPU
{958CEE47-CC2A-4CA2-BFE3-53653CB39604}.DebugLocal|Any CPU.Build.0 = Debug|Any CPU
{958CEE47-CC2A-4CA2-BFE3-53653CB39604}.DebugLocal|x64.ActiveCfg = Debug|Any CPU
{958CEE47-CC2A-4CA2-BFE3-53653CB39604}.DebugLocal|x64.Build.0 = Debug|Any CPU
{958CEE47-CC2A-4CA2-BFE3-53653CB39604}.DebugLocal|x86.ActiveCfg = Debug|Any CPU
{958CEE47-CC2A-4CA2-BFE3-53653CB39604}.DebugLocal|x86.Build.0 = Debug|Any CPU
{958CEE47-CC2A-4CA2-BFE3-53653CB39604}.Release|Any CPU.ActiveCfg = Release|Any CPU
{958CEE47-CC2A-4CA2-BFE3-53653CB39604}.Release|Any CPU.Build.0 = Release|Any CPU
{958CEE47-CC2A-4CA2-BFE3-53653CB39604}.Release|x64.ActiveCfg = Release|Any CPU
{958CEE47-CC2A-4CA2-BFE3-53653CB39604}.Release|x64.Build.0 = Release|Any CPU
{958CEE47-CC2A-4CA2-BFE3-53653CB39604}.Release|x86.ActiveCfg = Release|Any CPU
{958CEE47-CC2A-4CA2-BFE3-53653CB39604}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Loading