GithubHelp home page GithubHelp logo

ppx_rapper's Introduction

Build

ppx_rapper

An extension that allows named parameters in SQL with types inferred, and syntax checking of SQL as a preprocessing step. Like ppx_mysql but using Caqti. The name comes from the idea of Dapper but with Records.

The syntax checking feature only works for PostgreSQL, but other features should work with other Caqti backends such as MariaDB and SQLite. If you are using a non-Postgres dialect you should use the syntax_off option to avoid spurious errors.

Installation

You can install ppx_rapper with opam:

$ opam install ppx_rapper ppx_rapper_lwt

(or ppx_rapper_async if you are using async instead).

To use in a project built with dune, add these lines to the relevant stanzas:

(libraries ppx_rapper_lwt)
(preprocess (pps ppx_rapper))

or similar for async.

Example usage

let my_query =
  [%rapper
    get_opt
      {sql|
      SELECT @int{id}, @string{username}, @bool{following}, @string?{bio}
      FROM users
      WHERE username <> %string{wrong_user} AND id > %int{min_id}
      |sql}]

turns into

let my_query =
  let query =
    (let open Caqti_request in
    find_opt)
      ((let open Caqti_type in
       tup2 string int) [@ocaml.warning "-33"])
      ((let open Caqti_type in
       tup2 int (tup2 string (tup2 bool (option string))))
      [@ocaml.warning "-33"])
      "\n\
      \      SELECT id, username, following, bio\n\
      \      FROM users\n\
      \      WHERE username <> ? AND id > ?\n\
      \      "
  in
  let wrapped ~wrong_user ~min_id (module Db : Rapper_helper.CONNECTION) =
    let f result =
      let g (id, (username, (following, bio))) =
        (id, username, following, bio)
      in
      let f =
        (fun f x -> match x with Some x -> Some (f x) | None -> None) g
      in
      match result with Ok x -> Ok (f x) | Error e -> Error e
    in
    Rapper_helper.map f (Db.find_opt query (wrong_user, min_id))
  in
  wrapped

For further examples, see the examples directory.

Query functions

Query functions are

  • execute for queries that return 0 rows, represented as ()
  • get_one for queries that return 1 rows, represented as a tuple/record
  • get_opt for queries that return 0 or 1 rows, represented as a tuple/record option
  • get_many for queries that may return any number of rows, represented as a list of tuples/records

These correspond to exec, find, find_opt and collect in Caqti_request.

Since 1-tuples don't exist, single values are used instead for that case.

Parameters

Syntax for input/output parameters is the same as ppx_mysql: %type{name} for inputs and @type{name} for outputs. The set of currently supported base types overlaps with Caqti's: int,int32,int64, string, octets, float, bool, pdate, ptime and ptime_span are supported, in addition to cdate and ctime, provided by caqti-type-calendar. Option types can be specified by appending a ? to the type specification, e.g.int?{id}.

Custom types

In the style of ppx_mysql, ppx_rapper also provides (limited) support for custom types via user-provided encoding and decoding functions. Consider the following example, adapted from the mysql_ppx section for the same feature:

module Suit : Rapper.CUSTOM = struct
  type t = Clubs | Diamonds | Hearts | Spades

  let t =
    let encode = function
      | Clubs -> Ok "c"
      | Diamonds -> Ok "d"
      | Hearts -> Ok "h"
      | Spades -> Ok "s"
    in
    let decode = function
      | "c" -> Ok Clubs
      | "d" -> Ok Diamonds
      | "h" -> Ok Hearts
      | "s" -> Ok Spades
      | _   -> Error "invalid suit"
    in
    Caqti_type.(custom ~encode ~decode string)
end

let get_cards =
  [%rapper get_many
   {sql| SELECT @int{id}, @Suit{suit} FROM cards WHERE suit <> %Suit{suit} |sql}]

The syntax extension will recognize type specifications that start with an uppercase letter -- Suit in our example -- and assume they refer to a module (available in the scope where the extension is evaluated) that implements the Rapper.CUSTOM signature, as listed below:

module type CUSTOM = sig
  type t

  val t : t Caqti_type.t
end

Note: custom type support in this syntax extension is fairly limited and not meant to be used for e.g. composite types in the output. If you intend to get the return values for your query in a record, there's support for that with the record_out option (described below).

