Code Monkey home page Code Monkey logo

sqlfun's Introduction

SqlFun

Idiomatic data access for F#

SqlFun is a tool for writing data access code in F# functional way. It's fast, type safe and gives you all powers of SQL, no custom query language constraints you. It's also lightweight, you need to know a general idea and few functions (and, of course SQL).

It's available as a Nuget package There are also extensions for MS SQL ([1], [2]), PostgreSQL and Oracle databases.

Features

  • Works with any ADO.NET provider
  • All SQL features available
  • Type safety
  • High performance
  • Compound, hierarchical query parameters
  • Compound, hierarchical query results
  • Support for parameter conversions
  • Support for result transformations
  • Support for enum types
  • Asynchronous queries
  • Composable, template-based queries
  • Auto-generated CRUD operations
  • Computation expressions for connection and transaction handling
  • Support for large dataset processing

Supported databases

In its core SqlFun does not use any features specific to some db provider, so it works with any ADO.NET provider. The only limitation is possibility of execution of commands in SchemaOnly mode.

It was tested against MS SqlServer, PostgreSQL, Oracle, MySQL and SQLite.

There are four extensions, enabling provider-specific features:

  • the extension for MS SQL, that allows to use table valued parameters
  • the extension for PostgreSQL, making use of array parameters possible and adding more comfortable Bulk Copy mechanism
  • the extension for Oracle, adding some adaptations, like binding parameters by name, and allowing to use array parameters
  • the extension for SQLite, that allows to use date and time values

Limitations

Not all databases manage SchemaOnly behavior properly:

  • MySQL, PostgreSQL and Oracle performs well only for queries that return some results - commands not returning any are executed as with Default behavior
  • MS SQL doesn't recognize temporary tables in SchemaOnly executions, although, you can use table variables instead

How it works

Most of us think about data access code as a separate layer. We don't like to spread SQL queries across all the application. Better way is to build an API exposing your database, consisting of structures representing database data, and functions responsible for processing this data (great object-oriented example is Insight.Database automatic interface implementation). SqlFun makes it a design requirement.

Installation

SqlFun can be added to your solution from Package Manager Console:

PM> Install-Package SqlFun

Configuration

First step is to define function creating database connection and config record:

let createConnection () = new SqlConnection(<your database connection string>)
let generatorConfig = createDefaultConfig createConnection

and wire it up with functions responsible for generating queries (using partial application):

let sql commandText = sql generatorConfig commandText

let proc name = proc generatorConfig name

and for executing them:

let run f = DbAction.run createConnection f

let runAsync f = AsyncDb.run createConnection f

Data structures

Then, data structures should be defined for results of your queries.

type Post = {
    id: int
    blogId: int
    name: string
    title: string
    content: string
    author: string
    createdAt: DateTime
    modifiedAt: DateTime option
    modifiedBy: string option
    status: PostStatus
}
    
type Blog = {
    id: int
    name: string
    title: string
    description: string
    owner: string
    createdAt: DateTime
    modifiedAt: DateTime option
    modifiedBy: string option
    posts: Post list
}

The most preferrable way is to use F# record types. Record fields should reflect query result columns, because they are mapped by name.

Defining queries

The best way of defining queries is to create variables for them and place in some module:

module Blogging =    
 
    let getBlog: int -> DbAction<Blog> = 
        sql "select id, name, title, description, owner, createdAt, modifiedAt, modifiedBy 
             from Blog 
             where id = @id"
            
    let getPosts: int -> DbAction<Post list> = 
        sql "select id, blogId, name, title, content, author, createdAt, modifiedAt, modifiedBy, status 
             from post 
             where blogId = @blogId"

The functions executing queries are generated during a first access to the module contents.

At that stage, all the type checking is performed, so it's easy to make type checking part of automatic testing - one line of code for each module is needed.

The generating process uses reflection heavily, but no reflection is used while processing a query, since generated code is executed.

Executing queries

Since your queries return DbAction<'t>, they can be passed to the run function after applying preceding parameters.

let blog = Blogging.getBlog 1 |> run

Async support

The preferrable way is to define query as asynchronous:

let getBlog: int -> AsyncDb<Blog> = 
    sql "select id, name, title, description, owner, createdAt, modifiedAt, modifiedBy 
         from Blog 
         where id = @id"

and then, execute as async:

async {
    let! blog = Blogging.getBlog 1 |> runAsync
    ...
}

Result transformations

Since the ADO.NET allows to execute many sql commands at once, it's possible to utilize it with SqlFun. The result is a tuple:

let getBlogWithPosts: int -> AsyncDb<Blog * Post list> = 
    sql "select id, name, title, description, owner, createdAt, modifiedAt, modifiedBy 
         from Blog 
         where id = @id;
         select id, blogId, name, title, content, author, createdAt, modifiedAt, modifiedBy, status 
         from post 
         where blogId = @id"

The call of sql returns some function, thus it can be composed with another function, possibly performing result transformations. Let extend the blog type with a posts: Post list property. In this case, two results can be combined with simple function:

let getBlogWithPosts: int -> AsyncDb<Blog> = 
   sql "select id, name, title, description, owner, createdAt, modifiedAt, modifiedBy 
        from Blog 
        where id = @id;
        select id, blogId, name, title, content, author, createdAt, modifiedAt, modifiedBy, status 
        from post 
        where blogId = @id"
   >> AsyncDb.map (fun b pl -> { b with posts = pl })

In simple cases, when code follows conventions, transormations can be specified more declarative way:

let getBlogWithPosts: int -> AsyncDb<Blog> = 
    sql "select id, name, title, description, owner, createdAt, modifiedAt, modifiedBy 
         from Blog 
         where id = @id;
         select id, blogId, name, title, content, author, createdAt, modifiedAt, modifiedBy, status 
         from post 
         where blogId = @id"
    >> AsyncDb.map combine<_, Post>

There are also functions that allow to combine multi-row results by joining many results or grouping wide results.

Compound parameters

Records can be parameters as well:

let insertPost: Post -> AsyncDb<int> = 
    sql "insert into post 
                (blogId, name, title, content, author, createdAt, status)
         values (@blogId, @name, @title, @content, @author, @createdAt, @status);
         select scope_identity()"

The record fields are mapped to query parameters by name.

Stored procedures

The result of a function calling stored procedure should be a three-element tuple (return code, output params, result):

let findPosts: (PostSearchCriteria * SignatureSearchCriteria) -> AsyncDb<int * unit * Post list> =
    proc "FindPosts"

but there are transformers, that allow to ignore parts of it:

let findPosts: (PostSearchCriteria * SignatureSearchCriteria) -> Post list AsyncDb =
    proc "FindPosts"
    >> AsyncDb.map resultOnly

Utilizing dbaction and asyncdb computation expressions

It's easy to execute one query with runAsync or run function. To execute more queries in a context of one open connection, computation expression can be used:

asyncdb {
    let! postId = Blogging.insertPost post
    do! Blogging.insertComments postId comments
    do! Blogging.insertTags postId tags
} |> runAsync

The synchronous equivalent of this expression is dbaction.

Transactions

To execute some queries in transaction, the inTransaction function should be used:

asyncdb {
    let! postId = Blogging.insertPost post
    do! Blogging.insertComments postId comments
    do! Blogging.insertTags postId tags
} 
|> AsyncDb.inTransaction
|> runAsync

Its synchronous equivalent is DbAction.inTransaction.

Documentation & examples

For more comprehensive documentation refer project github pages.

For more examples refer test project.

sqlfun's People

Contributors

dependabot[bot] avatar jacentino avatar sabinewren avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

sqlfun's Issues

AsyncDb helper functions

Last week I ripped out the last Dapper query in my company's codebase, so we're now entirely using SqlFun. Some helper functions seem to be missing, so I built my own. Either SqlFun should include these functions, or I'm doing something wrong.

SqlFun/SqlFun/DbBuilder.fs module AsyncDb defines a useful 'map' function, but not much else.

