This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

View file

@ -0,0 +1,5 @@
_build
*.install
.merlin
_opam
*~

View file

@ -0,0 +1,90 @@
### v6.1.1 (2023-03-29)
* Leave it up to implementations how to interpret `last_modified` for
dictionaries, or even not implement it (then returning ``Error
(`Value_expected _)``. The previous definition was not well founded when the
dictionary doesn't contain any values directly, and some implementations
implements `last_modified` differently from the description on dictionaries.
(reported in #41, fixed in #42 by @reynir)
* Leave it up to implemetations how to interpret `digest` for dictionaries,
or even not implement it (returning ``Error (`Value_expected _)``.
(reported mirage/ocaml-tar#111, fixed in #43 by @reynir)
### v6.1.0 (2023-01-31)
* Mirage_kv.Key.add now raises Invalid_argument (instead of Failure). Document
that it raises (#40 @reynir)
### v6.0.1 (2022-12-15)
* Specify in RO.list that the returned list consists of keys being absolute, and
kinds (#38 #39 @reynir @hannesm)
* BREAKING: Before 6.0.0, the return type of RO.list consisted of a string and
kind list, where the string was relative. Now it is absolute.
### v6.0.0 (2022-12-12)
* Use ptime directly for RO.last_modified, instead of the int * int64 pair
(#34)
* Add RW.allocate to allocate a key and fill it with zero bytes (#34)
* RO.list: return Key.t instead of string (#37, fixes #33)
* Introduce a custom error for RW.rename with a source which is a prefix of
destination (#37, fixes #31)
* Use Optint.Int63.t for RO.size, RO.get_partial, RO.set_partial (#37, fixes #32)
* Remove RW.batch (#37, fixes #29 #36, discussed at the MirageOS meeting in
November, and on the mirageos-devel mailing list in January 2022)
* Key.pp: escape the entire string, not individual fragments (#35)
### v5.0.0 (2022-09-07)
* Add `get_partial` and `size` to the RO interface (@palainp #28, review by
@yomimono @hannesm)
* Add `set_partial` and `rename` to the RW interface (@palainp #28, review by
@yomimono @hannesm)
* Mirage_kv.Key.pp: escape binary keys (#30 @hannesm)
### v4.0.1 (2022-02-28)
* Return `/` for `parent /` and `basename /` (@yomimono, @talex5, #25)
### v4.0.0 (2021-11-15)
* Remove Mirage_kv_lwt module (#24 @hannesm)
* remove mirage-device dependency (#24 @hannesm)
* Adapt to fmt 0.8.7 dependency (#23 @MisterDA)
### v3.0.1 (2019-11-04)
* provide deprecated Mirage_kv_lwt for smooth transition (#21 @hannesm)
### v3.0.0 (2019-10-22)
* remove mirage-kv-lwt (#19 @hannesm)
* specialise mirage-kv on Lwt.t and value being string (#19 @hannesm)
* raise lower OCaml bound to 4.06.0 (#19 @hannesm)
### v2.0.0 (2019-02-24)
* Major revision of the `RO` signature:
- values are of type `string`
- keys are segments instead of a string
- `read` is now named `get`, and does no longer take an offset and length
- the new function `list` is provided
- the new functions `last_modified` and `digest` are provided
* A module `Key` is provided with convenience functions to build keys
* An `RW` signature is provided, extending `RO` with
- a function `set` to replace a value
- a function `remove` to remove a key
- `batch` to batch operations
### v1.1.1 (2017-06-29)
* Remove `open Result` statements (and drop support to 4.02)
### v1.1.0 (2017-05-26)
* Port to Jbuilder.
### v1.0.0 (2016-12-27)
* First release, import `V1.KV_RO` and `V1_LWT.KV_RO` from mirage-types.

View file

@ -0,0 +1,11 @@
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View file

@ -0,0 +1,10 @@
.PHONY: build clean test
build:
dune build
test:
dune runtest
clean:
dune clean

View file

@ -0,0 +1,29 @@
## mirage-kv — MirageOS signatures for key/value devices
mirage-kv provides the [Mirage_kv.RO][ro] and [Mirage_kv.RW][rw]
signatures the MirageOS key/value devices should implement.
mirage-kv is distributed under the ISC license.
[ro]: https://mirage.github.io/mirage-kv/mirage-kv/Mirage_kv/module-type-RO/index.html
[rw]: https://mirage.github.io/mirage-kv/mirage-kv/Mirage_kv/module-type-RW/index.html
[![Build Status](https://travis-ci.org/mirage/mirage-kv.svg?branch=master)](https://travis-ci.org/mirage/mirage-kv)
## Installation
mirage-kv can be installed with `opam`:
opam install mirage-kv
If you don't use `opam` consult the [`opam`](opam) file for build
instructions.
## Documentation
The documentation and API reference is generated from the source
interfaces. API docs for both [mirage-kv][doc-mirage-kv]
can be consulted online or via `odig
doc mirage-kv`.
[doc-mirage-kv]: http://docs.mirage.io/mirage-kv/

View file

@ -0,0 +1 @@
6.1.1

View file

@ -0,0 +1,3 @@
(lang dune 1.0)
(name mirage-kv)
(version v6.1.1)

View file

@ -0,0 +1,30 @@
version: "6.1.1"
opam-version: "2.0"
maintainer: "Thomas Gazagnaire <thomas@gazagnaire.org>"
authors: ["Thomas Gazagnaire <thomas@gazagnaire.org>" "Stefanie Schirmer" "Hannes Mehnert"]
homepage: "https://github.com/mirage/mirage-kv"
doc: "https://mirage.github.io/mirage-kv/"
license: "ISC"
dev-repo: "git+https://github.com/mirage/mirage-kv.git"
bug-reports: "https://github.com/mirage/mirage-kv/issues"
tags: ["org:mirage"]
build: [
["dune" "subst"] {dev}
["dune" "build" "-p" name "-j" jobs]
["dune" "runtest" "-p" name] {with-test}
]
depends: [
"ocaml" {>= "4.08.0"}
"dune"
"fmt" {>= "0.8.7"}
"lwt" {>= "4.0.0"}
"optint" {>= "0.2.0"}
"ptime" {>= "1.0.0"}
"alcotest" {with-test & >= "0.8.1"}
]
synopsis: "MirageOS signatures for key/value devices"
description: """
mirage-kv provides the `Mirage_kv.RO` and `Mirage_kv.RW`
signatures the MirageOS key/value devices should implement.
"""

View file

@ -0,0 +1,4 @@
(library
(name mirage_kv)
(public_name mirage-kv)
(libraries fmt optint lwt ptime))

View file

@ -0,0 +1,95 @@
(*
* Copyright (c) 2011-2015 Anil Madhavapeddy <anil@recoil.org>
* Copyright (c) 2013-2015 Thomas Gazagnaire <thomas@gazagnaire.org>
* Copyright (c) 2013 Citrix Systems Inc
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*)
module Key = struct
type t = string list
(* Store the path as a reverse list to optimise basename and (/)
operations *)
let err_invalid_segment x = Fmt.kstr invalid_arg "%S is not a valid segment" x
let check_segment x =
String.iter (function '/' -> err_invalid_segment x | _ -> ()) x;
x
let empty = []
let v s = List.filter ((<>)"") @@ List.rev (String.split_on_char '/' s)
let add t v = (check_segment v) :: t
let ( / ) = add
let append x y = y @ x
let ( // ) = append
let segments = List.rev
let basename = function | [] -> "" | hd::_ -> hd
let parent = function | _::tl -> tl | [] -> []
let compare = compare
let equal = (=)
let to_string k = "/" ^ String.concat "/" (List.rev k)
let pp ppf k = Fmt.string ppf (String.escaped (to_string k))
end
type key = Key.t
type error = [
| `Not_found of key
| `Dictionary_expected of key
| `Value_expected of key
]
let pp_error ppf = function
| `Not_found k -> Fmt.pf ppf "Cannot find the key %a" Key.pp k
| `Dictionary_expected k ->
Fmt.pf ppf "Expecting a dictionary for the key %a" Key.pp k
| `Value_expected k ->
Fmt.pf ppf "Expecting a value for the key %a" Key.pp k
module type RO = sig
type nonrec error = private [> error]
val pp_error: error Fmt.t
type t
val disconnect: t -> unit Lwt.t
type key = Key.t
val exists: t -> key -> ([`Value | `Dictionary] option, error) result Lwt.t
val get: t -> key -> (string, error) result Lwt.t
val get_partial: t -> key -> offset:Optint.Int63.t -> length:int -> (string, error) result Lwt.t
val list: t -> key -> ((key * [`Value | `Dictionary]) list, error) result Lwt.t
val last_modified: t -> key -> (Ptime.t, error) result Lwt.t
val digest: t -> key -> (string, error) result Lwt.t
val size: t -> key -> (Optint.Int63.t, error) result Lwt.t
end
type write_error = [ error | `No_space | `Rename_source_prefix of key * key | `Already_present of key ]
let pp_write_error ppf = function
| #error as e -> pp_error ppf e
| `No_space -> Fmt.string ppf "No space left on device"
| `Rename_source_prefix (src, dest) ->
Fmt.pf ppf "Rename: source %a is prefix of destination %a"
Key.pp src Key.pp dest
| `Already_present k -> Fmt.pf ppf "Key %a is already present" Key.pp k
module type RW = sig
include RO
type nonrec write_error = private [> write_error]
val pp_write_error: write_error Fmt.t
val allocate : t -> key -> ?last_modified:Ptime.t -> Optint.Int63.t -> (unit, write_error) result Lwt.t
val set: t -> key -> string -> (unit, write_error) result Lwt.t
val set_partial: t -> key -> offset:Optint.Int63.t -> string -> (unit, write_error) result Lwt.t
val remove: t -> key -> (unit, write_error) result Lwt.t
val rename: t -> source:key -> dest:key -> (unit, write_error) result Lwt.t
end

View file

@ -0,0 +1,255 @@
(*
* Copyright (c) 2011-2015 Anil Madhavapeddy <anil@recoil.org>
* Copyright (c) 2013-2015 Thomas Gazagnaire <thomas@gazagnaire.org>
* Copyright (c) 2013 Citrix Systems Inc
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*)
(** MirageOS signatures for key/value devices
{e v6.1.1 } *)
(** {2 Mirage_kv} *)
(** MirageOS key-value stores are nested dictionaries, associating
structured {{!Key}keys} to either dictionaries or values. *)
module Key: sig
(** {2 Structured keys} *)
type t
(** The type for structured keys. *)
val empty: t
(** [empty] is the empty key. It refers to the top-level
dictionary. *)
val v : string -> t
(** [v s] is the string [s] as a key. A key ["/foo/bar"] is
decomposed into the segments ["foo"] and ["bar"]. The initial
["/"] is always ignored so ["foo/bar"] and ["/foo/bar"] are
equal. *)
val add : t -> string -> t
(** [add t s] is the concatenated key [t/s].
@raise Invalid_argument if [s] contains ['/']. *)
val ( / ) : t -> string -> t
(** [t / x] is [add t x].
@raise Invalid_argument if [s] contains ['/']. *)
val append : t -> t -> t
(** [append x y] is the concatenated key [x/y]. *)
val ( // ) : t -> t -> t
(** [x // y] is [append x y]. *)
val segments : t-> string list
(** [segments t] is [t]'s list of segments. *)
val basename : t -> string
(** [basename t] is the last segment of [t]. [basename empty] is
the empty string [""]. *)
val parent : t -> t
(** [parent t] is the key without the last segment. [parent empty]
is [empty].
For any [t], the invariant have [parent t / basename t] is [t].
*)
val compare : t-> t -> int
(** The comparison function for keys. *)
val equal : t -> t -> bool
(** The equality function for keys. *)
val pp : t Fmt.t
(** The pretty printer for keys. *)
val to_string: t -> string
(** [to_string t] is the string representation of [t]. ["/"] is used
as separator between segements and it always starts with
["/"]. *)
end
type key = Key.t
(** The type for keys. *)
type error = [
| `Not_found of key (** key not found *)
| `Dictionary_expected of key (** key does not refer to a dictionary. *)
| `Value_expected of key (** key does not refer to a value. *)
]
(** The type for errors. *)
val pp_error: error Fmt.t
(** [pp_error] is the pretty-printer for errors. *)
module type RO = sig
(** {2 Read-only key-value stores} *)
type nonrec error = private [> error]
(** The type for errors. *)
val pp_error: error Fmt.t
(** [pp_error] is the pretty-printer for errors. *)
type t
(** The type representing the internal state of the key-value store. *)
val disconnect: t -> unit Lwt.t
(** Disconnect from the key-value store. While this might take some time to
complete, it can never result in an error. *)
type key = Key.t
(** The type for keys. *)
val exists: t -> key -> ([`Value | `Dictionary] option, error) result Lwt.t
(** [exists t k] is [Some `Value] if [k] is bound to a value in [t],
[Some `Dictionary] if [k] is a prefix of a valid key in [t] and
[None] if no key with that prefix exists in [t].
{!exists} answers two questions: does the key exist and is it
referring to a value or a dictionary.
An error occurs when the underlying storage layer fails. *)
val get: t -> key -> (string, error) result Lwt.t
(** [get t k] is the value bound to [k] in [t].
The result is [Error (`Value_expected k)] if [k] refers to a
dictionary in [t]. *)
val get_partial: t -> key -> offset:Optint.Int63.t -> length:int -> (string, error) result Lwt.t
(** [get_partial t k ~offset ~length] is the [length] bytes wide value
bound at [offset] of [k] in [t].
If the size of [k] is less than [offset], [get_partial] returns an
empty string.
If the size of [k] is less than [offset]+[length], [get_partial]
returns a short string.
The result is [Error (`Value_expected k)] if [k] refers to a
dictionary in [t]. *)
val list: t -> key -> ((key * [`Value | `Dictionary]) list, error) result Lwt.t
(** [list t k] is the list of entries and their types in the
dictionary referenced by [k] in [t]. The returned keys are all absolute
(i.e. [Key.add k entry]).
The result is [Error (`Dictionary_expected k)] if [k] refers to a
value in [t]. *)
val last_modified: t -> key -> (Ptime.t, error) result Lwt.t
(** [last_modified t k] is the last time the value bound to [k] in
[t] has been modified.
When the value bound to [k] is a dictionary, the implementation is free
to decide how to compute a last modified timestamp, or return [Error
(`Value_expected _)]. *)
val digest: t -> key -> (string, error) result Lwt.t
(** [digest t k] is the unique digest of the value bound to [k] in
[t].
When the value bound to [k] is a dictionary, the implementation is
allowed to return [Error (`Value_expected _)]. Otherwise, the [digest] is a
unique and deterministic digest of its entries. *)
val size: t -> key -> (Optint.Int63.t, error) result Lwt.t
(** [size t k] is the size of [k] in [t]. *)
end
type write_error = [
| error
| `No_space (** No space left on the device. *)
| `Rename_source_prefix of key * key (** The source is a prefix of destination in rename. *)
| `Already_present of key (** The key is already present. *)
]
val pp_write_error: write_error Fmt.t
(** [pp_write_error] is the pretty-printer for write errors. *)
module type RW = sig
(** {2 Read-write Stores} *)
(** The functions {!set} and {!remove} will cause a flush in
the underlying storage layer every time, which can degrade
performance. *)
include RO
type nonrec write_error = private [> write_error]
(** The type for write errors. *)
val pp_write_error: write_error Fmt.t
(** The pretty-printer for [pp_write_error]. *)
val allocate : t -> key -> ?last_modified:Ptime.t -> Optint.Int63.t ->
(unit, write_error) result Lwt.t
(** [allocate t key ~last_modified size] allocates space for [key] in [t] with
the provided [size] and [last_modified]. This is useful for e.g.
append-only backends that could still use {!set_partial}. The data will
be filled with 0. If [key] already exists, [Error (`Already_present key)]
is returned. If there's not enough space, [Error `No_space] is returned.
*)
val set: t -> key -> string -> (unit, write_error) result Lwt.t
(** [set t k v] replaces the binding [k -> v] in [t].
Durability is guaranteed. *)
val set_partial: t -> key -> offset:Optint.Int63.t -> string -> (unit, write_error) result Lwt.t
(** [set_partial t k offset v] attempts to write [v] at [offset] in the
value bound to [k] in [t].
If [k] contains directories that do not exist, [set_partial] will
attempt to create them.
If the size of [k] is less than [offset], [set_partial] appends [v]
at the end of [k].
If the size of [k] is greater than [offset]+length of [v],
[set_partial] leaves the last bytes of [k] unchanged.
The result is [Error (`Value_expected k)] if [k] refers to a
dictionary in [t]. *)
val remove: t -> key -> (unit, write_error) result Lwt.t
(** [remove t k] removes any binding of [k] in [t]. If [k] was bound
to a dictionary, the full dictionary will be removed.
Durability is guaranteed. *)
val rename: t -> source:key -> dest:key -> (unit, write_error) result Lwt.t
(** [rename t source dest] rename [source] to [dest] in [t].
If [source] and [dest] are both bound to values in [t], [dest]
is removed and the binding of [source] is moved to [dest].
If [dest] is bound to a dictionary in [t], [source] is moved
inside [dest]. If [source] is bound to a dictionary, the full
dictionary is moved.
The result is [Error (`Not_found source)] if [source] does not
exists in [t].
The result is [Error (`Value_expected source)] if [source] is
bound to a dictionary in [t] and [dest] is bound to a value in [t].
The result id [Error (`Rename_source_prefix (source, dest))] if [source]
is a prefix of [dest], and [source] is a directory.
*)
end

View file

@ -0,0 +1,4 @@
(test
(name test)
(package mirage-kv)
(libraries mirage-kv alcotest))

View file

@ -0,0 +1,50 @@
open Mirage_kv
let key = Alcotest.testable Key.pp Key.equal
let path_v () =
let check s e = Alcotest.(check string) s e Key.(to_string @@ v s) in
check "/foo/bar" "/foo/bar";
check "/foo" "/foo";
check "/" "/";
check "foo/bar" "/foo/bar";
check "" "/"
let path_add () =
let check p b exp =
let f = p ^ "/" ^ b in
let vp = Key.v p in
Alcotest.(check string) f exp Key.(to_string @@ vp / b);
Alcotest.(check key) f vp Key.(parent @@ vp / b);
Alcotest.(check string) f b Key.(basename @@ vp / b)
in
let check_exn p b =
try
let _ = Key.(v p / b) in
Alcotest.failf "%s is not a valid segment, should fail" b
with Invalid_argument _ -> ()
in
check "" "bar" "/bar";
check "/" "foo" "/foo";
check "/foo" "bar" "/foo/bar";
check "/foo/bar" "toto" "/foo/bar/toto";
check_exn "" "foo/bar"
let path_append () =
let check x y =
let f = x ^ "/" ^ y in
let vf = Key.v f in
Alcotest.(check key) f vf Key.(v x // v y);
Alcotest.(check string) x Key.(basename vf) Key.(basename @@ v y)
in
check "" "/foo/bar";
check "/foo" "bar";
check "/foo/bar" "/toto/foox/ko"
let () = Alcotest.run "mirage-kv" [
"path", [
"Path.v" , `Quick, path_v;
"Path.add_seg", `Quick, path_add;
"Path.append" , `Quick, path_append;
]
]