List support for input parameters

ppx_rapper has limited support for queries that take a list of values as input, through the special %list{} construct. An example is shown below:

let users =
  [%rapper
    get_opt
      {sql|
      SELECT @int{id}, @string{username}, @bool{following}, @string?{bio}
      FROM users
      WHERE following = %bool{following} and username IN (%list{%int{ids}})
      |sql}]

Current limitations for list include:

  • Only one list input parameter is supported at this time;
  • Generated Caqti queries are dynamically generated, and thus oneshot as per the documentation. Turning this off is not currently supported, but please let us know if you have a use case for it.

Extension options

If record_in or record_out are given as options like so:

let my_query =
  [%rapper
    get_opt
      {sql|
      SELECT @int{id}, @string{username}, @bool{following}, @string?{bio}
      FROM users
      WHERE username <> %string{wrong_user} AND id > %int{min_id}
      |sql}
      record_in record_out]

then the input and/or output of the query will be records. For the example above, they would have type {id: int; wrong_user: string} and {id: int; username: string; following: bool; bio: string option} respectively. The default non-record methods are labelled arguments and tuples respectively.

Instead of record_out you can give function_out, in which case the first argument to the generated function should be a function with labelled arguments of the types of the output parameters, like so:

let show_user_names =
  [%rapper
    get_many {sql| SELECT @int{id}, @string{name} FROM users |sql} function_out]
    (fun ~name ~id -> Printf.sprintf "User %d is called %s" id name)

By default, queries are syntax checked using pg_query-ocaml and the extension will error if syntax checking fails. If you are using a non-Postgres SQL dialect or this gives a false positive error for a query it can be suppressed using the syntax_off option.

Multiple outputs

With the record_out or function_out option, an output parameter @type{param_name} will usually map to a record field name or labelled argument param_name. However, different behaviour occurs if there are output parameters containing dots. In this case, multiple outputs will be produced. For example:

let get_user_hat =
  [%rapper
    get_one
      {sql|
      SELECT @int{users.user_id}, @string{users.name},
             @int{hats.hat_id}, @string{hats.colour}
      FROM users
      JOIN hats ON hats.hat_id = users.hat_id
      WHERE users.id = 7
      |sql}
      record_out]

will produce output with type { user_id: int; name: string} * { hat_id: int; colour: string}. Similarly, with function_out the generated function will take a tuple of loading functions. Ordering of elements of these tuples is given by the order of their first output parameters in the query.

Note that multiple outputs that share field names (for instance @int{users.id} and @int{hats.id} in the same query) will not work with record_out, but will work fine with function_out.

Loading data with one-to-many relationships

The multiple outputs feature can be used with the runtime function Rapper.load_many to conveniently load entities with one-to-many relationships, as in the following example:

module Twoot = struct
  type t = { id: int; content: string; likes: int }

  let make ~id ~content ~likes = { id; content; likes }
end

module User = struct
  type t = { id: int; name: string; twoots: Twoot.t list }

  let make ~id ~name = { id; name; twoots = [] }
end

let get_multiple_function_out () dbh =
  let open Lwt_result.Infix in
  [%rapper
    get_many
      {sql|
      SELECT @int{users.id}, @string{users.name},
             @int{twoots.id}, @string{twoots.content}, @int{twoots.likes}
      FROM users
      JOIN twoots ON twoots.user_id = users.id
      ORDER BY users.id
      |sql}
      function_out]
    (User.make, Twoot.make) () dbh
  >|= Rapper.load_many
        (fst, fun { User.id; _ } -> id)
        [ (snd, fun user twoots -> { user with twoots }) ]

Here, the query itself produces a list of tuples, where the first element is a user and the second element is one of that user's "twoots". The query is sorted by user id, so all twoots belonging to one user are adjacent. Using Rapper.load_many produces a list of the unique users with the twoots field filled correctly.

Contributions

Contributions are very welcome!

ppx_rapper's People

Contributors

anmonteiro avatar apeschar avatar bikallem avatar darioteixeira avatar kit-ty-kate avatar leonidas-from-xiv avatar rizo avatar roddyyaga 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  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  avatar  avatar  avatar

