Skip to content

Commit 7c2df5c

Browse files
committed
Merge pull request #211 from fsprojects/Issue201
Units of measure in SqlEnum
2 parents 3b76f44 + fb4a05b commit 7c2df5c

File tree

2 files changed

+83
-7
lines changed

2 files changed

+83
-7
lines changed

src/SqlClient.Tests/SqlEnumTests.fs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
open System
44
open Xunit
55

6-
type EnumMapping = SqlEnumProvider<"SELECT * FROM (VALUES(('One'), 1), ('Two', 2)) AS T(Tag, Value)", ConnectionStrings.LocalHost, CLIEnum = true>
6+
type EnumMapping = SqlEnumProvider<"SELECT * FROM (VALUES(('One'), 1), ('Two', 2)) AS T(Tag, Value)", ConnectionStrings.LocalHost, Kind = SqlEnumKind.CLI>
77

88
[<Literal>]
99
let connectionString = ConnectionStrings.LocalHost
@@ -88,3 +88,62 @@ let MoreThan2ColumnReturnsCorrectTuples() =
8888
|],
8989
actual = Array.ofSeq MoreThan2Columns.Items
9090
)
91+
92+
type CurrencyCode =
93+
SqlEnumProvider<"
94+
SELECT CurrencyCode
95+
FROM Sales.Currency
96+
WHERE CurrencyCode IN ('USD', 'EUR', 'GBP')
97+
", ConnectionStrings.AdventureWorksLiteral, Kind = SqlEnumKind.UnitsOfMeasure>
98+
99+
[<Fact>]
100+
let ConvertUsdToGbp() =
101+
let getLatestRate = new SqlCommandProvider<"
102+
SELECT TOP 1 *
103+
FROM Sales.CurrencyRate
104+
WHERE FromCurrencyCode = @fromCurrency
105+
AND ToCurrencyCode = @toCurrency
106+
ORDER BY CurrencyRateDate DESC
107+
", ConnectionStrings.AdventureWorksNamed, SingleRow = true>()
108+
let rate =
109+
getLatestRate.Execute(fromCurrency = "USD", toCurrency = "GBP")
110+
|> Option.map(fun x -> x.AverageRate * 1M<CurrencyCode.GBP/CurrencyCode.USD>)
111+
|> Option.get
112+
113+
let actual = 42M<CurrencyCode.USD> * rate
114+
let expected = 26.5986M<CurrencyCode.GBP>
115+
Assert.Equal( expected, actual)
116+
117+
type ProductsUnitsOfMeasure = SqlEnumProvider<"SELECT UnitMeasureCode FROM Production.UnitMeasure", ConnectionStrings.AdventureWorksLiteral, Kind = SqlEnumKind.UnitsOfMeasure>
118+
type ProductCategory = SqlEnumProvider<"SELECT Name FROM Production.ProductCategory", ConnectionStrings.AdventureWorksLiteral>
119+
120+
type Bikes = {
121+
Id: int
122+
Name: string
123+
Weight: decimal<ProductsUnitsOfMeasure.``LB ``> option
124+
Size: float<ProductsUnitsOfMeasure.``CM ``> option
125+
}
126+
127+
[<Fact>]
128+
let ProductWeightAndSizeUnitsOfMeasure() =
129+
let allBikes = [
130+
use cmd =
131+
new SqlCommandProvider<"
132+
SELECT ProductID, Product.Name, Size, SizeUnitMeasureCode, Weight, WeightUnitMeasureCode
133+
FROM Production.Product
134+
JOIN Production.ProductCategory ON ProductSubcategoryID = ProductCategoryID
135+
WHERE ProductCategory.Name = @category
136+
", ConnectionStrings.AdventureWorksNamed>()
137+
138+
for x in cmd.Execute(ProductCategory.Bikes) do
139+
yield {
140+
Id = x.ProductID
141+
Name = x.Name
142+
Weight = x.Weight |> Option.map(fun weight -> weight * 1M<_>)
143+
Size = x.Size |> Option.map(fun size -> size |> float |> LanguagePrimitives.FloatWithMeasure )
144+
}
145+
]
146+
147+
let bigBikes = allBikes |> List.choose ( fun x -> if x.Size = Some 52.<ProductsUnitsOfMeasure.``CM ``> then Some x.Name else None)
148+
149+
Assert.Equal<_ list>( ["Mountain-500 Silver, 52"; "Mountain-500 Black, 52"], bigBikes)

