Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a new static parameter ProcResulstSets to allow config the with result sets clause #308

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all 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
39 changes: 33 additions & 6 deletions src/SqlClient/SqlClientProvider.fs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ type SqlProgrammabilityProvider(config : TypeProviderConfig) as this =

let cache = new MemoryCache(name = this.GetType().Name)
let methodsCache = new MemoryCache(name = this.GetType().Name)

let splitKeyVal (s : string) =
match s.Split(':') with
| [|key; value|] -> (key.Trim().ToLower(), value)
|_ -> invalidArg "s" "parameter must be of type key:value"

let mapFromString (s : string) =
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd rename this mapFromString to something more descriptive of the usage of this code ("why/what") rather than "how" it is done, for example parseResultSetsDeclarationList, and then make splitKeyVal local to that function.

Copy link
Author

Choose a reason for hiding this comment

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

I don't know if passing the string is the correct way, I'd prefer to pass a Map to as the static parameter but I couldn't find how. Maybe is a constrain in what type can be passed. I'm not really happy passing the string with the dictionary syntax.

Also I'm not sure if the syntax I use is the best, I was considering also:
key1->value1, key2->value2.... but again, this is not relevant, what is really important is to pass the information to the TP so it knows when to append to the sys.sp_describe_first_result_set command.

If we ended up using this with a string this code maybe even needs to be moved to a better place, this was just a way to keep the change small while proving that something like this might work.

Copy link
Collaborator

Choose a reason for hiding this comment

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

For now AFAIK, the only things that can be passed to a TP as static parameters are literals (string, numbers and enum), we are indeed stuck to string and figuring out a format that makes the most sense.

Workarounds for now:

  • make a sql command as I was describing, basically containing the same text you are running through sys.sp_describe_first_result_set
  • changing the stored procedures involving temp tables to run the code as dynamic SQL and add the with result sets inside

I'm doing the later in most cases but I understand it makes maintenance of those procedures a more annoying.

match s with
| "" -> Map.empty
| x ->
x.Split('|')
|> Array.map splitKeyVal
|> Map.ofArray

do
this.Disposing.Add <| fun _ ->
Expand All @@ -44,10 +57,11 @@ type SqlProgrammabilityProvider(config : TypeProviderConfig) as this =
ProvidedStaticParameter("ConfigFile", typeof<string>, "")
ProvidedStaticParameter("DataDirectory", typeof<string>, "")
ProvidedStaticParameter("UseReturnValue", typeof<bool>, false)
ProvidedStaticParameter("ResultType", typeof<ResultType>, ResultType.Records)
ProvidedStaticParameter("ResultType", typeof<ResultType>, ResultType.Records)
ProvidedStaticParameter("ProcResultSets", typeof<string>, "")
],
instantiationFunction = (fun typeName args ->
let root = lazy this.CreateRootType(typeName, unbox args.[0], unbox args.[1], unbox args.[2], unbox args.[3], unbox args.[4])
let root = lazy this.CreateRootType(typeName, unbox args.[0], unbox args.[1], unbox args.[2], unbox args.[3], unbox args.[4], unbox args.[5] |> mapFromString)
cache.GetOrAdd(typeName, root)
)
)
Expand All @@ -59,6 +73,7 @@ type SqlProgrammabilityProvider(config : TypeProviderConfig) as this =
<param name='DataDirectory'>The name of the data directory that replaces |DataDirectory| in connection strings. The default value is the project or script directory.</param>
<param name='UseReturnValue'>To be documented.</param>
<param name='ResultType'>A value that defines structure of result: Records, Tuples, DataTable, or SqlDataReader, this affects only Stored Procedures.</param>
<param name='ProcResultSets'>String defining a dictionary using the format key1:value1|key2:value2|...</param>
"""

this.AddNamespace(nameSpace, [ providerType ])
Expand All @@ -70,7 +85,7 @@ type SqlProgrammabilityProvider(config : TypeProviderConfig) as this =
|> defaultArg
<| base.ResolveAssembly args

member internal this.CreateRootType( typeName, connectionStringOrName, configFile, dataDirectory, useReturnValue, resultType) =
member internal this.CreateRootType(typeName, connectionStringOrName, configFile, dataDirectory, useReturnValue, resultType, procResultSets) =
if String.IsNullOrWhiteSpace connectionStringOrName then invalidArg "ConnectionStringOrName" "Value is empty!"

let designTimeConnectionString = DesignTimeConnectionString.Parse(connectionStringOrName, config.ResolutionFolder, configFile)
Expand Down Expand Up @@ -124,7 +139,7 @@ type SqlProgrammabilityProvider(config : TypeProviderConfig) as this =

schemaType.AddMembersDelayed <| fun() ->
[
let routines = this.Routines(conn, schemaType.Name, udttsPerSchema, resultType, designTimeConnectionString, useReturnValue, uomPerSchema)
let routines = this.Routines(conn, schemaType.Name, udttsPerSchema, resultType, designTimeConnectionString, useReturnValue, uomPerSchema, procResultSets)
routines |> List.iter tagProvidedType
yield! routines

Expand Down Expand Up @@ -159,11 +174,16 @@ type SqlProgrammabilityProvider(config : TypeProviderConfig) as this =
yield units
]

member internal __.Routines(conn, schema, uddtsPerSchema, resultType, designTimeConnectionString, useReturnValue, unitsOfMeasurePerSchema) =
member internal __.Routines(conn, schema, uddtsPerSchema, resultType, designTimeConnectionString, useReturnValue, unitsOfMeasurePerSchema, procResultSets: Map<string, string>) =
[
use _ = conn.UseLocally()
let isSqlAzure = conn.IsSqlAzure
let routines = conn.GetRoutines( schema, isSqlAzure)

let appendResultSet (commandText: string) =
commandText + " " + defaultArg (procResultSets.TryFind (commandText.ToLower()) ) ""


for routine in routines do

let cmdProvidedType = ProvidedTypeDefinition(snd routine.TwoPartName, Some typeof<``ISqlCommand Implementation``>, HideObjectMethods = true)
Expand All @@ -176,8 +196,15 @@ type SqlProgrammabilityProvider(config : TypeProviderConfig) as this =
use __ = conn.UseLocally()
let parameters = conn.GetParameters( routine, isSqlAzure, useReturnValue)

// there is a typo in function name `ToCommantText` should be Command instead of Commant
let commandText = routine.ToCommantText(parameters)
let outputColumns = DesignTime.GetOutputColumns(conn, commandText, parameters, routine.IsStoredProc)

let commandTextWithResultSets =
if not routine.IsStoredProc
then commandText
else commandText |> appendResultSet

let outputColumns = DesignTime.GetOutputColumns(conn, commandTextWithResultSets, parameters, routine.IsStoredProc)
let rank = if routine.Type = ScalarValuedFunction then ResultRank.ScalarValue else ResultRank.Sequence

let hasOutputParameters = parameters |> List.exists (fun x -> x.Direction.HasFlag( ParameterDirection.Output))
Expand Down