ppx_rapper's Issues

Align function_out semantics with ppx_deriving's make plugin when fields are optional

First, thank you so much for this ppx, it makes working with postgresql in OCaml extremely pleasant ๐Ÿค—

I am regularly running into cases where I need to emit multiple output types, but cannot use the record_out option because of same-named columns and fields among the records involved. Conveniently, the function_out option pairs perfectly with ppx_deriving's make plugin, but unfortunately only when no optional fields / arguments are involved. When a type does have an optional field, the ppx generates a make function like so:

val make: id:int -> name:string -> ?created:Ptime.t -> unit -> 'a

...while ppx_rapper continues to expect a function like:

id:int -> name:string -> created:Ptime.t option -> 'a

ppx_deriving's approach makes a lot of sense for typical usage (a mandatory labeled option argument does seem sort of odd), so I can't fault it much.

Are you interested in squaring this circle? Of course one option is to simply change (break) how function_out works; alternatively, another _out option could be added to accommodate ppx_deriving's make convention.

multiple lists in WHERE IN

Currently doing a WHERE with multiple lists is not supported - it fails with error:

The query contains multiple lists. Multiple lists are not supported

Just curious - what is the limitation that you can't use multiple lists?
Are there any alternatives ?
Trying to run something similar to:

    WHERE
    some_id IN (%list{%int{my_ids}})
    OR another_id IN (%list{%int{other_ids}})

Is a manually generated query the only way?

Deprecation alert with Caqti

Alert deprecated: Caqti_request.exec
Replaced by the Infix module.
File "lib/storage.ml", lines 2-11, characters 2-15:
 2 | ..[%rapper
 3 |     execute
 4 |       {sql|CREATE TABLE IF NOT EXISTS logs (
 5 |             id uuid PRIMARY KEY NOT NULL,
 6 |             created_at timestamp NOT NULL,
 7 |             message varchar NOT NULL,
 8 |             level varchar NOT NULL,
 9 |             origin varchar NOT NULL,
10 |             stack varchar NOT NULL
11 |         )|sql}]

Which infix operator replaced execute and how would I use it with rapper?

escaping of ocaml keywords in record_out

Is there a way to create a query that relies on a column name identical to an ocaml keyword, such as type? I tried both of the typical conventions of type_ and _type, but they are passed through to the query. Or is there perhaps a way to alias a field when using record_out, having the struct's field differ from the table's?

Need Lower Bound on the Caqti Dependencies

The Caqti_request.Infix module was introduced in version 1.7.0, so I think that must be the lower bond for all sub-packages. We can probably sort out the published opams in ocaml/opam-repository#22070. It's only failing for the lwt package, but I presume because that's where the runtime-level testing happens.

schema creation and semicolon

Hello,
How do I create a schema using ppx_rapper with the semicolon restriction, here's my schema creation code, which happen to not work.
I'm using the sqlite3 backend on ubuntu 20.04 and OCaml 4.13.1:

Thanks!

let init_db =
    [%rapper execute {sql|
        DROP TABLE IF EXISTS client

        CREATE TABLE IF NOT EXISTS client(
                    host TEXT NON NULL,
                    useragent TEXT
                )

        DROP TABLE IF EXISTS request

        CREATE TABLE IF NOT EXISTS request(
                    since_begin REAL,
                    request TEXT NON NULL,
                    retcode INTEGER,
                    size INTEGER,
                    referrer TEXT
                )

        CREATE INDEX i0 ON request(request)

        CREATE INDEX i1 ON request(referrer)

        DROP TABLE IF EXISTS visit

        CREATE TABLE IF NOT EXISTS visit(
                    begin REAL,
                    id_client INTEGER,
                    spam BOOLEAN,
                    resources INTEGER,
                    pages INTEGER
                )

        CREATE INDEX i2 ON visit(id_client)

        DROP TABLE IF EXISTS req_visit

        CREATE TABLE IF NOT EXISTS req_visit(
                    id_visit INTEGER,
                    id_request INTEGER
                )

        CREATE INDEX i3 ON req_visit(id_visit)

        CREATE INDEX i4 ON req_visit(id_request)
    |sql} syntax_off]

How to represent SELECT EXISTS and the like