src/SqlClient/SqlEnumProvider.fs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ open ProviderImplementation.ProvidedTypes
1717

1818
open FSharp.Data.SqlClient
1919

20+
type SqlEnumKind =
21+
| Default = 0
22+
| CLI = 1
23+
| UnitsOfMeasure = 2
24+
2025
[<TypeProvider>]
2126
type public SqlEnumProvider(config : TypeProviderConfig) as this =
2227
inherit TypeProviderForNamespaces()
@@ -49,7 +54,7 @@ type public SqlEnumProvider(config : TypeProviderConfig) as this =
4954
ProvidedStaticParameter("ConnectionStringOrName", typeof<string>)
5055
ProvidedStaticParameter("Provider", typeof<string>, "System.Data.SqlClient")
5156
ProvidedStaticParameter("ConfigFile", typeof<string>, "")
52-
ProvidedStaticParameter("CLIEnum", typeof<bool>, false)
57+
ProvidedStaticParameter("Kind", typeof<SqlEnumKind>, SqlEnumKind.Default)
5358
],
5459
instantiationFunction = (fun typeName args ->
5560
cache.GetOrAdd(typeName, lazy this.CreateRootType(typeName, unbox args.[0], unbox args.[1], unbox args.[2], unbox args.[3], unbox args.[4]))
@@ -62,12 +67,12 @@ type public SqlEnumProvider(config : TypeProviderConfig) as this =
6267
<param name='ConnectionString'>String used to open a data connection.</param>
6368
<param name='Provider'>Invariant name of a ADO.NET provider. Default is "System.Data.SqlClient".</param>
6469
<param name='ConfigFile'>The name of the configuration file that’s used for connection strings at DESIGN-TIME. The default value is app.config or web.config.</param>
65-
<param name='CLIEnum'>Generate standard CLI Enum. Default is false.</param>
70+
<param name='Kind'></param>
6671
"""
6772

6873
this.AddNamespace( nameSpace, [ providerType ])
6974

70-
member internal this.CreateRootType( typeName, query, connectionStringOrName, provider, configFile, cliEnum) =
75+
member internal this.CreateRootType( typeName, query, connectionStringOrName, provider, configFile, kind: SqlEnumKind) =
7176
let tempAssembly = ProvidedAssembly( Path.ChangeExtension(Path.GetTempFileName(), ".dll"))
7277

7378
let providedEnumType = ProvidedTypeDefinition(assembly, nameSpace, typeName, baseType = Some typeof<obj>, HideObjectMethods = true, IsErased = false)
@@ -147,8 +152,8 @@ type public SqlEnumProvider(config : TypeProviderConfig) as this =
147152
|> Seq.groupBy id
148153
|> Seq.iter (fun (key, xs) -> if Seq.length xs > 1 then failwithf "Non-unique label %s." key)
149154

150-
if cliEnum
151-
then
155+
match kind with
156+
| SqlEnumKind.CLI ->
152157

153158
if not( allowedTypesForEnum.Contains( valueType))
154159
then failwithf "Enumerated types can only have one of the following underlying types: %A." [| for t in allowedTypesForEnum -> t.Name |]
@@ -160,7 +165,19 @@ type public SqlEnumProvider(config : TypeProviderConfig) as this =
160165
||> List.map2 (fun name value -> ProvidedLiteralField(name, providedEnumType, fst value))
161166
|> providedEnumType.AddMembers
162167

163-
else
168+
| SqlEnumKind.UnitsOfMeasure ->
169+
170+
for name in names do
171+
let units = ProvidedTypeDefinition( name, None, IsErased = false)
172+
units.AddCustomAttribute {
173+
new CustomAttributeData() with
174+
member __.Constructor = typeof<MeasureAttribute>.GetConstructor [||]
175+
member __.ConstructorArguments = upcast Array.empty
176+
member __.NamedArguments = upcast Array.empty
177+
}
178+
providedEnumType.AddMember units
179+
180+
| _ ->
164181
let valueFields, setFieldValues =
165182
(names, values) ||> List.map2 (fun name value ->
166183
if allowedTypesForLiteral.Contains valueType

0 commit comments

Comments
 (0)