JordanMarr / SqlHydra

SqlHydra is a suite of NuGet packages for working with databases in F# including code generation tools and query expressions.
MIT License
212 stars 20 forks source link

Reading Option<byte[]> is broken #57

Closed jwosty closed 1 year ago

jwosty commented 1 year ago

I have the following table:

CREATE TABLE account
(
    account_pk uuid NOT NULL DEFAULT gen_random_uuid(),
    inserted_on timestamptz NOT NULL DEFAULT current_timestamp,
    updated_on timestamptz,
    email varchar(200) NOT NULL,
    name varchar(200) NOT NULL,
    password_salt bytea,
    password_hash bytea,
    password_hash_parameters json,

    CONSTRAINT account__pk PRIMARY KEY (account_pk),
    CONSTRAINT account__email__nonempty                         CHECK (length(email) > 0),
    CONSTRAINT account__email__unique                           UNIQUE (email),
    -- In other words, these three fields must either all be set or all be null 
    CONSTRAINT account__password_all_or_nothing_set             CHECK (((password_salt IS NULL) = (password_hash IS NULL)) AND ((password_hash IS NULL) = (password_hash_parameters IS NULL)))
);

And I query it like this:

selectAsync () {
    for a in Tables.account do
        where (a.email =% email)
        select (a.account_pk, a.email, a.name, a.password_salt, a.password_hash, a.password_hash_parameters)
        tryHead
}

But for rows where password_salt or password_hash are not null, SqlHydra throws this exception:

System.AggregateException: One or more errors occurred. (Object of type 'Microsoft.FSharp.Core.FSharpOption`1[System.Object]' cannot be converted to type 'Microsoft.FSharp.Core.FSharpOption`1[System.Byte[]]'.)
 ---> System.ArgumentException: Object of type 'Microsoft.FSharp.Core.FSharpOption`1[System.Object]' cannot be converted to type 'Microsoft.FSharp.Core.FSharpOption`1[System.Byte[]]'.
   at System.RuntimeType.CheckValue(Object& value, ParameterCopyBackAction& copyBack, Binder binder, CultureInfo culture, BindingFlags invokeAttr)
   at System.Reflection.MethodBase.CheckArguments(Span`1 copyOfParameters, IntPtr* byrefParameters, Span`1 shouldCopyBack, ReadOnlySpan`1 parameters, RuntimeType[] sigTypes, Binder binder, CultureInfo culture, BindingFlags invokeAttr)
   at System.Reflection.RuntimeConstructorInfo.InvokeWithManyArguments(RuntimeConstructorInfo ci, Int32 argCount, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at <StartupCode$Sessionizer-Data>.$Database.Read@298.Invoke(Unit unitVar0) in /Users/jwostenberg/Code/Sessionizer/src/Sessionizer.Data/Database.fs:line 300
   at <StartupCode$SqlHydra-Query>.$QueryContext.ReadAsyncWithOptions@120-3.Invoke(DbDataReader reader)
   at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvoke[T,TResult](AsyncActivation`1 ctxt, TResult result1, FSharpFunc`2 part2) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 510
   at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in D:\a\_work\1\s\src\FSharp.Core\async.fs:line 112
   --- End of inner exception stack trace ---
   at Fable.Remoting.Server.Proxy.makeEndpointProxy@87-2.MoveNext()
   at Fable.Remoting.Server.Proxy.memberVisitor@143-3.MoveNext()

The problem can be fixed by changing lines in GetPrimitiveReader (in Database.fs) from:

else if t = typedefof<byte []> then Some(wrap reader.GetValue)

to:

else if t = typedefof<byte []> then Some(wrap reader.GetFieldValue<byte[]>)

I'm working around this for now by manually making that change, but a fix in the code generator itself would be great.

I suspect that all array types are affected, not just byte[] in particular.

JordanMarr commented 1 year ago

Thanks for posting the issue and the fix. This should be resolved for all array types now in SqlHydra.Cli v2.0.1. https://github.com/JordanMarr/SqlHydra/releases/tag/v2.0.1

jwosty commented 1 year ago

Sweet, thank you for being so proactive on this. I really like this library. I want to like SqlProvider but it's just way too flaky.

JordanMarr commented 1 year ago

fyi, you don’t need to maintain your own Tables module anymore because tables can now be generated:

https://github.com/JordanMarr/SqlHydra/releases/tag/v1.2.0

jwosty commented 1 year ago

fyi, you don’t need to maintain your own Tables module anymore because tables can now be generated:

https://github.com/JordanMarr/SqlHydra/releases/tag/v1.2.0

Very nice!