A common interface for SQL-based Node.js drivers.
To provide a common interface for MySQL, PostgreSQL and sqlite implementations.
A rewrite of the entire package to expose it as a Functor that can accept
any module which implements the Queryable interface.
Use Belt.Result for responses so to better integrate with then BuckleScript ecosystem.
Provide response decoding and inspection functions so that the user has a consistent view into responses from any library.
Provide an ID type that properly encodes large integers as strings.
Provide batch inserts and queries
The standard things are there and this library is being used live within several production projects.
- Query parameter substitution
- Named parameters
- Promise based interface.
- Connection pooling
- Custom Streams
Inside of a BuckleScript project:
yarn add bs-sql-commonThen add bs-sql-common to your bs-dependencies in your bsconfig.json
{
"bs-dependencies": [ "bs-sql-common" ]
}Then add a bs-sql-common compatible package to your repository or create your
own. All of the examples use the bs-mysql2 package, here are the
requirements to use that package:
yarn add bs-mysql2{
"bs-dependencies": [ "bs-sql-common", "bs-mysql2" ]
}moduleSql=SqlCommon.Make(MySql2)
let db =Sql.Connection.connect
~host="127.0.0.1"~port=3306~user="root"()Sql.query ~db~sql:"SHOW DATABASES" (funres ->
match res with|Belt.Result.Errore -> raise e
|Belt.Result.Okselect ->
select
|.Sql.Response.Select.flatMap (Json.Decode.dict Json.Decode.string)
|.Belt.Array.map (funx -> Js.dict.unsafeGet x "Database")
|.Expect.expect
|>Expect.toContain @@"test"
)Note: All of the examples use the bs-mysql2 package as the
connection provider. Any other provider should have the same behavior with
differing connection creation requirements.
The following connection and module will be use within the rest of the examples.
moduleSql=SqlCommon.Make(MySql2);letdb=Sql.Connection.connect(~host="127.0.0.1", ~port=3306, ~user="root",());Assume the following statement occurs at the end of each example.
Sql.Connection.close(conn);Sql.query(~db, ~sql="SHOW DATABASES",fun|Belt.Result.Errore=>Js.log2("ERROR: ", e)
|Belt.Result.Okselect=>
select
|.Sql.Response.Select.rows
|.Js.log2("RESPONSE ROWS: ", _)
);Sql.mutate(
~db,
~sql="INSERT INTO test (foo) VALUES (?)",
~params=Sql.Params.positional(Json.Encode.([|string("bar")|]|. array)),
(res) =>fun|Belt.Result.Error=>Js.log2("ERROR: ", e)
|Belt.Result.Okmutation=>
mutation
|.Sql.Response.Mutation.insertId
|.Js.log2("INSERT ID: ", _)
);letjson=Sql.Params.named(
Json.Encode.(object_([
("x", int(1)),
("y", int(2)),]))
));letdecoder=Json.Encode.array(Json.Encode.int)
Sql.query(~db, ~sql:"SELECT :x + :y AS z", ~params, (res) =>
switch res {
| Belt.Result.Error => Js.log2("ERROR: ", e)
| Belt.Result.Ok select =>
select
|.Sql.Response.flatMap(decoder)
|.Js.log2("DECODEDROWS: ", _)
}
);Sql.mutate(~db, ~sql:"INSERTINTO test (foo, bar)VALUES(:x, :y)", ~params, (res) =>
switch res {
| Belt.Result.Error => Js.log2("ERROR: ", e)
| Belt.Result.Ok mutation =>
mutation
|.Sql.Response.Mutation.insertId
|.Js.log2("INSERTID: ", _)
}
);letparams=Sql.Params.positional(
Json.Encode.(array(int,[|5,6|]))
));Sql.query(~db, ~sql:"SELECT 1 + ? + ? AS result", ~params, (res) =>
switch res {
| Belt.Result.Error => Js.log2("ERROR: ", e)
| Belt.Result.Ok select =>
select
|.Sql.Response.rows
|.Js.log2("RAWROWS: ", _)
}
);Sql.mutate(~db, ~sql:"INSERTINTO test (foo, bar)VALUES(?, ?)", ~params, (res) =>
switch res {
| Belt.Result.Error => Js.log2("ERROR: ", e)
| Belt.Result.Ok mutation =>
mutation
|.Sql.Response.Mutation.insertId
|.Js.log2("INSERTID: ", _)
}
);letparams=Sql.Params.positional(
Json.Encode.(array(int,[|"%schema"|]))
));Sql.query(~db, ~params, ~sql="SELECT ? AS search")
|>Js.Promise.then_(select =>
select
|.Sql.Response.rows
|.Js.log2("RAW ROWS: ", _)
|.ignore
)
|>Js.Promise.catch(err =>Js.log2("Failure!!!", err)
|.ignore
)moduleId: sigtypet = Driver.Id.tvalfromJson : Js.Json.t -> Driver.Id.tvaltoJson : Driver.Id.t -> Js.Json.tvaltoString : Driver.Id.t -> stringendmoduleResponse: sigmoduleMutation: sigvalinsertId : Driver.Mutation.t -> Id.toptionvalaffectedRows: Driver.Mutation.t -> intendmoduleSelect: sigmoduleMeta : sigvalschema : Driver.Select.Meta.t -> stringvalname : Driver.Select.Meta.t -> stringvaltable : Driver.Select.Meta.t -> stringendvalmeta : Driver.Select.t -> Driver.Select.Meta.tarrayvalconcat : Driver.Select.t -> Driver.Select.t -> Driver.Select.tvalcount : Driver.Select.t -> intvalflatMap :
Driver.Select.t ->
(Js.Json.t -> Driver.Select.Meta.tarray -> 'a) ->
'aarrayvalflatMap : Driver.Select.t -> (Js.Json.t -> 'a) -> 'aarrayvalrows : Driver.Select.t -> Js.Json.tarrayendendmodule type Queryable=sigmoduleConnection : sigtypetvalconnect :
?host:string ->
?port:int ->
?user:string ->
?password:string ->
?database:string ->
unit -> tvalclose : t -> unitendmoduleExn : sigvalfromJs : Js.Json.t -> exnendmoduleId : sigtypetvalfromJson : Js.Json.t -> tvaltoJson : t -> Js.Json.tvaltoString : t -> stringendmoduleMutation : sigtypetvalinsertId : t -> Id.toptionvalaffectedRows : t -> intendmoduleParams : sigtypetvalnamed : Js.Json.t -> tvalpositional : Js.Json.t -> tendmoduleSelect : sigtypetmoduleMeta : sigtypetvalschema : t -> stringvalname : t -> stringvaltable : t -> stringendvalmeta : t -> Meta.tarrayvalconcat : t -> t -> tvalcount : t -> intvalflatMapWithMeta : t -> (Js.Json.t -> Meta.tarray -> 'a) -> 'aarrayvalflatMap : t -> (Js.Json.t -> 'a) -> 'aarrayvalrows : t -> Js.Json.tarrayendtyperesponse =
[
| `Errorofexn
| `MutationofMutation.t
| `SelectofSelect.t
]
typecallback = response -> unitvalexecute : Connection.t -> string -> Params.toption -> callback -> unitend