├── .github └── workflows │ ├── gh-pages.yml │ └── main.yml ├── .gitignore ├── .ocp-indent ├── CHANGES.md ├── LICENSE ├── Makefile ├── README.md ├── default.nix ├── docker-compose.yml ├── docs ├── index.html └── style.css ├── dune-project ├── example.sh ├── examples ├── bench_merge_sort.ml ├── bench_merge_sort.mli ├── dune ├── examples.ml ├── subscribe_lwt.ml └── subscribe_sync.ml ├── nix ├── default.nix ├── ocamlDefaultVersion.nix ├── opam-selection_4_11.nix ├── opam-selection_4_12.nix ├── opam-selection_4_13.nix └── opam2nix.nix ├── redis-lwt.opam ├── redis-sync.opam ├── redis.opam ├── shell.nix ├── src ├── .gitignore ├── cache.ml ├── cache.mli ├── client.ml ├── client.mli ├── crc16.ml ├── crc16.mli ├── dune ├── mutex.ml ├── mutex.mli ├── pool.ml ├── pool.mli ├── s.ml ├── utils.ml └── utils.mli ├── src_lwt ├── dune ├── redis_lwt.ml └── redis_lwt.mli ├── src_sync ├── dune ├── redis_sync.ml └── redis_sync.mli └── test ├── docker └── with_acl │ ├── Dockerfile │ ├── acl.conf │ └── redis.conf ├── dune ├── reg_78.ml ├── test.ml ├── test_lwt.ml └── test_sync.ml /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master # Set a branch name to trigger deployment 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - uses: ocaml/setup-ocaml@v3 15 | with: 16 | ocaml-compiler: 4.14.x 17 | 18 | - name: Pin 19 | run: opam pin -n . 20 | 21 | - name: Depext 22 | run: opam depext -yt redis redis-sync redis-lwt 23 | 24 | - name: Deps 25 | run: opam install -d . --deps-only 26 | 27 | - name: Build 28 | run: opam exec -- dune build @doc 29 | 30 | - name: Deploy 31 | uses: peaceiris/actions-gh-pages@v3 32 | with: 33 | github_token: ${{ secrets.GITHUB_TOKEN }} 34 | publish_dir: ./_build/default/_doc/_html/ 35 | destination_dir: . 36 | enable_jekyll: true 37 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build redis 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | jobs: 8 | run: 9 | name: Build 10 | strategy: 11 | matrix: 12 | os: 13 | - ubuntu-latest 14 | #- macos-latest 15 | #- windows-latest 16 | ocaml-compiler: 17 | - 4.03.x 18 | - 4.14.x 19 | - 5.03.x 20 | runs-on: ${{ matrix.os }} 21 | steps: 22 | - uses: actions/checkout@v2 23 | - uses: ocaml/setup-ocaml@v3 24 | with: 25 | ocaml-compiler: ${{ matrix.ocaml-compiler }} 26 | - run: opam pin -n . 27 | - run: opam install -t . --deps-only 28 | - run: opam exec -- dune build 29 | - name: Install docker compose 30 | run: sudo apt install docker-compose 31 | if: ${{ matrix.os == 'ubuntu-latest'}} 32 | - name: Start containers 33 | run: docker-compose up -d 34 | if: ${{ matrix.os == 'ubuntu-latest'}} 35 | - run: OCAML_REDIS_TEST_SOCKET=$PWD/socket/redis.sock opam exec -- dune runtest 36 | if: ${{ matrix.os == 'ubuntu-latest'}} 37 | - name: Stop containers 38 | run: docker-compose down 39 | if: ${{ matrix.os == 'ubuntu-latest'}} 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /_build/ 2 | /setup.data 3 | /setup.log 4 | /*.byte 5 | /*.native 6 | /*.docdir 7 | /.gh-pages 8 | .merlin 9 | *.install 10 | *.exe 11 | /result* 12 | /socket/redis.sock 13 | -------------------------------------------------------------------------------- /.ocp-indent: -------------------------------------------------------------------------------- 1 | base=2 2 | with=0 3 | type=2 4 | max_indent=4 5 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.8 4 | 5 | - printer for connection spec 6 | - add `send_pipelined_custom_requests` 7 | - add more docs about pubsub 8 | 9 | ## 0.7.1 10 | 11 | - fix: remove debug message in pool 12 | 13 | ## 0.7 14 | 15 | 16 | - add ability to connect to redis via unix sockets (Albert Peschar) 17 | - add a connection pool implementation 18 | - bugfix: fix out of order arguments caused by excessive concurrency 19 | in redis-lwt (Khady) 20 | 21 | ## 0.6 22 | 23 | - feat: exposed `send_custom_request` in the client 24 | - add lmove and blmove 25 | - Add support for OCaml 5.0 26 | - Add bzpopmin, bzpopmax 27 | - Add zpopmin, zpopmax 28 | 29 | ## 0.5 30 | 31 | - add xadd, xlen, xdel, xrange, xrevrange, xtrim, xread 32 | - add `connection_spec` constructor with default port 33 | - require ocaml 4.03 at least 34 | - Add support for authenticating with ACL (thanks Steven Anderson) 35 | 36 | maintenance: 37 | - move to ounit2 for tests 38 | - add github actions for CI and doc generation, remove travis 39 | 40 | ## 0.4 41 | 42 | - feat: also have `>|=` in IO; in Cache, atomic set+expire 43 | - fix(api): export type of cache 44 | - add printer for `reply` 45 | - migrate opam files to 2.0 46 | - migration to dune 47 | - fix: redis-lwt: pass fd type 48 | 49 | - add some benchmarks and integration tests 50 | - update doc generation, fix errors for odoc 51 | - reindent, style, etc. 52 | 53 | ## 0.3.7 54 | 55 | * Add EXPIRE to mass insert command set (thanks Malthe Borch) 56 | * Various fixes (thanks Kate and Varun Kohli) 57 | 58 | ## 0.3.6 59 | 60 | * Fix to work with safe-string flag/compilers (thanks Jacques-Pascal Deplaix) 61 | 62 | ## 0.3.5 63 | 64 | ## 0.3.4 65 | 66 | * Port to jbuilder (thanks Rudi Grinberg) 67 | * *Drop support of ocaml-4.01* (due to port to jbuilder) and run CI against ocaml-4.04 68 | * Force read_reply to be atomic (thanks Jams Long) 69 | * Pass hints to getaddrinfo (thanks Doğan Çeçen) 70 | 71 | ## 0.3.3 72 | 73 | * Fix fd leak for `Redis_lwt.Client.with_connection`, thanks @domsj 74 | * Fix socket leak on connection failure, thanks @ahrefs 75 | 76 | ## 0.3.2 77 | 78 | Fixed EX/PX bug with SET (see issue #34, thanks @briancaine). 79 | 80 | Introduce string and float bound type. 81 | 82 | Following commands were added or fixed (due to bound type introduction): 83 | 84 | * ZRANGEBYSCORE 85 | * ZRANGEBYLEX 86 | * ZREVRANGEBYSCORE 87 | * ZREVRANGEBYLEX 88 | * ZREMRANGEBYLEX 89 | * ZREMRANGEBYSCORE 90 | * ZREMRANGEBYRANK 91 | * ZCARD 92 | * ZCOUNT 93 | * ZLEXCOUNT 94 | * ZRANK 95 | * ZREVRANK 96 | 97 | ## 0.3.1 98 | 99 | Expose stream type for both lwt and sync backends (see issue #32, thanks @acs1) 100 | 101 | ## 0.3.0 102 | 103 | Now package contains 3 modules: `Redis`, `Redis_lwt` and `Redis_sync`. 104 | 105 | * `Redis` - `Client`/`Cache`/`Mutex` modules type signatures 106 | * `Redis_sync` - synchronous implementation of client library 107 | * `Redis_lwt` - Lwt-based implementation of client library 108 | 109 | Commands implementations: 110 | 111 | * A few improvements to the sorted set operations, thanks @domsj 112 | * Add PFADD/PFCOUNT/PFMERGE 113 | * Add HSCAN/HSTRLEN/HINCRBYFLOAT commands 114 | * Add MIGRATE command 115 | * Add PSETEX and OBJECT command 116 | * Add PUNSUBSCRIBE/PSUBSCRIBE commands, thanks @j0sh. 117 | * Add MSET/MSETNX/MGET commands 118 | * Add ZSCORE, thanks @ipfix 119 | * Fail explicitly when PING command was failed 120 | 121 | Testing changes: 122 | 123 | * Rework all test cases due IO module usage, thanks @rgrinberg 124 | * Fix test exit code to return non-zero code on failure 125 | 126 | Infrastructure changes: 127 | 128 | * String.create -> Bytes.create to silence warning on recent OCaml versions 129 | * Require OCaml version to be >= 4.01.0, thanks @hcarty 130 | * Replace `Lwt_chan` use with `Lwt_io` 131 | * Properly resolve string hostnames, e.g. localhost, google.com etc.., thanks @toots 132 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Mr.Number, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of the Mr. Number, Inc nor the names of its 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 21 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 24 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DOCKER_COMPOSE ?= docker-compose 2 | 3 | export OCAML_REDIS_TEST_SOCKET=$(CURDIR)/socket/redis.sock 4 | 5 | all: build test 6 | 7 | build: 8 | @dune build @all 9 | 10 | test: 11 | @$(DOCKER_COMPOSE) up -d 12 | @(dune runtest --force --no-buffer; EXIT_CODE="$$?"; $(DOCKER_COMPOSE) down; exit $$EXIT_CODE) 13 | 14 | clean: 15 | @dune clean 16 | 17 | watch: 18 | @dune build @all -w 19 | 20 | reindent: 21 | @for dir in src examples tests/ ; do \ 22 | find $(dir) -name '*.ml*' -exec ocp-indent -i {} \; ; \ 23 | done 24 | 25 | .PHONY: all build test clean watch 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OCaml-redis [![Build](https://github.com/0xffea/ocaml-redis/workflows/Build%20redis/badge.svg)](https://github.com/0xffea/ocaml-redis/actions) 2 | 3 | Client library for [Redis](http://redis.io/) in pure OCaml. 4 | 5 | Changelog can be found in [CHANGES.md](/CHANGES.md) file. 6 | 7 | ## Dependencies 8 | 9 | - For `redis-lwt`, [Lwt](http://ocsigen.org/lwt/install) is needed. 10 | 11 | ## Documentation 12 | 13 | http://0xffea.github.io/ocaml-redis/ 14 | 15 | ## Quick start 16 | 17 | ### Installation 18 | 19 | `ocaml-redis` implements synchronous and lwt clients. Each of them is in a separate package. 20 | 21 | - synchronous version: 22 | ``` 23 | opam install redis-sync 24 | ``` 25 | - lwt version: 26 | ``` 27 | opam install redis-lwt 28 | ``` 29 | 30 | **Note**: connections are not safe to share among threads. 31 | 32 | ## Contribution 33 | 34 | The tests require [Docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/). 35 | 36 | Once they have been installed, the tests can be run with `make test`. 37 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {} 2 | , ocamlVersion ? import ./nix/ocamlDefaultVersion.nix 3 | , opam2nix ? 4 | pkgs.callPackage ./nix/opam2nix.nix { 5 | inherit pkgs; 6 | ocamlPackagesOverride = pkgs.ocaml-ng."ocamlPackages_${ocamlVersion}"; 7 | 8 | } }: 9 | 10 | pkgs.callPackage ./nix { inherit ocamlVersion opam2nix; } 11 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | no_auth: 5 | image: "redis:6-alpine" 6 | ports: 7 | - "63791:6379" 8 | with_auth: 9 | image: "redis:6-alpine" 10 | command: redis-server --requirepass some-password 11 | ports: 12 | - "63792:6379" 13 | with_acl: 14 | build: ./test/docker/with_acl 15 | ports: 16 | - "63793:6379" 17 | unix_socket: 18 | image: "redis:6-alpine" 19 | command: | 20 | sh -xc "chmod 777 /socket && redis-server --unixsocket /socket/redis.sock --unixsocketperm 777" 21 | volumes: 22 | - ./socket:/socket 23 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ocaml-redis API documentation 4 | 5 | 6 | 7 |

ocaml-redis API documentation