hey @roddyyaga
I tried to write some simple Postgres queries, but can't figure out how I'm supposed to represent these:

  type regclass_response = { to_regclass : string option }
  let get_something = [%rapper get_opt {sql| SELECT to_regclass(my_table_name) |sql} record_out]
|| Fatal error: exception Error in ppx_rapper: 'record_out' should not be set when there are no output parameters

So I guess I'm supposed to somehow include @string?{to_regclass} in the query, but in practice the SELECT doesn't include such slot like SELECT @string?{to_regclass} FROM table

It's the same with:

SELECT EXISTS (
  SELECT 1
  FROM pg_tables
  WHERE schemaname = 'schema_name' AND tablename = 'table_name'
);

what should the record_out for SELECT EXISTS be and how it should be represented?

MariaDB/SQLite support

Although the README states that ppx_rapper is intended for use with PostgreSQL, it actually seems to work fine with MariaDB. Queries that are not also valid PostgreSQL syntax (mainly INSERT ... SET ...) need syntax_off, but that seems to be it.

Perhaps that should be documented? I'm willing to submit a PR for that, I just wanted to know if you have any thoughts on this, or whether I'm missing something that would make this library not (yet) useful with MariaDB.

Async

Hi,

Thanks for the nice library. Are you open to make this ppx/library compatible with async library in addition to lwt? It probably needs to be split into two packages, ppx_rapper-lwt and ppx_rapper.async ?

insert multiple values

I'm trying to insert multiple values in a table

Inserting a single value works:

type my_thing = { a: int; b: string }
let insert_my_thing = [%rapper execute {sql|
  INSERT INTO my_things (a, b)
  VALUES (%int{a}, %string{b});
|sql} record_in]

(*
my_thing ->
(module Caqti_lwt.CONNECTION) ->
(unit, [> Caqti_error.call_or_retrieve ]) result Lwt.t
*)

but I can't get how to use the list syntax to insert my_thing list:

type my_thing = { a: int; b: string }
let insert_my_thing = [%rapper execute {sql|
  INSERT INTO my_things (a, b)
  VALUES %list{(%int{a}, %string{b})};
|sql} record_in]
Fatal error: exception "Assert_failure ppx/ppx_rapper.ml:51:6"

The 3.1.0 package has changed in place

It looks like the archive of version 3.1.0 on github has changed in place and is now a snapshot of 03fd3d4 whereas until a few days ago I'm guessing it was a snapshot of 66c198a.

The consequence of this is that installing ppx_rapper with opam no longer works as the package hash no longer matches what opam expects. I have tried experimentally updating the hash expected by opam in an opam-monorepo project depending on ppx_rapper and code that once compiled no longer compiles:

File "duniverse/ppx_rapper/lib-runtime/rapper.mli", line 29, characters 18-36:
29 |   module Stream : Caqti_stream_sig.S with type 'a future := 'a t
                       ^^^^^^^^^^^^^^^^^^
Error: Unbound module Caqti_stream_sig

My guess is this was caused by force pushing the tag 3.1.0 to a different revision. If so I would recommend forcing pushing it back to its original revision and letting github rebuild the archive for that release.

fails mapping nullable type to optional column

When using a custom decoder module with a nullable column such as @Settings?{settings} and querying the rows with get_many, I see the decoder getting called with an empty string instead of a null. I'm using the postgres driver against cockroachdb. Not sure what layer this could be going wrong at yet - the postgres driver providing a string instead of null or caqti parsing it wrong, so I wanted to verify that this the use case of custom types and optionals is supported by the ppx first. I see the following error when running a select statement:

Failed handling request Caqti_type.Reject("Line 1, bytes -1-0:\nUnexpected end of input")

CREATE TABLE %string{table_name} results in an error

When creating a table, this works:

    [%rapper execute {sql| CREATE TABLE my_table ( ... ) |sql}]

but this:

    let table_name = "my_table"
    [%rapper execute {sql| CREATE TABLE %string{table_name} ( ... ) |sql}] ~table_name

results in

Fatal error: exception Error in ppx_rapper: Syntax error in SQL: 'syntax error at or near "?"'

is this expected or my syntax is wrong?

Example with multiple joins.

It would be nice to have an example of how to use Rapper.load_many with multiple joins. For example:

SELECT @string{topic.name}, @string{topic.slug},
       @string{post.id}, @string{post.title}, @string{post.content},
       @string{usr.name}, @int{usr.reputation}
FROM board
JOIN post ON post.topic = topic.id
JOIN usr ON usr.id = post.creator
WHERE topic.id = @string{topic.id};

dune utop fails

I'm using ppx_rapper in a project that builds and tests successfully, but I cannot run utop in it. dune utop test (for example) results in:

File "_none_", line 1:
Error: Module `Pg_query' is unavailable (required by `Ppx_rapper')

Pg_query is provided by the pg_query package, which is available: ppx_wrapper depends on it directly, and regular builds would fail otherwise.

I've created a repo that minimally reproduces the failure; see the README for an invocation to create a fresh switch, build, and try to run a toplevel:

https://github.com/cemerick/rapper_utop

Maybe I'm doing something wrong, but I've been stumped so far. ๐Ÿค”

Removing Pg_query dependency ?

I had some trouble compiling pg_query on Windows (some include files unavailable and more).

I think that it could be interesting to replace the Pg_query.parse by a pure Ocaml parser (menhir/ocamllex). I could grab a Postgress grammar easily (from the antlr repository), then it could be quite straightforward.

What do you think of this project?

Embed errors in the AST instead of raising

Currently, when ppx_rapper encounters an error, it uses the raise_errorf function to raise a located error.

The exception is caught by ppxlib, which in this case:

  • Catch the error,
  • stops the rewriting process
  • add the error (as a [%%%ocaml.error ...] extension node) to the last valid ast
  • Use the resulting AST

The interruption of the rewriting is quite bad for the user experience! The implication for the users are:

  • Since ppx_rapper runs at the "context-free" phase, the "last valid AST" is before the context-free phase. So, no other derivers/extenders get run, which generates a lot of noise in the errors (such as "uninterpreted extensions" or "unbound identifiers")
  • Only one (meaningful) error from your PPX is reported at a time.
Example

For instance:

let invalid1 = [%rapper invalid {sql| |sql}]

let invalid2 = [%rapper invalid {sql| |sql}]

let valid = [%rapper get_opt {sql| |sql}]

would report several errors:

  • Error in ppx_rapper: Supported actions are [...] for invalid1 (the right error)
  • 'Uninterpreted extension 'rapper'forinvalid1, invalid2andvalid`.

The right error for invalid2 is not shown, and the "uninterpreted extension" errors add a lot of noise!

You can find more information about error reporting in PPXs in this section of the ppxlib manual.

โ“ Would you be willing to accept contributions to this issue? I'm considering assigning its resolution as part of an outreachy internship: see more information here.

INSERT has more expressions than target columns using list of strings

Hello,

I'm running into issues with the ppx generating an extra input param when using the nested list of strings. I'm not sure if this is a bug or I'm misinterpreting the docs?

Given this query:

  type t = {
    chords : string list;
  }
  [@@deriving make,show]


  let query = [%rapper get_one {sql| 
    insert into videos (
      chords
    )
    VALUES (
      %list{%string{chords}}
    )
    returning @string{id}
  |sql} record_in]

and when I run it with the input {chords = ["one"; "two"]} (with Postgres) I get the error:

REQ 1 Database error: Request to <postgresql://localhost/dev> failed: 
ERROR:  INSERT has more expressions than target columns
LINE 6:       $1, $2
                  ^
 Query: " 
    insert into videos (
      chords
    )
    VALUES (
      $1, $2
    )
    returning id
  ".

opam lock:

  "caqti" {= "1.9.0"}
  "caqti-driver-postgresql" {= "1.9.1"}
  "caqti-lwt" {= "1.9.0"}
  "ppx_rapper" {= "3.1.0"}
  "ppx_rapper_lwt" {= "3.1.0"}
  "pg_query" {= "0.9.7"}
  "postgresql" {= "5.0.0"}  

SQL Syntax Error when input variable used as array length

The following code:

let create_countries =
  let country_alias_length = 3 in
  [%rapper
    execute
      {sql|
      CREATE TABLE countries(
        id int PRIMARY KEY,
        alias char( %int{country_alias_length} ),
        name char(20),
        time_created timestamp,
        UNIQUE(alias)
      )
      |sql}
  ]

