Add small sqlite3 wrapper + start of schema.

This commit is contained in:
Scott Lamb 2016-01-07 22:59:34 -08:00
parent a46df2c2e5
commit 9af7eb8c14
6 changed files with 490 additions and 2 deletions

View File

@ -61,6 +61,7 @@ pkg_check_modules(FFMPEG REQUIRED libavutil libavcodec libavformat)
pkg_check_modules(LIBEVENT REQUIRED libevent)
pkg_check_modules(GLOG REQUIRED libglog)
pkg_check_modules(OPENSSL REQUIRED libcrypto)
pkg_check_modules(SQLITE REQUIRED sqlite3)
# Check if ffmpeg support "stimeout".
set(CMAKE_REQUIRED_INCLUDES ${FFMPEG_INCLUDES})

View File

@ -38,7 +38,8 @@ set(MOONFIRE_DEPS
${OPENSSL_LIBRARIES}
${PROFILER_LIBRARIES}
${PROTOBUF_LIBRARIES}
${RE2_LIBRARIES})
${RE2_LIBRARIES}
${SQLITE_LIBRARIES})
set(MOONFIRE_NVR_SRCS
coding.cc
@ -49,6 +50,7 @@ set(MOONFIRE_NVR_SRCS
moonfire-nvr.cc
profiler.cc
recording.cc
sqlite.cc
string.cc
time.cc)
@ -63,7 +65,7 @@ install_programs(/bin FILES moonfire-nvr)
include_directories(${GTest_INCLUDE_DIR})
include_directories(${GMock_INCLUDE_DIR})
foreach(test coding crypto http moonfire-nvr recording string)
foreach(test coding crypto http moonfire-nvr recording sqlite string)
add_executable(${test}-test ${test}-test.cc testutil.cc)
target_link_libraries(${test}-test GTest GMock moonfire-nvr-lib)
add_test(NAME ${test}-test

102
src/schema.sql Normal file
View File

@ -0,0 +1,102 @@
-- This file is part of Moonfire NVR, a security camera digital video recorder.
-- Copyright (C) 2016 Scott Lamb <slamb@slamb.org>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- In addition, as a special exception, the copyright holders give
-- permission to link the code of portions of this program with the
-- OpenSSL library under certain conditions as described in each
-- individual source file, and distribute linked combinations including
-- the two.
--
-- You must obey the GNU General Public License in all respects for all
-- of the code used other than OpenSSL. If you modify file(s) with this
-- exception, you may extend this exception to your version of the
-- file(s), but you are not obligated to do so. If you do not wish to do
-- so, delete this exception statement from your version. If you delete
-- this exception statement from all source files in the program, then
-- also delete it here.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
--
-- schema.sql: SQLite3 database schema for Moonfire NVR.
-- See also design/schema.md.
create table camera (
id integer primary key,
uuid blob unique not null,
-- A short name of the camera, used in log messages.
short_name text not null,
-- A short description of the camera.
description text,
-- The host (or IP address) to use in rtsp:// URLs when accessing the camera.
host text,
-- The username to use when accessing the camera.
-- If empty, no username or password will be supplied.
username text,
-- The password to use when accessing the camera.
password text,
-- The path (starting with "/") to use in rtsp:// URLs to reference this
-- camera's "main" (full-quality) video stream.
main_rtsp_path text,
-- The path (starting with "/") to use in rtsp:// URLs to reference this
-- camera's "sub" (low-bandwidth) video stream.
sub_rtsp_path text,
-- The number of bytes of video to retain, excluding the currently-recording
-- file. Older files will be deleted as necessary to stay within this limit.
retain_bytes integer
);
-- A single, typically 60-second, recorded segment of video.
create table recording (
id integer primary key,
camera_id integer references camera (id) not null,
status integer not null, -- 0 (WRITING), 1 (WRITTEN), or 2 (DELETING)
sample_file_uuid blob unique not null,
sample_file_sha1 blob,
sample_file_size integer,
-- The starting and ending time of the recording, in 90 kHz units since
-- 1970-01-01 00:00:00 UTC.
start_time_90k integer not null,
end_time_90k integer,
video_samples integer,
video_sample_entry_sha1 blob references visual_sample_entry (sha1),
video_index blob
);
-- A concrete box derived from a ISO/IEC 14496-12 section 8.5.2
-- VisualSampleEntry box. Describes the codec, width, height, etc.
create table visual_sample_entry (
-- A SHA-1 hash of |bytes|.
sha1 blob primary key,
-- The width and height in pixels; must match values within
-- |sample_entry_bytes|.
width integer,
height integer,
-- A serialized SampleEntry box, including the leading length and box
-- type (avcC in the case of H.264).
bytes blob
);

105
src/sqlite-test.cc Normal file
View File

@ -0,0 +1,105 @@
// This file is part of Moonfire NVR, a security camera network video recorder.
// Copyright (C) 2016 Scott Lamb <slamb@slamb.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// In addition, as a special exception, the copyright holders give
// permission to link the code of portions of this program with the
// OpenSSL library under certain conditions as described in each
// individual source file, and distribute linked combinations including
// the two.
//
// You must obey the GNU General Public License in all respects for all
// of the code used other than OpenSSL. If you modify file(s) with this
// exception, you may extend this exception to your version of the
// file(s), but you are not obligated to do so. If you do not wish to do
// so, delete this exception statement from your version. If you delete
// this exception statement from all source files in the program, then
// also delete it here.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// sqlite-test.cc: tests of the sqlite.h interface.
#include <string>
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "sqlite.h"
#include "string.h"
#include "testutil.h"
DECLARE_bool(alsologtostderr);
namespace moonfire_nvr {
namespace {
class SqliteTest : public testing::Test {
protected:
SqliteTest() {
std::string error_message;
CHECK(db_.Open(":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
&error_message))
<< error_message;
std::string create_sql = ReadFileOrDie("../src/schema.sql");
CHECK(RunStatements(&db_, create_sql, &error_message)) << error_message;
}
Database db_;
};
TEST_F(SqliteTest, JustCreate) {}
TEST_F(SqliteTest, BindAndColumn) {
std::string error_message;
auto insert_stmt = db_.Prepare(
"insert into camera (uuid, short_name, retain_bytes) "
"values (?, ?, ?)",
nullptr, &error_message);
ASSERT_TRUE(insert_stmt != nullptr) << error_message;
const char kBlob[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
re2::StringPiece blob_piece = re2::StringPiece(kBlob, sizeof(kBlob));
const char kText[] = "foo";
const int64_t kInt64 = INT64_C(0xdeadbeeffeedface);
ASSERT_TRUE(insert_stmt->BindBlob(1, blob_piece, &error_message))
<< error_message;
ASSERT_TRUE(insert_stmt->BindText(2, kText, &error_message)) << error_message;
ASSERT_TRUE(insert_stmt->BindInt64(3, kInt64, &error_message))
<< error_message;
ASSERT_EQ(SQLITE_DONE, insert_stmt->Step());
auto select_stmt =
db_.Prepare("select uuid, short_name, retain_bytes from camera", nullptr,
&error_message);
ASSERT_TRUE(select_stmt != nullptr) << error_message;
ASSERT_EQ(SQLITE_ROW, select_stmt->Step());
EXPECT_EQ(ToHex(blob_piece), ToHex(select_stmt->ColumnBlob(0)));
EXPECT_EQ(kText, select_stmt->ColumnText(1).as_string());
EXPECT_EQ(kInt64, select_stmt->ColumnInt64(2));
ASSERT_EQ(SQLITE_DONE, select_stmt->Step());
}
} // namespace
} // namespace moonfire_nvr
int main(int argc, char **argv) {
FLAGS_alsologtostderr = true;
google::ParseCommandLineFlags(&argc, &argv, true);
testing::InitGoogleTest(&argc, argv);
google::InitGoogleLogging(argv[0]);
return RUN_ALL_TESTS();
}

150
src/sqlite.cc Normal file
View File

@ -0,0 +1,150 @@
// This file is part of Moonfire NVR, a security camera digital video recorder.
// Copyright (C) 2016 Scott Lamb <slamb@slamb.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// In addition, as a special exception, the copyright holders give
// permission to link the code of portions of this program with the
// OpenSSL library under certain conditions as described in each
// individual source file, and distribute linked combinations including
// the two.
//
// You must obey the GNU General Public License in all respects for all
// of the code used other than OpenSSL. If you modify file(s) with this
// exception, you may extend this exception to your version of the
// file(s), but you are not obligated to do so. If you do not wish to do
// so, delete this exception statement from your version. If you delete
// this exception statement from all source files in the program, then
// also delete it here.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// sqlite.cc: implementation of the sqlite.h interface.
#include "sqlite.h"
#include <glog/logging.h>
#include "string.h"
namespace moonfire_nvr {
bool Statement::BindBlob(int param, re2::StringPiece value,
std::string *error_message) {
int err = sqlite3_bind_blob64(me_, param, value.data(), value.size(),
SQLITE_TRANSIENT);
if (err != SQLITE_OK) {
*error_message = sqlite3_errstr(err);
return false;
}
return true;
}
bool Statement::BindInt64(int param, int64_t value,
std::string *error_message) {
int err = sqlite3_bind_int64(me_, param, value);
if (err != SQLITE_OK) {
*error_message = sqlite3_errstr(err);
return false;
}
return true;
}
bool Statement::BindText(int param, re2::StringPiece value,
std::string *error_message) {
int err = sqlite3_bind_text64(me_, param, value.data(), value.size(),
SQLITE_TRANSIENT, SQLITE_UTF8);
if (err != SQLITE_OK) {
*error_message = sqlite3_errstr(err);
return false;
}
return true;
}
re2::StringPiece Statement::ColumnBlob(int col) {
// Order matters: call _blob first, then _bytes.
const void *data = sqlite3_column_blob(me_, col);
size_t len = sqlite3_column_bytes(me_, col);
return re2::StringPiece(reinterpret_cast<const char *>(data), len);
}
int64_t Statement::ColumnInt64(int col) {
return sqlite3_column_int64(me_, col);
}
re2::StringPiece Statement::ColumnText(int col) {
// Order matters: call _text first, then _bytes.
const unsigned char *data = sqlite3_column_text(me_, col);
size_t len = sqlite3_column_bytes(me_, col);
return re2::StringPiece(reinterpret_cast<const char *>(data), len);
}
int Statement::Step() { return sqlite3_step(me_); }
Database::~Database() {
int err = sqlite3_close(me_);
CHECK_EQ(SQLITE_OK, err) << "sqlite3_close: " << sqlite3_errstr(err);
}
bool Database::Open(const char *filename, int flags,
std::string *error_message) {
int err = sqlite3_open_v2(filename, &me_, flags, nullptr);
if (err != SQLITE_OK) {
*error_message = sqlite3_errstr(err);
return false;
}
return true;
}
std::unique_ptr<Statement> Database::Prepare(re2::StringPiece sql, size_t *used,
std::string *error_message) {
std::unique_ptr<Statement> statement(new Statement);
const char *tail;
int err =
sqlite3_prepare_v2(me_, sql.data(), sql.size(), &statement->me_, &tail);
if (err != SQLITE_OK) {
*error_message = sqlite3_errstr(err);
statement.release();
}
if (used != nullptr) {
*used = tail - sql.data();
}
if (statement->me_ == nullptr) {
error_message->clear();
statement.release();
}
return statement;
}
bool RunStatements(Database *db, re2::StringPiece stmts,
std::string *error_message) {
while (true) {
size_t used;
auto stmt = db->Prepare(stmts, &used, error_message);
if (stmt == nullptr) {
// Statement didn't parse. If |error_message| is empty, there are just no
// more statements. Otherwise this is due to an error. Either way, return.
return error_message->empty();
}
VLOG(1) << "Running statement:\n" << stmts.substr(0, used).as_string();
stmts.remove_prefix(used);
int ret = stmt->Step();
if (ret != SQLITE_DONE) {
*error_message =
StrCat("Unexpected status \"", sqlite3_errstr(ret),
"\" from statement: \"", stmts.substr(0, used), "\"");
return false;
}
}
}
} // namespace moonfire_nvr

128
src/sqlite.h Normal file
View File

@ -0,0 +1,128 @@
// This file is part of Moonfire NVR, a security camera digital video recorder.
// Copyright (C) 2016 Scott Lamb <slamb@slamb.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// In addition, as a special exception, the copyright holders give
// permission to link the code of portions of this program with the
// OpenSSL library under certain conditions as described in each
// individual source file, and distribute linked combinations including
// the two.
//
// You must obey the GNU General Public License in all respects for all
// of the code used other than OpenSSL. If you modify file(s) with this
// exception, you may extend this exception to your version of the
// file(s), but you are not obligated to do so. If you do not wish to do
// so, delete this exception statement from your version. If you delete
// this exception statement from all source files in the program, then
// also delete it here.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// sqlite.h: a quick C++ wrapper interface around the SQLite3 C API.
// This provides RAII and takes advantage of some types like re2::StringPiece.
// It makes no attempt to hide how the underlying API works, so read
// alongside: <https://www.sqlite.org/capi3ref.html>
#ifndef MOONFIRE_NVR_SQLITE_H
#define MOONFIRE_NVR_SQLITE_H
#include <memory>
#include <re2/stringpiece.h>
#include <sqlite3.h>
namespace moonfire_nvr {
// Thread-compatible (not thread-safe).
class Statement {
public:
~Statement() { sqlite3_finalize(me_); }
// Bind a value (of various types).
// |param| is 1-indexed.
// In the case of BindBlob, |value| will be copied immediately using
// SQLITE_TRANSIENT.
// In the case of BindText, UTF-8 is assumed.
bool BindBlob(int param, re2::StringPiece value, std::string *error_message);
bool BindInt64(int param, int64_t value, std::string *error_message);
bool BindText(int param, re2::StringPiece value, std::string *error_message);
bool Reset(std::string *error_message);
// Evaluate the statement.
// Returns a SQLite3 result code or extended result code.
// (Notably including SQLITE_ROW and SQLITE_DONE.)
int Step();
// Retrieve a value of various types.
// StringPiece values are only valid until a type conversion or a following
// call to Step(), Reset(), or Statement's destructor.
// Note that these don't have any real way to report error.
// In particular, on error a default value is returned, but that's a valid
// value. The error code is set on the database, but it's not guaranteed to
// not be set otherwise.
re2::StringPiece ColumnBlob(int col);
int64_t ColumnInt64(int col);
re2::StringPiece ColumnText(int col);
private:
friend class Database;
Statement() {}
Statement(const Statement &) = delete;
Statement &operator=(const Statement &) = delete;
sqlite3_stmt *me_ = nullptr;
};
// Thread-safe; the database is used in the default SQLITE_CONFIG_SERIALIZED.
class Database {
public:
Database() {}
Database(const Database &) = delete;
Database &operator=(const Database &) = delete;
// PRE: there are no unfinalized prepared statements or unfinished backup
// objects.
~Database();
bool Open(const char *filename, int flags, std::string *error_message);
// Prepare a statement.
//
// |used|, if non-null, will be updated with the number of bytes used from
// |sql| on success. (Only the first statement is parsed.)
//
// Returns the statement, or nullptr if there is no valid statement.
// |error_message| will be empty if there is simply no statement to parse.
std::unique_ptr<Statement> Prepare(re2::StringPiece sql, size_t *used,
std::string *error_message);
// Return the number of rows modified/inserted/deleted by the last DML
// statement executed.
int Changes() { return sqlite3_changes(me_); }
private:
sqlite3 *me_ = nullptr;
};
// Convenience routines below.
// Run through all the statements in |stmts|.
// Return error if any do not parse or return something other than SQLITE_DONE
// when stepped.
bool RunStatements(Database *db, re2::StringPiece stmts,
std::string *error_message);
} // namespace moonfire_nvr
#endif // MOONFIRE_NVR_SQLITE_H