8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | /* A style for ocamldoc. Daniel C. Buenzli */ 2 | 3 | /* Reset a few things. */ 4 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre, 5 | a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp, 6 | small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset, 7 | form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td 8 | { margin: 0; padding: 0; border: 0 none; outline: 0; font-size: 100%; 9 | font-weight: inherit; font-style:inherit; font-family:inherit; 10 | line-height: inherit; vertical-align: baseline; text-align:inherit; 11 | color:inherit; background: transparent; } 12 | 13 | table { border-collapse: collapse; border-spacing: 0; } 14 | 15 | /* Basic page layout */ 16 | 17 | body { font: normal 10pt/1.375em helvetica, arial, sans-serif; text-align:left; 18 | margin: 1.375em 10%; min-width: 40ex; max-width: 72ex; 19 | color: black; background: transparent /* url(line-height-22.gif) */; } 20 | 21 | b { font-weight: bold } 22 | em { font-style: italic } 23 | 24 | tt, code, pre { font-family: WorkAroundWebKitAndMozilla, monospace; 25 | font-size: 1em; } 26 | pre code { font-size : inherit; } 27 | .codepre { margin-bottom:1.375em /* after code example we introduce space. */ } 28 | 29 | .superscript,.subscript 30 | { font-size : 0.813em; line-height:0; margin-left:0.4ex;} 31 | .superscript { vertical-align: super; } 32 | .subscript { vertical-align: sub; } 33 | 34 | /* ocamldoc markup workaround hacks */ 35 | 36 | 37 | 38 | hr, hr + br, div + br, center + br, span + br, ul + br, ol + br, pre + br 39 | { display: none } /* annoying */ 40 | 41 | div.info + br { display:block} 42 | 43 | .codepre br + br { display: none } 44 | h1 { margin-bottom:1.375em} /* Toplevel module description */ 45 | 46 | /* Sections and document divisions */ 47 | 48 | /* .navbar { margin-bottom: -1.375em } */ 49 | h1 { font-weight: bold; font-size: 1.5em; /* margin-top:1.833em; */ 50 | margin-top:0.917em; padding-top:0.875em; 51 | border-top-style:solid; border-width:1px; border-color:#AAA; } 52 | h2 { font-weight: bold; font-size: 1.313em; margin-top: 1.048em } 53 | h3 { font-weight: bold; font-size: 1.125em; margin-top: 1.222em } 54 | h3 { font-weight: bold; font-size: 1em; margin-top: 1.375em} 55 | h4 { font-style: italic; } 56 | 57 | /* Used by OCaml's own library documentation. */ 58 | h6 { font-weight: bold; font-size: 1.125em; margin-top: 1.222em } 59 | .h7 { font-weight: bold; font-size: 1em; margin-top: 1.375em } 60 | 61 | p { margin-top: 1.375em } 62 | pre { margin-top: 1.375em } 63 | .info { margin: 0.458em 0em -0.458em 2em;}/* Description of types values etc. */ 64 | td .info { margin:0; padding:0; margin-left: 2em;} /* Description in indexes */ 65 | 66 | ul, ol { margin-top:0.688em; padding-bottom:0.687em; 67 | list-style-position:outside} 68 | ul + p, ol + p { margin-top: 0em } 69 | ul { list-style-type: square } 70 | 71 | 72 | /* h2 + ul, h3 + ul, p + ul { } */ 73 | ul > li { margin-left: 1.375em; } 74 | ol > li { margin-left: 1.7em; } 75 | /* Links */ 76 | 77 | a, a:link, a:visited, a:active, a:hover { color : #00B; text-decoration: none } 78 | a:hover { text-decoration : underline } 79 | *:target {background-color: #FFFF99;} /* anchor highlight */ 80 | 81 | /* Code */ 82 | 83 | .keyword { font-weight: bold; } 84 | .comment { color : red } 85 | .constructor { color : green } 86 | .string { color : brown } 87 | .warning { color : red ; font-weight : bold } 88 | 89 | /* Functors */ 90 | 91 | .paramstable { border-style : hidden ; padding-bottom:1.375em} 92 | .paramstable code { margin-left: 1ex; margin-right: 1ex } 93 | .sig_block {margin-left: 1em} 94 | 95 | /* Images */ 96 | 97 | img { margin-top: 1.375em } -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.0) 2 | (name redis) 3 | -------------------------------------------------------------------------------- /example.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | exec dune exec --profile=release -- examples/examples.exe $@ 4 | -------------------------------------------------------------------------------- /examples/bench_merge_sort.ml: -------------------------------------------------------------------------------- 1 | open Lwt.Infix 2 | module C = Redis_lwt.Client 3 | module P = Redis_lwt.Pool 4 | 5 | type t = { 6 | pool: P.t; 7 | l: int list; 8 | n: int; 9 | } 10 | 11 | (* generate a random list, but always the same *) 12 | let mk_list n : int list = 13 | let st = Random.State.make [| 42 |] in 14 | CCList.init n (fun _ -> Random.State.int st 5_000) 15 | 16 | (* make a fresh index *) 17 | let mk_id (self:t) (pre:string) : string Lwt.t = 18 | P.with_connection self.pool (fun c -> C.incr c "bms:cur_id") >|= fun i -> 19 | Printf.sprintf "bms:id:%s:%d" pre i 20 | 21 | let ignore_int (_x:int Lwt.t) = _x >|= fun _ -> () 22 | 23 | let str_of_list (self:t) (id:string) : (int * string) Lwt.t = 24 | P.with_connection self.pool (fun c -> C.lrange c id 0 self.n) >|= fun l -> 25 | List.length l, Printf.sprintf "[%s]" (String.concat "," l) 26 | 27 | let unwrap_opt_ msg = function 28 | | Some x -> x 29 | | None -> failwith msg 30 | 31 | let run (self:t) : unit Lwt.t = 32 | mk_id self "list" >>= fun id_list -> 33 | (* insert the whole list *) 34 | P.with_connection self.pool 35 | (fun c -> C.rpush c id_list (List.rev_map string_of_int self.l)) 36 | >>= fun _n -> 37 | assert (_n = self.n); 38 | str_of_list self id_list >>= fun (len,s_list) -> 39 | Printf.printf "initial (len %d): %s\n%!" len s_list; 40 | (* merge [id1] and [id2] into [into] *) 41 | let merge (id1:string) (id2:string) ~into : unit Lwt.t = 42 | (*Lwt.async (fun () -> 43 | str_of_list self id1 >>= fun (_,s1) -> 44 | str_of_list self id2 >>= fun (_,s2) -> 45 | str_of_list self into >|= fun (_,sinto) -> 46 | Printf.printf "merge %s=%s and %s=%s into %s=%s\n%!" 47 | id1 s1 id2 s2 into sinto);*) 48 | assert (id1 <> id2); 49 | let rec loop () : unit Lwt.t = 50 | let len1 = P.with_connection self.pool (fun c -> C.llen c id1) in 51 | let len2 = P.with_connection self.pool (fun c -> C.llen c id2) in 52 | len1 >>= fun len1 -> 53 | len2 >>= fun len2 -> 54 | (* Printf.printf " len1=%d, len2=%d\n%!" len1 len2; *) 55 | if len1=0 && len2=0 then Lwt.return () 56 | else if len1=0 then ( 57 | P.with_connection self.pool 58 | (fun c -> C.lrange c id2 0 len2 >>= C.rpush c into) |> ignore_int 59 | ) else if len2=0 then ( 60 | P.with_connection self.pool 61 | (fun c -> C.lrange c id1 0 len1 >>= C.rpush c into) |> ignore_int 62 | ) else ( 63 | let x = 64 | P.with_connection self.pool 65 | (fun c -> C.lpop c id1 >|= unwrap_opt_ "lpop id1" >|= int_of_string) 66 | and y = 67 | P.with_connection self.pool 68 | (fun c -> C.lpop c id2 >|= unwrap_opt_ "lpop id2" >|= int_of_string) 69 | in 70 | x >>= fun x -> 71 | y >>= fun y -> 72 | (* Printf.printf " x=%d, y=%d\n%!" x y; *) 73 | if x 75 | C.lpush c id2 [string_of_int y] >>= fun _ -> 76 | C.rpush c into [string_of_int x] |> ignore_int) 77 | >>= loop 78 | ) else ( 79 | P.with_connection self.pool (fun c -> 80 | C.lpush c id1 [string_of_int x] >>= fun _ -> 81 | C.rpush c into [string_of_int y] |> ignore_int) 82 | >>= loop 83 | ) 84 | ) 85 | in 86 | (* str_of_list self into >>= fun (_,s) -> Printf.printf " -> [%s]=%s\n%!" into s; *) 87 | loop () 88 | in 89 | (* now recursively do merge sort *) 90 | let rec sort (id_list:string) : unit Lwt.t = 91 | P.with_connection self.pool (fun c -> C.llen c id_list) 92 | >>= fun len -> 93 | if len >= 2 then ( 94 | let mid = len/2 in 95 | let l1 = mk_id self "list_tmp" in 96 | let l2 = mk_id self "list_tmp" in 97 | l1 >>= fun l1 -> 98 | l2 >>= fun l2 -> 99 | let fut1 = 100 | P.with_connection self.pool 101 | (fun c -> C.lrange c id_list 0 (mid-1) >>= C.rpush c l1) 102 | and fut2 = 103 | P.with_connection self.pool 104 | (fun c -> C.lrange c id_list mid len >>= C.rpush c l2) 105 | in 106 | fut1 >>= fun len1 -> 107 | fut2 >>= fun len2 -> 108 | assert (len1 + len2 = len); 109 | P.with_connection self.pool 110 | (fun c -> C.del c [id_list] |> ignore_int) >>= fun () -> 111 | (* sort sublists in parallel *) 112 | let fut1 = sort l1 in 113 | let fut2 = sort l2 in 114 | fut1 >>= fun () -> 115 | fut2 >>= fun () -> 116 | merge l1 l2 ~into:id_list >>= fun () -> 117 | (* cleanup tmp clauses *) 118 | P.with_connection self.pool (fun c -> C.del c [l1; l2]) >|= fun _ -> () 119 | ) else Lwt.return () 120 | in 121 | sort id_list >>= fun () -> 122 | str_of_list self id_list >>= fun (len,s_res) -> 123 | Printf.printf "result (len %d): %s\n%!" len s_res; 124 | P.with_connection self.pool (fun c -> 125 | (C.lrange c id_list 0 self.n >|= List.map int_of_string) >>= fun l -> 126 | C.del c [id_list] >>= fun _ -> 127 | C.del c ["bms:cur_id"] >|= fun _ -> l) 128 | >>= fun l -> 129 | (* must be sorted *) 130 | assert (CCList.is_sorted ~cmp:CCInt.compare l); 131 | (* same length *) 132 | assert (List.length l = List.length self.l); 133 | (* same elements *) 134 | assert ( 135 | let module IS = CCSet.Make(CCInt) in 136 | IS.equal (IS.of_list l) (IS.of_list self.l)); 137 | Lwt.return () 138 | 139 | let run ?(n=100_000) host port : unit = 140 | let spec = {C.host; port} in 141 | let start = Unix.gettimeofday () in 142 | Lwt_main.run 143 | (P.with_pool ~size:32 spec 144 | (fun pool -> 145 | let st = {n; pool; l=mk_list n} in 146 | run st)); 147 | let stop = Unix.gettimeofday () in 148 | Printf.printf "time: %.3fs\n%!" (stop -. start); 149 | () 150 | 151 | -------------------------------------------------------------------------------- /examples/bench_merge_sort.mli: -------------------------------------------------------------------------------- 1 | 2 | val run : ?n:int -> string -> int -> unit 3 | -------------------------------------------------------------------------------- /examples/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (libraries threads containers redis-lwt redis-sync) 3 | (flags :standard -safe-string -warn-error -a+8) 4 | (modes native) 5 | (name examples)) 6 | 7 | (alias 8 | (name runtest) 9 | (locks ../test) 10 | (package redis-lwt) 11 | (action 12 | (progn 13 | (echo "do a merge sort on redis for n=100") 14 | (run ./examples.exe --name bench_merge_sort -n 100)))) 15 | -------------------------------------------------------------------------------- /examples/examples.ml: -------------------------------------------------------------------------------- 1 | open Subscribe_sync 2 | open Subscribe_lwt 3 | 4 | let host = ref "127.0.0.1" 5 | let port = ref 63791 6 | let n = ref 100_000 7 | 8 | let names = [ 9 | "subscribe_sync", (fun () -> subscribe_sync !host !port); 10 | "subscribe_lwt", (fun () -> subscribe_lwt !host !port); 11 | "bench_merge_sort", (fun () -> Bench_merge_sort.run ~n:!n !host !port); 12 | ] 13 | 14 | let () = 15 | let name = ref (fst @@ List.hd names) in 16 | let opts = [ 17 | "--name", Arg.Symbol (List.map fst names, (fun s -> name := s)), " pick example to run"; 18 | "--host", Arg.Set_string host, " host to connect to"; 19 | "--port", Arg.Set_int port, " port to connect to"; 20 | "-n", Arg.Set_int n, " size (for benchmarks)"; 21 | ] |> Arg.align in 22 | Arg.parse opts (fun _ -> ()) "Example of usage ocaml-redis"; 23 | match List.assoc !name names with 24 | | f -> f() 25 | | exception _ -> 26 | failwith @@ "no such example: " ^ !name 27 | -------------------------------------------------------------------------------- /examples/subscribe_lwt.ml: -------------------------------------------------------------------------------- 1 | open Lwt.Infix 2 | 3 | let subscribe_lwt host port = 4 | let open Redis_lwt.Client in 5 | 6 | let print_value v = Lwt_io.printf "%s " (string_of_reply v) in 7 | 8 | let print_stream_value v = 9 | Lwt_list.iter_s print_value v >>= fun () -> 10 | Lwt_io.printf "%s" "\n" >>= fun () -> 11 | Lwt_io.flush Lwt_io.stdout 12 | in 13 | 14 | let t = (connect {host=host; port=port}) 15 | >>= fun conn -> Redis_lwt.Client.subscribe conn ["example"] 16 | >>= fun () -> Lwt.return conn 17 | >>= fun conn -> Lwt_stream.iter_s print_stream_value (stream conn) 18 | in 19 | Lwt_main.run t; 20 | () 21 | -------------------------------------------------------------------------------- /examples/subscribe_sync.ml: -------------------------------------------------------------------------------- 1 | let subscribe_sync host port = 2 | let open Redis_sync.Client in 3 | 4 | let print_value f = Printf.printf "%s" (string_of_reply f) in 5 | 6 | let print_stream_value v = 7 | List.iter print_value v; 8 | print_string "\n"; 9 | flush stdout in 10 | 11 | let conn = connect {host=host; port=port} in 12 | let stream = (stream conn) in 13 | subscribe conn ["example"]; 14 | while true do 15 | let response = Stream.next stream in 16 | print_stream_value response; 17 | done 18 | -------------------------------------------------------------------------------- /nix/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, stdenv, ocamlVersion, opam2nix }: 2 | let 3 | inherit (lib) strings; 4 | args = { 5 | inherit (pkgs.ocaml-ng."ocamlPackages_${ocamlVersion}") ocaml; 6 | src = 7 | let ignores = pkgs.lib.strings.fileContents ../.gitignore; 8 | in pkgs.nix-gitignore.gitignoreSourcePure ignores ../.; 9 | }; 10 | 11 | opam-selection = opam2nix.build (args // { 12 | selection = "${./opam-selection_${ocamlVersion}.nix}"; 13 | }); 14 | 15 | localPackages = 16 | let contents = builtins.attrNames (builtins.readDir ../.); 17 | in builtins.filter (strings.hasSuffix ".opam") contents; 18 | 19 | # list of dependencies with "with-test" flag manually 20 | testPackageNames = [ "ounit2" "containers" ]; 21 | testPackages = 22 | builtins.map (name: builtins.getAttr name opam-selection) testPackageNames; 23 | 24 | resolve = opam2nix.resolve (args // { 25 | selection = "./nix/opam-selection_${ocamlVersion}.nix"; 26 | }) (localPackages ++ testPackageNames); 27 | 28 | 29 | in (builtins.listToAttrs (builtins.map (fname: 30 | let packageName = strings.removeSuffix ".opam" fname; 31 | in { 32 | name = packageName; 33 | value = builtins.getAttr packageName opam-selection; 34 | }) localPackages)) // { 35 | inherit resolve opam-selection testPackages; 36 | } 37 | -------------------------------------------------------------------------------- /nix/ocamlDefaultVersion.nix: -------------------------------------------------------------------------------- 1 | "4_13" 2 | -------------------------------------------------------------------------------- /nix/opam-selection_4_11.nix: -------------------------------------------------------------------------------- 1 | ### This file is generated by opam2nix. 2 | 3 | self: 4 | let 5 | lib = self.lib; 6 | pkgs = self.pkgs; 7 | selection = self.selection; 8 | repoPath = self.repoPath; 9 | repos = 10 | { 11 | opam-repository = 12 | rec { 13 | fetch = 14 | { 15 | owner = "ocaml"; 16 | repo = "opam-repository"; 17 | rev = "448eb8eb91b9e5db70daeb6fff52877ff6d72791"; 18 | sha256 = "1653jc4886z7spwkbfpblrjjwsfbwd0xzggrmrxfjps5a1zwi88q"; 19 | }; 20 | src = (pkgs.fetchFromGitHub) fetch; 21 | }; 22 | }; 23 | in 24 | { 25 | format-version = 4; 26 | inherit repos; 27 | ocaml-version = "4.11.2"; 28 | selection = 29 | { 30 | base-bytes = 31 | { 32 | pname = "base-bytes"; 33 | version = "base"; 34 | src = null; 35 | opamInputs = { 36 | inherit (selection) ocamlfind ocaml; 37 | }; 38 | opamSrc = repoPath (repos.opam-repository.src) 39 | { 40 | package = "packages/base-bytes/base-bytes.base"; 41 | hash = "sha256:0a68lmbf68jgm1i3b59j2sc3ha9yhv4678f9mfwwvczw88prq7l3"; 42 | }; 43 | }; 44 | base-threads = 45 | { 46 | pname = "base-threads"; 47 | version = "base"; 48 | src = null; 49 | opamInputs = { 50 | }; 51 | opamSrc = repoPath (repos.opam-repository.src) 52 | { 53 | package = "packages/base-threads/base-threads.base"; 54 | hash = "sha256:1c4bpyh61ampjgk5yh3inrgcpf1z1xv0pshn54ycmpn4dyzv0p2x"; 55 | }; 56 | }; 57 | base-unix = 58 | { 59 | pname = "base-unix"; 60 | version = "base"; 61 | src = null; 62 | opamInputs = { 63 | }; 64 | opamSrc = repoPath (repos.opam-repository.src) 65 | { 66 | package = "packages/base-unix/base-unix.base"; 67 | hash = "sha256:0mpsvb7684g723ylngryh15aqxg3blb7hgmq2fsqjyppr36iyzwg"; 68 | }; 69 | }; 70 | containers = 71 | { 72 | pname = "containers"; 73 | version = "3.6.1"; 74 | src = pkgs.fetchurl 75 | { 76 | url = "https://github.com/c-cube/ocaml-containers/archive/v3.6.1.tar.gz"; 77 | sha256 = "0vizy0hq84irgzz5n97qmsm87nf2m7lyhdv1vl0wcp72wxcwfj85"; 78 | }; 79 | opamInputs = 80 | { 81 | inherit (selection) seq ocaml either dune-configurator 82 | dune; 83 | base-unix = selection.base-unix or null; 84 | base-threads = selection.base-threads or null; 85 | }; 86 | opamSrc = repoPath (repos.opam-repository.src) 87 | { 88 | package = "packages/containers/containers.3.6.1"; 89 | hash = "sha256:0hr5m1zgrzz8dzngasrd2nwgngg33xksc80zg0gbnhndl7bib6lk"; 90 | }; 91 | }; 92 | cppo = 93 | { 94 | pname = "cppo"; 95 | version = "1.6.8"; 96 | src = pkgs.fetchurl 97 | { 98 | url = "https://github.com/ocaml-community/cppo/archive/v1.6.8.tar.gz"; 99 | sha256 = "0lxy4xkkkwgs1cj6d9lyzsqi9f6fc9r6cir5imi7yjqrpd86s1by"; 100 | }; 101 | opamInputs = 102 | { 103 | inherit (selection) ocaml dune base-unix; 104 | }; 105 | opamSrc = repoPath (repos.opam-repository.src) 106 | { 107 | package = "packages/cppo/cppo.1.6.8"; 108 | hash = "sha256:0pzd8irqkkkpfgw8nq9d21z9rj5m3qlzixyb7ybfy4b1fwh3n8bp"; 109 | }; 110 | }; 111 | csexp = 112 | { 113 | pname = "csexp"; 114 | version = "1.5.1"; 115 | src = pkgs.fetchurl 116 | { 117 | url = "https://github.com/ocaml-dune/csexp/releases/download/1.5.1/csexp-1.5.1.tbz"; 118 | sha256 = "00mc19f89pxpmjl62862ya5kjcfrl8rjzvs00j05h2m9bw3f81fn"; 119 | }; 120 | opamInputs = { 121 | inherit (selection) ocaml dune; 122 | }; 123 | opamSrc = repoPath (repos.opam-repository.src) 124 | { 125 | package = "packages/csexp/csexp.1.5.1"; 126 | hash = "sha256:01lc95kz13gpki4xazyh6n20kv1g9inyb5myv240wl2n9v50z8fl"; 127 | }; 128 | }; 129 | dune = 130 | { 131 | pname = "dune"; 132 | version = "2.9.2"; 133 | src = pkgs.fetchurl 134 | { 135 | url = "https://github.com/ocaml/dune/releases/download/2.9.2/dune-site-2.9.2.tbz"; 136 | sha256 = "0ivl5r9diky8r1rfka7427kc3lh168mrz0ywdxgv8y5rgx8crrxq"; 137 | }; 138 | opamInputs = 139 | { 140 | inherit (selection) base-unix 141 | base-threads; 142 | ocamlfind-secondary = selection.ocamlfind-secondary or null; 143 | ocaml = selection.ocaml or null; 144 | }; 145 | opamSrc = repoPath (repos.opam-repository.src) 146 | { 147 | package = "packages/dune/dune.2.9.2"; 148 | hash = "sha256:0qzzy2kn29ww9ryl2fvhkph9lmc112yr86xn8r8s9rbaz2zfkm6x"; 149 | }; 150 | }; 151 | dune-configurator = 152 | { 153 | pname = "dune-configurator"; 154 | version = "2.9.1"; 155 | src = pkgs.fetchurl 156 | { 157 | url = "https://github.com/ocaml/dune/releases/download/2.9.1/dune-2.9.1.tbz"; 158 | sha256 = "09lzq04b642iy0ljp59p32lgk3q8iphjh8fkdp69q29l5frgwx5k"; 159 | }; 160 | opamInputs = 161 | { 162 | inherit (selection) result ocaml dune csexp; 163 | }; 164 | opamSrc = repoPath (repos.opam-repository.src) 165 | { 166 | package = "packages/dune-configurator/dune-configurator.2.9.1"; 167 | hash = "sha256:1aik9w82r8vbb9fsfphpb0vam68s19ggkxprnqr99ygyalp2b71j"; 168 | }; 169 | }; 170 | either = 171 | { 172 | pname = "either"; 173 | version = "1.0.0"; 174 | src = pkgs.fetchurl 175 | { 176 | url = "https://github.com/mirage/either/releases/download/1.0.0/either-1.0.0.tbz"; 177 | sha256 = "112qa1zdkf4mvn6932jb2rwxdcvfm7lg2zgh2mr7pvid67ilsrxz"; 178 | }; 179 | opamInputs = { 180 | inherit (selection) dune; 181 | }; 182 | opamSrc = repoPath (repos.opam-repository.src) 183 | { 184 | package = "packages/either/either.1.0.0"; 185 | hash = "sha256:1qcj3zl0hjlc4djn5q6qjiagm5xq1q1vgbshwlf4gj7h7kzx11bf"; 186 | }; 187 | }; 188 | lwt = 189 | { 190 | pname = "lwt"; 191 | version = "5.5.0"; 192 | src = pkgs.fetchurl 193 | { 194 | url = "https://github.com/ocsigen/lwt/archive/refs/tags/5.5.0.tar.gz"; 195 | sha256 = "15gr6nhhfjyh91v9chvm6j7vnp1hhc60y3plgvcgl5yl5k7xbbj9"; 196 | }; 197 | opamInputs = 198 | { 199 | inherit (selection) seq result ocplib-endian ocaml mmap 200 | dune-configurator dune 201 | cppo; 202 | ocaml-syntax-shims = selection.ocaml-syntax-shims or null; 203 | conf-libev = selection.conf-libev or null; 204 | base-unix = selection.base-unix or null; 205 | base-threads = selection.base-threads or null; 206 | }; 207 | opamSrc = repoPath (repos.opam-repository.src) 208 | { 209 | package = "packages/lwt/lwt.5.5.0"; 210 | hash = "sha256:0iadlycd4q93lsk07k9zaimlysgd38sj6v50dc3cb491vl0icipz"; 211 | }; 212 | }; 213 | mmap = 214 | { 215 | pname = "mmap"; 216 | version = "1.1.0"; 217 | src = pkgs.fetchurl 218 | { 219 | url = "https://github.com/mirage/mmap/releases/download/v1.1.0/mmap-v1.1.0.tbz"; 220 | sha256 = "0l6waidal2n8mkdn74avbslvc10sf49f5d889n838z03pra5chsc"; 221 | }; 222 | opamInputs = { 223 | inherit (selection) ocaml dune; 224 | }; 225 | opamSrc = repoPath (repos.opam-repository.src) 226 | { 227 | package = "packages/mmap/mmap.1.1.0"; 228 | hash = "sha256:1snhaf6mixmmb49gcin5wmbz4bfjz406mip4556lids8ajm22ibh"; 229 | }; 230 | }; 231 | ocaml = 232 | { 233 | pname = "ocaml"; 234 | version = "4.11.2"; 235 | src = null; 236 | opamInputs = 237 | { 238 | inherit (selection) 239 | ocaml-config; 240 | ocaml-variants = selection.ocaml-variants or null; 241 | ocaml-system = selection.ocaml-system or null; 242 | ocaml-base-compiler = selection.ocaml-base-compiler or null; 243 | }; 244 | opamSrc = repoPath (repos.opam-repository.src) 245 | { 246 | package = "packages/ocaml/ocaml.4.11.2"; 247 | hash = "sha256:14clfk687d596dyhfblf9sljbk31937rrnyx2nqi7ii52qwknn5x"; 248 | }; 249 | }; 250 | ocaml-base-compiler = 251 | { 252 | pname = "ocaml-base-compiler"; 253 | version = "4.11.2"; 254 | src = pkgs.fetchurl 255 | { 256 | url = "https://github.com/ocaml/ocaml/archive/4.11.2.tar.gz"; 257 | sha256 = "02day17rxrd0vla89mxl1yac657d1hs0lxspj8sd58ygyrl2ljh4"; 258 | }; 259 | opamInputs = { 260 | }; 261 | opamSrc = repoPath (repos.opam-repository.src) 262 | { 263 | package = "packages/ocaml-base-compiler/ocaml-base-compiler.4.11.2"; 264 | hash = "sha256:0zaxk9ndzqf392h8arbl11qdllm0aljgahzam9ql2mq41pccpfdg"; 265 | }; 266 | }; 267 | ocaml-config = 268 | { 269 | pname = "ocaml-config"; 270 | version = "1"; 271 | src = null; 272 | opamInputs = 273 | { 274 | ocaml-variants = selection.ocaml-variants or null; 275 | ocaml-system = selection.ocaml-system or null; 276 | ocaml-base-compiler = selection.ocaml-base-compiler or null; 277 | }; 278 | opamSrc = repoPath (repos.opam-repository.src) 279 | { 280 | package = "packages/ocaml-config/ocaml-config.1"; 281 | hash = "sha256:1gc3pyhb64j8a91bgbpy9fzljpb1ps2ylgw31ldx879vdkk46nhp"; 282 | }; 283 | }; 284 | ocamlbuild = 285 | { 286 | pname = "ocamlbuild"; 287 | version = "0.14.0"; 288 | src = pkgs.fetchurl 289 | { 290 | url = "https://github.com/ocaml/ocamlbuild/archive/0.14.0.tar.gz"; 291 | sha256 = "0y1fskw9rg2y1zgb7whv3v8v4xw04svgxslf3856q2aqd7lrrcl7"; 292 | }; 293 | opamInputs = { 294 | inherit (selection) ocaml; 295 | }; 296 | opamSrc = repoPath (repos.opam-repository.src) 297 | { 298 | package = "packages/ocamlbuild/ocamlbuild.0.14.0"; 299 | hash = "sha256:1r5qnrcxfypzyvjvb3zd9f66kr5ldxdl3g9rsi3pb6040krqb5w7"; 300 | }; 301 | }; 302 | ocamlfind = 303 | { 304 | pname = "ocamlfind"; 305 | version = "1.9.3"; 306 | src = pkgs.fetchurl 307 | { 308 | url = "http://download.camlcity.org/download/findlib-1.9.3.tar.gz"; 309 | sha256 = "0hfcwamcvinmww59b5i4yxbf0kxyzkp5qv3d1c7ybn9q52vgq463"; 310 | }; 311 | opamInputs = 312 | { 313 | inherit (selection) ocaml; 314 | graphics = selection.graphics or null; 315 | }; 316 | opamSrc = repoPath (repos.opam-repository.src) 317 | { 318 | package = "packages/ocamlfind/ocamlfind.1.9.3"; 319 | hash = "sha256:1wq1lkw3rpgilprlakznsxxf60mcidn3fzi3vjrgjdrnnp9hvh19"; 320 | }; 321 | }; 322 | ocplib-endian = 323 | { 324 | pname = "ocplib-endian"; 325 | version = "1.2"; 326 | src = pkgs.fetchurl 327 | { 328 | url = "https://github.com/OCamlPro/ocplib-endian/archive/refs/tags/1.2.tar.gz"; 329 | sha256 = "085kskr0cxcnv2d62n3jq1r273p7giisy56zfl26mm7amvl79blp"; 330 | }; 331 | opamInputs = 332 | { 333 | inherit (selection) ocaml dune cppo base-bytes; 334 | }; 335 | opamSrc = repoPath (repos.opam-repository.src) 336 | { 337 | package = "packages/ocplib-endian/ocplib-endian.1.2"; 338 | hash = "sha256:1jldvb471gyhkrzwfvqg722l3a8dc37hhhyas66skjzfmqyi2pnh"; 339 | }; 340 | }; 341 | ounit2 = 342 | { 343 | pname = "ounit2"; 344 | version = "2.2.4"; 345 | src = pkgs.fetchurl 346 | { 347 | url = "https://github.com/gildor478/ounit/releases/download/v2.2.4/ounit-v2.2.4.tbz"; 348 | sha256 = "0i9kiqbf2dp12c4qcvbn4abdpdp6h4g5z54ycsh0q8jpv6jnkh5m"; 349 | }; 350 | opamInputs = 351 | { 352 | inherit (selection) stdlib-shims ocaml dune base-unix base-bytes; 353 | }; 354 | opamSrc = repoPath (repos.opam-repository.src) 355 | { 356 | package = "packages/ounit2/ounit2.2.2.4"; 357 | hash = "sha256:1z5dlin5x6l5s7sbgkirsxgg00pr5l4gq7bdg2287kp4mlm5vgw0"; 358 | }; 359 | }; 360 | re = 361 | { 362 | pname = "re"; 363 | version = "1.10.3"; 364 | src = pkgs.fetchurl 365 | { 366 | url = "https://github.com/ocaml/ocaml-re/releases/download/1.10.3/re-1.10.3.tbz"; 367 | sha256 = "1fqfg609996bgxr14yyfxhvl6hm9c1j0mm2xjdjigqrzgyb4crc4"; 368 | }; 369 | opamInputs = { 370 | inherit (selection) seq ocaml dune; 371 | }; 372 | opamSrc = repoPath (repos.opam-repository.src) 373 | { 374 | package = "packages/re/re.1.10.3"; 375 | hash = "sha256:1f2xgscc07g800ia8z43i1p377dj2fjdrpzsqgzvw1dnplwnklya"; 376 | }; 377 | }; 378 | redis = 379 | { 380 | pname = "redis"; 381 | version = "0.5"; 382 | src = self.directSrc "redis"; 383 | opamInputs = 384 | { 385 | inherit (selection) uuidm re ocaml dune base-unix; 386 | }; 387 | opamSrc = "redis.opam"; 388 | }; 389 | redis-lwt = 390 | { 391 | pname = "redis-lwt"; 392 | version = "0.5"; 393 | src = self.directSrc "redis-lwt"; 394 | opamInputs = 395 | { 396 | inherit (selection) redis ocaml lwt dune; 397 | }; 398 | opamSrc = "redis-lwt.opam"; 399 | }; 400 | redis-sync = 401 | { 402 | pname = "redis-sync"; 403 | version = "0.5"; 404 | src = self.directSrc "redis-sync"; 405 | opamInputs = 406 | { 407 | inherit (selection) redis ocaml dune; 408 | }; 409 | opamSrc = "redis-sync.opam"; 410 | }; 411 | result = 412 | { 413 | pname = "result"; 414 | version = "1.5"; 415 | src = pkgs.fetchurl 416 | { 417 | url = "https://github.com/janestreet/result/releases/download/1.5/result-1.5.tbz"; 418 | sha256 = "0cpfp35fdwnv3p30a06wd0py3805qxmq3jmcynjc3x2qhlimwfkw"; 419 | }; 420 | opamInputs = { 421 | inherit (selection) ocaml dune; 422 | }; 423 | opamSrc = repoPath (repos.opam-repository.src) 424 | { 425 | package = "packages/result/result.1.5"; 426 | hash = "sha256:0ybmvlisfz5swvbcq855czz1ysv9zxmb79f1m0x8284hczmfm98f"; 427 | }; 428 | }; 429 | seq = 430 | { 431 | pname = "seq"; 432 | version = "base"; 433 | src = null; 434 | opamInputs = { 435 | inherit (selection) ocaml; 436 | }; 437 | opamSrc = repoPath (repos.opam-repository.src) 438 | { 439 | package = "packages/seq/seq.base"; 440 | hash = "sha256:1vm8mk6zm2q3fwnkprl6jib048zr4ysldw0bl74x6wwbxj0vx6k9"; 441 | }; 442 | }; 443 | stdlib-shims = 444 | { 445 | pname = "stdlib-shims"; 446 | version = "0.3.0"; 447 | src = pkgs.fetchurl 448 | { 449 | url = "https://github.com/ocaml/stdlib-shims/releases/download/0.3.0/stdlib-shims-0.3.0.tbz"; 450 | sha256 = "0jnqsv6pqp5b5g7lcjwgd75zqqvcwcl5a32zi03zg1kvj79p5gxs"; 451 | }; 452 | opamInputs = { 453 | inherit (selection) ocaml dune; 454 | }; 455 | opamSrc = repoPath (repos.opam-repository.src) 456 | { 457 | package = "packages/stdlib-shims/stdlib-shims.0.3.0"; 458 | hash = "sha256:19g9dnaxyh2ajz6pdczdsqzzvsmfrxwx6f613inkr31jw5hrqkiz"; 459 | }; 460 | }; 461 | topkg = 462 | { 463 | pname = "topkg"; 464 | version = "1.0.4"; 465 | src = pkgs.fetchurl 466 | { 467 | url = "https://erratique.ch/software/topkg/releases/topkg-1.0.4.tbz"; 468 | sha256 = "1kzw5cxkizcvh4rgzwgpjlj9hfxfk6yr686bxx6wrbsfs8as371k"; 469 | }; 470 | opamInputs = 471 | { 472 | inherit (selection) ocamlfind ocamlbuild ocaml; 473 | }; 474 | opamSrc = repoPath (repos.opam-repository.src) 475 | { 476 | package = "packages/topkg/topkg.1.0.4"; 477 | hash = "sha256:0gvngd4nayhhw02gcsljvmx6jkjpv9m3mqwpgimcfq04h0cf4knb"; 478 | }; 479 | }; 480 | uuidm = 481 | { 482 | pname = "uuidm"; 483 | version = "0.9.7"; 484 | src = pkgs.fetchurl 485 | { 486 | url = "https://erratique.ch/software/uuidm/releases/uuidm-0.9.7.tbz"; 487 | sha256 = "1ivxb3hxn9bk62rmixx6px4fvn52s4yr1bpla7rgkcn8981v45r8"; 488 | }; 489 | opamInputs = 490 | { 491 | inherit (selection) topkg ocamlfind ocamlbuild 492 | ocaml; 493 | cmdliner = selection.cmdliner or null; 494 | }; 495 | opamSrc = repoPath (repos.opam-repository.src) 496 | { 497 | package = "packages/uuidm/uuidm.0.9.7"; 498 | hash = "sha256:0gczj4p886wzyjr11x4wg5qwvj6lvzb1rnhy0l9ya7z01n51bkwr"; 499 | }; 500 | }; 501 | }; 502 | } 503 | 504 | -------------------------------------------------------------------------------- /nix/opam-selection_4_12.nix: -------------------------------------------------------------------------------- 1 | ### This file is generated by opam2nix. 2 | 3 | self: 4 | let 5 | lib = self.lib; 6 | pkgs = self.pkgs; 7 | selection = self.selection; 8 | repoPath = self.repoPath; 9 | repos = 10 | { 11 | opam-repository = 12 | rec { 13 | fetch = 14 | { 15 | owner = "ocaml"; 16 | repo = "opam-repository"; 17 | rev = "448eb8eb91b9e5db70daeb6fff52877ff6d72791"; 18 | sha256 = "1653jc4886z7spwkbfpblrjjwsfbwd0xzggrmrxfjps5a1zwi88q"; 19 | }; 20 | src = (pkgs.fetchFromGitHub) fetch; 21 | }; 22 | }; 23 | in 24 | { 25 | format-version = 4; 26 | inherit repos; 27 | ocaml-version = "4.12.0"; 28 | selection = 29 | { 30 | base-bytes = 31 | { 32 | pname = "base-bytes"; 33 | version = "base"; 34 | src = null; 35 | opamInputs = { 36 | inherit (selection) ocamlfind ocaml; 37 | }; 38 | opamSrc = repoPath (repos.opam-repository.src) 39 | { 40 | package = "packages/base-bytes/base-bytes.base"; 41 | hash = "sha256:0a68lmbf68jgm1i3b59j2sc3ha9yhv4678f9mfwwvczw88prq7l3"; 42 | }; 43 | }; 44 | base-threads = 45 | { 46 | pname = "base-threads"; 47 | version = "base"; 48 | src = null; 49 | opamInputs = { 50 | }; 51 | opamSrc = repoPath (repos.opam-repository.src) 52 | { 53 | package = "packages/base-threads/base-threads.base"; 54 | hash = "sha256:1c4bpyh61ampjgk5yh3inrgcpf1z1xv0pshn54ycmpn4dyzv0p2x"; 55 | }; 56 | }; 57 | base-unix = 58 | { 59 | pname = "base-unix"; 60 | version = "base"; 61 | src = null; 62 | opamInputs = { 63 | }; 64 | opamSrc = repoPath (repos.opam-repository.src) 65 | { 66 | package = "packages/base-unix/base-unix.base"; 67 | hash = "sha256:0mpsvb7684g723ylngryh15aqxg3blb7hgmq2fsqjyppr36iyzwg"; 68 | }; 69 | }; 70 | containers = 71 | { 72 | pname = "containers"; 73 | version = "3.6.1"; 74 | src = pkgs.fetchurl 75 | { 76 | url = "https://github.com/c-cube/ocaml-containers/archive/v3.6.1.tar.gz"; 77 | sha256 = "0vizy0hq84irgzz5n97qmsm87nf2m7lyhdv1vl0wcp72wxcwfj85"; 78 | }; 79 | opamInputs = 80 | { 81 | inherit (selection) seq ocaml either dune-configurator 82 | dune; 83 | base-unix = selection.base-unix or null; 84 | base-threads = selection.base-threads or null; 85 | }; 86 | opamSrc = repoPath (repos.opam-repository.src) 87 | { 88 | package = "packages/containers/containers.3.6.1"; 89 | hash = "sha256:0hr5m1zgrzz8dzngasrd2nwgngg33xksc80zg0gbnhndl7bib6lk"; 90 | }; 91 | }; 92 | cppo = 93 | { 94 | pname = "cppo"; 95 | version = "1.6.8"; 96 | src = pkgs.fetchurl 97 | { 98 | url = "https://github.com/ocaml-community/cppo/archive/v1.6.8.tar.gz"; 99 | sha256 = "0lxy4xkkkwgs1cj6d9lyzsqi9f6fc9r6cir5imi7yjqrpd86s1by"; 100 | }; 101 | opamInputs = 102 | { 103 | inherit (selection) ocaml dune base-unix; 104 | }; 105 | opamSrc = repoPath (repos.opam-repository.src) 106 | { 107 | package = "packages/cppo/cppo.1.6.8"; 108 | hash = "sha256:0pzd8irqkkkpfgw8nq9d21z9rj5m3qlzixyb7ybfy4b1fwh3n8bp"; 109 | }; 110 | }; 111 | csexp = 112 | { 113 | pname = "csexp"; 114 | version = "1.5.1"; 115 | src = pkgs.fetchurl 116 | { 117 | url = "https://github.com/ocaml-dune/csexp/releases/download/1.5.1/csexp-1.5.1.tbz"; 118 | sha256 = "00mc19f89pxpmjl62862ya5kjcfrl8rjzvs00j05h2m9bw3f81fn"; 119 | }; 120 | opamInputs = { 121 | inherit (selection) ocaml dune; 122 | }; 123 | opamSrc = repoPath (repos.opam-repository.src) 124 | { 125 | package = "packages/csexp/csexp.1.5.1"; 126 | hash = "sha256:01lc95kz13gpki4xazyh6n20kv1g9inyb5myv240wl2n9v50z8fl"; 127 | }; 128 | }; 129 | dune = 130 | { 131 | pname = "dune"; 132 | version = "2.9.2"; 133 | src = pkgs.fetchurl 134 | { 135 | url = "https://github.com/ocaml/dune/releases/download/2.9.2/dune-site-2.9.2.tbz"; 136 | sha256 = "0ivl5r9diky8r1rfka7427kc3lh168mrz0ywdxgv8y5rgx8crrxq"; 137 | }; 138 | opamInputs = 139 | { 140 | inherit (selection) base-unix 141 | base-threads; 142 | ocamlfind-secondary = selection.ocamlfind-secondary or null; 143 | ocaml = selection.ocaml or null; 144 | }; 145 | opamSrc = repoPath (repos.opam-repository.src) 146 | { 147 | package = "packages/dune/dune.2.9.2"; 148 | hash = "sha256:0qzzy2kn29ww9ryl2fvhkph9lmc112yr86xn8r8s9rbaz2zfkm6x"; 149 | }; 150 | }; 151 | dune-configurator = 152 | { 153 | pname = "dune-configurator"; 154 | version = "2.9.1"; 155 | src = pkgs.fetchurl 156 | { 157 | url = "https://github.com/ocaml/dune/releases/download/2.9.1/dune-2.9.1.tbz"; 158 | sha256 = "09lzq04b642iy0ljp59p32lgk3q8iphjh8fkdp69q29l5frgwx5k"; 159 | }; 160 | opamInputs = 161 | { 162 | inherit (selection) result ocaml dune csexp; 163 | }; 164 | opamSrc = repoPath (repos.opam-repository.src) 165 | { 166 | package = "packages/dune-configurator/dune-configurator.2.9.1"; 167 | hash = "sha256:1aik9w82r8vbb9fsfphpb0vam68s19ggkxprnqr99ygyalp2b71j"; 168 | }; 169 | }; 170 | either = 171 | { 172 | pname = "either"; 173 | version = "1.0.0"; 174 | src = pkgs.fetchurl 175 | { 176 | url = "https://github.com/mirage/either/releases/download/1.0.0/either-1.0.0.tbz"; 177 | sha256 = "112qa1zdkf4mvn6932jb2rwxdcvfm7lg2zgh2mr7pvid67ilsrxz"; 178 | }; 179 | opamInputs = { 180 | inherit (selection) dune; 181 | }; 182 | opamSrc = repoPath (repos.opam-repository.src) 183 | { 184 | package = "packages/either/either.1.0.0"; 185 | hash = "sha256:1qcj3zl0hjlc4djn5q6qjiagm5xq1q1vgbshwlf4gj7h7kzx11bf"; 186 | }; 187 | }; 188 | lwt = 189 | { 190 | pname = "lwt"; 191 | version = "5.5.0"; 192 | src = pkgs.fetchurl 193 | { 194 | url = "https://github.com/ocsigen/lwt/archive/refs/tags/5.5.0.tar.gz"; 195 | sha256 = "15gr6nhhfjyh91v9chvm6j7vnp1hhc60y3plgvcgl5yl5k7xbbj9"; 196 | }; 197 | opamInputs = 198 | { 199 | inherit (selection) seq result ocplib-endian ocaml mmap 200 | dune-configurator dune 201 | cppo; 202 | ocaml-syntax-shims = selection.ocaml-syntax-shims or null; 203 | conf-libev = selection.conf-libev or null; 204 | base-unix = selection.base-unix or null; 205 | base-threads = selection.base-threads or null; 206 | }; 207 | opamSrc = repoPath (repos.opam-repository.src) 208 | { 209 | package = "packages/lwt/lwt.5.5.0"; 210 | hash = "sha256:0iadlycd4q93lsk07k9zaimlysgd38sj6v50dc3cb491vl0icipz"; 211 | }; 212 | }; 213 | mmap = 214 | { 215 | pname = "mmap"; 216 | version = "1.1.0"; 217 | src = pkgs.fetchurl 218 | { 219 | url = "https://github.com/mirage/mmap/releases/download/v1.1.0/mmap-v1.1.0.tbz"; 220 | sha256 = "0l6waidal2n8mkdn74avbslvc10sf49f5d889n838z03pra5chsc"; 221 | }; 222 | opamInputs = { 223 | inherit (selection) ocaml dune; 224 | }; 225 | opamSrc = repoPath (repos.opam-repository.src) 226 | { 227 | package = "packages/mmap/mmap.1.1.0"; 228 | hash = "sha256:1snhaf6mixmmb49gcin5wmbz4bfjz406mip4556lids8ajm22ibh"; 229 | }; 230 | }; 231 | ocaml = 232 | { 233 | pname = "ocaml"; 234 | version = "4.12.0"; 235 | src = null; 236 | opamInputs = 237 | { 238 | inherit (selection) 239 | ocaml-config; 240 | ocaml-variants = selection.ocaml-variants or null; 241 | ocaml-system = selection.ocaml-system or null; 242 | ocaml-base-compiler = selection.ocaml-base-compiler or null; 243 | }; 244 | opamSrc = repoPath (repos.opam-repository.src) 245 | { 246 | package = "packages/ocaml/ocaml.4.12.0"; 247 | hash = "sha256:038vq14gnl2qzgk4mjr0xif1xln30wk5hbj2icvq3ql6hszyrf9g"; 248 | }; 249 | }; 250 | ocaml-base-compiler = 251 | { 252 | pname = "ocaml-base-compiler"; 253 | version = "4.12.0"; 254 | src = pkgs.fetchurl 255 | { 256 | url = "https://github.com/ocaml/ocaml/archive/4.12.0.tar.gz"; 257 | sha256 = "0i37laikik5vwydw1cwygxd8xq2d6n35l20irgrh691njlwpmh5d"; 258 | }; 259 | opamInputs = { 260 | }; 261 | opamSrc = repoPath (repos.opam-repository.src) 262 | { 263 | package = "packages/ocaml-base-compiler/ocaml-base-compiler.4.12.0"; 264 | hash = "sha256:12clsv2kmwsfy685kjkjsyv41krfrr04ccpc11xhv6ar0090bsh5"; 265 | }; 266 | }; 267 | ocaml-config = 268 | { 269 | pname = "ocaml-config"; 270 | version = "2"; 271 | src = null; 272 | opamInputs = 273 | { 274 | ocaml-variants = selection.ocaml-variants or null; 275 | ocaml-system = selection.ocaml-system or null; 276 | ocaml-base-compiler = selection.ocaml-base-compiler or null; 277 | }; 278 | opamSrc = repoPath (repos.opam-repository.src) 279 | { 280 | package = "packages/ocaml-config/ocaml-config.2"; 281 | hash = "sha256:0h0hgqq9mbywvqygppfdc50gf9ss8a97l4dgsv3hszmzh6gglgrg"; 282 | }; 283 | }; 284 | ocamlbuild = 285 | { 286 | pname = "ocamlbuild"; 287 | version = "0.14.0"; 288 | src = pkgs.fetchurl 289 | { 290 | url = "https://github.com/ocaml/ocamlbuild/archive/0.14.0.tar.gz"; 291 | sha256 = "0y1fskw9rg2y1zgb7whv3v8v4xw04svgxslf3856q2aqd7lrrcl7"; 292 | }; 293 | opamInputs = { 294 | inherit (selection) ocaml; 295 | }; 296 | opamSrc = repoPath (repos.opam-repository.src) 297 | { 298 | package = "packages/ocamlbuild/ocamlbuild.0.14.0"; 299 | hash = "sha256:1r5qnrcxfypzyvjvb3zd9f66kr5ldxdl3g9rsi3pb6040krqb5w7"; 300 | }; 301 | }; 302 | ocamlfind = 303 | { 304 | pname = "ocamlfind"; 305 | version = "1.9.3"; 306 | src = pkgs.fetchurl 307 | { 308 | url = "http://download.camlcity.org/download/findlib-1.9.3.tar.gz"; 309 | sha256 = "0hfcwamcvinmww59b5i4yxbf0kxyzkp5qv3d1c7ybn9q52vgq463"; 310 | }; 311 | opamInputs = 312 | { 313 | inherit (selection) ocaml; 314 | graphics = selection.graphics or null; 315 | }; 316 | opamSrc = repoPath (repos.opam-repository.src) 317 | { 318 | package = "packages/ocamlfind/ocamlfind.1.9.3"; 319 | hash = "sha256:1wq1lkw3rpgilprlakznsxxf60mcidn3fzi3vjrgjdrnnp9hvh19"; 320 | }; 321 | }; 322 | ocplib-endian = 323 | { 324 | pname = "ocplib-endian"; 325 | version = "1.2"; 326 | src = pkgs.fetchurl 327 | { 328 | url = "https://github.com/OCamlPro/ocplib-endian/archive/refs/tags/1.2.tar.gz"; 329 | sha256 = "085kskr0cxcnv2d62n3jq1r273p7giisy56zfl26mm7amvl79blp"; 330 | }; 331 | opamInputs = 332 | { 333 | inherit (selection) ocaml dune cppo base-bytes; 334 | }; 335 | opamSrc = repoPath (repos.opam-repository.src) 336 | { 337 | package = "packages/ocplib-endian/ocplib-endian.1.2"; 338 | hash = "sha256:1jldvb471gyhkrzwfvqg722l3a8dc37hhhyas66skjzfmqyi2pnh"; 339 | }; 340 | }; 341 | ounit2 = 342 | { 343 | pname = "ounit2"; 344 | version = "2.2.4"; 345 | src = pkgs.fetchurl 346 | { 347 | url = "https://github.com/gildor478/ounit/releases/download/v2.2.4/ounit-v2.2.4.tbz"; 348 | sha256 = "0i9kiqbf2dp12c4qcvbn4abdpdp6h4g5z54ycsh0q8jpv6jnkh5m"; 349 | }; 350 | opamInputs = 351 | { 352 | inherit (selection) stdlib-shims ocaml dune base-unix base-bytes; 353 | }; 354 | opamSrc = repoPath (repos.opam-repository.src) 355 | { 356 | package = "packages/ounit2/ounit2.2.2.4"; 357 | hash = "sha256:1z5dlin5x6l5s7sbgkirsxgg00pr5l4gq7bdg2287kp4mlm5vgw0"; 358 | }; 359 | }; 360 | re = 361 | { 362 | pname = "re"; 363 | version = "1.10.3"; 364 | src = pkgs.fetchurl 365 | { 366 | url = "https://github.com/ocaml/ocaml-re/releases/download/1.10.3/re-1.10.3.tbz"; 367 | sha256 = "1fqfg609996bgxr14yyfxhvl6hm9c1j0mm2xjdjigqrzgyb4crc4"; 368 | }; 369 | opamInputs = { 370 | inherit (selection) seq ocaml dune; 371 | }; 372 | opamSrc = repoPath (repos.opam-repository.src) 373 | { 374 | package = "packages/re/re.1.10.3"; 375 | hash = "sha256:1f2xgscc07g800ia8z43i1p377dj2fjdrpzsqgzvw1dnplwnklya"; 376 | }; 377 | }; 378 | redis = 379 | { 380 | pname = "redis"; 381 | version = "0.5"; 382 | src = self.directSrc "redis"; 383 | opamInputs = 384 | { 385 | inherit (selection) uuidm re ocaml dune base-unix; 386 | }; 387 | opamSrc = "redis.opam"; 388 | }; 389 | redis-lwt = 390 | { 391 | pname = "redis-lwt"; 392 | version = "0.5"; 393 | src = self.directSrc "redis-lwt"; 394 | opamInputs = 395 | { 396 | inherit (selection) redis ocaml lwt dune; 397 | }; 398 | opamSrc = "redis-lwt.opam"; 399 | }; 400 | redis-sync = 401 | { 402 | pname = "redis-sync"; 403 | version = "0.5"; 404 | src = self.directSrc "redis-sync"; 405 | opamInputs = 406 | { 407 | inherit (selection) redis ocaml dune; 408 | }; 409 | opamSrc = "redis-sync.opam"; 410 | }; 411 | result = 412 | { 413 | pname = "result"; 414 | version = "1.5"; 415 | src = pkgs.fetchurl 416 | { 417 | url = "https://github.com/janestreet/result/releases/download/1.5/result-1.5.tbz"; 418 | sha256 = "0cpfp35fdwnv3p30a06wd0py3805qxmq3jmcynjc3x2qhlimwfkw"; 419 | }; 420 | opamInputs = { 421 | inherit (selection) ocaml dune; 422 | }; 423 | opamSrc = repoPath (repos.opam-repository.src) 424 | { 425 | package = "packages/result/result.1.5"; 426 | hash = "sha256:0ybmvlisfz5swvbcq855czz1ysv9zxmb79f1m0x8284hczmfm98f"; 427 | }; 428 | }; 429 | seq = 430 | { 431 | pname = "seq"; 432 | version = "base"; 433 | src = null; 434 | opamInputs = { 435 | inherit (selection) ocaml; 436 | }; 437 | opamSrc = repoPath (repos.opam-repository.src) 438 | { 439 | package = "packages/seq/seq.base"; 440 | hash = "sha256:1vm8mk6zm2q3fwnkprl6jib048zr4ysldw0bl74x6wwbxj0vx6k9"; 441 | }; 442 | }; 443 | stdlib-shims = 444 | { 445 | pname = "stdlib-shims"; 446 | version = "0.3.0"; 447 | src = pkgs.fetchurl 448 | { 449 | url = "https://github.com/ocaml/stdlib-shims/releases/download/0.3.0/stdlib-shims-0.3.0.tbz"; 450 | sha256 = "0jnqsv6pqp5b5g7lcjwgd75zqqvcwcl5a32zi03zg1kvj79p5gxs"; 451 | }; 452 | opamInputs = { 453 | inherit (selection) ocaml dune; 454 | }; 455 | opamSrc = repoPath (repos.opam-repository.src) 456 | { 457 | package = "packages/stdlib-shims/stdlib-shims.0.3.0"; 458 | hash = "sha256:19g9dnaxyh2ajz6pdczdsqzzvsmfrxwx6f613inkr31jw5hrqkiz"; 459 | }; 460 | }; 461 | topkg = 462 | { 463 | pname = "topkg"; 464 | version = "1.0.4"; 465 | src = pkgs.fetchurl 466 | { 467 | url = "https://erratique.ch/software/topkg/releases/topkg-1.0.4.tbz"; 468 | sha256 = "1kzw5cxkizcvh4rgzwgpjlj9hfxfk6yr686bxx6wrbsfs8as371k"; 469 | }; 470 | opamInputs = 471 | { 472 | inherit (selection) ocamlfind ocamlbuild ocaml; 473 | }; 474 | opamSrc = repoPath (repos.opam-repository.src) 475 | { 476 | package = "packages/topkg/topkg.1.0.4"; 477 | hash = "sha256:0gvngd4nayhhw02gcsljvmx6jkjpv9m3mqwpgimcfq04h0cf4knb"; 478 | }; 479 | }; 480 | uuidm = 481 | { 482 | pname = "uuidm"; 483 | version = "0.9.7"; 484 | src = pkgs.fetchurl 485 | { 486 | url = "https://erratique.ch/software/uuidm/releases/uuidm-0.9.7.tbz"; 487 | sha256 = "1ivxb3hxn9bk62rmixx6px4fvn52s4yr1bpla7rgkcn8981v45r8"; 488 | }; 489 | opamInputs = 490 | { 491 | inherit (selection) topkg ocamlfind ocamlbuild 492 | ocaml; 493 | cmdliner = selection.cmdliner or null; 494 | }; 495 | opamSrc = repoPath (repos.opam-repository.src) 496 | { 497 | package = "packages/uuidm/uuidm.0.9.7"; 498 | hash = "sha256:0gczj4p886wzyjr11x4wg5qwvj6lvzb1rnhy0l9ya7z01n51bkwr"; 499 | }; 500 | }; 501 | }; 502 | } 503 | 504 | -------------------------------------------------------------------------------- /nix/opam-selection_4_13.nix: -------------------------------------------------------------------------------- 1 | ### This file is generated by opam2nix. 2 | 3 | self: 4 | let 5 | lib = self.lib; 6 | pkgs = self.pkgs; 7 | selection = self.selection; 8 | repoPath = self.repoPath; 9 | repos = 10 | { 11 | opam-repository = 12 | rec { 13 | fetch = 14 | { 15 | owner = "ocaml"; 16 | repo = "opam-repository"; 17 | rev = "448eb8eb91b9e5db70daeb6fff52877ff6d72791"; 18 | sha256 = "1653jc4886z7spwkbfpblrjjwsfbwd0xzggrmrxfjps5a1zwi88q"; 19 | }; 20 | src = (pkgs.fetchFromGitHub) fetch; 21 | }; 22 | }; 23 | in 24 | { 25 | format-version = 4; 26 | inherit repos; 27 | ocaml-version = "4.13.1"; 28 | selection = 29 | { 30 | base-bytes = 31 | { 32 | pname = "base-bytes"; 33 | version = "base"; 34 | src = null; 35 | opamInputs = { 36 | inherit (selection) ocamlfind ocaml; 37 | }; 38 | opamSrc = repoPath (repos.opam-repository.src) 39 | { 40 | package = "packages/base-bytes/base-bytes.base"; 41 | hash = "sha256:0a68lmbf68jgm1i3b59j2sc3ha9yhv4678f9mfwwvczw88prq7l3"; 42 | }; 43 | }; 44 | base-threads = 45 | { 46 | pname = "base-threads"; 47 | version = "base"; 48 | src = null; 49 | opamInputs = { 50 | }; 51 | opamSrc = repoPath (repos.opam-repository.src) 52 | { 53 | package = "packages/base-threads/base-threads.base"; 54 | hash = "sha256:1c4bpyh61ampjgk5yh3inrgcpf1z1xv0pshn54ycmpn4dyzv0p2x"; 55 | }; 56 | }; 57 | base-unix = 58 | { 59 | pname = "base-unix"; 60 | version = "base"; 61 | src = null; 62 | opamInputs = { 63 | }; 64 | opamSrc = repoPath (repos.opam-repository.src) 65 | { 66 | package = "packages/base-unix/base-unix.base"; 67 | hash = "sha256:0mpsvb7684g723ylngryh15aqxg3blb7hgmq2fsqjyppr36iyzwg"; 68 | }; 69 | }; 70 | containers = 71 | { 72 | pname = "containers"; 73 | version = "3.6.1"; 74 | src = pkgs.fetchurl 75 | { 76 | url = "https://github.com/c-cube/ocaml-containers/archive/v3.6.1.tar.gz"; 77 | sha256 = "0vizy0hq84irgzz5n97qmsm87nf2m7lyhdv1vl0wcp72wxcwfj85"; 78 | }; 79 | opamInputs = 80 | { 81 | inherit (selection) seq ocaml either dune-configurator 82 | dune; 83 | base-unix = selection.base-unix or null; 84 | base-threads = selection.base-threads or null; 85 | }; 86 | opamSrc = repoPath (repos.opam-repository.src) 87 | { 88 | package = "packages/containers/containers.3.6.1"; 89 | hash = "sha256:0hr5m1zgrzz8dzngasrd2nwgngg33xksc80zg0gbnhndl7bib6lk"; 90 | }; 91 | }; 92 | cppo = 93 | { 94 | pname = "cppo"; 95 | version = "1.6.8"; 96 | src = pkgs.fetchurl 97 | { 98 | url = "https://github.com/ocaml-community/cppo/archive/v1.6.8.tar.gz"; 99 | sha256 = "0lxy4xkkkwgs1cj6d9lyzsqi9f6fc9r6cir5imi7yjqrpd86s1by"; 100 | }; 101 | opamInputs = 102 | { 103 | inherit (selection) ocaml dune base-unix; 104 | }; 105 | opamSrc = repoPath (repos.opam-repository.src) 106 | { 107 | package = "packages/cppo/cppo.1.6.8"; 108 | hash = "sha256:0pzd8irqkkkpfgw8nq9d21z9rj5m3qlzixyb7ybfy4b1fwh3n8bp"; 109 | }; 110 | }; 111 | csexp = 112 | { 113 | pname = "csexp"; 114 | version = "1.5.1"; 115 | src = pkgs.fetchurl 116 | { 117 | url = "https://github.com/ocaml-dune/csexp/releases/download/1.5.1/csexp-1.5.1.tbz"; 118 | sha256 = "00mc19f89pxpmjl62862ya5kjcfrl8rjzvs00j05h2m9bw3f81fn"; 119 | }; 120 | opamInputs = { 121 | inherit (selection) ocaml dune; 122 | }; 123 | opamSrc = repoPath (repos.opam-repository.src) 124 | { 125 | package = "packages/csexp/csexp.1.5.1"; 126 | hash = "sha256:01lc95kz13gpki4xazyh6n20kv1g9inyb5myv240wl2n9v50z8fl"; 127 | }; 128 | }; 129 | dune = 130 | { 131 | pname = "dune"; 132 | version = "2.9.2"; 133 | src = pkgs.fetchurl 134 | { 135 | url = "https://github.com/ocaml/dune/releases/download/2.9.2/dune-site-2.9.2.tbz"; 136 | sha256 = "0ivl5r9diky8r1rfka7427kc3lh168mrz0ywdxgv8y5rgx8crrxq"; 137 | }; 138 | opamInputs = 139 | { 140 | inherit (selection) base-unix 141 | base-threads; 142 | ocamlfind-secondary = selection.ocamlfind-secondary or null; 143 | ocaml = selection.ocaml or null; 144 | }; 145 | opamSrc = repoPath (repos.opam-repository.src) 146 | { 147 | package = "packages/dune/dune.2.9.2"; 148 | hash = "sha256:0qzzy2kn29ww9ryl2fvhkph9lmc112yr86xn8r8s9rbaz2zfkm6x"; 149 | }; 150 | }; 151 | dune-configurator = 152 | { 153 | pname = "dune-configurator"; 154 | version = "2.9.1"; 155 | src = pkgs.fetchurl 156 | { 157 | url = "https://github.com/ocaml/dune/releases/download/2.9.1/dune-2.9.1.tbz"; 158 | sha256 = "09lzq04b642iy0ljp59p32lgk3q8iphjh8fkdp69q29l5frgwx5k"; 159 | }; 160 | opamInputs = 161 | { 162 | inherit (selection) result ocaml dune csexp; 163 | }; 164 | opamSrc = repoPath (repos.opam-repository.src) 165 | { 166 | package = "packages/dune-configurator/dune-configurator.2.9.1"; 167 | hash = "sha256:1aik9w82r8vbb9fsfphpb0vam68s19ggkxprnqr99ygyalp2b71j"; 168 | }; 169 | }; 170 | either = 171 | { 172 | pname = "either"; 173 | version = "1.0.0"; 174 | src = pkgs.fetchurl 175 | { 176 | url = "https://github.com/mirage/either/releases/download/1.0.0/either-1.0.0.tbz"; 177 | sha256 = "112qa1zdkf4mvn6932jb2rwxdcvfm7lg2zgh2mr7pvid67ilsrxz"; 178 | }; 179 | opamInputs = { 180 | inherit (selection) dune; 181 | }; 182 | opamSrc = repoPath (repos.opam-repository.src) 183 | { 184 | package = "packages/either/either.1.0.0"; 185 | hash = "sha256:1qcj3zl0hjlc4djn5q6qjiagm5xq1q1vgbshwlf4gj7h7kzx11bf"; 186 | }; 187 | }; 188 | lwt = 189 | { 190 | pname = "lwt"; 191 | version = "5.5.0"; 192 | src = pkgs.fetchurl 193 | { 194 | url = "https://github.com/ocsigen/lwt/archive/refs/tags/5.5.0.tar.gz"; 195 | sha256 = "15gr6nhhfjyh91v9chvm6j7vnp1hhc60y3plgvcgl5yl5k7xbbj9"; 196 | }; 197 | opamInputs = 198 | { 199 | inherit (selection) seq result ocplib-endian ocaml mmap 200 | dune-configurator dune 201 | cppo; 202 | ocaml-syntax-shims = selection.ocaml-syntax-shims or null; 203 | conf-libev = selection.conf-libev or null; 204 | base-unix = selection.base-unix or null; 205 | base-threads = selection.base-threads or null; 206 | }; 207 | opamSrc = repoPath (repos.opam-repository.src) 208 | { 209 | package = "packages/lwt/lwt.5.5.0"; 210 | hash = "sha256:0iadlycd4q93lsk07k9zaimlysgd38sj6v50dc3cb491vl0icipz"; 211 | }; 212 | }; 213 | mmap = 214 | { 215 | pname = "mmap"; 216 | version = "1.1.0"; 217 | src = pkgs.fetchurl 218 | { 219 | url = "https://github.com/mirage/mmap/releases/download/v1.1.0/mmap-v1.1.0.tbz"; 220 | sha256 = "0l6waidal2n8mkdn74avbslvc10sf49f5d889n838z03pra5chsc"; 221 | }; 222 | opamInputs = { 223 | inherit (selection) ocaml dune; 224 | }; 225 | opamSrc = repoPath (repos.opam-repository.src) 226 | { 227 | package = "packages/mmap/mmap.1.1.0"; 228 | hash = "sha256:1snhaf6mixmmb49gcin5wmbz4bfjz406mip4556lids8ajm22ibh"; 229 | }; 230 | }; 231 | ocaml = 232 | { 233 | pname = "ocaml"; 234 | version = "4.13.1"; 235 | src = null; 236 | opamInputs = 237 | { 238 | inherit (selection) 239 | ocaml-config; 240 | ocaml-variants = selection.ocaml-variants or null; 241 | ocaml-system = selection.ocaml-system or null; 242 | ocaml-base-compiler = selection.ocaml-base-compiler or null; 243 | }; 244 | opamSrc = repoPath (repos.opam-repository.src) 245 | { 246 | package = "packages/ocaml/ocaml.4.13.1"; 247 | hash = "sha256:12aajj2hc636jr6wbv4vba8pmjdndbcvnw7q35gj4agxmrjspziw"; 248 | }; 249 | }; 250 | ocaml-base-compiler = 251 | { 252 | pname = "ocaml-base-compiler"; 253 | version = "4.13.1"; 254 | src = pkgs.fetchurl 255 | { 256 | url = "https://github.com/ocaml/ocaml/archive/4.13.1.tar.gz"; 257 | sha256 = "1i7ad8lh5l74wb3yzmhlv529wc75a5sjybzkad7wdl8zrj47jk0r"; 258 | }; 259 | opamInputs = { 260 | }; 261 | opamSrc = repoPath (repos.opam-repository.src) 262 | { 263 | package = "packages/ocaml-base-compiler/ocaml-base-compiler.4.13.1"; 264 | hash = "sha256:069i5vsndd9lsvdm5wk898nfnc85ww7459nibjg6881ww43w8ck1"; 265 | }; 266 | }; 267 | ocaml-config = 268 | { 269 | pname = "ocaml-config"; 270 | version = "2"; 271 | src = null; 272 | opamInputs = 273 | { 274 | ocaml-variants = selection.ocaml-variants or null; 275 | ocaml-system = selection.ocaml-system or null; 276 | ocaml-base-compiler = selection.ocaml-base-compiler or null; 277 | }; 278 | opamSrc = repoPath (repos.opam-repository.src) 279 | { 280 | package = "packages/ocaml-config/ocaml-config.2"; 281 | hash = "sha256:0h0hgqq9mbywvqygppfdc50gf9ss8a97l4dgsv3hszmzh6gglgrg"; 282 | }; 283 | }; 284 | ocamlbuild = 285 | { 286 | pname = "ocamlbuild"; 287 | version = "0.14.0"; 288 | src = pkgs.fetchurl 289 | { 290 | url = "https://github.com/ocaml/ocamlbuild/archive/0.14.0.tar.gz"; 291 | sha256 = "0y1fskw9rg2y1zgb7whv3v8v4xw04svgxslf3856q2aqd7lrrcl7"; 292 | }; 293 | opamInputs = { 294 | inherit (selection) ocaml; 295 | }; 296 | opamSrc = repoPath (repos.opam-repository.src) 297 | { 298 | package = "packages/ocamlbuild/ocamlbuild.0.14.0"; 299 | hash = "sha256:1r5qnrcxfypzyvjvb3zd9f66kr5ldxdl3g9rsi3pb6040krqb5w7"; 300 | }; 301 | }; 302 | ocamlfind = 303 | { 304 | pname = "ocamlfind"; 305 | version = "1.9.3"; 306 | src = pkgs.fetchurl 307 | { 308 | url = "http://download.camlcity.org/download/findlib-1.9.3.tar.gz"; 309 | sha256 = "0hfcwamcvinmww59b5i4yxbf0kxyzkp5qv3d1c7ybn9q52vgq463"; 310 | }; 311 | opamInputs = 312 | { 313 | inherit (selection) ocaml; 314 | graphics = selection.graphics or null; 315 | }; 316 | opamSrc = repoPath (repos.opam-repository.src) 317 | { 318 | package = "packages/ocamlfind/ocamlfind.1.9.3"; 319 | hash = "sha256:1wq1lkw3rpgilprlakznsxxf60mcidn3fzi3vjrgjdrnnp9hvh19"; 320 | }; 321 | }; 322 | ocplib-endian = 323 | { 324 | pname = "ocplib-endian"; 325 | version = "1.2"; 326 | src = pkgs.fetchurl 327 | { 328 | url = "https://github.com/OCamlPro/ocplib-endian/archive/refs/tags/1.2.tar.gz"; 329 | sha256 = "085kskr0cxcnv2d62n3jq1r273p7giisy56zfl26mm7amvl79blp"; 330 | }; 331 | opamInputs = 332 | { 333 | inherit (selection) ocaml dune cppo base-bytes; 334 | }; 335 | opamSrc = repoPath (repos.opam-repository.src) 336 | { 337 | package = "packages/ocplib-endian/ocplib-endian.1.2"; 338 | hash = "sha256:1jldvb471gyhkrzwfvqg722l3a8dc37hhhyas66skjzfmqyi2pnh"; 339 | }; 340 | }; 341 | ounit2 = 342 | { 343 | pname = "ounit2"; 344 | version = "2.2.4"; 345 | src = pkgs.fetchurl 346 | { 347 | url = "https://github.com/gildor478/ounit/releases/download/v2.2.4/ounit-v2.2.4.tbz"; 348 | sha256 = "0i9kiqbf2dp12c4qcvbn4abdpdp6h4g5z54ycsh0q8jpv6jnkh5m"; 349 | }; 350 | opamInputs = 351 | { 352 | inherit (selection) stdlib-shims ocaml dune base-unix base-bytes; 353 | }; 354 | opamSrc = repoPath (repos.opam-repository.src) 355 | { 356 | package = "packages/ounit2/ounit2.2.2.4"; 357 | hash = "sha256:1z5dlin5x6l5s7sbgkirsxgg00pr5l4gq7bdg2287kp4mlm5vgw0"; 358 | }; 359 | }; 360 | re = 361 | { 362 | pname = "re"; 363 | version = "1.10.3"; 364 | src = pkgs.fetchurl 365 | { 366 | url = "https://github.com/ocaml/ocaml-re/releases/download/1.10.3/re-1.10.3.tbz"; 367 | sha256 = "1fqfg609996bgxr14yyfxhvl6hm9c1j0mm2xjdjigqrzgyb4crc4"; 368 | }; 369 | opamInputs = { 370 | inherit (selection) seq ocaml dune; 371 | }; 372 | opamSrc = repoPath (repos.opam-repository.src) 373 | { 374 | package = "packages/re/re.1.10.3"; 375 | hash = "sha256:1f2xgscc07g800ia8z43i1p377dj2fjdrpzsqgzvw1dnplwnklya"; 376 | }; 377 | }; 378 | redis = 379 | { 380 | pname = "redis"; 381 | version = "0.5"; 382 | src = self.directSrc "redis"; 383 | opamInputs = 384 | { 385 | inherit (selection) uuidm re ocaml dune base-unix; 386 | }; 387 | opamSrc = "redis.opam"; 388 | }; 389 | redis-lwt = 390 | { 391 | pname = "redis-lwt"; 392 | version = "0.5"; 393 | src = self.directSrc "redis-lwt"; 394 | opamInputs = 395 | { 396 | inherit (selection) redis ocaml lwt dune; 397 | }; 398 | opamSrc = "redis-lwt.opam"; 399 | }; 400 | redis-sync = 401 | { 402 | pname = "redis-sync"; 403 | version = "0.5"; 404 | src = self.directSrc "redis-sync"; 405 | opamInputs = 406 | { 407 | inherit (selection) redis ocaml dune; 408 | }; 409 | opamSrc = "redis-sync.opam"; 410 | }; 411 | result = 412 | { 413 | pname = "result"; 414 | version = "1.5"; 415 | src = pkgs.fetchurl 416 | { 417 | url = "https://github.com/janestreet/result/releases/download/1.5/result-1.5.tbz"; 418 | sha256 = "0cpfp35fdwnv3p30a06wd0py3805qxmq3jmcynjc3x2qhlimwfkw"; 419 | }; 420 | opamInputs = { 421 | inherit (selection) ocaml dune; 422 | }; 423 | opamSrc = repoPath (repos.opam-repository.src) 424 | { 425 | package = "packages/result/result.1.5"; 426 | hash = "sha256:0ybmvlisfz5swvbcq855czz1ysv9zxmb79f1m0x8284hczmfm98f"; 427 | }; 428 | }; 429 | seq = 430 | { 431 | pname = "seq"; 432 | version = "base"; 433 | src = null; 434 | opamInputs = { 435 | inherit (selection) ocaml; 436 | }; 437 | opamSrc = repoPath (repos.opam-repository.src) 438 | { 439 | package = "packages/seq/seq.base"; 440 | hash = "sha256:1vm8mk6zm2q3fwnkprl6jib048zr4ysldw0bl74x6wwbxj0vx6k9"; 441 | }; 442 | }; 443 | stdlib-shims = 444 | { 445 | pname = "stdlib-shims"; 446 | version = "0.3.0"; 447 | src = pkgs.fetchurl 448 | { 449 | url = "https://github.com/ocaml/stdlib-shims/releases/download/0.3.0/stdlib-shims-0.3.0.tbz"; 450 | sha256 = "0jnqsv6pqp5b5g7lcjwgd75zqqvcwcl5a32zi03zg1kvj79p5gxs"; 451 | }; 452 | opamInputs = { 453 | inherit (selection) ocaml dune; 454 | }; 455 | opamSrc = repoPath (repos.opam-repository.src) 456 | { 457 | package = "packages/stdlib-shims/stdlib-shims.0.3.0"; 458 | hash = "sha256:19g9dnaxyh2ajz6pdczdsqzzvsmfrxwx6f613inkr31jw5hrqkiz"; 459 | }; 460 | }; 461 | topkg = 462 | { 463 | pname = "topkg"; 464 | version = "1.0.4"; 465 | src = pkgs.fetchurl 466 | { 467 | url = "https://erratique.ch/software/topkg/releases/topkg-1.0.4.tbz"; 468 | sha256 = "1kzw5cxkizcvh4rgzwgpjlj9hfxfk6yr686bxx6wrbsfs8as371k"; 469 | }; 470 | opamInputs = 471 | { 472 | inherit (selection) ocamlfind ocamlbuild ocaml; 473 | }; 474 | opamSrc = repoPath (repos.opam-repository.src) 475 | { 476 | package = "packages/topkg/topkg.1.0.4"; 477 | hash = "sha256:0gvngd4nayhhw02gcsljvmx6jkjpv9m3mqwpgimcfq04h0cf4knb"; 478 | }; 479 | }; 480 | uuidm = 481 | { 482 | pname = "uuidm"; 483 | version = "0.9.7"; 484 | src = pkgs.fetchurl 485 | { 486 | url = "https://erratique.ch/software/uuidm/releases/uuidm-0.9.7.tbz"; 487 | sha256 = "1ivxb3hxn9bk62rmixx6px4fvn52s4yr1bpla7rgkcn8981v45r8"; 488 | }; 489 | opamInputs = 490 | { 491 | inherit (selection) topkg ocamlfind ocamlbuild 492 | ocaml; 493 | cmdliner = selection.cmdliner or null; 494 | }; 495 | opamSrc = repoPath (repos.opam-repository.src) 496 | { 497 | package = "packages/uuidm/uuidm.0.9.7"; 498 | hash = "sha256:0gczj4p886wzyjr11x4wg5qwvj6lvzb1rnhy0l9ya7z01n51bkwr"; 499 | }; 500 | }; 501 | }; 502 | } 503 | 504 | -------------------------------------------------------------------------------- /nix/opam2nix.nix: -------------------------------------------------------------------------------- 1 | { source ? builtins.fetchTarball "https://github.com/timbertson/opam2nix/archive/v1.tar.gz" 2 | , pkgs 3 | , ocamlPackagesOverride }: 4 | import source { inherit pkgs ocamlPackagesOverride; } 5 | -------------------------------------------------------------------------------- /redis-lwt.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "Simon Cruanes" 3 | version: "0.8" 4 | authors: [ 5 | "Mike Wells" 6 | "David Höppner" 7 | "Aleksandr Dinu" 8 | ] 9 | homepage: "https://github.com/0xffea/ocaml-redis" 10 | bug-reports: "https://github.com/0xffea/ocaml-redis/issues" 11 | license: "BSD-3-Clause" 12 | tags: ["redis" "lwt"] 13 | dev-repo: "git+https://github.com/0xffea/ocaml-redis.git" 14 | synopsis: "Redis client (lwt interface)" 15 | build: [ 16 | ["dune" "build" "-p" name "-j" jobs] 17 | # ["dune" "runtest" "-p" name "-j" jobs] {with-test} # need network 18 | ["dune" "build" "@doc" "-p" name "-j" jobs] {with-doc} 19 | ] 20 | depends: [ 21 | "dune" {>= "1.0"} 22 | "redis" { = version } 23 | "lwt" {>= "2.4.7"} 24 | "ocaml" { >= "4.03.0" } 25 | "ounit2" {with-test} 26 | "containers" {with-test & >= "3.0"} 27 | "odoc" {with-doc} 28 | ] 29 | -------------------------------------------------------------------------------- /redis-sync.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | version: "0.8" 3 | maintainer: "Simon Cruanes" 4 | authors: [ 5 | "Mike Wells" 6 | "David Höppner" 7 | "Aleksandr Dinu" 8 | ] 9 | homepage: "https://github.com/0xffea/ocaml-redis" 10 | bug-reports: "https://github.com/0xffea/ocaml-redis/issues" 11 | license: "BSD-3-Clause" 12 | tags: ["redis" "unix"] 13 | synopsis: "Redis client (blocking)" 14 | dev-repo: "git+https://github.com/0xffea/ocaml-redis.git" 15 | build: [ 16 | ["dune" "build" "-p" name "-j" jobs] 17 | # ["dune" "runtest" "-p" name "-j" jobs] {with-test} # need network 18 | ["dune" "build" "@doc" "-p" name "-j" jobs] {with-doc} 19 | ] 20 | depends: [ 21 | "dune" {>= "1.0"} 22 | "redis" { = version } 23 | "ocaml" { >= "4.03.0" } 24 | "camlp-streams" 25 | "ounit2" {with-test} 26 | "containers" {with-test & >= "3.0"} 27 | "odoc" {with-doc} 28 | ] 29 | -------------------------------------------------------------------------------- /redis.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | version: "0.8" 3 | maintainer: "Simon Cruanes" 4 | authors: [ 5 | "Mike Wells" 6 | "David Höppner" 7 | "Aleksandr Dinu" 8 | ] 9 | homepage: "https://github.com/0xffea/ocaml-redis" 10 | bug-reports: "https://github.com/0xffea/ocaml-redis/issues" 11 | license: "BSD-3-Clause" 12 | tags: ["redis"] 13 | dev-repo: "git+https://github.com/0xffea/ocaml-redis.git" 14 | synopsis: "Redis client" 15 | build: [ 16 | ["dune" "build" "-p" name "-j" jobs] 17 | # ["dune" "runtest" "-p" name "-j" jobs] {with-test} # need network 18 | ["dune" "build" "@doc" "-p" name "-j" jobs] {with-doc} 19 | ] 20 | depends: [ 21 | "dune" { >= "1.0" } 22 | "base-unix" 23 | "uuidm" 24 | "stdlib-shims" 25 | "re" {>= "1.7.2"} 26 | "ocaml" { >= "4.03.0" } 27 | "containers" {with-test & >= "3.0"} 28 | "odoc" {with-doc} 29 | "containers" {with-test} 30 | "ounit2" {with-test} 31 | ] 32 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {} 2 | , ocamlVersion ? import ./nix/ocamlDefaultVersion.nix }: 3 | let 4 | ocamlPackages = pkgs.ocaml-ng."ocamlPackages_${ocamlVersion}"; 5 | local = pkgs.callPackage ./. { inherit ocamlVersion; }; 6 | in 7 | pkgs.mkShell { 8 | inputsFrom = with local; [ redis redis-lwt redis-sync ]; 9 | buildInputs = [ ocamlPackages.ocaml-lsp ocamlPackages.ocp-indent ] ++ local.testPackages; 10 | } 11 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | -------------------------------------------------------------------------------- /src/cache.ml: -------------------------------------------------------------------------------- 1 | module Make(IO : S.IO)(Client : S.Client with module IO = IO)(Params : S.Cache_params) = struct 2 | module IO = IO 3 | module Client = Client 4 | module Params = Params 5 | 6 | let (>|=) = IO.(>|=) 7 | let (>>=) = IO.(>>=) 8 | 9 | let set r key data = 10 | let key = Params.cache_key key in 11 | let data = Params.string_of_data data in 12 | (* atomic set+expire *) 13 | match Params.cache_expiration with 14 | | None -> Client.set r key data >|= fun _ -> () 15 | | Some ex -> Client.setex r key ex data >|= fun _ -> () 16 | 17 | let get r key = 18 | let key = Params.cache_key key in 19 | Client.get r key >>= fun value -> 20 | IO.return (Utils.Option.map Params.data_of_string value) 21 | 22 | let delete r key = 23 | let key = Params.cache_key key in 24 | IO.ignore_result (Client.del r [key]) 25 | end 26 | -------------------------------------------------------------------------------- /src/cache.mli: -------------------------------------------------------------------------------- 1 | (** 2 | Cache module 3 | *) 4 | module Make(IO : S.IO)(Client : S.Client with module IO = IO)(Params : S.Cache_params) : S.Cache 5 | with module IO = IO 6 | with module Client = Client 7 | with module Params = Params 8 | -------------------------------------------------------------------------------- /src/client.mli: -------------------------------------------------------------------------------- 1 | (** 2 | Redis client 3 | *) 4 | module Make(IO : S.IO) : S.Client with module IO = IO 5 | 6 | (** 7 | Redis cluster client 8 | *) 9 | module MakeCluster(IO : S.IO) : S.Client with module IO = IO 10 | -------------------------------------------------------------------------------- /src/crc16.ml: -------------------------------------------------------------------------------- 1 | let table = [| 2 | 0x0000;0x1021;0x2042;0x3063;0x4084;0x50a5;0x60c6;0x70e7; 3 | 0x8108;0x9129;0xa14a;0xb16b;0xc18c;0xd1ad;0xe1ce;0xf1ef; 4 | 0x1231;0x0210;0x3273;0x2252;0x52b5;0x4294;0x72f7;0x62d6; 5 | 0x9339;0x8318;0xb37b;0xa35a;0xd3bd;0xc39c;0xf3ff;0xe3de; 6 | 0x2462;0x3443;0x0420;0x1401;0x64e6;0x74c7;0x44a4;0x5485; 7 | 0xa56a;0xb54b;0x8528;0x9509;0xe5ee;0xf5cf;0xc5ac;0xd58d; 8 | 0x3653;0x2672;0x1611;0x0630;0x76d7;0x66f6;0x5695;0x46b4; 9 | 0xb75b;0xa77a;0x9719;0x8738;0xf7df;0xe7fe;0xd79d;0xc7bc; 10 | 0x48c4;0x58e5;0x6886;0x78a7;0x0840;0x1861;0x2802;0x3823; 11 | 0xc9cc;0xd9ed;0xe98e;0xf9af;0x8948;0x9969;0xa90a;0xb92b; 12 | 0x5af5;0x4ad4;0x7ab7;0x6a96;0x1a71;0x0a50;0x3a33;0x2a12; 13 | 0xdbfd;0xcbdc;0xfbbf;0xeb9e;0x9b79;0x8b58;0xbb3b;0xab1a; 14 | 0x6ca6;0x7c87;0x4ce4;0x5cc5;0x2c22;0x3c03;0x0c60;0x1c41; 15 | 0xedae;0xfd8f;0xcdec;0xddcd;0xad2a;0xbd0b;0x8d68;0x9d49; 16 | 0x7e97;0x6eb6;0x5ed5;0x4ef4;0x3e13;0x2e32;0x1e51;0x0e70; 17 | 0xff9f;0xefbe;0xdfdd;0xcffc;0xbf1b;0xaf3a;0x9f59;0x8f78; 18 | 0x9188;0x81a9;0xb1ca;0xa1eb;0xd10c;0xc12d;0xf14e;0xe16f; 19 | 0x1080;0x00a1;0x30c2;0x20e3;0x5004;0x4025;0x7046;0x6067; 20 | 0x83b9;0x9398;0xa3fb;0xb3da;0xc33d;0xd31c;0xe37f;0xf35e; 21 | 0x02b1;0x1290;0x22f3;0x32d2;0x4235;0x5214;0x6277;0x7256; 22 | 0xb5ea;0xa5cb;0x95a8;0x8589;0xf56e;0xe54f;0xd52c;0xc50d; 23 | 0x34e2;0x24c3;0x14a0;0x0481;0x7466;0x6447;0x5424;0x4405; 24 | 0xa7db;0xb7fa;0x8799;0x97b8;0xe75f;0xf77e;0xc71d;0xd73c; 25 | 0x26d3;0x36f2;0x0691;0x16b0;0x6657;0x7676;0x4615;0x5634; 26 | 0xd94c;0xc96d;0xf90e;0xe92f;0x99c8;0x89e9;0xb98a;0xa9ab; 27 | 0x5844;0x4865;0x7806;0x6827;0x18c0;0x08e1;0x3882;0x28a3; 28 | 0xcb7d;0xdb5c;0xeb3f;0xfb1e;0x8bf9;0x9bd8;0xabbb;0xbb9a; 29 | 0x4a75;0x5a54;0x6a37;0x7a16;0x0af1;0x1ad0;0x2ab3;0x3a92; 30 | 0xfd2e;0xed0f;0xdd6c;0xcd4d;0xbdaa;0xad8b;0x9de8;0x8dc9; 31 | 0x7c26;0x6c07;0x5c64;0x4c45;0x3ca2;0x2c83;0x1ce0;0x0cc1; 32 | 0xef1f;0xff3e;0xcf5d;0xdf7c;0xaf9b;0xbfba;0x8fd9;0x9ff8; 33 | 0x6e17;0x7e36;0x4e55;0x5e74;0x2e93;0x3eb2;0x0ed1;0x1ef0; 34 | |] 35 | 36 | let crc16 s = 37 | let char_list = ref [] in 38 | let () = String.iter (fun c -> char_list := Char.code c :: !char_list) s in 39 | let char_list = List.rev !char_list in 40 | List.fold_left (fun crc c -> (((crc lsl 8) land 0xffff) lxor table.(((crc lsr 8) lxor c) land 0xff))) 0 char_list 41 | -------------------------------------------------------------------------------- /src/crc16.mli: -------------------------------------------------------------------------------- 1 | (** 2 | This is the CRC16 algorithm used by Redis Cluster to hash keys. 3 | Implementation according to CCITT standards. 4 | 5 | This is actually the XMODEM CRC 16 algorithm, using the 6 | following parameters: 7 | 8 | Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN" 9 | Width : 16 bit 10 | Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1) 11 | Initialization : 0000 12 | Reflect Input byte : False 13 | Reflect Output CRC : False 14 | Xor constant to output CRC : 0000 15 | Output for "123456789" : 31C3 16 | *) 17 | val crc16 : string -> int 18 | -------------------------------------------------------------------------------- /src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name redis) 3 | (public_name redis) 4 | (flags :standard -safe-string -warn-error -3-32-34) 5 | (libraries re.str uuidm unix stdlib-shims)) 6 | -------------------------------------------------------------------------------- /src/mutex.ml: -------------------------------------------------------------------------------- 1 | module Make(IO : S.IO)(Client : S.Client with module IO = IO) = struct 2 | module IO = IO 3 | module Client = Client 4 | 5 | exception Error of string 6 | 7 | let (>>=) = IO.(>>=) 8 | 9 | let acquire conn ?(atime=10.) ?(ltime=10) mutex id = 10 | let etime = Unix.time() +. atime in 11 | 12 | let update_ttl () = 13 | Client.ttl conn mutex >>= function 14 | | None -> Client.expire conn mutex ltime >>= fun _ -> IO.return () 15 | | _ -> IO.return () in 16 | 17 | let rec loop () = 18 | Client.setnx conn mutex id >>= function 19 | | true -> Client.expire conn mutex ltime >>= fun _ -> IO.return () 20 | | _ -> update_ttl () >>= fun _ -> 21 | if Unix.time() < etime then IO.sleep(0.1) >>= loop 22 | else IO.fail (Error ("could not acquire lock " ^ mutex)) 23 | in 24 | loop () 25 | 26 | let release conn mutex id = 27 | Client.watch conn [mutex] >>= fun _ -> 28 | Client.get conn mutex >>= function 29 | | Some x when x = id -> 30 | Client.multi conn >>= fun _ -> 31 | Client.queue (fun () -> Client.del conn [mutex]) >>= fun _ -> 32 | Client.exec conn >>= fun _ -> 33 | IO.return () 34 | | _ -> 35 | Client.unwatch conn >>= fun _ -> 36 | IO.fail (Error ("lock was lost: " ^ mutex)) 37 | 38 | let with_mutex conn ?atime ?ltime mutex fn = 39 | let id = Uuidm.(to_string (create `V4) [@ocaml.alert "-deprecated"]) in 40 | acquire conn ?atime ?ltime mutex id >>= fun _ -> 41 | IO.catch 42 | (* try *) (fun () -> 43 | fn () >>= fun res -> 44 | release conn mutex id >>= fun _ -> 45 | IO.return res) 46 | (* catch *) (function e -> 47 | release conn mutex id >>= fun _ -> 48 | IO.fail e) 49 | end 50 | -------------------------------------------------------------------------------- /src/mutex.mli: -------------------------------------------------------------------------------- 1 | (** 2 | Mutex manager 3 | *) 4 | module Make(IO : S.IO)(Client : S.Client with module IO = IO) : S.Mutex 5 | with module IO = IO 6 | with module Client = Client 7 | -------------------------------------------------------------------------------- /src/pool.ml: -------------------------------------------------------------------------------- 1 | 2 | 3 | module Make(IO : S.IO)(Client : S.Client with module IO=IO) 4 | : S.POOL with module IO = IO and module Client = Client 5 | = struct 6 | module IO = IO 7 | module Client = Client 8 | 9 | open IO 10 | 11 | type t = { 12 | mutex: IO.mutex; 13 | condition: IO.condition; (* for threads waiting for a connection *) 14 | pool: Client.connection Queue.t; (* connections available *) 15 | spec: Client.connection_spec; 16 | size: int; 17 | mutable closed: bool; (* once true, no query accepted *) 18 | } 19 | 20 | let size self = self.size 21 | 22 | (* initialize [i] connections *) 23 | let rec init_conns (self:t) i : unit IO.t = 24 | if i<=0 then IO.return () 25 | else ( 26 | Client.connect self.spec >>= fun c -> 27 | Queue.push c self.pool; 28 | init_conns self (i-1) 29 | ) 30 | 31 | let create ~size spec : t IO.t = 32 | if size < 1 then invalid_arg "pool.create: size >= 1 required"; 33 | let self = { 34 | mutex=IO.mutex_create (); 35 | condition=IO.condition_create(); 36 | pool=Queue.create (); 37 | spec; 38 | size; 39 | closed = false; 40 | } in 41 | init_conns self size >>= fun () -> 42 | IO.return self 43 | 44 | let close (self:t) : unit IO.t = 45 | self.closed <- true; (* should always be atomic *) 46 | (* wake up waiters eagerly, to have them die earlier *) 47 | IO.condition_broadcast self.condition; 48 | (* close remaining connections *) 49 | let rec close_conns_in_pool_ () = 50 | if Queue.is_empty self.pool then IO.return () 51 | else ( 52 | let c = Queue.pop self.pool in 53 | Client.disconnect c >>= close_conns_in_pool_ 54 | ) 55 | in 56 | close_conns_in_pool_ () 57 | 58 | let with_pool ~size spec f : _ IO.t = 59 | create ~size spec >>= fun pool -> 60 | IO.try_bind 61 | (fun () -> f pool) 62 | (fun x -> close pool >|= fun () -> x) 63 | (fun e -> close pool >>= fun () -> IO.fail e) 64 | 65 | (* release a connection back into the pool, or close it if the 66 | pool is closed. *) 67 | let release_conn_ (self:t) (c:Client.connection) : unit IO.t = 68 | IO.mutex_with self.mutex 69 | (fun () -> 70 | if self.closed then ( 71 | (* close connection *) 72 | Client.disconnect c 73 | ) else ( 74 | (* release connection, and potentially wake up a waiter to grab it *) 75 | Queue.push c self.pool; 76 | IO.condition_signal self.condition; 77 | IO.return () 78 | ) 79 | ) 80 | 81 | (* open a new connection and put it into the pool *) 82 | let reopen_conn_ (self:t) : unit IO.t = 83 | Client.connect self.spec >>= release_conn_ self 84 | 85 | let rec with_connection (self:t) (f: _ -> 'a IO.t) : 'a IO.t = 86 | if self.closed then IO.fail (Failure "pool closed") 87 | else ( 88 | (* try to acquire a connection *) 89 | IO.mutex_with self.mutex 90 | (fun () -> 91 | if Queue.is_empty self.pool then ( 92 | IO.condition_wait self.condition self.mutex >|= fun () -> 93 | None 94 | ) else ( 95 | let c = Queue.pop self.pool in 96 | IO.return (Some c) 97 | )) 98 | >>= function 99 | | None -> with_connection self f (* try again *) 100 | | Some c -> 101 | (* run [f c], and be sure to cleanup afterwards *) 102 | IO.try_bind 103 | (fun () -> f c) 104 | (fun x -> release_conn_ self c >|= fun () -> x) 105 | (fun e -> 106 | (* close [c] and reopen a new one instead; 107 | could have been interrupted during a transfer! *) 108 | let fut1 = reopen_conn_ self in 109 | let fut2 = Client.disconnect c in 110 | fut1 >>= fun () -> 111 | fut2 >>= fun () -> 112 | IO.fail e) 113 | ) 114 | 115 | end 116 | -------------------------------------------------------------------------------- /src/pool.mli: -------------------------------------------------------------------------------- 1 | 2 | module Make(IO : S.IO)(Client : S.Client with module IO=IO) 3 | : S.POOL with module IO = IO and module Client = Client 4 | -------------------------------------------------------------------------------- /src/s.ml: -------------------------------------------------------------------------------- 1 | module type IO = sig 2 | type 'a t 3 | 4 | type fd 5 | type in_channel 6 | type out_channel 7 | 8 | type 'a stream 9 | type stream_count 10 | 11 | val getaddrinfo : string -> string -> Unix.getaddrinfo_option list -> Unix.addr_info list t 12 | val connect : Unix.socket_domain -> Unix.sockaddr -> fd t 13 | val close : fd -> unit t 14 | val sleep : float -> unit t 15 | 16 | val (>>=) : 'a t -> ('a -> 'b t) -> 'b t 17 | val (>|=) : 'a t -> ('a -> 'b) -> 'b t 18 | val catch : (unit -> 'a t) -> (exn -> 'a t) -> 'a t 19 | val try_bind : (unit -> 'a t) -> ('a -> 'b t) -> (exn -> 'b t) -> 'b t 20 | val ignore_result : 'a t -> unit 21 | val return : 'a -> 'a t 22 | val fail : exn -> 'a t 23 | val run : 'a t -> 'a 24 | val atomic : (in_channel -> 'a t) -> in_channel -> 'a t 25 | 26 | val in_channel_of_descr : fd -> in_channel 27 | val out_channel_of_descr : fd -> out_channel 28 | val input_char : in_channel -> char t 29 | val really_input : in_channel -> bytes -> int -> int -> unit t 30 | val output_string : out_channel -> string -> unit t 31 | val flush : out_channel -> unit t 32 | 33 | val iter : ('a -> unit t) -> 'a list -> unit t 34 | val iter_serial : ('a -> unit t) -> 'a list -> unit t 35 | val map : ('a -> 'b t) -> 'a list -> 'b list t 36 | val map_serial : ('a -> 'b t) -> 'a list -> 'b list t 37 | val fold_left : ('a -> 'b -> 'a t) -> 'a -> 'b list -> 'a t 38 | 39 | val stream_from : (stream_count -> 'b option t) -> 'b stream 40 | val stream_next: 'a stream -> 'a t 41 | 42 | type mutex 43 | val mutex_create : unit -> mutex 44 | val mutex_with : mutex -> (unit -> 'a t) -> 'a t 45 | 46 | type condition 47 | val condition_create : unit -> condition 48 | val condition_wait : condition -> mutex -> unit t 49 | val condition_signal : condition -> unit 50 | val condition_broadcast: condition -> unit 51 | end 52 | 53 | module type Client = sig 54 | module IO : IO 55 | 56 | module StringBound : sig 57 | type t = NegInfinity | PosInfinity | Exclusive of string | Inclusive of string 58 | 59 | val to_string : t -> string 60 | end 61 | 62 | module FloatBound : sig 63 | type t = NegInfinity | PosInfinity | Exclusive of float | Inclusive of float 64 | 65 | val to_string : t -> string 66 | end 67 | 68 | (** {2 Types and exceptions } *) 69 | 70 | type redirection = { 71 | slot: int; 72 | host: string; 73 | port: int; 74 | } 75 | 76 | type reply = [ 77 | | `Status of string 78 | | `Error of string 79 | | `Int of int 80 | | `Int64 of Int64.t 81 | | `Bulk of string option 82 | | `Multibulk of reply list 83 | | `Ask of redirection 84 | | `Moved of redirection 85 | ] 86 | 87 | val string_of_reply : reply -> string 88 | (** For debugging purpose. 89 | @since 0.4 *) 90 | 91 | (** Server connection info *) 92 | type connection_spec = { 93 | host : string; 94 | port : int; 95 | } 96 | 97 | val string_of_connection_spec : connection_spec -> string 98 | (** Print the spec 99 | @since 0.8 *) 100 | 101 | val pp_connection_spec : Format.formatter -> connection_spec -> unit 102 | (** Print the spec 103 | @since 0.8 *) 104 | 105 | val connection_spec : ?port:int -> string -> connection_spec 106 | (** Create a connection spec with the given host. 107 | @param port port to connect to (default [6379]) 108 | @since 0.5 *) 109 | 110 | val connection_spec_unix_socket : string -> connection_spec 111 | (** Create a connection spec to use the given Unix socket. 112 | @since 0.7 *) 113 | 114 | module SlotMap : Map.S with type key = int 115 | module ConnectionSpecMap : Map.S with type key = connection_spec 116 | 117 | type cluster_connections = private { 118 | mutable connections_spec : connection_spec SlotMap.t; 119 | mutable connections : connection ConnectionSpecMap.t; 120 | } 121 | and connection = private { 122 | fd : IO.fd; 123 | in_ch : IO.in_channel; 124 | out_ch : IO.out_channel; 125 | stream : reply list IO.stream; 126 | cluster : cluster_connections; 127 | } 128 | 129 | (** Error responses from server *) 130 | exception Redis_error of string 131 | 132 | (** Protocol errors *) 133 | exception Unexpected of reply 134 | exception Unrecognized of string * string (* explanation, data *) 135 | 136 | (** Possible BITOP operations *) 137 | type bit_operation = AND | OR | XOR | NOT 138 | 139 | (** {2 Connection handling } *) 140 | 141 | val connect : connection_spec -> connection IO.t 142 | val disconnect : connection -> unit IO.t 143 | val with_connection : connection_spec -> (connection -> 'a IO.t) -> 'a IO.t 144 | val stream : connection -> reply list IO.stream 145 | 146 | (** {2 Connection commands } *) 147 | 148 | (** Authenticate to server. *) 149 | val auth : connection -> string -> unit IO.t 150 | 151 | (** Sends a custom request to the Redis server. Example: [ send_request connection ["set"; "foo"; "bar"] ] @since 0.6*) 152 | val send_custom_request : connection -> string list -> reply IO.t 153 | 154 | val send_pipelined_custom_requests : connection -> string list list -> reply list IO.t 155 | (** Send a list of custom requests in a pipelined fashion (all are written, then all 156 | replies are read) 157 | @since 0.8 *) 158 | 159 | (** Authenticate to server with username and password. *) 160 | val auth_acl : connection -> string -> string -> unit IO.t 161 | 162 | (** Echo given string. *) 163 | val echo : connection -> string -> string option IO.t 164 | 165 | (** Ping connection; returns [ true ] if ping was successfull. *) 166 | val ping : connection -> bool IO.t 167 | 168 | (** Close connection. *) 169 | val quit : connection -> unit IO.t 170 | 171 | (** Switch to a different db; raises {!Error} if index is invalid. *) 172 | val select : connection -> int -> unit IO.t 173 | 174 | (** {2 SENTINEL commands } *) 175 | val sentinel_masters : connection -> (string * string) list list IO.t 176 | val sentinel_get_master_addr_by_name : connection -> string -> (string * string) option IO.t 177 | 178 | (** {2 Keys commands} *) 179 | 180 | (** Delete a key; returns the number of keys removed. *) 181 | val del : connection -> string list -> int IO.t 182 | 183 | (** Determine if a key exists. *) 184 | val exists : connection -> string -> bool IO.t 185 | 186 | (** Set a key's time to live in seconds; returns [ true ] if timeout was set, false otherwise. *) 187 | val expire : connection -> string -> int -> bool IO.t 188 | 189 | (** Set a key's time to live in milliseconds; returns [ true ] if timeout was set, false otherwise. *) 190 | val pexpire : connection -> string -> int -> bool IO.t 191 | 192 | (** Set the expiration for a key as a UNIX timestamp, the time is truncated to the nearest second; returns [ true ] if timeout was set, [ false ] otherwise. *) 193 | val expireat : connection -> string -> float -> bool IO.t 194 | 195 | (** Set the expiration for a key as a UNIX timestamp in milliseconds; returns [ true ] if timeout was set, [ false ] otherwise. *) 196 | val pexpireat : connection -> string -> int -> bool IO.t 197 | 198 | (** Find all keys matching the given pattern. *) 199 | val keys : connection -> string -> string list IO.t 200 | 201 | (** Incrementally iterate the keys space; see tests for usage example. *) 202 | val scan : ?pattern:string -> ?count:int -> connection -> int -> (int * string list) IO.t 203 | 204 | (** Move key to a different db; returns [ true ] if key was moved, [ false ] otherwise. *) 205 | val move : connection -> string -> int -> bool IO.t 206 | 207 | (** Remove timeout on key; returns [ true ] if timeout was removed, [ false ] otherwise. *) 208 | val persist : connection -> string -> bool IO.t 209 | 210 | (** Return a random key from the keyspace; returns [ None ] if db is empty. *) 211 | val randomkey : connection -> string option IO.t 212 | 213 | (** Rename a key; raises {!Error} if key doesn't exist. *) 214 | val rename : connection -> string -> string -> unit IO.t 215 | 216 | (** Rename a key, only if the new key does not exist; returns [ true ] if key was renamed, [ false ] if newkey already exists. *) 217 | val renamenx : connection -> string -> string -> bool IO.t 218 | 219 | (** Sort elements in a list, set or sorted set; return sorted list of items. *) 220 | val sort : 221 | connection -> 222 | ?by:string -> 223 | ?limit:int * int -> 224 | ?get:'a list -> 225 | ?order:[< `Asc | `Desc ] -> ?alpha:bool -> string -> string list IO.t 226 | 227 | (** Sort and store elements in a list, set or sorted set; returns length of sorted items list which was stored. *) 228 | val sort_and_store : 229 | connection -> 230 | ?by:string -> 231 | ?limit:int * int -> 232 | ?get:'a list -> 233 | ?order:[< `Asc | `Desc ] -> 234 | ?alpha:bool -> string -> string -> int IO.t 235 | 236 | (** Time to live for a key in seconds; returns [ None ] if key doesn't exist or doesn't have a timeout. *) 237 | val ttl : connection -> string -> int option IO.t 238 | 239 | (** Time to live for a key in milliseconds; returns [ None ] if key doesn't exist or doesn't have a timeout. *) 240 | val pttl : connection -> string -> int option IO.t 241 | 242 | (** Determine the type stored as key. *) 243 | val type_of : connection -> string -> [> `Hash | `List | `None | `String | `Zset ] IO.t 244 | 245 | (** Return a serialized version of the value stored at the specified key; returns [ None ] if key doesn't exist. *) 246 | val dump: connection -> string -> string option IO.t 247 | 248 | (** Create a key with serialized value (obtained via DUMP). *) 249 | val restore: connection -> string -> int -> string -> unit IO.t 250 | 251 | (** Atomically transfer a key from a source Redis instance to a destination Redis instance. *) 252 | val migrate : connection -> 253 | ?copy:bool -> ?replace:bool -> 254 | string -> int -> string -> int -> int -> 255 | unit IO.t 256 | 257 | (** Inspect the internals of Redis objects; returns the number of references of the value associated with the specified key. *) 258 | val object_refcount: connection -> string -> int option IO.t 259 | 260 | (** Inspect the internals of Redis objects; returns the kind of internal representation used in order to store the value associated with a key. *) 261 | val object_encoding: connection -> string -> string option IO.t 262 | 263 | (** Inspect the internals of Redis objects; returns the number of seconds since the object stored at the specified key is idle. *) 264 | val object_idletime: connection -> string -> int option IO.t 265 | 266 | (** {2 String commands} *) 267 | 268 | (** Append a value to a key; returns length of string after append. *) 269 | val append : connection -> string -> string -> int IO.t 270 | 271 | (** Sets or clears the bit at offset in the string value stored at key. *) 272 | val setbit : connection -> string -> int -> int -> int IO.t 273 | 274 | (** Returns the bit value at offset in the string value stored at key. *) 275 | val getbit : connection -> string -> int -> int IO.t 276 | 277 | (** Perform a bitwise operation between multiple keys (containing string values) and store the result in the destination key. 278 | See {!bit_operation} type for available operations. *) 279 | val bitop : connection -> bit_operation -> string -> string list -> int IO.t 280 | 281 | (** Count the number of set bits (population counting) in a string. *) 282 | val bitcount : ?first:int -> ?last:int -> connection -> string -> int IO.t 283 | 284 | (** Return the position of the first bit set to 1 or 0 in a string. *) 285 | val bitpos : ?first:int -> ?last:int -> connection -> string -> int -> int IO.t 286 | 287 | (** Decrements the number stored at key by one. If the key does not exist, it is set to 0 before performing the operation. *) 288 | val decr : connection -> string -> int IO.t 289 | 290 | (** Decrements the number stored at key by decrement. If the key does not exist, it is set to 0 before performing the operation. *) 291 | val decrby : connection -> string -> int -> int IO.t 292 | 293 | (** Get the value of key. *) 294 | val get : connection -> string -> string option IO.t 295 | 296 | (** Returns the substring of the string value stored at key, determined by the offsets start and end (both are inclusive). *) 297 | val getrange : connection -> string -> int -> int -> string option IO.t 298 | 299 | (** Atomically sets key to value and returns the old value stored at key. Returns [ None ] when key exists but does not hold a string value. *) 300 | val getset : connection -> string -> string -> string option IO.t 301 | 302 | (** Increments the number stored at key by one. If the key does not exist, it is set to 0 before performing the operation. *) 303 | val incr : connection -> string -> int IO.t 304 | 305 | (** Increments the number stored at key by increment. If the key does not exist, it is set to 0 before performing the operation. *) 306 | val incrby : connection -> string -> int -> int IO.t 307 | 308 | (** Increment the string representing a floating point number stored at key by the specified increment. If the key does not exist, it is set to 0 before performing the operation. *) 309 | val incrbyfloat : connection -> string -> float -> float IO.t 310 | 311 | (** Returns the values of all specified keys. *) 312 | val mget : connection -> string list -> string option list IO.t 313 | 314 | (** Sets the given keys to their respective values. *) 315 | val mset : connection -> (string * string) list -> unit IO.t 316 | 317 | (** Sets the given keys to their respective values. MSETNX will not perform any operation at all even if just a single key already exists. *) 318 | val msetnx : connection -> (string * string) list -> bool IO.t 319 | 320 | (** Set key to hold the string value. *) 321 | val set : 322 | connection -> 323 | ?ex:int -> ?px:int -> ?nx:bool -> ?xx:bool -> 324 | string -> string -> bool IO.t 325 | 326 | (** Set key to hold the string value and set key to timeout after a given number of seconds. *) 327 | val setex : connection -> string -> int -> string -> unit IO.t 328 | 329 | (** PSETEX works exactly like SETEX with the sole difference that the expire time is specified in milliseconds instead of seconds. *) 330 | val psetex : connection -> string -> int -> string -> unit IO.t 331 | 332 | (** Set key to hold string value if key does not exist. *) 333 | val setnx : connection -> string -> string -> bool IO.t 334 | 335 | (** Overwrites part of the string stored at key, starting at the specified offset, for the entire length of value. *) 336 | val setrange : connection -> string -> int -> string -> int IO.t 337 | 338 | (** Returns the length of the string value stored at key. An error is returned when key holds a non-string value. *) 339 | val strlen : connection -> string -> int IO.t 340 | 341 | (** {2 Hash commands} *) 342 | 343 | (** Removes the specified fields from the hash stored at key. Specified fields that do not exist within this hash are ignored. *) 344 | val hdel : connection -> string -> string -> bool IO.t 345 | 346 | (** Returns if field is an existing field in the hash stored at key. *) 347 | val hexists : connection -> string -> string -> bool IO.t 348 | 349 | (** Returns the value associated with field in the hash stored at key. *) 350 | val hget : connection -> string -> string -> string option IO.t 351 | 352 | (** Returns all fields and values of the hash stored at key. *) 353 | val hgetall : connection -> string -> (string * string) list IO.t 354 | 355 | (** Increments the number stored at field in the hash stored at key by increment. *) 356 | val hincrby : connection -> string -> string -> int -> int IO.t 357 | 358 | (** Increments the number stored at field in the hash stored at key by increment. *) 359 | val hincrbyfloat : connection -> string -> string -> float -> float IO.t 360 | 361 | (** Returns all field names in the hash stored at key. *) 362 | val hkeys : connection -> string -> string list IO.t 363 | 364 | (** Returns the number of fields contained in the hash stored at key. *) 365 | val hlen : connection -> string -> int IO.t 366 | 367 | (** Returns the values associated with the specified fields in the hash stored at key. *) 368 | val hmget : connection -> string -> string list -> string option list IO.t 369 | 370 | (** Sets the specified fields to their respective values in the hash stored at key. *) 371 | val hmset : connection -> string -> (string * string) list -> unit IO.t 372 | 373 | (** Sets field in the hash stored at key to value. *) 374 | val hset : connection -> string -> string -> string -> bool IO.t 375 | 376 | (** Sets field in the hash stored at key to value, only if field does not yet exist. *) 377 | val hsetnx : connection -> string -> string -> string -> bool IO.t 378 | 379 | (** Get the length of the value of a hash field *) 380 | val hstrlen : connection -> string -> string -> int IO.t 381 | 382 | (** Incrementally iterate hash fields and associated values *) 383 | val hscan : ?pattern:string -> ?count:int -> connection -> string -> int -> (int * (string * string) list) IO.t 384 | 385 | (** Returns all values in the hash stored at key. *) 386 | val hvals : connection -> string -> string list IO.t 387 | 388 | (** {2 List commands} *) 389 | 390 | (** Remove and get the first element in a list, or block until one is available *) 391 | val blpop : connection -> string list -> int -> (string * string) option IO.t 392 | 393 | (** Remove and get the last element in a list, or block until one is available *) 394 | val brpop : connection -> string list -> int -> (string * string) option IO.t 395 | 396 | (** Pop a value from a list, push it to another list and return it; or block until one is available *) 397 | val brpoplpush : connection -> string -> string -> int -> string option IO.t 398 | 399 | (** Get an element from a list by its index *) 400 | val lindex : connection -> string -> int -> string option IO.t 401 | 402 | (** Insert an element before or after another element in a list *) 403 | val linsert : connection -> string -> [< `After | `Before ] -> string -> string -> int option IO.t 404 | 405 | (** Get the length of a list *) 406 | val llen : connection -> string -> int IO.t 407 | 408 | (** Remove and get the first element in a list *) 409 | val lpop : connection -> string -> string option IO.t 410 | 411 | (** Prepend one or multiple values to a list *) 412 | val lpush : connection -> string -> string list -> int IO.t 413 | 414 | (** Prepend a value to a list, only if the list exists *) 415 | val lpushx : connection -> string -> string list -> int IO.t 416 | 417 | (** Get a range of elements from a list *) 418 | val lrange : connection -> string -> int -> int -> string list IO.t 419 | 420 | (** Remove elements from a list *) 421 | val lrem : connection -> string -> int -> string -> int IO.t 422 | 423 | (** Set the value of an element in a list by its index *) 424 | val lset : connection -> string -> int -> string -> unit IO.t 425 | 426 | (** Trim a list to the specified range *) 427 | val ltrim : connection -> string -> int -> int -> unit IO.t 428 | 429 | (** Remove and get the last element in a list *) 430 | val rpop : connection -> string -> string option IO.t 431 | 432 | (** Remove the last element in a list, prepend it to another list and return it *) 433 | val rpoplpush : connection -> string -> string -> string option IO.t 434 | 435 | (** Append one or multiple values to a list *) 436 | val rpush : connection -> string -> string list -> int IO.t 437 | 438 | (** Append a value to a list, only if the list exists *) 439 | val rpushx : connection -> string -> string list -> int IO.t 440 | 441 | val lmove : connection -> 442 | string -> string -> 443 | [`Left | `Right] -> [`Left | `Right] -> 444 | string option IO.t 445 | (** [lmove from into sidefrom sideinto] moves an element from [from] 446 | into [into], picking which side to pop/push based on the last arguments, 447 | and returns the element. 448 | @since 0.6 449 | since redis 6.2 *) 450 | 451 | val blmove : connection -> 452 | string -> string -> 453 | [`Left | `Right] -> [`Left | `Right] -> 454 | timeout:int -> 455 | string option IO.t 456 | (** same as {!lmove} but blocks for up to [timeout] seconds. *) 457 | 458 | (** {2 HyperLogLog commands} *) 459 | 460 | (** Adds values to the HyperLogLog data structure. *) 461 | val pfadd : connection -> string -> string list -> bool IO.t 462 | 463 | (** Returns the approximated cardinality of the union of the HyperLogLogs passed. *) 464 | val pfcount : connection -> string list -> int IO.t 465 | 466 | (** Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of the observed Sets of the source HyperLogLog structures. *) 467 | val pfmerge : connection -> string list -> unit IO.t 468 | 469 | (** {2 Set commands} *) 470 | 471 | (** Returns true if member was added, false otherwise. *) 472 | val sadd : connection -> string -> string -> bool IO.t 473 | 474 | val scard : connection -> string -> int IO.t 475 | 476 | (** Difference between first and all successive sets. *) 477 | val sdiff : connection -> string list -> string list IO.t 478 | 479 | (** like sdiff, but store result in destination. returns size of result. *) 480 | val sdiffstore : connection -> string -> string list -> int IO.t 481 | 482 | val sinter : connection -> string list -> string list IO.t 483 | 484 | (** Like SINTER, but store result in destination. Returns size of result. *) 485 | val sinterstore : connection -> string -> string list -> int IO.t 486 | 487 | val sismember : connection -> string -> string -> bool IO.t 488 | 489 | val smembers : connection -> string -> string list IO.t 490 | 491 | (** Returns true if an element was moved, false otherwise. *) 492 | val smove : connection -> string -> string -> string -> bool IO.t 493 | 494 | (** Remove random element from set. *) 495 | val spop : connection -> string -> string option IO.t 496 | 497 | (** Like SPOP, but doesn't remove chosen element. *) 498 | val srandmember : connection -> string -> string option IO.t 499 | 500 | (** Returns true if element was removed. *) 501 | val srem : connection -> string -> string -> bool IO.t 502 | 503 | val sunion : connection -> string list -> string list IO.t 504 | 505 | (** Like SUNION, but store result in destination. Returns size of result. *) 506 | val sunionstore : connection -> string -> string list -> int IO.t 507 | 508 | (** {2 Pub/sub commands} *) 509 | 510 | (** Post a message to a channel. Returns number of clients that received the message. *) 511 | val publish : connection -> string -> string -> int IO.t 512 | 513 | (** Lists the currently active channels. If no pattern is specified, all channels are listed. *) 514 | val pubsub_channels : connection -> string option -> reply list IO.t 515 | 516 | (** Returns the number of subscribers (not counting clients subscribed to patterns) for the specified channels. *) 517 | val pubsub_numsub : connection -> string list -> reply list IO.t 518 | 519 | (** Subscribes the client to the specified channels. 520 | 521 | From now on only pubsub related commands are accepted on the connection. 522 | Use {!stream} to read the stream the notifications (possibly-concurrently). 523 | Each notification in the form of [ ["message"; ""; ""] ] for 524 | subscribed messages, or [ ["subscribe"; ] ] or [ ["unsubscribe"; string list -> unit IO.t 529 | 530 | (** Unsubscribes the client from the given channels, or from all of them if an empty list is given *) 531 | val unsubscribe : connection -> string list -> unit IO.t 532 | 533 | (** Subscribes the client to the given patterns. See {!subscribe} for more details. *) 534 | val psubscribe : connection -> string list -> unit IO.t 535 | 536 | (** Unsubscribes the client from the given patterns. *) 537 | val punsubscribe : connection -> string list -> unit IO.t 538 | 539 | (** {2 Sorted set commands} *) 540 | 541 | (** Add one or more members to a sorted set, or update its score if it already exists. *) 542 | val zadd : connection -> 543 | ?x:[< `NX | `XX ] -> ?ch:bool -> 544 | string -> (float * string) list -> int IO.t 545 | 546 | (** Return a range of members in a sorted set, by index. *) 547 | val zrange : connection -> ?withscores:bool -> string -> int -> int -> reply list IO.t 548 | 549 | (** Return a reversed range of members in a sorted set, by index. *) 550 | val zrevrange : connection -> ?withscores:bool -> string -> int -> int -> reply list IO.t 551 | 552 | (** Return a range of members in a sorted set, by score. *) 553 | val zrangebyscore : connection -> ?withscores:bool -> ?limit:(int * int) -> string -> FloatBound.t -> FloatBound.t -> reply list IO.t 554 | 555 | (** Return a range of members in a sorted set, by lexicographical range. *) 556 | val zrangebylex : connection -> ?limit:(int * int) -> string -> StringBound.t -> StringBound.t -> reply list IO.t 557 | 558 | (** Return a range of members in a sorted set, by score. *) 559 | val zrevrangebyscore : connection -> ?withscores:bool -> ?limit:(int * int) -> string -> FloatBound.t -> FloatBound.t -> reply list IO.t 560 | 561 | (** Return a range of members in a sorted set, by lexicographical range. *) 562 | val zrevrangebylex : connection -> ?limit:(int * int) -> string -> StringBound.t -> StringBound.t -> reply list IO.t 563 | 564 | (** Remove one or more members from a sorted set. *) 565 | val zrem : connection -> string -> string list -> int IO.t 566 | 567 | (** Remove all members in a sorted set between the given lexicographical range. *) 568 | val zremrangebylex : connection -> string -> StringBound.t -> StringBound.t -> int IO.t 569 | 570 | (** Remove all members in a sorted set between the given score range. *) 571 | val zremrangebyscore : connection -> string -> FloatBound.t -> FloatBound.t -> int IO.t 572 | 573 | (** Remove all members in a sorted set between the given rank range. *) 574 | val zremrangebyrank : connection -> string -> int -> int -> int IO.t 575 | 576 | (** Returns the sorted set cardinality (number of elements) of the sorted set stored at key. *) 577 | val zcard : connection -> string -> int IO.t 578 | 579 | (** Increment the score of a member in the sorted set *) 580 | val zincrby : connection -> string -> float -> string -> float IO.t 581 | 582 | (** Returns the score of a member in the sorted set. *) 583 | val zscore : connection -> string -> string -> float option IO.t 584 | 585 | (** Returns the number of elements in the sorted set at key with a score between min and max. *) 586 | val zcount : connection -> string -> FloatBound.t -> FloatBound.t -> int IO.t 587 | 588 | (** Returns the number of members in a sorted set between a given lexicographical range. *) 589 | val zlexcount : connection -> string -> StringBound.t -> StringBound.t -> int IO.t 590 | 591 | (** Returns the rank of member in the sorted set stored at key. *) 592 | val zrank : connection -> string -> string -> int option IO.t 593 | 594 | (** Returns the reversed rank of member in the sorted set stored at key. *) 595 | val zrevrank : connection -> string -> string -> int option IO.t 596 | 597 | (** Removes and returns one or more members with the lowest scores in a sorted set. *) 598 | val zpopmin : connection -> string -> int -> (string * float) list IO.t 599 | 600 | (** Remove and return one or more members with the highest scores in a sorted set. *) 601 | val zpopmax : connection -> string -> int -> (string * float) list IO.t 602 | 603 | (** Remove and return the member with the lowest score in a sorted set, or block until one is available. *) 604 | val bzpopmin : connection -> string list -> float -> (string * string * float) option IO.t 605 | 606 | (** Remove and return the member with the highest score in a sorted set, or block until one is available. *) 607 | val bzpopmax : connection -> string list -> float -> (string * string * float) option IO.t 608 | 609 | (** {2 Stream commands} 610 | 611 | For redis >= 5. We only support a subset of the commands for now. *) 612 | 613 | (** Add a stream event, as a list of key-value pairs, to the given stream. 614 | @return the ID of the new event 615 | @param maxlen can be used to trim the stream. 616 | @param id specify a custom ID. Most of the of time you don't want to 617 | set this. 618 | 619 | @see {{: https://redis.io/commands/xadd } the official doc} 620 | @since 0.5 *) 621 | val xadd : 622 | connection -> 623 | string -> 624 | ?maxlen:[`Exact of int | `Approximate of int] -> 625 | ?id:string -> 626 | (string * string) list -> 627 | string IO.t 628 | 629 | (** Delete specific stream events. Should be rarely useful. 630 | @return the number of deleted events. 631 | 632 | @see {{: https://redis.io/commands/xdel } the official doc} 633 | @since 0.5 *) 634 | val xdel : 635 | connection -> 636 | string -> 637 | string list -> 638 | int IO.t 639 | 640 | (** Length of a stream. 641 | @see https://redis.io/commands/xlen . 642 | @since 0.5 *) 643 | val xlen : connection -> string -> int IO.t 644 | 645 | (** Trim stream to the given maximum length. 646 | @param maxlen the maximum number of entries to preserve, prioritizing 647 | the most recent ones. [`Approximate n] is faster, and should be preferred. 648 | @return number of deleted entries 649 | 650 | @see {{: https://redis.io/commands/xtrim } the official doc} 651 | @since 0.5 *) 652 | val xtrim : 653 | connection -> string -> 654 | maxlen:[`Exact of int | `Approximate of int] -> 655 | unit -> int IO.t 656 | 657 | (** A stream event as returned by Redis. 658 | It is composed of a stream ID (timestamp + counter), 659 | and a list of key/value pairs. 660 | @since 0.5 *) 661 | type stream_event = string * (string * string) list 662 | 663 | (** [xrange connection stream ~start ~end_ ()] returns a range of 664 | events in the stream. 665 | 666 | @param start beginning of the range. It can be one of: 667 | 668 | - [StringBound.NegInfinity] ("-" in the doc) to indicate the earliest possible time 669 | - [StringBound.Inclusive "timestamp"] or [StringBound.Inclusive "timestamp-number"] 670 | for a left-inclusive bound 671 | - [StringBound.Exclusive "timestamp"] or [StringBound.Exclusive "timestamp-number"] 672 | for a left-exclusive bound ("(" in the doc) 673 | only since Redis 6.2 674 | 675 | @param end_ same as start but for the right bound 676 | @param count maximum number of events returned 677 | 678 | @return a lits of events (at most [count] if specified). Each event is 679 | a pair [(id, pairs)] where [id] is the unique ID of the event, 680 | of the form "-", and [pairs] is a list of 681 | key-value pairs associated with the event. 682 | 683 | @see {{: https://redis.io/commands/xrange} the official doc} 684 | @since 0.5 *) 685 | val xrange : 686 | connection -> 687 | string -> 688 | start:StringBound.t -> 689 | end_:StringBound.t -> 690 | ?count:int -> 691 | unit -> 692 | stream_event list IO.t 693 | 694 | (** Like {!xrange} but in reverse order. 695 | @see {{: https://redis.io/commands/xrevrange } the official doc} 696 | @since 0.5 *) 697 | val xrevrange : 698 | connection -> 699 | string -> 700 | start:StringBound.t -> 701 | end_:StringBound.t -> 702 | ?count:int -> 703 | unit -> 704 | stream_event list IO.t 705 | 706 | (** [xread connection pairs] reads data from the multiple streams 707 | specified in [pairs]. 708 | 709 | Each item of [pairs] is a pair [("stream-name", )] where 710 | [] is either: 711 | 712 | - [`Last] ("$" in the doc) to get events coming after 713 | the last current event (so, new events); 714 | - or [`After i] to get events coming after the given ID [i], 715 | excluding [i] itself. 716 | 717 | @return a list of [("stream-name", )]. 718 | Each pair contains the name of a stream (that was among the 719 | input [pairs]), along with events of that stream coming after the 720 | corresponding position. 721 | 722 | @param count max number of events returned {b per stream} 723 | @param block_ms if provided, [xread] blocks at most [block_ms] milliseconds 724 | for new events. Otherwise [xread] is synchronous and returns immediately. 725 | 726 | @see {{: https://redis.io/commands/xread} the official doc} 727 | @since 0.5 *) 728 | val xread : 729 | connection -> 730 | ?count:int -> 731 | ?block_ms:int -> 732 | (string * [`Last | `After of string]) list -> 733 | (string * stream_event list) list IO.t 734 | 735 | (** {2 Transaction commands} *) 736 | 737 | (** Marks the start of a transaction block. Subsequent commands will be queued for atomic execution using EXEC. *) 738 | val multi : connection -> unit IO.t 739 | 740 | (** Executes all previously queued commands in a transaction and restores the connection state to normal. *) 741 | val exec : connection -> reply list IO.t 742 | 743 | (** Flushes all previously queued commands in a transaction and restores the connection state to normal. *) 744 | val discard : connection -> unit IO.t 745 | 746 | (** Marks the given keys to be watched for conditional execution of a transaction. *) 747 | val watch : connection -> string list -> unit IO.t 748 | 749 | (** Flushes all the previously watched keys for a transaction. *) 750 | val unwatch : connection -> unit IO.t 751 | 752 | val queue : (unit -> 'a IO.t) -> unit IO.t 753 | (** Within a transaction (see {!multi}, {!exec}, and {!discard}), 754 | commands will not return their normal value. It is necessary to 755 | wrap each of them in their individual [Client.queue (fun () -> the_command)] 756 | to avoid getting an exception [Unexpected (Status "QUEUED")]. 757 | *) 758 | 759 | (** {2 Scripting commands} *) 760 | 761 | (** Load the specified Lua script into the script cache. Returns the SHA1 digest of the script for use with EVALSHA. *) 762 | val script_load : connection -> string -> string IO.t 763 | 764 | (** Evaluates a script using the built-in Lua interpreter. *) 765 | val eval : connection -> string -> string list -> string list -> reply IO.t 766 | 767 | (** Evaluates a script cached on the server side by its SHA1 digest. *) 768 | val evalsha : connection -> string -> string list -> string list -> reply IO.t 769 | 770 | (** {2 Server} *) 771 | 772 | val bgrewriteaof : connection -> unit IO.t 773 | 774 | val bgsave : connection -> unit IO.t 775 | 776 | val config_resetstat : connection -> unit IO.t 777 | 778 | val dbsize : connection -> int IO.t 779 | 780 | (** clear all databases *) 781 | val flushall : connection -> unit IO.t 782 | 783 | (** clear current database *) 784 | val flushdb : connection -> unit IO.t 785 | 786 | val info : connection -> (string * string) list IO.t 787 | 788 | (** last successful save as Unix timestamp *) 789 | val lastsave : connection -> float IO.t 790 | 791 | (** role in context of replication *) 792 | val role : connection -> reply list IO.t 793 | 794 | (** synchronous save *) 795 | val save : connection -> unit IO.t 796 | 797 | (** save and shutdown server *) 798 | val shutdown : connection -> unit IO.t 799 | 800 | (** Batch commands for mass insertion *) 801 | module MassInsert : sig 802 | type command 803 | 804 | val empty : command 805 | 806 | val set : ?ex:int -> ?px:int -> ?nx:bool -> ?xx:bool -> string -> string -> command 807 | 808 | (** Delete a key; returns the number of keys removed. *) 809 | val del : string list -> command 810 | 811 | val expire : string -> int -> command 812 | 813 | val hset : string -> string -> string -> command 814 | 815 | (** Removes the specified fields from the hash stored at key. Specified fields that do not exist within this hash are ignored. *) 816 | val hdel : string -> string -> command 817 | 818 | val hget : string -> string -> command 819 | 820 | val hincrby : string -> string -> int -> command 821 | 822 | val write : 823 | connection -> 824 | command list -> 825 | reply list IO.t 826 | 827 | val incr : string -> command 828 | 829 | val decr : string -> command 830 | end 831 | end 832 | 833 | module type Cache_params = sig 834 | type key 835 | type data 836 | 837 | val cache_key : key -> string 838 | val cache_expiration : int option 839 | 840 | val data_of_string : string -> data 841 | val string_of_data : data -> string 842 | end 843 | 844 | module type Cache = sig 845 | module IO : IO 846 | module Client : Client 847 | module Params : Cache_params 848 | 849 | val set : Client.connection -> Params.key -> Params.data -> unit IO.t 850 | val get : Client.connection -> Params.key -> Params.data option IO.t 851 | val delete : Client.connection -> Params.key -> unit 852 | end 853 | 854 | module type Mutex = sig 855 | module IO : IO 856 | module Client : Client 857 | 858 | exception Error of string 859 | 860 | val acquire : Client.connection -> ?atime:float -> ?ltime:int -> string -> string -> unit IO.t 861 | val release : Client.connection -> string -> string -> unit IO.t 862 | val with_mutex : Client.connection -> ?atime:float -> ?ltime:int -> string -> (unit -> 'a IO.t) -> 'a IO.t 863 | end 864 | 865 | (** {2 Connection pool} *) 866 | module type POOL = sig 867 | module IO : IO 868 | module Client : Client 869 | 870 | type t 871 | 872 | val size : t -> int 873 | 874 | val create : size:int -> Client.connection_spec -> t IO.t 875 | (** Create a pool of [size] connections, using the given spec. *) 876 | 877 | val close : t -> unit IO.t 878 | (** Close all connections *) 879 | 880 | val with_pool : size:int -> Client.connection_spec -> (t -> 'a IO.t) -> 'a IO.t 881 | (** Create a pool of [size] connections, using the given spec, 882 | pass it to the callback, and then destroy it. *) 883 | 884 | val with_connection : t -> (Client.connection -> 'a IO.t) -> 'a IO.t 885 | (** Temporarily require a connection to perform some operation. 886 | The connection must not escape the scope of the callback *) 887 | end 888 | -------------------------------------------------------------------------------- /src/utils.ml: -------------------------------------------------------------------------------- 1 | module Option = struct 2 | let default d = function 3 | | None -> d 4 | | Some v -> v 5 | 6 | let may f = function 7 | | None -> () 8 | | Some v -> f v; () 9 | 10 | let map f = function 11 | | None -> None 12 | | Some v -> Some (f v) 13 | end 14 | 15 | module List = struct 16 | let filter_map f l = 17 | let rec loop l accum = 18 | match l with 19 | | [] -> accum 20 | | hd :: tl -> 21 | match f hd with 22 | | Some x -> loop tl (x :: accum) 23 | | None -> loop tl accum 24 | in 25 | List.rev (loop l []) 26 | 27 | let rec pairs_of_list l = match l with 28 | | k :: (v :: rest) -> 29 | Option.map (fun l -> (k, v) :: l) (pairs_of_list rest) 30 | | _ :: [] -> None 31 | | [] -> Some [] 32 | end 33 | 34 | module String = struct 35 | module Str = Re_str 36 | 37 | let nsplit str delim = Str.split (Str.regexp delim) str 38 | 39 | let split str delim = 40 | match Str.bounded_split (Str.regexp delim) str 2 with 41 | | a :: b :: [] -> Some (a, b) 42 | | _ -> None 43 | end 44 | -------------------------------------------------------------------------------- /src/utils.mli: -------------------------------------------------------------------------------- 1 | module Option : sig 2 | val default : 'a -> 'a option -> 'a 3 | val may : ('a -> unit) -> 'a option -> unit 4 | val map : ('a -> 'b) -> 'a option -> 'b option 5 | end 6 | 7 | module List : sig 8 | val filter_map : ('a -> 'b option) -> 'a list -> 'b list 9 | val pairs_of_list : 'a list -> ('a * 'a) list option 10 | end 11 | 12 | module String : sig 13 | val nsplit : string -> string -> string list 14 | val split : string -> string -> (string * string) option 15 | end 16 | -------------------------------------------------------------------------------- /src_lwt/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name redis_lwt) 3 | (wrapped false) 4 | (public_name redis-lwt) 5 | (flags :standard -safe-string) 6 | (libraries redis lwt.unix lwt)) 7 | -------------------------------------------------------------------------------- /src_lwt/redis_lwt.ml: -------------------------------------------------------------------------------- 1 | module IO = struct 2 | type 'a t = 'a Lwt.t 3 | 4 | type fd = Lwt_unix.file_descr 5 | type in_channel = Lwt_io.input_channel 6 | type out_channel = Lwt_io.output_channel 7 | 8 | type 'a stream = 'a Lwt_stream.t 9 | type stream_count = unit 10 | 11 | let (>>=) = Lwt.(>>=) 12 | let (>|=) = Lwt.(>|=) 13 | let catch = Lwt.catch 14 | let try_bind = Lwt.try_bind 15 | let ignore_result = Lwt.ignore_result 16 | let return = Lwt.return 17 | let fail = Lwt.fail 18 | let run = Lwt_main.run 19 | let atomic = Lwt_io.atomic 20 | 21 | let getaddrinfo = Lwt_unix.getaddrinfo 22 | 23 | let connect family addr = 24 | let fd = Lwt_unix.socket family Lwt_unix.SOCK_STREAM 0 in 25 | let do_connect () = 26 | Lwt_unix.connect fd addr >>= fun () -> 27 | return fd 28 | in 29 | catch do_connect (fun exn -> Lwt_unix.close fd >>= fun () -> fail exn) 30 | 31 | let close = Lwt_unix.close 32 | let sleep = Lwt_unix.sleep 33 | 34 | let in_channel_of_descr fd = Lwt_io.of_fd ~mode:Lwt_io.input fd 35 | let out_channel_of_descr fd = Lwt_io.of_fd ~mode:Lwt_io.output fd 36 | let input_char = Lwt_io.read_char 37 | let really_input = Lwt_io.read_into_exactly 38 | let output_string = Lwt_io.write 39 | let flush = Lwt_io.flush 40 | 41 | let iter = Lwt_list.iter_p 42 | let iter_serial = Lwt_list.iter_s 43 | let map = Lwt_list.map_p 44 | let map_serial = Lwt_list.map_s 45 | let fold_left = Lwt_list.fold_left_s 46 | 47 | let stream_from = Lwt_stream.from 48 | let stream_next = Lwt_stream.next 49 | 50 | type mutex = Lwt_mutex.t 51 | let mutex_create = Lwt_mutex.create 52 | let mutex_with m f = Lwt_mutex.with_lock m f 53 | 54 | type condition = unit Lwt_condition.t 55 | let condition_create () = Lwt_condition.create () 56 | let condition_wait c m = Lwt_condition.wait ~mutex:m c 57 | let condition_signal c = Lwt_condition.signal c () 58 | let condition_broadcast c = Lwt_condition.broadcast c () 59 | end 60 | 61 | module Client = Redis.Client.Make(IO) 62 | module Cache = Redis.Cache.Make(IO)(Client) 63 | module Mutex = Redis.Mutex.Make(IO)(Client) 64 | 65 | module ClusterClient = Redis.Client.MakeCluster(IO) 66 | module ClusterCache = Redis.Cache.Make(IO)(ClusterClient) 67 | module ClusterMutex = Redis.Mutex.Make(IO)(ClusterClient) 68 | 69 | module Pool = Redis.Pool.Make(IO)(Client) 70 | -------------------------------------------------------------------------------- /src_lwt/redis_lwt.mli: -------------------------------------------------------------------------------- 1 | module IO : Redis.S.IO with type 'a t = 'a Lwt.t and type 'a stream = 'a Lwt_stream.t and type fd = Lwt_unix.file_descr 2 | 3 | module Client : Redis.S.Client with module IO = IO 4 | 5 | module Cache (Params : Redis.S.Cache_params) : Redis.S.Cache 6 | with module IO = IO 7 | with module Client = Client 8 | with module Params = Params 9 | 10 | module Mutex : Redis.S.Mutex 11 | with module IO = IO 12 | with module Client = Client 13 | 14 | module ClusterClient : Redis.S.Client with module IO = IO 15 | 16 | module ClusterCache (Params : Redis.S.Cache_params) : Redis.S.Cache 17 | with module IO = IO 18 | with module Client = ClusterClient 19 | 20 | module ClusterMutex : Redis.S.Mutex 21 | with module IO = IO 22 | with module Client = ClusterClient 23 | 24 | module Pool : Redis.S.POOL 25 | with module IO = IO 26 | and module Client = Client 27 | -------------------------------------------------------------------------------- /src_sync/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name redis_sync) 3 | (wrapped false) 4 | (public_name redis-sync) 5 | (flags :standard -safe-string -warn-error -3) 6 | (libraries redis unix camlp-streams threads)) 7 | -------------------------------------------------------------------------------- /src_sync/redis_sync.ml: -------------------------------------------------------------------------------- 1 | module IO = struct 2 | type 'a t = 'a 3 | 4 | type fd = Unix.file_descr 5 | type nonrec in_channel = in_channel 6 | type nonrec out_channel = out_channel 7 | 8 | type 'a stream = 'a Stream.t 9 | type stream_count = int 10 | 11 | let (>>=) a f = f a 12 | let (>|=) a f = f a 13 | let catch f exn_handler = try f () with e -> exn_handler e 14 | let try_bind f bind_handler exn_handler = try f () >>= bind_handler with e -> exn_handler e 15 | let ignore_result = ignore 16 | let return a = a 17 | let fail e = raise e 18 | let run a = a 19 | let atomic f ch = f ch 20 | 21 | let getaddrinfo = Unix.getaddrinfo 22 | 23 | let connect family addr = 24 | let fd = Unix.socket family Unix.SOCK_STREAM 0 in 25 | try 26 | Unix.connect fd addr; fd 27 | with 28 | exn -> Unix.close fd; raise exn 29 | 30 | let close = Unix.close 31 | let sleep a = ignore (Unix.select [] [] [] a) 32 | 33 | let in_channel_of_descr = Unix.in_channel_of_descr 34 | let out_channel_of_descr = Unix.out_channel_of_descr 35 | let input_char = input_char 36 | let really_input = really_input 37 | let output_string = output_string 38 | let flush = flush 39 | 40 | let iter = List.iter 41 | let iter_serial = List.iter 42 | let map = List.map 43 | let map_serial = List.map 44 | let fold_left = List.fold_left 45 | 46 | let stream_from = Stream.from 47 | let stream_next = Stream.next 48 | 49 | type mutex = Mutex.t 50 | let mutex_create = Mutex.create 51 | let mutex_with m f = 52 | Mutex.lock m; 53 | try 54 | let x = f() in 55 | Mutex.unlock m; 56 | x 57 | with e -> 58 | Mutex.unlock m; 59 | raise e 60 | 61 | type condition = Condition.t 62 | let condition_create () = Condition.create () 63 | let condition_wait c m = Condition.wait c m 64 | let condition_signal = Condition.signal 65 | let condition_broadcast = Condition.broadcast 66 | end 67 | 68 | module Client = Redis.Client.Make(IO) 69 | module Cache = Redis.Cache.Make(IO)(Client) 70 | module Mutex = Redis.Mutex.Make(IO)(Client) 71 | 72 | module ClusterClient = Redis.Client.MakeCluster(IO) 73 | module ClusterCache = Redis.Cache.Make(IO)(ClusterClient) 74 | module ClusterMutex = Redis.Mutex.Make(IO)(ClusterClient) 75 | -------------------------------------------------------------------------------- /src_sync/redis_sync.mli: -------------------------------------------------------------------------------- 1 | (** 2 | Blocking client for Redis 3 | *) 4 | 5 | module IO : Redis.S.IO with type 'a t = 'a and type 'a stream = 'a Stream.t 6 | 7 | module Client : Redis.S.Client with module IO = IO 8 | 9 | module Cache (Params : Redis.S.Cache_params) : Redis.S.Cache 10 | with module IO = IO 11 | with module Client = Client 12 | with module Params = Params 13 | 14 | module Mutex : Redis.S.Mutex 15 | with module IO = IO 16 | with module Client = Client 17 | 18 | module ClusterClient : Redis.S.Client with module IO = IO 19 | 20 | module ClusterCache (Params : Redis.S.Cache_params) : Redis.S.Cache 21 | with module IO = IO 22 | with module Client = ClusterClient 23 | 24 | module ClusterMutex : Redis.S.Mutex 25 | with module IO = IO 26 | with module Client = ClusterClient 27 | -------------------------------------------------------------------------------- /test/docker/with_acl/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM redis:6-alpine 2 | 3 | COPY ./acl.conf acl.conf 4 | COPY ./redis.conf redis.conf 5 | 6 | CMD ["redis-server", "./redis.conf"] -------------------------------------------------------------------------------- /test/docker/with_acl/acl.conf: -------------------------------------------------------------------------------- 1 | user superuser on >superpass ~* +@all 2 | user readonly on >ropass ~* +@read -------------------------------------------------------------------------------- /test/docker/with_acl/redis.conf: -------------------------------------------------------------------------------- 1 | aclfile ./acl.conf 2 | -------------------------------------------------------------------------------- /test/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name redis_test) 3 | (modules test) 4 | (wrapped false) 5 | (flags :standard -safe-string -warn-error -a) 6 | (libraries containers unix redis ounit2)) 7 | 8 | (executable 9 | (name test_sync) 10 | (modules test_sync) 11 | (flags :standard -safe-string) 12 | (libraries redis_test redis_sync threads)) 13 | 14 | (executable 15 | (name test_lwt) 16 | (modules test_lwt) 17 | (flags :standard -safe-string) 18 | (libraries redis_test redis_lwt)) 19 | 20 | (executable 21 | (name reg_78) 22 | (modules reg_78) 23 | (modes native) 24 | (flags :standard -safe-string) 25 | (libraries redis_lwt)) 26 | 27 | (alias 28 | (name runtest) 29 | (locks ../test) 30 | (package redis-lwt) 31 | (action (run ./test_lwt.exe -ci true -runner sequential))) 32 | 33 | (alias 34 | (name runtest) 35 | (locks ../test) 36 | (package redis-lwt) 37 | (action (run ./reg_78.exe))) 38 | 39 | (alias 40 | (name runtest) 41 | (locks ../test) 42 | (package redis-sync) 43 | (action (run ./test_sync.exe -ci true -runner sequential))) 44 | -------------------------------------------------------------------------------- /test/reg_78.ml: -------------------------------------------------------------------------------- 1 | module Client = Redis_lwt.Client 2 | 3 | module IntStr_params = struct 4 | type key = int 5 | type data = string 6 | 7 | let cache_key = string_of_int 8 | let cache_expiration = Some 200 9 | 10 | let data_of_string (str:string) = (str:data) 11 | let string_of_data (data:data) = (data:string) 12 | end 13 | 14 | module IntStrCache 15 | : (Redis.S.Cache with 16 | module Params = IntStr_params and 17 | module IO = Redis_lwt.IO and 18 | module Client = Redis_lwt.Client) 19 | = Redis.Cache.Make(Redis_lwt.IO)(Redis_lwt.Client)(IntStr_params) 20 | 21 | let redis_test_host () = 22 | try Sys.getenv "OCAML_REDIS_TEST_IP" 23 | with Not_found -> "127.0.0.1" 24 | 25 | let redis_test_port () = 63791 26 | 27 | let redis_spec : Client.connection_spec = 28 | Client.({host=redis_test_host (); 29 | port=redis_test_port () }) 30 | 31 | let () = 32 | let open Lwt.Infix in 33 | let r = 34 | Client.with_connection redis_spec (fun conn -> 35 | Lwt_io.printl "connected" >>= fun () -> 36 | let value = String.make 300000 'c' in 37 | Lwt_result.bind_lwt (Lwt_result.return conn) 38 | (fun conn -> IntStrCache.set conn 1 value)) 39 | |> Lwt_main.run 40 | in 41 | match r with 42 | | Ok () -> () 43 | | Error e -> print_endline e; exit 1 44 | 45 | -------------------------------------------------------------------------------- /test/test.ml: -------------------------------------------------------------------------------- 1 | open OUnit2 2 | 3 | let redis_test_host () = 4 | try 5 | Sys.getenv("OCAML_REDIS_TEST_IP") 6 | with Not_found -> 7 | "127.0.0.1" 8 | 9 | let redis_test_socket () = 10 | try 11 | Sys.getenv("OCAML_REDIS_TEST_SOCKET") 12 | with Not_found -> 13 | failwith "Environment variable OCAML_REDIS_TEST_SOCKET must be set" 14 | 15 | let redis_test_port = 63791 16 | let redis_test_port_with_auth = 63792 17 | let redis_test_port_with_acl = 63793 18 | 19 | let redis_string_bucket () = 20 | let number = Random.bits () in 21 | "ounit_" ^ string_of_int(number) 22 | 23 | let redis_integer_bucket = Random.bits 24 | 25 | let redis_float_bucket () = 26 | (* Redis' float operations precision differs from OCaml's float operation 27 | precision. Limit our floats to 11 digits after the decimal point to have 28 | possibility to test float operations. *) 29 | let a = float_of_int (Random.bits ()) in 30 | let b = float_of_int (Random.bits ()) in 31 | float_of_string (Printf.sprintf "%.8f" (a /. b)) 32 | 33 | let redis_n_strings_bucket n = 34 | let rec helper acc n = 35 | if n = 0 then acc else helper (redis_string_bucket () :: acc) (n - 1) in 36 | helper [] n 37 | 38 | module type UTILS = sig 39 | module IO : Redis.S.IO 40 | val spawn : (unit -> 'a IO.t) -> on_complete:('a -> unit) -> unit 41 | end 42 | 43 | module Make(Client : Redis.S.Client)(U : UTILS with module IO = Client.IO) : sig 44 | type containers 45 | val suite : string -> OUnit2.test 46 | val teardown : unit -> unit 47 | val redis_specs : containers 48 | val redis_spec_no_auth : Client.connection_spec 49 | val bracket : ?spec:Client.connection_spec -> (Client.connection -> 'a Client.IO.t) -> 'ctx -> 'a 50 | end = struct 51 | 52 | module IO = Client.IO 53 | 54 | let (>>=) = IO.(>>=) 55 | let (>>|) x f = x >>= fun x -> IO.return (f x) 56 | 57 | type containers = { 58 | no_auth : Client.connection_spec; 59 | with_auth : Client.connection_spec; 60 | with_acl : Client.connection_spec; 61 | unix_socket : Client.connection_spec; 62 | } 63 | 64 | let redis_specs : containers = 65 | { 66 | no_auth = Client.connection_spec ~port:redis_test_port (redis_test_host ()); 67 | with_auth = Client.connection_spec ~port:redis_test_port_with_auth (redis_test_host ()); 68 | with_acl = Client.connection_spec ~port:redis_test_port_with_acl (redis_test_host ()); 69 | unix_socket = Client.connection_spec_unix_socket (redis_test_socket ()); 70 | } 71 | 72 | let redis_spec_no_auth = redis_specs.no_auth 73 | 74 | let io_assert msg check result = 75 | IO.return (assert_bool msg (check result)) 76 | 77 | let assert_throws fn err msg = 78 | IO.catch (fun () -> fn () >>= (fun x -> IO.return (`Ok x))) (fun e -> IO.return @@ `Error e) 79 | >>= io_assert msg @@ function 80 | | `Error (Client.Redis_error e) -> e = err 81 | | _ -> false 82 | 83 | let test_case_auth conn = 84 | assert_throws (fun () -> Client.ping conn) 85 | "NOAUTH Authentication required." 86 | "Didn't fail to connect without password" >>= fun () -> 87 | Client.auth conn "some-password" >>= 88 | io_assert "Failed to authenticate" ((=) ()) >>= fun () -> 89 | Client.ping conn >>= 90 | io_assert "Can't connect to Redis server" ((=) true) 91 | 92 | let test_case_acl conn = 93 | assert_throws (fun () -> Client.auth_acl conn "notauser" "invalidpass") 94 | "WRONGPASS invalid username-password pair or user is disabled." 95 | "Didn't fail to connect with invalid username/password" >>= fun () -> 96 | Client.auth_acl conn "superuser" "superpass" >>= 97 | io_assert "Failed to authenticate" ((=) ()) >>= fun () -> 98 | Client.ping conn >>= 99 | io_assert "Can't connect to Redis server" ((=) true) 100 | 101 | (* PING *) 102 | let test_case_ping conn = 103 | Client.ping conn >>= 104 | io_assert "Can't connect to Redis server" ((=) true) 105 | 106 | (* ECHO *) 107 | let test_case_echo conn = 108 | Client.echo conn "ECHO" >>= 109 | io_assert "Can't echo to Redis server" ((=) (Some "ECHO")) 110 | 111 | (* INFO *) 112 | let test_case_info conn = 113 | Client.info conn >>| fun result -> 114 | (let tcp_port = 115 | CCList.find_map (fun (k,v) -> if k = "tcp_port" then Some v else None) result 116 | |> CCOption.get_lazy (fun () -> assert_failure "didn't find any port") 117 | in 118 | assert_bool "Got wrong data about port with INFO command" 119 | (int_of_string tcp_port = 6379)) 120 | 121 | (* Keys test case *) 122 | let test_case_keys conn = 123 | let key = redis_string_bucket () in 124 | let value = redis_string_bucket () in 125 | let key' = redis_string_bucket () in 126 | let key'' = redis_string_bucket () in 127 | 128 | Client.object_encoding conn key >>= 129 | io_assert "Unexpected encoding for empty key" ((=) None) >>= fun () -> 130 | Client.object_idletime conn key >>= 131 | io_assert "Unexpected idletime for empty key" ((=) None) >>= fun () -> 132 | Client.object_refcount conn key >>= 133 | 134 | io_assert "Unexpected refcount for empty key" ((=) None) >>= fun () -> 135 | Client.set conn key value >>= 136 | io_assert "Can't set key" ((=) true) >>= fun () -> 137 | Client.set conn ~xx:true key value >>= 138 | io_assert "Can set xx key which is already set" ((=) true) >>= fun () -> 139 | Client.setnx conn key value >>= 140 | io_assert "Can setnx key which is already set" ((=) false) >>= fun () -> 141 | Client.set conn ~nx:true key value >>= 142 | io_assert "Can set nx key which is already set" ((=) false) >>= fun () -> 143 | Client.set conn ~ex:20 key value >>= 144 | io_assert "Can set ex key which is already set" ((=) true) >>= fun () -> 145 | Client.set conn ~px:200 key value >>= 146 | io_assert "Can set ex key which is already set" ((=) true) >>= fun () -> 147 | Client.get conn key >>= 148 | io_assert "Key and value mismatch" ((=) (Some value)) >>= fun () -> 149 | Client.getset conn key value >>= 150 | io_assert "Got unexpected value" ((=) (Some value)) >>= fun () -> 151 | Client.getrange conn key 0 (String.length value) >>= 152 | io_assert "Value and it's getrange copy differs" ((=) (Some value)) >>= fun () -> 153 | Client.setrange conn key 0 value >>= 154 | io_assert "Value and it's copy setrange result differs" ((=) (String.length value)) >>= fun () -> 155 | Client.strlen conn key >>= 156 | io_assert "Value length and it's strlen differs" ((=) (String.length value)) >>= fun () -> 157 | 158 | Client.object_encoding conn key >>= 159 | io_assert "Unexpected encoding for raw value" ((=) (Some "raw")) >>= fun () -> 160 | Client.object_idletime conn key >>= 161 | io_assert "Unexpected idletime for just requested key" ((=) (Some 0)) >>= fun () -> 162 | Client.object_refcount conn key >>= 163 | io_assert "Unexpected refcount for referenced key" ((=) (Some 1)) >>= fun () -> 164 | 165 | Client.exists conn key >>= 166 | io_assert "Key doesn't exist" ((=) true) >>= fun () -> 167 | Client.keys conn key >>= 168 | io_assert "Can't find with itself as a pattern in KEYS command" 169 | (fun keys -> let found_key = List.find (fun k -> k = key) keys in 170 | (found_key = key)) >>= fun () -> 171 | Client.randomkey conn >>= 172 | io_assert "Can't find key with RANDOMKEY command" ((<>) None) >>= fun () -> 173 | Client.move conn key 2 >>= 174 | io_assert "Can't move key to redis database #2" ((=) true) >>= fun () -> 175 | Client.select conn 2 >>= 176 | io_assert "Can't select redis database #2" ((=) ()) >>= fun () -> 177 | Client.rename conn key key' >>= 178 | io_assert "Can't rename key" ((=) ()) >>= fun () -> 179 | Client.set conn key'' value >>= 180 | io_assert "Can't set key''" ((=) true) >>= fun () -> 181 | Client.renamenx conn key' key'' >>= 182 | io_assert "Can renamenx key" ((=) false) >>= fun () -> 183 | Client.rename conn key' key >>= 184 | io_assert "Can't rename key" ((=) ()) >>= fun () -> 185 | Client.del conn [key; key''] >>= 186 | io_assert "Key wasn't deleted" ((=) 2) >>= fun () -> 187 | Client.select conn 0 >>= 188 | io_assert "Can't select redis database #0" ((=) ()) 189 | 190 | let test_case_multiple_keys conn = 191 | let keys = redis_n_strings_bucket 10 in 192 | let values = List.rev keys in 193 | let kv_pairs = List.combine keys values in 194 | Client.mset conn kv_pairs >>= 195 | io_assert "Can't set multiple keys" ((=) ()) >>= fun () -> 196 | 197 | let expected_values = List.map (fun x -> Some x) values in 198 | Client.mget conn keys >>| (fun actual_values -> 199 | (List.iter2 200 | (fun expected actual -> assert_bool "Got unexpected value" (expected = actual)) 201 | expected_values actual_values)) >>= fun () -> 202 | 203 | let another_values = redis_n_strings_bucket 10 in 204 | let kv_pairs = List.combine keys another_values in 205 | Client.msetnx conn kv_pairs >>= 206 | io_assert "It's possible MSETNX multiple keys" ((=) false) >>= fun () -> 207 | 208 | let another_keys = redis_n_strings_bucket 10 in 209 | let kv_pairs = List.combine another_keys another_values in 210 | Client.msetnx conn kv_pairs >>= 211 | io_assert "Can't MSETNX multiple keys" ((=) true) 212 | 213 | let test_case_dump_restore conn = 214 | let key = redis_string_bucket () in 215 | let value = redis_string_bucket () in 216 | Client.set conn key value >>= 217 | io_assert "Can't set key" ((=) true) >>= fun () -> 218 | Client.dump conn key >>= function 219 | | None -> assert_failure "Can't dump value" 220 | | Some value_dump -> 221 | let key' = String.concat "" [key; redis_string_bucket ()] in 222 | Client.restore conn key' 0 value_dump >>= 223 | io_assert "Can't restore value" ((=) ()) >>= fun () -> 224 | Client.get conn key' >>= 225 | io_assert "Key value and restored value mismatch" ((=) (Some value)) 226 | 227 | let test_case_expire conn = 228 | let key = redis_string_bucket () in 229 | let value = redis_string_bucket () in 230 | Client.set conn key value >>= 231 | io_assert "Can't set key" ((=) true) >>= fun () -> 232 | Client.setex conn key 1 value >>= 233 | io_assert "Can't setex key" ((=) ()) >>= fun () -> 234 | Client.psetex conn key 1000 value >>= 235 | io_assert "Can't psetex key" ((=) ()) >>= fun () -> 236 | Client.ttl conn key >>= 237 | io_assert "Can't check expiration timeout for key" 238 | (fun x -> List.mem x [Some 0; Some 1]) >>= fun () -> 239 | Client.pttl conn key >>| (function 240 | | Some pttl -> assert_bool "Expiration timeout differs from setted" (0 <= pttl && pttl <= 1000) 241 | | None -> assert_failure "Can't check expiration timeout for key") 242 | >>= fun () -> 243 | Client.expire conn key 1 >>= 244 | io_assert "Can't set expiration timeout for key" ((=) true) >>= fun () -> 245 | Client.pexpire conn key 1000 >>= 246 | io_assert "Can't set expiration timeout in milliseconds for key" ((=) true) >>= fun () -> 247 | Client.ttl conn key >>= 248 | io_assert "Can't check expiration timeout for key" 249 | (fun x -> List.mem x [Some 0; Some 1]) >>= fun () -> 250 | Client.pttl conn key >>| (function 251 | | Some pttl -> assert_bool "Expiration timeout differs from setted" (0 <= pttl && pttl <= 1000) 252 | | None -> assert_failure "Can't check expiration timeout for key") 253 | >>= fun () -> 254 | Client.persist conn key >>= 255 | io_assert "Can't remove existing timeout on key" ((=) true) >>= fun () -> 256 | Client.ttl conn key >>= 257 | io_assert "Can't check expiration timeout for key" ((=) None) 258 | 259 | let test_case_expireat conn = 260 | let key = redis_string_bucket () in 261 | let value = redis_string_bucket () in 262 | Client.set conn key value >>= 263 | io_assert "Can't set key" ((=) true) >>= fun () -> 264 | let expiry = Unix.gettimeofday () +. 5. in 265 | Client.expireat conn key expiry >>= 266 | io_assert "Can't set expiration timeout for key" ((=) true) >>= fun () -> 267 | Client.ttl conn key >>= 268 | io_assert "Can't check expiration timeout for key" 269 | (function None -> false | Some x -> x >= 4 && x <= 6) >>= fun () -> 270 | 271 | let pexpiry = int_of_float (Unix.gettimeofday () *. 1000. +. 1000.) in 272 | Client.pexpireat conn key pexpiry >>= 273 | io_assert "Can't set expiration timeout for key (in ms)" ((=) true) >>= fun () -> 274 | Client.pttl conn key >>| function 275 | | Some pttl -> 276 | assert_bool (Printf.sprintf "Expiration timeout differs from setted (by %dms)" pttl) (0 <= pttl && pttl <= 1000) 277 | | None -> 278 | assert_failure "Can't check expiration timeout for key" 279 | 280 | let test_case_type conn = 281 | let value = redis_string_bucket () in 282 | let string_key = redis_string_bucket () in 283 | Client.set conn string_key value >>= 284 | io_assert "Can't set key" ((=) true) >>= fun () -> 285 | let list_key = redis_string_bucket () in 286 | Client.lpush conn list_key [value] >>= 287 | io_assert "Can't push value to list" ((=) 1) >>= fun () -> 288 | 289 | Client.type_of conn string_key >>= 290 | io_assert "Got wrong key type for string_key" ((=) `String) >>= fun () -> 291 | Client.type_of conn list_key >>= 292 | io_assert "Got wrong key type for list_key" ((=) `List) 293 | 294 | (* APPEND *) 295 | let test_case_append conn = 296 | let key = redis_string_bucket () in 297 | let value = redis_string_bucket () in 298 | Client.append conn key value >>= 299 | io_assert "Can't append initial value to key" 300 | (fun x -> x = String.length value) >>= fun () -> 301 | Client.append conn key value >>= 302 | io_assert "Can't append additional value to key" 303 | (fun x -> x = (String.length value + String.length value)) >>= fun () -> 304 | Client.get conn key >>= 305 | io_assert "Can't get key" ((=) (Some (String.concat "" [value; value]))) 306 | 307 | (* INCR/DECR/INCRBY/DECRBY/INCRBYFLOAT *) 308 | let test_case_incr_decr conn = 309 | let key = redis_string_bucket () in 310 | let value = redis_integer_bucket () in 311 | let increment = redis_integer_bucket () in 312 | Client.set conn key (string_of_int value) >>= 313 | io_assert "Can't set float value to key" ((=) true) >>= fun () -> 314 | Client.incrby conn key increment >>= 315 | io_assert "Can't increment value by integer" ((=) (value + increment)) >>= fun () -> 316 | Client.incr conn key >>= 317 | io_assert "Can't increment value by one" ((=) (value + increment + 1)) >>= fun () -> 318 | Client.decrby conn key increment >>= 319 | io_assert "Can't decrement value by integer" ((=) (value + 1)) >>= fun () -> 320 | Client.decr conn key >>= 321 | io_assert "Can't decrement value by one" ((=) value) >>= fun () -> 322 | Client.incrbyfloat conn key 2. >>= 323 | io_assert "Can't increment value by float" 324 | ((=) (float_of_int value +. 2.)) >>= fun () -> 325 | Client.incrbyfloat conn key (- 2.) >>= 326 | io_assert "Can't increment value by negative float" 327 | ((=) (float_of_int value)) 328 | 329 | (* BITOP/BITCOUNT/BITPOS/GETBIT/SETBIT *) 330 | let test_case_bit_operations conn = 331 | let dest = redis_string_bucket () in 332 | let key1 = redis_string_bucket () in 333 | let key2 = redis_string_bucket () in 334 | let value1 = "foobar" in 335 | let value2 = "abcdef" in 336 | let value3 = "\x00\xff\xf0" in 337 | Client.set conn key1 value1 >>= 338 | io_assert "Can't set value1 to key1" ((=) true) >>= fun () -> 339 | Client.set conn key2 value2 >>= 340 | io_assert "Can't set value2 to key2" ((=) true) >>= fun () -> 341 | Client.bitop conn Client.AND dest [key1; key2] >>= 342 | io_assert "Can't execute BITOP AND key1 and key2" ((=) 6) >>= fun () -> 343 | Client.get conn dest >>= 344 | io_assert "Got unexpected value from dest" ((=) (Some "`bc`ab")) >>= fun () -> 345 | Client.bitop conn Client.NOT dest [key1] >>= 346 | io_assert "Can't execute BITOP NOT key1" ((=) 6) >>= fun () -> 347 | Client.get conn dest >>= 348 | io_assert "Got unexpected value from dest" ((=) (Some "\x99\x90\x90\x9d\x9e\x8d")) >>= fun () -> 349 | Client.set conn key1 value3 >>= 350 | io_assert "Can't set value3 to key1" ((=) true) >>= fun () -> 351 | Client.bitpos conn key1 1 >>= 352 | io_assert "Got unexpected bit position" ((=) 8) >>= fun () -> 353 | Client.bitpos conn key1 1 ~first:0 >>= 354 | io_assert "Got unexpected bit position" ((=) 8) >>= fun () -> 355 | Client.bitpos conn key1 1 ~first:2 >>= 356 | io_assert "Got unexpected bit position" ((=) 16) >>= fun () -> 357 | Client.getbit conn key1 0 >>= 358 | io_assert "Can't get bit" ((=) 0) >>= fun () -> 359 | Client.setbit conn key1 0 1 >>= 360 | io_assert "Can't set bit" ((=) 0) >>= fun () -> 361 | Client.getbit conn key1 0 >>= 362 | io_assert "Can't get bit" ((=) 1) >>= fun () -> 363 | Client.set conn key1 value1 >>= 364 | io_assert "Can't set value1 to key1" ((=) true) >>= fun () -> 365 | Client.bitcount conn key1 >>= 366 | io_assert "Got unexpected bit count" ((=) 26) >>= fun () -> 367 | Client.bitcount conn key1 ~first:1 >>= 368 | io_assert "Got unexpected bit count" ((=) 22) >>= fun () -> 369 | Client.bitcount conn key1 ~first:0 ~last:0 >>= 370 | io_assert "Got unexpected bit count" ((=) 4) >>= fun () -> 371 | Client.bitcount conn key1 ~first:1 ~last:1 >>= 372 | io_assert "Got unexpected bit count" ((=) 6) 373 | 374 | let test_case_scan conn = 375 | let rec scan_keys cursor keys = 376 | Client.scan conn cursor >>= fun (next_cursor, next_keys) -> 377 | let next_keys = List.concat [keys; next_keys] in 378 | if next_cursor = 0 then 379 | IO.return next_keys 380 | else 381 | scan_keys next_cursor next_keys 382 | in 383 | let scan_all_keys () = scan_keys 0 [] in 384 | Client.keys conn "*" >>= fun keys -> 385 | scan_all_keys () >>= 386 | io_assert "Number of keys got with KEYS command is not equal to number of keys got with SCAN command" 387 | (fun scanned_keys -> List.length keys = List.length scanned_keys) 388 | 389 | let test_case_list conn = 390 | let key = redis_string_bucket () in 391 | let value1 = redis_string_bucket () in 392 | let value2 = redis_string_bucket () in 393 | Client.lpush conn key [value1] >>= 394 | io_assert "Got unexpected list length" ((=) 1) >>= fun () -> 395 | Client.rpush conn key [value2] >>= 396 | io_assert "Got unexpected list length" ((=) 2) >>= fun () -> 397 | Client.lrange conn key 0 2 >>= 398 | io_assert "Got unexpected list contents" ((=) [value1; value2]) >>= fun () -> 399 | Client.del conn [key] >>= fun _ -> 400 | let key = redis_string_bucket () in 401 | Client.lpush conn key [value1] >>= 402 | io_assert "Got unexpected list length" ((=) 1) >>= fun () -> 403 | Client.blpop conn [key] 1 >>= 404 | io_assert "Got unexpected value" ((=) (Some (key, value1))) >>= fun () -> 405 | Client.del conn [key] >>= fun _ -> IO.return () 406 | 407 | let test_case_hash conn = 408 | let key = redis_string_bucket () in 409 | let field = redis_string_bucket () in 410 | let value = redis_string_bucket () in 411 | Client.hmset conn key [(field, value)] >>= 412 | io_assert "Can not set multiple fields for hash" ((=) ()) >>= fun () -> 413 | Client.hget conn key field >>= 414 | io_assert "Got unexpected value of the field" ((=) (Some value)) >>= fun () -> 415 | Client.hlen conn key >>= 416 | io_assert "Got unexpected hash size" ((=) 1) >>= fun () -> 417 | Client.hdel conn key field >>= 418 | io_assert "Got unexpected result of field deletion" ((=) true) >>= fun () -> 419 | let new_value = redis_integer_bucket () in 420 | let new_value_s = string_of_int new_value in 421 | Client.hset conn key field new_value_s >>= 422 | io_assert "Can not set value for hash field" ((=) true) >>= fun _ -> 423 | Client.hset conn key field new_value_s >>= 424 | io_assert "Result of hash set is unexpected" ((=) false) >>= fun _ -> 425 | Client.hincrbyfloat conn key field 1.0 >>= 426 | io_assert "Got unexpected value" ((=) ((float_of_int new_value) +. 1.0)) 427 | 428 | let test_case_hyper_log_log conn = 429 | let key1 = redis_string_bucket () in 430 | let key2 = redis_string_bucket () in 431 | Client.pfadd conn key1 ["a"; "b"; "c"] >>= 432 | io_assert "Can not add items to hyperloglog" ((=) true) >>= fun () -> 433 | Client.pfadd conn key1 ["a"; "b"; "c"] >>= 434 | io_assert "Can not add items to hyperloglog" ((=) false) >>= fun () -> 435 | Client.pfcount conn [key1] >>= 436 | io_assert "Got wrong items count" ((=) 3) >>= fun () -> 437 | Client.pfadd conn key2 ["d"; "e"; "f"] >>= 438 | io_assert "Can not add items to hyperloglog" ((=) true) >>= fun () -> 439 | Client.pfcount conn [key1; key2] >>= 440 | io_assert "Got wrong items count" ((=) 6) >>= fun () -> 441 | Client.pfmerge conn [key1; key2] >>= 442 | io_assert "Got wrong items count" ((=) ()) 443 | 444 | let test_case_hscan conn = 445 | let key = redis_string_bucket () in 446 | let fields = redis_n_strings_bucket 10 in 447 | let pairs = List.map (fun f -> (f, f)) fields in 448 | 449 | Client.hmset conn key pairs >>= 450 | io_assert "Can not set multiple fields for hash" ((=) ()) >>= fun () -> 451 | 452 | let rec hscan_fields key cursor fields = 453 | Client.hscan conn key cursor >>= fun (next_cursor, next_fields) -> 454 | let next_fields = List.concat [fields; next_fields] in 455 | if next_cursor == 0 then 456 | IO.return next_fields 457 | else 458 | hscan_fields key next_cursor next_fields in 459 | let hscan_all_fields () = hscan_fields key 0 [] in 460 | 461 | Client.hkeys conn key >>= fun fields -> 462 | hscan_all_fields () >>= 463 | io_assert "Number of keys got with HKEYS command is not equal to number of keys got with HSCAN command" 464 | (fun scanned_fields -> 465 | List.length fields = List.length scanned_fields) 466 | 467 | let test_case_sorted_set conn = 468 | let key = redis_string_bucket () in 469 | Client.zadd conn key [ 1.4, "obj1"; 1.6, "obj2"; ] >>= 470 | io_assert "added 2 items to set" ((=) 2) >>= fun () -> 471 | Client.zadd conn key ~x:`NX [ 1.4, "obj1"; 1.6, "obj2"; ] >>= 472 | io_assert "no elements were added" ((=) 0) >>= fun () -> 473 | Client.zadd conn key ~x:`XX [ 1.4, "obj1"; 1.6, "obj2"; ] >>= 474 | io_assert "no elements were added" ((=) 0) >>= fun () -> 475 | Client.zadd conn key ~ch:true [ 3., "obj1"; 3., "obj2"; ] >>= 476 | io_assert "2 elements were changed" ((=) 2) >>= fun () -> 477 | Client.zincrby conn key 2. "obj1" >>= fun _ -> 478 | Client.zscore conn key "obj1" >>= 479 | io_assert "score of obj1 should be 5 now" ((=) (Some 5.)) >>= fun () -> 480 | Client.zrange conn key 0 100 >>= fun _ -> 481 | Client.zrevrange conn key 0 100 >>= fun _ -> 482 | let open Client.FloatBound in 483 | Client.zrangebyscore conn key NegInfinity PosInfinity >>= fun _ -> 484 | let open Client.FloatBound in 485 | Client.zrevrangebyscore conn key NegInfinity PosInfinity >>= fun _ -> 486 | let open Client.StringBound in 487 | Client.zrangebylex conn key NegInfinity PosInfinity >>= fun _ -> 488 | let open Client.StringBound in 489 | Client.zrevrangebylex conn key NegInfinity PosInfinity >>= fun _ -> 490 | let open Client.FloatBound in 491 | Client.zcount conn key NegInfinity PosInfinity >>= 492 | io_assert "wrong sorted set size returned" ((=) 2) >>= fun () -> 493 | let open Client.FloatBound in 494 | Client.zcount conn key (Inclusive 5.) (Exclusive 100.) >>= 495 | io_assert "wrong sorted set size returned" ((=) 1) >>= fun () -> 496 | let open Client.StringBound in 497 | Client.zlexcount conn key NegInfinity PosInfinity >>= 498 | io_assert "wrong sorted set size returned" ((=) 2) >>= fun () -> 499 | let open Client.StringBound in 500 | Client.zlexcount conn key (Inclusive "obj") (PosInfinity) >>= 501 | io_assert "wrong sorted set size returned" ((=) 2) >>= fun () -> 502 | Client.zrem conn key [ "obj1"; "non_existing_key"; ] >>= 503 | io_assert "not removed 1 item from set" ((=) 1) >>= fun () -> 504 | Client.zrank conn key "obj1" >>= 505 | io_assert "returned wrong rank" ((=) (None)) >>= fun () -> 506 | Client.zrank conn key "obj2" >>= 507 | io_assert "returned wrong rank" ((=) (Some 0)) >>= fun () -> 508 | Client.zrevrank conn key "obj1" >>= 509 | io_assert "returned wrong rank" ((=) (None)) >>= fun () -> 510 | Client.zrevrank conn key "obj2" >>= 511 | io_assert "returned wrong rank" ((=) (Some 0)) >>= fun () -> 512 | Client.zcard conn key >>= 513 | io_assert "wrong sorted set size returned" ((=) 1) >>= fun () -> 514 | Client.zscore conn key "obj1" >>= 515 | io_assert "item was removed" ((=) None) 516 | 517 | let test_case_sorted_set_remove conn = 518 | let key = redis_string_bucket () in 519 | Client.zadd conn key [ 1.4, "obj1"; 1.6, "obj2"; ] >>= fun _ -> 520 | Client.zremrangebyrank conn key 0 100 >>= 521 | io_assert "removed wrong number of items" ((=) 2) >>= fun _ -> 522 | Client.zadd conn key [ 1.4, "obj1"; 1.6, "obj2"; ] >>= fun _ -> 523 | let open Client.FloatBound in 524 | Client.zremrangebyscore conn key (Inclusive 1.) (Exclusive 2.) >>= 525 | io_assert "removed wrong number of items" ((=) 2) >>= fun _ -> 526 | Client.zadd conn key [ 1.4, "obj1"; 1.6, "obj2"; ] >>= fun _ -> 527 | let open Client.StringBound in 528 | Client.zremrangebylex conn key NegInfinity PosInfinity >>= 529 | io_assert "removed wrong number of items" ((=) 2) 530 | 531 | let test_case_sorted_set_pop conn = 532 | let test_case f = 533 | let key = redis_string_bucket () in 534 | Client.zadd conn key [1., "a"; 2., "b"; 3., "c"; 4., "d"; 5., "e"] >>= fun _ -> 535 | f key 536 | in 537 | 538 | test_case (fun key -> 539 | Client.zpopmin conn key 2 >>= 540 | io_assert "zpopmin: items (a, b)" ((=) ["a", 1.; "b", 2.]) >>= fun () -> 541 | Client.zpopmax conn key 2 >>= 542 | io_assert "zpopmax: items (e, d)" ((=) ["e", 5.; "d", 4.])) >>= fun () -> 543 | 544 | test_case (fun key -> 545 | Client.bzpopmin conn [ key ] 1. >>= 546 | io_assert "bzpopmin: item (a)" ((=) (Some (key, "a", 1.))) >>= fun () -> 547 | Client.bzpopmax conn [ key ] 1. >>= 548 | io_assert "bzpopmax: item (e)" ((=) (Some (key, "e", 5.))) >>= fun () -> 549 | Client.bzpopmax conn [ redis_string_bucket () ] 1e-2 >>= 550 | io_assert "bzpopmax: nothing" ((=) None)) 551 | 552 | let test_case_stream conn = 553 | let key = redis_string_bucket() in 554 | Client.xadd conn key ["x", "1"; "y", "2"] >>= fun id1 -> 555 | Client.xadd conn key ["x", "10"; "y", "20"] >>= fun id2 -> 556 | io_assert "id1 String.compare id1 id2 < 0) () >>= fun () -> 557 | 558 | Client.xlen conn key >>= fun len -> 559 | io_assert "len=2" ((=) 2) len >>= fun () -> 560 | 561 | Client.xadd conn key ["x", "100"; "y", "200"] >>= fun id3 -> 562 | io_assert "id2 String.compare id2 id3 < 0) () >>= fun () -> 563 | 564 | Client.xlen conn key >>= fun len -> 565 | io_assert "len=3" ((=) 3) len >>= fun () -> 566 | 567 | Client.xrange conn key 568 | ~start:Client.StringBound.NegInfinity 569 | ~end_:(Client.StringBound.Inclusive id2) () >>= fun items -> 570 | io_assert "xrange:2 items" (fun l->List.length l=2) items >>= fun () -> 571 | io_assert "keys=[id1,id2]" (fun () -> List.map fst items = [id1;id2]) () >>= fun () -> 572 | 573 | (* 574 | Client.xtrim conn key ~maxlen:(`Exact 1) () >>= fun n_trimed -> 575 | io_assert "trimmed 2" ((=) 2) n_trimed >>= fun () -> 576 | 577 | Client.xdel conn key [id3] >>= fun n_del -> 578 | io_assert "ndel=1" ((=) 1) n_del >>= fun () -> 579 | 580 | Client.xlen conn key >>= fun len -> 581 | io_assert "len=0" ((=) 0) len >>= fun () -> 582 | *) 583 | 584 | IO.return () 585 | 586 | let test_case_stream_xread ~spec conn : unit IO.t = 587 | let key = redis_string_bucket() in 588 | let has_read = ref 0 in 589 | let is_done = ref false in 590 | (* another thread to read from stream *) 591 | U.spawn 592 | (fun () -> 593 | (* open another connection so the first one doesn't block *) 594 | Client.with_connection spec @@ fun conn -> 595 | Client.xread conn ~block_ms:1000 ~count:1 [key, `Last] >>= function 596 | | [key', [_ts, ["v", v]]] -> 597 | (* read one event *) 598 | incr has_read; 599 | io_assert "isk" ((=) key) key' >>= fun() -> 600 | io_assert "is1" ((=) "1") v >>= fun () -> 601 | begin 602 | Client.xread conn ~block_ms:1000 ~count:1 [key, `Last] >>= function 603 | | [key', [_ts, ["v", v]]] -> 604 | io_assert "isk" ((=) key) key' >>= fun() -> 605 | io_assert "is2" ((=) "2") v >>= fun () -> 606 | IO.return () 607 | | _ -> io_assert "bad shape" (fun _ -> false) () 608 | end 609 | | _ -> io_assert "bad shape" (fun _ -> false) ()) 610 | ~on_complete:(fun () -> is_done := true); 611 | 612 | io_assert "read=0" ((=) 0) !has_read >>= fun () -> 613 | io_assert "not is-done" (fun x->not x) !is_done >>= fun () -> 614 | 615 | IO.sleep 0.2 >>= fun () -> 616 | 617 | Client.xadd conn key ["v", "1"] >>= fun _id -> 618 | IO.sleep 0.2 >>= fun () -> 619 | 620 | io_assert "read=1" ((=) 1) !has_read >>= fun () -> 621 | io_assert "not is-done" (fun x->not x) !is_done >>= fun () -> 622 | 623 | Client.xadd conn key ["v", "2"] >>= fun _id -> 624 | IO.sleep 0.2 >>= fun () -> 625 | 626 | io_assert "read=2" ((=) 1) !has_read >>= fun () -> 627 | io_assert "is-done" (fun x->x) !is_done >>= fun () -> 628 | IO.return () 629 | 630 | let cleanup_keys conn = 631 | Client.keys conn "ounit_*" >>= function 632 | | [] -> IO.return () 633 | | keys -> 634 | Client.del conn keys >>= fun _ -> 635 | IO.return () 636 | 637 | let bracket ?spec test_case _ = 638 | let spec = match spec with | None -> redis_specs.no_auth | Some s -> s in 639 | try 640 | IO.run @@ Client.with_connection spec test_case 641 | with (Client.Unexpected reply as exn) -> 642 | let rec to_string = function 643 | | `Status s -> Printf.sprintf "(Status %s)" s 644 | | `Moved {Client.slot; host; port} -> Printf.sprintf "MOVED %d %s:%i" slot host port 645 | | `Ask {Client.slot; host; port} -> Printf.sprintf "ASK %d %s:%i" slot host port 646 | | `Error s -> Printf.sprintf "(Error %s)" s 647 | | `Int i -> Printf.sprintf "(Int %i)" i 648 | | `Int64 i -> Printf.sprintf "(Int64 %Li)" i 649 | | `Bulk None -> "(Bulk None)" 650 | | `Bulk (Some s) -> Printf.sprintf "(Bulk (Some %s))" s 651 | | `Multibulk replies -> 652 | let x = List.map to_string replies |> String.concat "; " in 653 | Printf.sprintf "Multibulk [ %s; ]" x 654 | in 655 | Printf.eprintf "Got unexpected reply: %s\n" (to_string reply); 656 | raise exn 657 | 658 | let teardown () = 659 | flush stderr; 660 | IO.run @@ ( 661 | Client.with_connection redis_specs.no_auth cleanup_keys >>= fun _ -> 662 | Client.with_connection redis_specs.with_auth @@ fun conn -> Client.auth conn "some-password" >>= fun _ -> cleanup_keys conn >>= fun _ -> 663 | Client.with_connection redis_specs.with_acl @@ fun conn -> Client.auth_acl conn "superuser" "superpass" >>= fun _ -> cleanup_keys conn 664 | ) 665 | 666 | let suite name = 667 | let suite_name = "redis." ^ name in 668 | suite_name >::: [ 669 | "test_case_auth" >:: (bracket ~spec:redis_specs.with_auth test_case_auth); 670 | "test_case_acl" >:: (bracket ~spec:redis_specs.with_acl test_case_acl); 671 | "test_case_ping" >:: (bracket test_case_ping); 672 | "test_case_unix_socket" >:: (bracket ~spec:redis_specs.unix_socket test_case_ping); 673 | "test_case_echo" >:: (bracket test_case_echo); 674 | "test_case_info" >:: (bracket test_case_info); 675 | "test_case_keys" >:: (bracket test_case_keys); 676 | "test_case_multiple_keys" >:: (bracket test_case_multiple_keys); 677 | "test_case_dump_restore" >:: (bracket test_case_dump_restore); 678 | "test_case_expire" >:: (bracket test_case_expire); 679 | "test_case_expireat" >:: (bracket test_case_expireat); 680 | "test_case_type" >:: (bracket test_case_type); 681 | "test_case_append" >:: (bracket test_case_append); 682 | "test_case_incr_decr" >:: (bracket test_case_incr_decr); 683 | "test_case_bit_operations" >:: (bracket test_case_bit_operations); 684 | "test_case_scan" >:: (bracket test_case_scan); 685 | "test_case_list" >:: (bracket test_case_list); 686 | "test_case_hash" >:: (bracket test_case_hash); 687 | "test_case_hscan" >:: (bracket test_case_hscan); 688 | "test_case_hyper_log_log" >:: (bracket test_case_hyper_log_log); 689 | "test_case_sorted_set" >:: (bracket test_case_sorted_set); 690 | "test_case_sorted_set_remove" >:: (bracket test_case_sorted_set_remove); 691 | "test_case_sorted_set_pop" >:: (bracket test_case_sorted_set_pop); 692 | "test_stream" >:: bracket test_case_stream; 693 | "test_stream_xread" >:: bracket (test_case_stream_xread ~spec:redis_specs.no_auth); 694 | ] 695 | end 696 | -------------------------------------------------------------------------------- /test/test_lwt.ml: -------------------------------------------------------------------------------- 1 | module Utils = struct 2 | module IO = Redis_lwt.Client.IO 3 | let spawn f ~on_complete = 4 | Lwt.async (fun () -> 5 | let open Lwt.Infix in 6 | let fut = f() in 7 | fut >|= fun x -> on_complete x) 8 | end 9 | module Test_lwt = Test.Make(Redis_lwt.Client)(Utils) 10 | module Test_lwt_cluster = Test.Make(Redis_lwt.ClusterClient)(Utils) 11 | 12 | open OUnit2 13 | open Lwt.Infix 14 | 15 | (* Compute fibonacci function using Redis as a memoization cache *) 16 | module Test_lwt_fib = struct 17 | module C = Redis_lwt.Client 18 | module P = Redis_lwt.Pool 19 | module Cache = Redis_lwt.Cache(struct 20 | type key = int 21 | type data = int 22 | let cache_key i = Printf.sprintf "test_fib_%d" i 23 | let cache_expiration = Some 500 24 | let data_of_string = int_of_string 25 | let string_of_data = string_of_int 26 | end) 27 | 28 | let fib_ref n = 29 | let prev = ref 1 in 30 | let cur = ref 1 in 31 | for _i = 2 to n do 32 | let n = !cur in 33 | cur := !cur + !prev; 34 | prev := n 35 | done; 36 | !cur 37 | 38 | let check_fib n (pool:P.t) = 39 | let rec fib n = 40 | if n <= 1 then 41 | Lwt.return 1 42 | else ( 43 | P.with_connection pool (fun c -> Cache.get c n) >>= function 44 | | Some n -> Lwt.return n 45 | | None -> 46 | let n1 = fib (n-1) in 47 | let n2 = fib (n-2) in 48 | n1 >>= fun n1 -> 49 | n2 >>= fun n2 -> 50 | let res = n1 + n2 in 51 | P.with_connection pool (fun c -> Cache.set c n res) >|= fun _ -> res 52 | ) 53 | in 54 | let start = Unix.gettimeofday () in 55 | fib n >>= fun res -> 56 | let stop = Unix.gettimeofday () in 57 | let ref = fib_ref n in 58 | Printf.eprintf "fib %d = %d (expected: %d) in %.3fs\n" n res ref (stop -. start); 59 | OUnit.assert_equal ~printer:string_of_int ref res; 60 | Lwt.return () 61 | 62 | let bracket test_case _ctx = 63 | try 64 | Lwt_main.run @@ P.with_pool ~size:32 Test_lwt.redis_spec_no_auth test_case 65 | with C.Unexpected reply as exn -> 66 | Printf.eprintf "Got unexpected reply: %s\n" (C.string_of_reply reply); 67 | raise exn 68 | 69 | let test_fib n = bracket (check_fib n) 70 | 71 | let suite = 72 | "fib" >::: [ 73 | "10" >:: test_fib 10; 74 | "20" >:: test_fib 20; 75 | "30" >:: test_fib 30; 76 | "40" >:: test_fib 40; 77 | "60" >:: test_fib 60; 78 | ] 79 | 80 | let teardown = 81 | Test_lwt.bracket 82 | (fun conn -> 83 | C.keys conn "test_fib_*" >>= fun keys -> 84 | if keys <> [] then C.del conn keys >>= fun _ -> Lwt.return () 85 | else Lwt.return ()) 86 | end 87 | 88 | let suite = 89 | "lwt" >::: [ 90 | Test_lwt.suite "simple"; 91 | Test_lwt_cluster.suite "cluster"; 92 | Test_lwt_fib.suite; 93 | ] 94 | 95 | let () = 96 | Random.self_init (); 97 | let code = ref 0 in 98 | OUnit2.run_test_tt_main ~exit:(fun i->code := i) suite; 99 | Test_lwt.teardown (); 100 | Test_lwt_fib.teardown(); 101 | exit @@ !code 102 | -------------------------------------------------------------------------------- /test/test_sync.ml: -------------------------------------------------------------------------------- 1 | module Utils = struct 2 | module IO = Redis_sync.Client.IO 3 | let spawn f ~on_complete = 4 | let _th = Thread.create (fun () -> 5 | let x = f() in 6 | on_complete x) () 7 | in 8 | () 9 | end 10 | module Test_sync = Test.Make(Redis_sync.Client)(Utils) 11 | module Test_sync_cluster = Test.Make(Redis_sync.ClusterClient)(Utils) 12 | open OUnit2 13 | 14 | let suite = 15 | "sync" >::: [ 16 | Test_sync.suite "simple"; 17 | Test_sync_cluster.suite "cluster"; 18 | ] 19 | 20 | let () = 21 | Random.self_init (); 22 | let code = ref 0 in 23 | OUnit2.run_test_tt_main ~exit:(fun i -> code := i) suite; 24 | Test_sync.teardown (); 25 | exit !code 26 | --------------------------------------------------------------------------------