let map (f: 't1 -> 't2) (v: AsyncDb<'t1>): AsyncDb<'t2> =
   fun ctx -> async {
      let! x = v ctx
      return f x
   }

The SqlFun documentation performs many transforms on multi-result queries, but doesn't remove the Ids used for the joins when denormalizing.

let getBlogWithPosts: int -> Blog AsyncDb = 
    sql "select id, name, title, description, owner, 
                createdAt, modifiedAt, modifiedBy 
         from Blog 
         where id = @id;

         select id, blogId, name, title, content, author, 
                createdAt, modifiedAt, modifiedBy, status 
         from post 
         where blogId = @id"
    >> AsyncDb.map (fun b p -> { b with posts = p })

This makes sense for some high performance queries, although I remove the Ids when denormalizing to reduce payload size.

type Cell = { Year: int16; Amount: float }
type Row = { Id: Guid; Cells: Cell[] }
type CellDb =
  { RowId: Guid; Year: int16; Amount: float } with
  static member FromDb(c: CellDb): Cell =
     { Year=c.Year; Amount=c.Amount }

let GetRows: Guid -> AsyncDb<Row seq> =
  let key1 (r: Row) = r.Id
  let key2 (c: CellDb) = c.RowId
  let combine: Row -> CellDb seq -> Row = fun r cs ->
     { r with Cells=cs |> Array.ofSeq |> Array.map CellDb.FromDb }
  Db.Sql "
  Select rows From TableA Where Id=@Id;
  Select cells From TableB Where RowId=@Id;"
  >> Db.Map (Db.Join key1 key2 combine)

For querying records with more complicated nested fields, I find it easier to use nested queries:

let private getA: CardId -> AsyncDb<TypeA[]> = sql ""
let private getB: CardId -> AsyncDb<TypeB[]> = sql ""
let private cardGet: UserId -> AsyncDb<CardDb[]> = sql ""

let private withFields(card: CardDb) = asyncdb {
  let! a = getA card.Id
  let! b = getB card.Id
  return { card with A=a; B=b }
}
let CardGet(userId: UserId): AsyncDb<Card[]> =
  cardGet userId
  |> Db.BindSequential withFields
  |> Db.MapArray Card.FromDb

These functions don't exist in the AsyncDb module, so I'm using an in-house helper module called 'Db':

let Bind<'t1,'t2>(f: 't1 -> AsyncDb<'t2>)(x: AsyncDb<'t1>): AsyncDb<'t2> =
   fun ctx -> async {
      let! v = x ctx
      return! (f v) ctx
   }

let MapArray<'t1,'t2>(f: 't1 -> 't2)(x: AsyncDb<'t1[]>): AsyncDb<'t2[]> =
   fun ctx -> async {
      let! vs = x ctx
      return vs |> (Array.map f)
   }

// WARNING -- this requires a SEQUENCE, not an ARRAY
let Sequential<'t1,'t2>(f: 't1 -> AsyncDb<'t2>)(items: seq<'t1>): AsyncDb<'t2[]> =
   fun ctx ->
      items
      |> Seq.map (fun x -> (f x) ctx)
      |> Async.Sequential

let BindSequential<'t1,'t2>(f: 't1 -> AsyncDb<'t2>) = Bind (Sequential f)

Is this a gap in SqlFun? If so, are you interested in contributions?

SQLite: Foreign key constraint failed on generation

type UserSession = {
  Session: string
  UserId: int
  ExpiresAt: DateTime
}

let saveUserSession: UserSession -> AsyncDb<unit> =
  sql "INSERT INTO UserSessions (Session, UserId, ExpiresAt) VALUES (@Session, @UserId, @ExpiresAt);"
Unhandled exception. System.TypeInitializationException: The type initializer for '<StartupCode$IssueTracker>.$Login' threw an exception.
 ---> SqlFun.Exceptions.CompileTimeException: Error generating function UserSession -> IDataContext -> unit Async for sql command INSERT INTO UserSessions (Session, UserId, ExpiresAt) VALUES (@Session, @UserId, @ExpiresAt);
 ---> Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 19: 'FOREIGN KEY constraint failed'.
   at Microsoft.Data.Sqlite.SqliteException.ThrowExceptionForRC(Int32 rc, sqlite3 db)
   at Microsoft.Data.Sqlite.SqliteDataReader.NextResult()
   at Microsoft.Data.Sqlite.SqliteCommand.ExecuteReader(CommandBehavior behavior)
   at Microsoft.Data.Sqlite.SqliteCommand.ExecuteDbDataReader(CommandBehavior behavior)
   at SqlFun.Queries.makeDiagnosticCall@347(FSharpFunc`2 createConnection, FSharpFunc`2 createCommand, String commandText, FSharpList`1 paramDefs)
   at SqlFun.Queries.genExecutor@372[a](FSharpFunc`2 createConnection, FSharpFunc`2 createCommand, FSharpOption`1 commandTimeout, FSharpFunc`2 rowBuilder, Boolean makeDiagnosticCalls, String commandText, a createCommand@372, FSharpList`1 paramDefs, Type returnType)
   at SqlFun.Queries.generateSqlCommandCaller(FSharpFunc`2 createConnection, FSharpFunc`2 createCommand, FSharpOption`1 commandTimeout, FSharpFunc`2 extractParameterNames, FSharpFunc`2 paramBuilder, FSharpFunc`2 rowBuilder, Boolean makeDiagnosticCalls, String commandText, Type t)
   --- End of inner exception stack trace ---
   at SqlFun.Queries.generateSqlCommandCaller(FSharpFunc`2 createConnection, FSharpFunc`2 createCommand, FSharpOption`1 commandTimeout, FSharpFunc`2 extractParameterNames, FSharpFunc`2 paramBuilder, FSharpFunc`2 rowBuilder, Boolean makeDiagnosticCalls, String commandText, Type t)
   at SqlFun.Queries.sql[t](GeneratorConfig config, String commandText)
   at Sql.sql[a](String commandText) in /home/christian/Programming/issue-tracker/src/IssueTracker/Sql.fs:line 32
   at <StartupCode$IssueTracker>.$Login..cctor() in /home/christian/Programming/issue-tracker/src/IssueTracker/Authentication/Login.fs:line 44
   --- End of inner exception stack trace ---
   at Login.get_handler()
   at <StartupCode$IssueTracker>.$Program.main@() in /home/christian/Programming/issue-tracker/src/IssueTracker/Program.fs:line 23

Question: sample that controls the SqlConnection

Hi,

I noticed that in all your samples that I've seen (see: https://jacentino.github.io/SqlFun/Configuration) you create a function let createConnection () = new SqlConnection(connectionString) and everything flows from here. But what if I want to update multiple databases? Do I have to add the set of functions createConnection/generatorConfig/sql/proc for each database in separate modules? It kind of defeats the purpose of functional programming which promotes re-usability. But more likely I am missing something.

Do you have a sample where I have a SqlConnection object and I pass it around to execute different statements?

It tried this:

let cn = new SqlConnection(connectionString)
cn.Open()

let getDataList (cn: SqlConnection) : DbAction<DataList array> =
  let config = createDefaultConfig (fun () -> cn) 
  Queries.sql config "select dl.ListCode, dl.ListName from dbo.DataList dl order by 1"


let data2 = getDataList cn |> DbAction.run (fun () -> cn)
data2 |> Array.length |> printfn "%d" 

but I am getting some weird error:

 ---> System.InvalidOperationException: The connection was not closed. The connection's current state is open.
   at Microsoft.Data.ProviderBase.DbConnectionInternal.TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
   at Microsoft.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry, SqlConnectionOverrides overrides)
   at Microsoft.Data.SqlClient.SqlConnection.Open(SqlConnectionOverrides overrides)
   at Microsoft.Data.SqlClient.SqlConnection.Open()
   at SqlFun.Queries.getResultMetadata@354(FSharpFunc`2 createConnection, FSharpFunc`2 createCommand, String commandText, FSharpList`1 paramDefs)
   at SqlFun.Queries.genExecutor@368[a](FSharpFunc`2 createConnection, FSharpFunc`2 createCommand, FSharpOption`1 commandTimeout, FSharpFunc`2 rowBuilder, Boolean makeDiagnosticCalls, String commandText, a createCommand@368, FSharpList`1 paramDefs, Type returnType)
   at SqlFun.Queries.generateSqlCommandCaller(FSharpFunc`2 createConnection, FSharpFunc`2 createCommand, FSharpOption`1 commandTimeout, FSharpFunc`2 extractParameterNames, FSharpFunc`2 paramBuilder, FSharpFunc`2 rowBuilder, Boolean makeDiagnosticCalls, String commandText, Type t)

I have to admit, I don't think I understand how SqlFun works. My expectation was to define queries and then run them anyway I want it against a connection object. But it looks like the code tries to figure out at runtime metadata information about the query (structure of the result, parameters) and that fails.

After tweaking the code, this works:

let cn = new SqlConnection(designTimeConnectionString)


let getDataList (cn: SqlConnection) : DbAction<DataList array> =
  let config = createDefaultConfig (fun () -> new SqlConnection(connectionString)) 
  Queries.sql config "select dl.ListCode, dl.ListName from dbo.DataList dl order by 1"


let data2 = getDataList cn |> DbAction.run (fun () -> cn)

It looks like the connection for the GeneratorConfig has to be independent of the connection that is used to run the queries. I guess it makes sense.

Thanks

Temporary Tables

Hi, I started using SqlFun yesterday. It works great so far for Select and Upsert queries.

Question

Is it possible to use temporary tables with SqlFun? It's throwing an exception, but my query works in Dapper and Azure Data Studio.

ClassName: "System.Data.SqlClient.SqlException",
Message: "Invalid object name '#tempRows'

More Detail

My existing codebase uses Dapper + M$ SQL Server, and includes queries that clone tables from one 'scenario' to another, often using this form:

Create Table #tempRows(
   RowIdNew UniqueIdentifier Not Null,
   RowIdOld UniqueIdentifier Not Null,
);

Insert Into #tempRows(RowIdNew, RowIdOld)
Select NewId() as RowIdNew, RowId as RowIdOld
From Rows
Where ScenarioId=@ScidToClone;

Insert Into Rows (ScenarioId, RowId)
Select @ScidNew, RowIdNew
From #tempRows;

-- Cells copy would go here

Drop Table #tempRows

Nuget package

Hi,

Are you planning to publish your library on nuget.org?

Dependency Injection Strategies

I have been toying around with SqlFun and have some questions regarding its design choices. Considering the README, this is how you are supposed to write code on it:

module Database =
    // ...
    let createConnection () = new SqliteConnection("Data Source=:memory:")
    let config = createDefaultConfig createConnection
    let runAsync f = AsyncDb.run createConnection f

module Repository =
    open Database
    // ...

    module Account =
        let create: Account -> AsyncDb<Account.Username> =
            sql config "INSERT INTO account (username, password) VALUES (@username, @password) RETURNING username;"

In this case, you can see that the module Repository depends on the variable config from the module Database. In this sense, if I were to configure my string connection, I would have something like this:

module Configuration =
    // imagine some private logic happening here
    let sqlConnection =
        sprintfn "Data Source=%s" config.Database.Host

module Database =
   open Configuration
   // ...
   let createConnection () = new SqliteConnection(sqlConnection)

I couldn't figure it out a way to use this library without depending on module state. Is there another way to manage the database connection?

Thanks for this awesome library, by the way!

A question about property mapping

Often times I use postgresql where its idiomatic to use snake_case for column names (PascalCase requires escaping column, tables, and types with quotations) . Is there any way to configure a mapper for snake_case -> PascalCase when reading query results into record types?

sqlite: don't matching query columns as expected

I was testing some ways to build queries from some table adding some additional field on query time.
Given the follow simple schema:

CREATE TABLE Main (
    pk INTEGER PRIMARY KEY AUTOINCREMENT 
    ,data TEXT  
);

INSERT INTO Main(data) VALUES ('Initial Data'), ('Last Data');

I'd expected to be able query like that:

type Main = {Tag: string; Data : string}

type Repo(db: Db) =
    member val query: int -> AsyncDb<Main list> =
        db.sql """
            SELECT
                'Some Tag' as tag
                ,m.data
            FROM Main as m
            WHERE m.pk = @id
        """

But for some reason, i get stuck with the error below.

SqlFun.Exceptions.CompileTimeException: Error generating function Int32 -> IDataContext -> Main list Async for sql command 
            SELECT
                'Some Tag' as tag
                ,m.data
            FROM Main as m
            WHERE m.pk = @id
        
 ---> System.Exception: No column found for Tag field. Expected: Tag
 ---> System.InvalidOperationException: No coercion operator is defined between types 'System.Byte[]' and 'System.String'.
   at System.Linq.Expressions.Expression.GetUserDefinedCoercionOrThrow(ExpressionType coercionType, Expression expression, Type convertToType)
   at System.Linq.Expressions.Expression.Convert(Expression expression, Type type, MethodInfo method)
   at SqlFun.ResultBuilder.buildColumnAccessor[a](ParameterExpression reader, Type colType, a ordinal, Type targetType) in C:\Projekty\FSharp\SqlFun\SqlFun\ResultBuilder.fs:line 267
   at SqlFun.ResultBuilder.getRowBuilderExpression[a](FSharpFunc`2 nextRB, a reader, FSharpMap`2 metadata, String prefix, String fieldName, Type returnType) in C:\Projekty\FSharp\SqlFun\SqlFun\ResultBuilder.fs:line 344

But i realize that when the WHERE clause is changed to WHERE m.pk <> @id that work as expected for some reason. ๐Ÿ˜•
Maybe the coercion made in compile time is getting some unexpected parser error?

Postgresql: The given key was not present in the dictionary.

Unhandled exception. SqlFun.Exceptions.CompileTimeException: Error generating function FeedQuery -> IDataContext -> Post list Async for sql command select * from feeds where name = @FeedName
 ---> System.Exception: No column found for FeedName field. Expected: FeedName
 ---> System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
   at Microsoft.FSharp.Collections.MapTreeModule.throwKeyNotFound[a]() in D:\a\_work\1\s\src\FSharp.Core\map.fs:line 187
   at Microsoft.FSharp.Collections.MapTreeModule.find[TKey,TValue](IComparer`1 comparer, TKey k, MapTree`2 m) in D:\a\_work\1\s\src\FSharp.Core\map.fs:line 196
   at SqlFun.ResultBuilder.getRowBuilderExpression[a](FSharpFunc`2 nextRB, a reader, FSharpMap`2 metadata, String prefix, String fieldName, Type returnType)
   --- End of inner exception stack trace ---
   at SqlFun.ResultBuilder.getRowBuilderExpression[a](FSharpFunc`2 nextRB, a reader, FSharpMap`2 metadata, String prefix, String fieldName, Type returnType)
   at [email protected](Type returnType)
   at SqlFun.ResultBuilder.getRowBuilderExpression[a](FSharpFunc`2 nextRB, a reader, FSharpMap`2 metadata, String prefix, String fieldName, Type returnType)
   at [email protected](Type returnType)
   at SqlFun.ResultBuilder.buildRow(FSharpFunc`2 rowBuilder, FSharpMap`2 metadata, Type returnType)
   at [email protected](FSharpMap`2 metadata, Type returnType)
   at SqlFun.ResultBuilder.buildOneResultSet$cont@417(Boolean isAsync, FSharpMap`2 collectionResultBuilders, FSharpMap`2 metadata, Type returnType, FSharpFunc`2 buildRow, Unit unitVar)
   at SqlFun.ResultBuilder.buildOneResultSet@414(FSharpFunc`2 rowBuilder, Boolean isAsync, FSharpMap`2 collectionResultBuilders, FSharpMap`2 metadata, Type returnType)
   at SqlFun.ResultBuilder.generateResultBuilder(FSharpFunc`2 rowBuilder, FSharpList`1 metadata, Type returnType, Boolean isAsync)
   at SqlFun.Queries.genExecutor@372[a](FSharpFunc`2 createConnection, FSharpFunc`2 createCommand, FSharpOption`1 commandTimeout, FSharpFunc`2 rowBuilder, Boolean makeDiagnosticCalls, String commandText, a createCommand@372, FSharpList`1 paramDefs, Type returnType)
   at SqlFun.Queries.generateSqlCommandCaller(FSharpFunc`2 createConnection, FSharpFunc`2 createCommand, FSharpOption`1 commandTimeout, FSharpFunc`2 extractParameterNames, FSharpFunc`2 paramBuilder, FSharpFunc`2 rowBuilder, Boolean makeDiagnosticCalls, String commandText, Type t)
   --- End of inner exception stack trace ---
   at SqlFun.Queries.generateSqlCommandCaller(FSharpFunc`2 createConnection, FSharpFunc`2 createCommand, FSharpOption`1 commandTimeout, FSharpFunc`2 extractParameterNames, FSharpFunc`2 paramBuilder, FSharpFunc`2 rowBuilder, Boolean makeDiagnosticCalls, String commandText, Type t)
   at SqlFun.Queries.sql[t](GeneratorConfig config, String commandText)
   at Program.sql[a](String commandText) in /home/christian/Programming/YoutubeVideos/RssReader/src/RssReader/Program.fs:line 29
   at <StartupCode$RssReader>.$Program.main@() in /home/christian/Programming/YoutubeVideos/RssReader/src/RssReader/Program.fs:line 77
type FeedQuery = { FeedName: string }

let queryPostsByFeed: FeedQuery -> AsyncDb<Post list> =
  sql "select * from feeds where name = @FeedName"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.