From a304c68fb9932c7d77849a243e84cd8c4c165d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Stolarczuk?= Date: Thu, 9 Sep 2021 10:53:22 +0200 Subject: [PATCH 1/5] example B: update pmemkv examples to use latest API and clean up the code and docs a little. --- examples/B/.gitignore | 1 + examples/B/Makefile | 2 +- examples/B/README.txt | 6 +++--- examples/B/kv.py | 8 +++++--- examples/B/kvinit.cpp | 34 +++++++++++++++------------------- examples/B/pmemkv.cpp | 11 ++++------- examples/B/run_py.sh | 2 +- 7 files changed, 30 insertions(+), 34 deletions(-) diff --git a/examples/B/.gitignore b/examples/B/.gitignore index 3c3d84c..b1eea6f 100644 --- a/examples/B/.gitignore +++ b/examples/B/.gitignore @@ -1 +1,2 @@ pmemkv +__pycache__/ diff --git a/examples/B/Makefile b/examples/B/Makefile index 59ec509..9f6b6c6 100644 --- a/examples/B/Makefile +++ b/examples/B/Makefile @@ -18,6 +18,6 @@ clean: $(RM) *.o core a.out clobber: clean - $(RM) pmemkv + $(RM) -r pmemkv __pycache__/ .PHONY: all clean clobber diff --git a/examples/B/README.txt b/examples/B/README.txt index 33d8135..d21a053 100644 --- a/examples/B/README.txt +++ b/examples/B/README.txt @@ -11,7 +11,7 @@ and kv.py program uses the Python language bindings. The programs are very simple, so even people who are not that familiar with C++ and Python should be able to understand them. -This is example consists of these files: +This example consists of these files: pmemkv.cpp -- simple C++ program using libpmemkv kvinit.cpp -- convenient function for creating/opening the kv store @@ -23,7 +23,7 @@ run_py.sh -- script to run the Python version To build this example run: make To run it and see what it illustrates run: ./run_cpp.sh or ./run_py.sh -Modifying the code and run steps is a great way to learn from this example. +Modifying the code and running steps is a great way to learn from this example. This example shows a generic key-value store, called libpmemkv, which handles all the details of persistent memory for you. Each "put" @@ -38,7 +38,7 @@ This example uses: - the "cmap" persistent memory concurrent hashmap, which uses: - libpmemobj for allocation & transactions, which uses: - libpmem for low-level mapping and flushing, which uses: - - a DAX-mounted file system to get direct access to pmem + - a DAX-mounted file system to get direct access to pmem. Although the above stack seems like lots of SW, it is all designed to give applications direct access to their data where sits in pmem, rather diff --git a/examples/B/kv.py b/examples/B/kv.py index 78f85ae..669f3ea 100755 --- a/examples/B/kv.py +++ b/examples/B/kv.py @@ -11,9 +11,9 @@ def kvprint(key, value): # # this is the main program, used this way: -# kv pmemfile -- print all the keys and values in the pmemfile -# kv pmemfile key -- lookup key and print the value -# kv pmemfile key value -- add a key/value pair to the pmemfile +# kv.py pmemfile -- print all the keys and values in the pmemfile +# kv.py pmemfile key -- lookup key and print the value +# kv.py pmemfile key value -- add a key/value pair to the pmemfile # # the pmemfile is created automatically if it doesn't already exist. # @@ -32,11 +32,13 @@ def kvprint(key, value): if len(sys.argv) == 2: # iterate through the key-value store, printing them db.get_all(kvprint) + elif len(sys.argv) == 3: # lookup the given key and print the value db.get(sys.argv[2], lambda value: print(f"{sys.argv[2]}=\"{memoryview(value).tobytes().decode()}\"")) + else: # add the given key-value pair db.put(sys.argv[2], sys.argv[3]) diff --git a/examples/B/kvinit.cpp b/examples/B/kvinit.cpp index 8cfa3c5..40900a6 100644 --- a/examples/B/kvinit.cpp +++ b/examples/B/kvinit.cpp @@ -1,23 +1,20 @@ #include -#include #include #include using namespace pmem::kv; using std::cerr; -using std::cout; using std::endl; -using std::string; // default size for this example: 10 Meg const uint64_t SIZE = 10 * 1024 * 1024; // -// must_open_or_create() is a convenience function to open the +// open_or_create() is a convenience function to open the // database if it already exists, or create it with a default // size if it doesn't. // -db *must_open_or_create(const char *path) { +db *open_or_create(const char *path) { // start by creating the db object db *kv = new db(); @@ -26,29 +23,28 @@ db *must_open_or_create(const char *path) { // create the config information for the pmemkv open config cfg; - if (cfg.put_string("path", path) != status::OK) { + // flag to control the behavior for open (create) method + if (cfg.put_create_if_missing(true) != status::OK) { cerr << pmemkv_errormsg() << endl; exit(1); } - if (!std::filesystem::exists(path)) { - // file doesn't exist, so add config flags to create it - if (cfg.put_uint64("force_create", 1) != status::OK) { - cerr << pmemkv_errormsg() << endl; - exit(1); - } - - if (cfg.put_uint64("size", SIZE) != status::OK) { - cerr << pmemkv_errormsg() << endl; - exit(1); - } + // specify path of DB + if (cfg.put_path(path) != status::OK) { + cerr << pmemkv_errormsg() << endl; + exit(1); + } + + // if file doesn't exist, we need to specify size of DB to create + if (cfg.put_size(SIZE) != status::OK) { + cerr << pmemkv_errormsg() << endl; + exit(1); } if (kv->open("cmap", std::move(cfg)) != status::OK) { - cerr << errormsg() << endl; + cerr << pmemkv_errormsg() << endl; exit(1); } return kv; - } diff --git a/examples/B/pmemkv.cpp b/examples/B/pmemkv.cpp index 1cae85d..32ceed7 100644 --- a/examples/B/pmemkv.cpp +++ b/examples/B/pmemkv.cpp @@ -5,9 +5,8 @@ using namespace pmem::kv; using std::cerr; using std::cout; using std::endl; -using std::string; -db *must_open_or_create(const char *path); +db *open_or_create(const char *path); // // kvprint is a callback function to process each key/value pair. it is @@ -18,7 +17,6 @@ int kvprint(string_view k, string_view v) { cout << k.data() << "=\"" << v.data() << "\"" << endl; return 0; - } // @@ -37,7 +35,7 @@ int main(int argc, char *argv[]) { exit(1); } - db *kv = must_open_or_create(argv[1]); + db *kv = open_or_create(argv[1]); if (argc == 2) { @@ -51,7 +49,7 @@ int main(int argc, char *argv[]) { cout << argv[2] << "=\"" << value.data() << "\"" << endl; }); if (ret != status::OK) { - cerr << errormsg() << endl; + cerr << pmemkv_errormsg() << endl; exit(1); } @@ -59,10 +57,9 @@ int main(int argc, char *argv[]) { // add the given key-value pair if (kv->put(argv[2], argv[3]) != status::OK) { - cerr << errormsg() << endl; + cerr << pmemkv_errormsg() << endl; exit(1); } - } // stop the pmemkv engine diff --git a/examples/B/run_py.sh b/examples/B/run_py.sh index debe833..95c0469 100755 --- a/examples/B/run_py.sh +++ b/examples/B/run_py.sh @@ -1,6 +1,6 @@ #!/bin/bash -ex # -# shell commands to run the C++ version of this example +# shell commands to run the Python version of this example # # remove file if left over from previous run From e3761c93082c9d1a78dd9c99a6f09ef8c5ac447c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Stolarczuk?= Date: Fri, 10 Sep 2021 09:35:50 +0200 Subject: [PATCH 2/5] example B: modify run_*.sh to set kvfile path only once --- examples/B/run_cpp.sh | 18 ++++++++++-------- examples/B/run_py.sh | 18 ++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/examples/B/run_cpp.sh b/examples/B/run_cpp.sh index e5d4639..a135688 100755 --- a/examples/B/run_cpp.sh +++ b/examples/B/run_cpp.sh @@ -3,20 +3,22 @@ # shell commands to run the C++ version of this example # +my_kvfile=/pmem/kvfile + # remove file if left over from previous run -rm -f /pmem/kvfile +rm -f $my_kvfile # add some values -./pmemkv /pmem/kvfile bach 1685-1750 -./pmemkv /pmem/kvfile mozart 1756-1791 +./pmemkv $my_kvfile bach 1685-1750 +./pmemkv $my_kvfile mozart 1756-1791 # print a value -./pmemkv /pmem/kvfile bach +./pmemkv $my_kvfile bach # add some more -./pmemkv /pmem/kvfile beethoven 1770-1827 -./pmemkv /pmem/kvfile brahms 1833-1897 -./pmemkv /pmem/kvfile haydn 1732-1809 +./pmemkv $my_kvfile beethoven 1770-1827 +./pmemkv $my_kvfile brahms 1833-1897 +./pmemkv $my_kvfile haydn 1732-1809 # print all k-v pairs in kvfile -./pmemkv /pmem/kvfile +./pmemkv $my_kvfile diff --git a/examples/B/run_py.sh b/examples/B/run_py.sh index 95c0469..397e4fa 100755 --- a/examples/B/run_py.sh +++ b/examples/B/run_py.sh @@ -3,20 +3,22 @@ # shell commands to run the Python version of this example # +my_kvfile=/pmem/kvfile + # remove file if left over from previous run -rm -f /pmem/kvfile +rm -f $my_kvfile # add some values -./kv.py /pmem/kvfile bach 1685-1750 -./kv.py /pmem/kvfile mozart 1756-1791 +./kv.py $my_kvfile bach 1685-1750 +./kv.py $my_kvfile mozart 1756-1791 # print a value -./kv.py /pmem/kvfile bach +./kv.py $my_kvfile bach # add some more -./kv.py /pmem/kvfile beethoven 1770-1827 -./kv.py /pmem/kvfile brahms 1833-1897 -./kv.py /pmem/kvfile haydn 1732-1809 +./kv.py $my_kvfile beethoven 1770-1827 +./kv.py $my_kvfile brahms 1833-1897 +./kv.py $my_kvfile haydn 1732-1809 # print all k-v pairs in kvfile -./kv.py /pmem/kvfile +./kv.py $my_kvfile From 4a46de63e8f6f1dd1341c1ab72faa196d39645e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Stolarczuk?= Date: Thu, 9 Sep 2021 12:17:12 +0200 Subject: [PATCH 3/5] example B: update pmemkv examples with non-existent key --- examples/B/kv.py | 9 ++++++--- examples/B/pmemkv.cpp | 4 +++- examples/B/run_cpp.sh | 2 ++ examples/B/run_py.sh | 2 ++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/examples/B/kv.py b/examples/B/kv.py index 669f3ea..8ecb8ec 100755 --- a/examples/B/kv.py +++ b/examples/B/kv.py @@ -35,9 +35,12 @@ def kvprint(key, value): elif len(sys.argv) == 3: # lookup the given key and print the value - db.get(sys.argv[2], - lambda value: - print(f"{sys.argv[2]}=\"{memoryview(value).tobytes().decode()}\"")) + try: + db.get(sys.argv[2], + lambda value: + print(f"{sys.argv[2]}=\"{memoryview(value).tobytes().decode()}\"")) + except KeyError: + print(f"Key '{sys.argv[2]}' wasn't found in DB") else: # add the given key-value pair diff --git a/examples/B/pmemkv.cpp b/examples/B/pmemkv.cpp index 32ceed7..c1c1014 100644 --- a/examples/B/pmemkv.cpp +++ b/examples/B/pmemkv.cpp @@ -48,9 +48,11 @@ int main(int argc, char *argv[]) { auto ret = kv->get(argv[2], [&](string_view value) { cout << argv[2] << "=\"" << value.data() << "\"" << endl; }); - if (ret != status::OK) { + if (ret != status::OK && ret != status::NOT_FOUND) { cerr << pmemkv_errormsg() << endl; exit(1); + } else if (ret == status::NOT_FOUND) { + cout << "Key \"" << argv[2] << "\" wasn't found in DB" << endl; } } else { diff --git a/examples/B/run_cpp.sh b/examples/B/run_cpp.sh index a135688..21e68c0 100755 --- a/examples/B/run_cpp.sh +++ b/examples/B/run_cpp.sh @@ -14,6 +14,8 @@ rm -f $my_kvfile # print a value ./pmemkv $my_kvfile bach +# print a non-existent value +./pmemkv $my_kvfile chopin # add some more ./pmemkv $my_kvfile beethoven 1770-1827 diff --git a/examples/B/run_py.sh b/examples/B/run_py.sh index 397e4fa..bb26f81 100755 --- a/examples/B/run_py.sh +++ b/examples/B/run_py.sh @@ -14,6 +14,8 @@ rm -f $my_kvfile # print a value ./kv.py $my_kvfile bach +# print a non-existent value +./kv.py $my_kvfile chopin # add some more ./kv.py $my_kvfile beethoven 1770-1827 From 249125a0902308d6cc370882479a1c039c3397bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Stolarczuk?= Date: Thu, 9 Sep 2021 21:51:10 +0200 Subject: [PATCH 4/5] example B: extend this example for Java binding --- examples/B/.gitignore | 1 + examples/B/Makefile | 8 +- examples/B/PmemkvExample/Makefile | 11 +++ examples/B/PmemkvExample/pom.xml | 80 +++++++++++++++++++ .../src/main/java/PmemkvExample.java | 79 ++++++++++++++++++ examples/B/README.txt | 27 ++++--- examples/B/run_java.sh | 30 +++++++ 7 files changed, 224 insertions(+), 12 deletions(-) create mode 100644 examples/B/PmemkvExample/Makefile create mode 100644 examples/B/PmemkvExample/pom.xml create mode 100644 examples/B/PmemkvExample/src/main/java/PmemkvExample.java create mode 100755 examples/B/run_java.sh diff --git a/examples/B/.gitignore b/examples/B/.gitignore index b1eea6f..1930020 100644 --- a/examples/B/.gitignore +++ b/examples/B/.gitignore @@ -1,2 +1,3 @@ pmemkv __pycache__/ +target/ diff --git a/examples/B/Makefile b/examples/B/Makefile index 9f6b6c6..b0a0e0c 100644 --- a/examples/B/Makefile +++ b/examples/B/Makefile @@ -11,13 +11,17 @@ pmemkv: $(OBJS) $(CXX) $(CXXFLAGS) -o $@ $(OBJS) $(LIBS) # for the Python script, the build step just checks for syntax errors -kv: kv.py +python: kv.py python3 -m py_compile kv.py +java: + $(MAKE) -C PmemkvExample java + clean: $(RM) *.o core a.out clobber: clean $(RM) -r pmemkv __pycache__/ + $(MAKE) -C PmemkvExample clobber -.PHONY: all clean clobber +.PHONY: all clean clobber java diff --git a/examples/B/PmemkvExample/Makefile b/examples/B/PmemkvExample/Makefile new file mode 100644 index 0000000..99a3e8b --- /dev/null +++ b/examples/B/PmemkvExample/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for java pmemkv example +# + +java: + mvn package + +clobber: + $(RM) -r target/ + +.PHONY: clobber java diff --git a/examples/B/PmemkvExample/pom.xml b/examples/B/PmemkvExample/pom.xml new file mode 100644 index 0000000..00512d7 --- /dev/null +++ b/examples/B/PmemkvExample/pom.xml @@ -0,0 +1,80 @@ + + + 4.0.0 + + io.pmem + PmemkvExample + 1.2.0 + Example for pmemkv Java binding + jar + + + + + io.pmem + pmemkv + [1.2.0,) + + + + + + 1.8 + 1.8 + UTF-8 + UTF-8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + true + false + + -Xlint:all + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + true + ${project.artifactId} + + + + + + maven-assembly-plugin + + + package + + single + + + + + + jar-with-dependencies + + + + ${project.artifactId} + + + + + + + diff --git a/examples/B/PmemkvExample/src/main/java/PmemkvExample.java b/examples/B/PmemkvExample/src/main/java/PmemkvExample.java new file mode 100644 index 0000000..04ee22d --- /dev/null +++ b/examples/B/PmemkvExample/src/main/java/PmemkvExample.java @@ -0,0 +1,79 @@ +import io.pmem.pmemkv.Converter; +import io.pmem.pmemkv.Database; +import io.pmem.pmemkv.NotFoundException; + +import java.nio.ByteBuffer; + +/* + * Implementation of Converter interface to allow + * storing in the Database keys and values as Strings. + */ +class StringConverter implements Converter { + public ByteBuffer toByteBuffer(String entry) { + return ByteBuffer.wrap(entry.getBytes()); + } + + public String fromByteBuffer(ByteBuffer entry) { + byte[] bytes; + bytes = new byte[entry.capacity()]; + entry.get(bytes); + return new String(bytes); + } +} + +public class PmemkvExample { + /* + * this is the main program, used this way: + * java PmemkvExample pmemfile -- print all the keys and values in the pmemfile + * java PmemkvExample pmemfile key -- lookup key and print the value + * java PmemkvExample pmemfile key value -- add a key/value pair to the pmemfile + * + * the pmemfile is created automatically if it doesn't already exist. + */ + + public static void main(String[] args) { + int SIZE = 10 * 1024 * 1024; // 10 MiB + String ENGINE = "cmap"; + + if (args.length < 1 || args.length > 3) { + System.err.println("Usage: java PmemkvExample kvfile [key [value]]"); + System.exit(1); + } + + /* + * Configure and open Database, using Builder. + * Part of the configuration is given (as an example) as JSON Object. + * Note, it is required to define key and value converter (to/from ByteBuffer). + */ + Database db = new Database.Builder(ENGINE) + .fromJson("{\"path\":\"" + args[0] + "\", \"create_if_missing\":1}") + .setSize(SIZE) + .setKeyConverter(new StringConverter()) + .setValueConverter(new StringConverter()) + .build(); + + if (args.length == 1) { + + // iterate through the key-value store, printing them + db.getAll((String k, String v) -> { + System.out.println(k + "=\"" + v + "\""); + }); + } else if (args.length == 2) { + + // lookup the given key and print the value + try { + db.get(args[1], (String v) -> { + System.out.println(args[1] + "=\"" + v + "\""); + }); + } catch (NotFoundException e) { + System.out.println("Key '" + args[1] + "' wasn't found in DB"); + } + } else { + + // add the given key-value pair + db.put(args[1], args[2]); + } + + db.stop(); + } +} diff --git a/examples/B/README.txt b/examples/B/README.txt index d21a053..2f4f10e 100644 --- a/examples/B/README.txt +++ b/examples/B/README.txt @@ -7,21 +7,28 @@ this example is recommended for everyone since it provides an overview of how the most common libraries are used together. The pmemkv.cpp program uses the C++ language bindings for libpmemkv, -and kv.py program uses the Python language bindings. The programs -are very simple, so even people who are not that familiar with C++ -and Python should be able to understand them. +kv.py program uses the Python language bindings, and PmemkvExample is +a simple project using the Java bindings. The programs are +very simple, so even people who are not that familiar with C++, Python, +and Java should be able to understand them. This example consists of these files: -pmemkv.cpp -- simple C++ program using libpmemkv -kvinit.cpp -- convenient function for creating/opening the kv store -Makefile -- rules for building this example -kv.py -- simple Python program using libpmemkv -run_cpp.sh -- script to run the C++ version -run_py.sh -- script to run the Python version +pmemkv.cpp -- simple C++ program using libpmemkv +kvinit.cpp -- convenient function for creating/opening the kv store +Makefile -- rules for building this example +kv.py -- simple Python program using libpmemkv +PmemkvExample/ -- simple Java project with its files: + Makefile -- rules for building the Java example + pom.xml -- project and dependencies configuration + PmemkvExample.java -- source file (located in sub-dir src/main/java) +run_cpp.sh -- script to run the C++ version +run_py.sh -- script to run the Python version +run_java.sh -- script to run the Java version To build this example run: make -To run it and see what it illustrates run: ./run_cpp.sh or ./run_py.sh +To run it and see what it illustrates run: +./run_cpp.sh, ./run_py.sh or ./run_java.sh Modifying the code and running steps is a great way to learn from this example. diff --git a/examples/B/run_java.sh b/examples/B/run_java.sh new file mode 100755 index 0000000..250fc96 --- /dev/null +++ b/examples/B/run_java.sh @@ -0,0 +1,30 @@ +#!/bin/bash -ex +# +# shell commands to run the Java version of this example +# + +cd PmemkvExample +jexec='java -cp ./target/PmemkvExample-1.2.0-jar-with-dependencies.jar PmemkvExample' +my_kvfile=/pmem/kvfile + +# remove file if left over from previous run +rm -f $my_kvfile + +# add some values +$jexec $my_kvfile bach 1685-1750 +$jexec $my_kvfile mozart 1756-1791 + +# print a value +$jexec $my_kvfile bach +# print a non-existent value +$jexec $my_kvfile chopin + +# add some more +$jexec $my_kvfile beethoven 1770-1827 +$jexec $my_kvfile brahms 1833-1897 +$jexec $my_kvfile haydn 1732-1809 + +# print all k-v pairs in kvfile +$jexec $my_kvfile + +cd .. From 0f1628cc0b698cbdd5a3ffa340c4e9d9a10e7482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Stolarczuk?= Date: Fri, 10 Sep 2021 11:05:03 +0200 Subject: [PATCH 5/5] example B: add script to run all examples accessing the same data --- examples/B/README.txt | 22 ++++++++++++++-------- examples/B/run_cross_lang.sh | 30 ++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) create mode 100755 examples/B/run_cross_lang.sh diff --git a/examples/B/README.txt b/examples/B/README.txt index 2f4f10e..afb6186 100644 --- a/examples/B/README.txt +++ b/examples/B/README.txt @@ -14,22 +14,28 @@ and Java should be able to understand them. This example consists of these files: -pmemkv.cpp -- simple C++ program using libpmemkv -kvinit.cpp -- convenient function for creating/opening the kv store -Makefile -- rules for building this example -kv.py -- simple Python program using libpmemkv -PmemkvExample/ -- simple Java project with its files: +pmemkv.cpp -- simple C++ program using libpmemkv +kvinit.cpp -- convenient function for creating/opening the kv store +Makefile -- rules for building this example +kv.py -- simple Python program using libpmemkv +PmemkvExample/ -- simple Java project with its files: Makefile -- rules for building the Java example pom.xml -- project and dependencies configuration PmemkvExample.java -- source file (located in sub-dir src/main/java) -run_cpp.sh -- script to run the C++ version -run_py.sh -- script to run the Python version -run_java.sh -- script to run the Java version +run_cpp.sh -- script to run the C++ version +run_py.sh -- script to run the Python version +run_java.sh -- script to run the Java version +run_cross_lang.sh -- script executing all languages alternatively To build this example run: make To run it and see what it illustrates run: ./run_cpp.sh, ./run_py.sh or ./run_java.sh +The last script - run_cross_lang.sh - shows how data in the same +database can be accessed using various programs, even implemented +in different programming languages. This will work in pmemkv +as long as they use the same engine. + Modifying the code and running steps is a great way to learn from this example. This example shows a generic key-value store, called libpmemkv, which diff --git a/examples/B/run_cross_lang.sh b/examples/B/run_cross_lang.sh new file mode 100755 index 0000000..957bf2b --- /dev/null +++ b/examples/B/run_cross_lang.sh @@ -0,0 +1,30 @@ +#!/bin/bash -ex +# +# shell commands to run this example using multiple language bindings +# alternatively. All examples use the same engine and can be used +# intertwined to access the same data (in the same kv file). +# + +jexec='java -cp ./target/PmemkvExample-1.2.0-jar-with-dependencies.jar PmemkvExample' +my_kvfile=/pmem/kvfile + +# remove file if left over from previous run +rm -f $my_kvfile + + +## add some values using Python +./kv.py $my_kvfile bach 1685-1750 +./kv.py $my_kvfile mozart 1756-1791 +./kv.py $my_kvfile beethoven 1770-1827 + + +# print some values using Java +cd PmemkvExample +$jexec $my_kvfile bach +$jexec $my_kvfile chopin +cd .. + +# add some more values and print all using C++ +./pmemkv $my_kvfile brahms 1833-1897 +./pmemkv $my_kvfile haydn 1732-1809 +./pmemkv $my_kvfile