diff --git a/.gitignore b/.gitignore index da24de1..e9ae729 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ c_src/snappy*/ c_src/system *~ .rebar +backup.db +test.db diff --git a/c_src/atoms.h b/c_src/atoms.h index a44fb2e..edebcc0 100644 --- a/c_src/atoms.h +++ b/c_src/atoms.h @@ -116,7 +116,7 @@ extern ERL_NIF_TERM ATOM_DISABLE_WAL; extern ERL_NIF_TERM ATOM_TIMEOUT_HINT_US; extern ERL_NIF_TERM ATOM_IGNORE_MISSING_COLUMN_FAMILIES; -// Related to Write Actions +// Related to Write Actions extern ERL_NIF_TERM ATOM_CLEAR; extern ERL_NIF_TERM ATOM_PUT; extern ERL_NIF_TERM ATOM_DELETE; @@ -157,6 +157,8 @@ extern ERL_NIF_TERM ATOM_ERROR_DB_DELETE; extern ERL_NIF_TERM ATOM_ERROR_DB_WRITE; extern ERL_NIF_TERM ATOM_ERROR_DB_DESTROY; extern ERL_NIF_TERM ATOM_ERROR_DB_REPAIR; +extern ERL_NIF_TERM ATOM_ERROR_DB_BACKUP; +extern ERL_NIF_TERM ATOM_ERROR_DB_RESTORE; extern ERL_NIF_TERM ATOM_BAD_WRITE_ACTION; extern ERL_NIF_TERM ATOM_KEEP_RESOURCE_FAILED; extern ERL_NIF_TERM ATOM_ITERATOR_CLOSED; diff --git a/c_src/build_deps.sh b/c_src/build_deps.sh index 71e36bc..107b6ae 100755 --- a/c_src/build_deps.sh +++ b/c_src/build_deps.sh @@ -8,7 +8,7 @@ if [ `uname -s` = 'SunOS' -a "${POSIX_SHELL}" != "true" ]; then fi unset POSIX_SHELL # clear it so if we invoke other scripts, they run as ksh as well -ROCKSDB_VSN="rocksdb-3.11.2" +ROCKSDB_VSN="rocksdb-4.1" SNAPPY_VSN="1.1.1" diff --git a/c_src/erocksdb.cc b/c_src/erocksdb.cc index 393b858..35c9c9c 100644 --- a/c_src/erocksdb.cc +++ b/c_src/erocksdb.cc @@ -42,6 +42,7 @@ #include "rocksdb/table.h" #include "rocksdb/filter_policy.h" #include "rocksdb/slice_transform.h" +#include "rocksdb/utilities/backupable_db.h" #ifndef INCL_THREADING_H #include "threading.h" @@ -67,6 +68,8 @@ static ErlNifFunc nif_funcs[] = {"destroy", 2, erocksdb_destroy}, {"repair", 2, erocksdb_repair}, {"is_empty", 1, erocksdb_is_empty}, + {"backup", 2, erocksdb_backup}, + {"restore", 2, erocksdb_restore}, {"async_open", 4, erocksdb::async_open}, {"async_write", 4, erocksdb::async_write}, @@ -172,7 +175,7 @@ ERL_NIF_TERM ATOM_DISABLE_WAL; ERL_NIF_TERM ATOM_TIMEOUT_HINT_US; ERL_NIF_TERM ATOM_IGNORE_MISSING_COLUMN_FAMILIES; -// Related to Write Actions +// Related to Write Actions ERL_NIF_TERM ATOM_CLEAR; ERL_NIF_TERM ATOM_PUT; ERL_NIF_TERM ATOM_DELETE; @@ -217,6 +220,8 @@ ERL_NIF_TERM ATOM_BAD_WRITE_ACTION; ERL_NIF_TERM ATOM_KEEP_RESOURCE_FAILED; ERL_NIF_TERM ATOM_ITERATOR_CLOSED; ERL_NIF_TERM ATOM_INVALID_ITERATOR; +ERL_NIF_TERM ATOM_ERROR_DB_BACKUP; +ERL_NIF_TERM ATOM_ERROR_DB_RESTORE; // Related to NIF initialize parameters ERL_NIF_TERM ATOM_WRITE_THREADS; @@ -362,7 +367,7 @@ ERL_NIF_TERM parse_db_option(ErlNifEnv* env, ERL_NIF_TERM item, rocksdb::Options ERL_NIF_TERM tail; char db_name[4096]; while(enif_get_list_cell(env, option[1], &head, &tail)) { - if (enif_get_string(env, head, db_name, sizeof(db_name), ERL_NIF_LATIN1)) + if (enif_get_string(env, head, db_name, sizeof(db_name), ERL_NIF_LATIN1)) { std::string str_db_name(db_name); rocksdb::DbPath db_path(str_db_name, 0); @@ -515,7 +520,7 @@ ERL_NIF_TERM parse_cf_option(ErlNifEnv* env, ERL_NIF_TERM item, rocksdb::Options if (enif_get_tuple(env, item, &arity, &option) && 2==arity) { if (option[0] == erocksdb::ATOM_BLOCK_CACHE_SIZE_MB_FOR_POINT_LOOKUP) - // @TODO ignored now + // @TODO ignored now ; else if (option[0] == erocksdb::ATOM_MEMTABLE_MEMORY_BUDGET) { @@ -741,7 +746,7 @@ ERL_NIF_TERM parse_cf_option(ErlNifEnv* env, ERL_NIF_TERM item, rocksdb::Options } return erocksdb::ATOM_OK; } - + ERL_NIF_TERM parse_read_option(ErlNifEnv* env, ERL_NIF_TERM item, rocksdb::ReadOptions& opts) { int arity; @@ -1186,6 +1191,7 @@ async_iterator_move( } // namespace erocksdb + /*** * HEY YOU, please convert this to an async operation */ @@ -1402,6 +1408,104 @@ erocksdb_is_empty( } // erocksdb_is_empty +ERL_NIF_TERM +erocksdb_backup( + ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + + erocksdb::DbObject * db_ptr; + ERL_NIF_TERM ret_term; + + db_ptr=erocksdb::DbObject::RetrieveDbObject(env, argv[0]); + + char backup_path[4096]; + + if(!enif_get_string(env, argv[1], backup_path, sizeof(backup_path), ERL_NIF_LATIN1)) + { + return enif_make_badarg(env); + } + + if (NULL!=db_ptr) + { + rocksdb::BackupEngine* backup_engine; + rocksdb::Status s = rocksdb::BackupEngine::Open(rocksdb::Env::Default(), + rocksdb::BackupableDBOptions(backup_path), &backup_engine); + + if(!s.ok()) + { + return error_tuple(env, erocksdb::ATOM_ERROR_DB_BACKUP, s); + } + + + rocksdb::Status status = backup_engine->CreateNewBackup(db_ptr->m_Db); + if(!status.ok()) + { + ret_term = error_tuple(env, erocksdb::ATOM_ERROR_DB_BACKUP, status); + } + else + { + ret_term=erocksdb::ATOM_OK; + } + delete backup_engine; + } + else + { + ret_term=enif_make_badarg(env); + } + + return(ret_term); +} // erocksdb_backup + + +ERL_NIF_TERM +erocksdb_restore( + ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + + ERL_NIF_TERM ret_term; + + char db_path[4096]; + char backup_path[4096]; + + if(!enif_get_string(env, argv[0], backup_path, sizeof(backup_path), ERL_NIF_LATIN1)) + { + return enif_make_badarg(env); + } + + if(!enif_get_string(env, argv[1], db_path, sizeof(db_path), ERL_NIF_LATIN1)) + { + return enif_make_badarg(env); + } + + + rocksdb::BackupEngineReadOnly* backup_engine; + rocksdb::Status s = rocksdb::BackupEngineReadOnly::Open(rocksdb::Env::Default(), + rocksdb::BackupableDBOptions(backup_path), &backup_engine); + + if(!s.ok()) + { + return error_tuple(env, erocksdb::ATOM_ERROR_DB_BACKUP, s); + } + + rocksdb::Status status = backup_engine->RestoreDBFromLatestBackup(db_path, db_path); + delete backup_engine; + + if(!status.ok()) + { + ret_term = error_tuple(env, erocksdb::ATOM_ERROR_DB_RESTORE, status); + } + else + { + ret_term=erocksdb::ATOM_OK; + } + return(ret_term); +} // erocksdb_restore + + static void on_unload(ErlNifEnv *env, void *priv_data) { erocksdb_priv_data *p = static_cast(priv_data); @@ -1558,6 +1662,9 @@ try ATOM(erocksdb::ATOM_KEEP_RESOURCE_FAILED, "keep_resource_failed"); ATOM(erocksdb::ATOM_ITERATOR_CLOSED, "iterator_closed"); ATOM(erocksdb::ATOM_INVALID_ITERATOR, "invalid_iterator"); + ATOM(erocksdb::ATOM_ERROR_DB_BACKUP, "error_db_backup"); + ATOM(erocksdb::ATOM_ERROR_DB_RESTORE, "error_db_restore"); + // Related to NIF initialize parameters ATOM(erocksdb::ATOM_WRITE_THREADS, "write_threads"); diff --git a/c_src/erocksdb.h b/c_src/erocksdb.h index 77b1a7c..6595e1a 100644 --- a/c_src/erocksdb.h +++ b/c_src/erocksdb.h @@ -37,6 +37,8 @@ ERL_NIF_TERM erocksdb_status(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[] ERL_NIF_TERM erocksdb_destroy(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM erocksdb_repair(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM erocksdb_is_empty(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM erocksdb_backup(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM erocksdb_restore(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); } namespace erocksdb { diff --git a/src/erocksdb.erl b/src/erocksdb.erl index 449a808..54c30b9 100644 --- a/src/erocksdb.erl +++ b/src/erocksdb.erl @@ -30,6 +30,7 @@ -export([iterator/2, iterator/3, iterator_with_cf/3, iterator_move/2, iterator_close/1]). -export([fold/4, fold/5, fold_keys/4, fold_keys/5]). -export([destroy/2, repair/2, is_empty/1]). +-export([backup/2, restore/2]). -export([count/1, count/2, status/1, status/2, status/3]). -export_type([db_handle/0, @@ -456,6 +457,18 @@ destroy(_Name, _DBOpts) -> repair(_Name, _DBOpts) -> erlang:nif_error({error, not_loaded}). +%% @doc backup a database +-spec backup(DbHandle::db_handle(), BackupPath::file:filename_all()) + -> ok | {error, error_db_backup}. +backup(_DbHandle, _BackupPath) -> + erlang:nif_error({error, not_loaded}). + +%% @doc restore a database from last backup +-spec restore(BackupPath::file:filename_all(), DbPath::file:filename_all()) + -> ok | {error, error_db_restore}. +restore(_BackupPath, _DbPath) -> + erlang:nif_error({error, not_loaded}). + %% @doc %% Return the approximate number of keys in the default column family. %% Implemented by calling GetIntProperty with "rocksdb.estimate-num-keys" diff --git a/test/backup_test.erl b/test/backup_test.erl new file mode 100644 index 0000000..225ffa6 --- /dev/null +++ b/test/backup_test.erl @@ -0,0 +1,54 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2016 BenoƮt Chesneau. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- +-module(backup_test). +-compile([export_all/1]). +-include_lib("eunit/include/eunit.hrl"). + +backup_test() -> + os:cmd("rm -rf test.db"), + os:cmd("rm -rf test_backup.db"), + + {ok, Ref} = erocksdb:open("test.db", [{create_if_missing, true}], []), + try + erocksdb:put(Ref, <<"a">>, <<"x">>, []), + ?assertEqual({ok, <<"x">>}, erocksdb:get(Ref, <<"a">>, [])), + ok = erocksdb:backup(Ref, "test_backup.db"), + ?assert(filelib:is_dir("test_backup.db")), + erocksdb:put(Ref, <<"a">>, <<"y">>, []), + ?assertEqual({ok, <<"y">>}, erocksdb:get(Ref, <<"a">>, [])) + after + erocksdb:close(Ref) + end, + ok = erocksdb:restore("test_backup.db", "test.db"), + {ok, Ref2} = erocksdb:open("test.db", [], []), + try + ?assertEqual({ok, <<"x">>}, erocksdb:get(Ref2, <<"a">>, [])) + after + erocksdb:close(Ref2) + end. + + + + + + + + +