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

Reusable types #10

Open
Risord opened this issue Aug 8, 2017 · 2 comments
Open

Reusable types #10

Risord opened this issue Aug 8, 2017 · 2 comments

Comments

@Risord
Copy link

@Risord Risord commented Aug 8, 2017

Is there way / plans to create types reusable?

If types have somewhat big complexity and there is lot of queries which have different filter / order logic but result data is identical you still have to duplicate mapping.

Minimal example:

type private getItemsQuery = SQL<"""
    select *
    from Item i
""">

type private getActiveItemsQuery = SQL<"""
    select *
    from Item i
    where i.IsActive = true
""">

//How to make this available for both queries?
let mapItem (dbRow : ???.Row) =
    (dbRow.ItemId, dbRow.DataField)

let getItems () =
    use context = new ConnectionContext()
    getItemsQuery.Command().Execute(context)
    result |> Seq.map mapItem

let getActiveItems () =
    use context = new ConnectionContext()
    getActiveItemsQuery.Command().Execute(context)
    result |> Seq.map mapItem
@rspeele

This comment has been minimized.

Copy link
Owner

@rspeele rspeele commented Aug 8, 2017

Unfortunately there's no way to have them share the same generated type.

What I'd like to do to support this in the future is let you specify paths to assemblies in rzsql.json.
These would be called "row interface assemblies" or something like that, and the type provider would load up the interfaces from those assemblies and automatically add interface implementations for all the ones matched by a provided row type.

So in this case you might have something like this in MyRowInterfaceAssembly.dll, which should be built before MyAssemblyWithRezoomSQLQueries.dll:

type IItem =
    abstract ItemId : int
    abstract DataField : string

Then both getItemsQuery.Row and getActiveItemsQuery.Row could automatically implement that interface because they have the necessary columns, and you can write mapItem to take an IItem instead of a concrete row type.

I've held off on doing this because ultimately, I want it to be smart enough to do stuff like automatically map Id : int to Id : ItemId where ItemId is a wrapper type around an int. But, I suppose it wouldn't hurt to introduce the feature in a bare-bones state where it'll only auto-implement the interface when column types match exactly.

Current workarounds

Right now there are a couple things you can do to make this less painful.

Inline functions

Write a helper to convert to your domain type with F# inline functions. This is especially useful if you have some wrapper types in your domain model that RZSQL doesn't know about anyway, like type UserId = UserId of int (highly recommended to avoid mixing up identifiers).

type ItemId = ItemId of int
type Item =
    {    Id : ItemId
         Name : string
    }
let inline itemFromRow x =
    {   Id = ItemId (^a : (member get_Id : unit -> int)(x))
        Name = (^a : (member get_Name : unit -> string)(x))
    }

Then you can use itemFromRow on the different row types returned from your different queries.
The downside, of course, is that those inline constrained property invocations are ugly to read and write.
You can mitigate it somewhat by moving them into their own functions which can be reused for common column names like Id, Name, etc., but it's still not great.

Configurable queries

Where possible, use fewer queries but make them configurable with parameters. E.g. for the minimal example, you could use:

select *
from Item i
where @activeFilter is null or i.IsActive = @activeFilter

And pass None for activeFilter to implement getItems, Some true to implement getActiveItems.

@Risord

This comment has been minimized.

Copy link
Author

@Risord Risord commented Aug 13, 2017

Making identifiers to it's own types is quite clearly "the right way" and it's great that it's on your concern list.

Auto generated interface implementations sounds good way to work with this. Although this makes me hope that F# should have better support for general purpose compile time programming.

I think it should be also considered that should interfaces must be explicitly expressed like:
type itemQuery = SQL<"""[query]""", MyPrecompiledModule.IItem>
It may help with implementation and functionality would be much less magical. Also if interface is not actually valid I think it would be easier to produce much better error messages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.