Produces the following error message:

Error in ppx_rapper: Syntax error in SQL: 'syntax error at or near "?"'

Support a list of tuples as input parameters.

I'd like to be able to write a query like this:

WITH foo(a, b) AS (values (1::int, 'a'::text), (2, 'b') ) SELECT * FROM foo;

that could translate to:

 [%rapper
    get_many
      {sql|
      WITH foo(a, b) AS (values %list{%tup2{int, string}}) ...
      |sql}]

The tup2, tup3, ... is not supported at this time though and although you can create a custom type with a tuple they generate only one parameter, so as an input type it doesn't work.

(as discussed on discord)

Don't allow trailing semicolon in SQL statement

I'm not sure about other drivers but at least with SQLite3 driver, it raises error if a statement ended with a semicolon.
I think ppx_rapper should at least catch this because I ran into this issues so many times

Using %list with function_out

I have 2 tables, with an id column and want to join them and return the results:

  let a =
    [%rapper
      get_many
        {sql|
           SELECT
             @int{a.id},
             @string{a.name},
             @int{b.id},
             @string{b.name}
           FROM public.table1 as a
           INNER JOIN public.table2 as b
           ON a.b_id = b.id
           WHERE
             a.id IN (%list{%string{ids}})
       |sql}]

this works, but it results in a tuple, which is not great (int * string * int * string) list

record_out doesn't work, because id is the same in both tables:

Variable id is bound several times in this matching

function_out doesn't work, because I get

Unbound value loaders

Not sure where this is coming from?
I noticed that not using %list compiles the ppx (WHERE a.id = %string{id}) (but doesn't work for my use case)

Is it possible to use %list and function_out somehow?

insert and return id

hey @roddyyaga , I can't understand how to simply insert a record and get the resulting id

module User = struct
  type t = { name: string; address: string }
end
    [%rapper execute {sql|
        INSERT INTO public.users(name, address)
        VALUES (
          %string{name},
          %string{address}
        )
        RETURNING @int{id}
     |sql} record_in]

but it seems that the resulting type is incorrect - I get Type unit is not compatible with type int

also is there a way to omit the types after % and @ when using record, since those are already defined in the records themselves?

Support all request processing functions

Hi, thanks for the work!
The ppx only allows bindings to exec, find, find_opt and collect_list. Are there plans to also support fold, fold_s and iter_s (and rev_collect_list for the sake of completeness), or were they omitted on purpose?

Proposed breaking change to API - generated queries to have DB connection as last parameter rather than first

Heads up for any ppx_rapper users: I'm planning on reordering the signature of generated queries by moving the connection parameter.

let update =
  [%rapper
    execute
      {sql|
      UPDATE things
      SET value = %string{value}
      WHERE id = %int{id}
      |sql}
      record_in]

will go from having type

(module Caqti_lwt.CONNECTION) ->
{id: int; value: string} -> (unit, [> Caqti_error.call_or_retrieve ]) result Lwt.t

to

{id: int; value: string} -> (module Caqti_lwt.CONNECTION) -> (unit, [> Caqti_error.call_or_retrieve ]) result Lwt.t

This is to make using queries with connection pools nicer. With a function execute defined as

let execute query = Caqti_lwt.Pool.use query pool

you will be able to do

let calls_update { id; value } =
  execute @@ update { id; key; value; account_id }

rather than

let calls_update { id; value } =
  execute @@ fun conn -> update conn { id; key; value; account_id }

Please let me know if this would cause any problems for you (other than having to update affected code).

more examples of connection creation, please!

Hello! My first encounter with Lwt is by using ppx_rapper_lwt (even first encounter with a monadic pattern to conform to) and I lack something which will probably looks trivial to you: a connection (db handler) creation and use example! a non pooled one, because I'm using sqlite3. I know, I know, shame on me, I'm an eternal beginner with ocaml.

Also, is the Db handler always a whole module or is there a lighter alternative?

Thank U!

Unbound module Rapper_helper

I am trying to use a rapper to create sql queries but I keep getting that error. Do I need to open something? I am not used to preprocessors so I don't really know what I need to have in scope.

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.