- r - 以节省内存的方式增长 data.frame
- ruby-on-rails - ruby/ruby on rails 内存泄漏检测
- android - 无法解析导入android.support.v7.app
- UNIX 域套接字与共享内存(映射文件)
有人知道允许 SQLite 从 std::iostream
加载数据的包装器吗?
更明确地说:
std::fstream dataStream("database.sqlite");
...
sqlite3_open(...something using dataStream...);
我想使用流是因为它们的模块化:能够在文件仍在从网络下载的同时加载它,即时解压缩数据等。
使用 sqlite3_open_v2 应该是可能的自己注册后VFS .在实现(记录不完整的)函数一个小时后,我的第一次尝试从 SQLite 中得到了一个奇怪的“内存不足”错误,我想我会在这里问一下是否有人知道现有的实现,然后再花几个小时调试我的。
最佳答案
因为似乎没有人知道现有的实现,所以我自己编写了一个 :)
它是如何工作的:
sqlite3_open_v2
并将自定义文件系统的名称作为最后一个参数std::shared_ptr<std::iostream>
sqlite3_open_v2
的文件的名称是 std::shared_ptr<std::iostream>*
的 lexical_cast ;文件系统的打开函数将复制这个 shared_ptr
您可能想知道为什么我使用 lexical_cast 而我本可以使用 reinterpret_cast<const char*>(&stream)
。这是因为 SQLite 在文件名上调用 strcpy
,这可能会导致缓冲区溢出或数据截断问题。 SQLite 还获取我们的文件名并附加“-journal”或“-wal”以生成临时文件名。这会导致其他问题。
代码确实远非完美(有些东西没有实现)但它适用于我尝试过的一些基本操作(创建表、插入一些条目和选择)。
编辑:临时表和提交/回滚也在工作
void openConnection(std::shared_ptr<std::iostream> stream) {
sqlite3* ptr = nullptr;
// lexical_cast'ing the pointer to the stream into a string
// take extra care that it is a shared_ptr<iostream> and not istream or ostream or whatever
std::stringstream filenameStream;
filenameStream << reinterpret_cast<const void*>(&stream);
if (sqlite3_open_v2(filenameStream.str().c_str(), &ptr, 0, getIostreamVFSName().c_str()) != SQLITE_OK)
throw std::runtime_error(sqlite3_errmsg(ptr));
// ...use 'ptr' here...
}
// this function registers our custom VFS and return its name
std::string getIostreamVFSName() {
// a mutex protects the body of this function because we don't want to register the VFS twice
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
// check if the VFS is already registered, in this case we directly return
static const char* vfsName = "iostream_vfs_handler";
if (sqlite3_vfs_find(vfsName) != nullptr)
return vfsName;
// this is the structure that will store all the custom informations about an opened file
// all the functions get in fact pointer to an sqlite3_file object
// we give SQLite the size of this structure and SQLite will allocate it for us
// 'xOpen' will have to call all the members' constructors (using placement-new), and 'xClose' will call all the destructors
struct File : sqlite3_file {
std::shared_ptr<std::iostream> stream; // pointer to the source stream
int lockLevel; // level of lock by SQLite ; goes from 0 (not locked) to 4 (exclusive lock)
};
// making sure that the 'sqlite3_file' structure is at offset 0 in the 'File' structure
static_assert(offsetof(File, pMethods) == 0, "Wrong data alignment in custom SQLite3 VFS, lots of weird errors will happen during runtime");
// structure which contains static functions that we are going to pass to SQLite
// TODO: VC++2010 doesn't support lambda function treated as regular functions, or we would use this
struct Functions {
// opens a file by filling a sqlite3_file structure
// the name of the file should be the offset in memory where to find a "std::shared_ptr<std::iostream>"
// eg. you create a "std::shared_ptr<std::iostream>" whose memory location is 0x12345678
// you have to pass "12345678" as the file name
// this function will make a copy of the shared_ptr and store it in the sqlite3_file
static int xOpen(sqlite3_vfs*, const char* zName, sqlite3_file* fileBase, int flags, int *pOutFlags) {
// filling a structure with a list of methods that will be used by SQLite3 for this particular file
static sqlite3_io_methods methods;
methods.iVersion = 1;
methods.xClose = &xClose;
methods.xRead = &xRead;
methods.xWrite = &xWrite;
methods.xTruncate = &xTruncate;
methods.xSync = &xSync;
methods.xFileSize = &xFileSize;
methods.xLock = &xLock;
methods.xUnlock = &xUnlock;
methods.xCheckReservedLock = &xCheckReservedLock;
methods.xFileControl = &xFileControl;
methods.xSectorSize = &xSectorSize;
methods.xDeviceCharacteristics = &xDeviceCharacteristics;
fileBase->pMethods = &methods;
// SQLite allocated a buffer large enough to use it as a "File" object (see above)
auto fileData = static_cast<File*>(fileBase);
fileData->lockLevel = 0;
// if the name of the file doesn't contain a lexical_cast'ed pointer, then this is not our main DB file
// (note: the flags can also be used to determine this)
if (zName == nullptr || strlen(zName) != sizeof(void*) * 2) {
assert(flags | SQLITE_OPEN_CREATE);
// if this is not our main DB file, we create a temporary stringstream that will be deleted when the file is closed
// this behavior is different than expected from a file system (where file are permanent)
// but SQLite seems to accept it
new (&fileData->stream) std::shared_ptr<std::iostream>(std::make_shared<std::stringstream>(std::ios_base::in | std::ios_base::out | std::ios_base::binary));
} else {
// decoding our pointer, ie. un-lexical_cast'ing it
std::stringstream filenameStream(zName);
void* sharedPtrAddress = nullptr;
filenameStream >> sharedPtrAddress;
// our pointer points to a shared_ptr<std::iostream>, we make a copy of it
new (&fileData->stream) std::shared_ptr<std::iostream>(*static_cast<std::shared_ptr<std::iostream>*>(sharedPtrAddress));
}
assert(fileData->stream->good());
// I don't really know what to output as flags
// the "winOpen" implementation only sets either "readwrite" or "readonly"
if (pOutFlags != nullptr)
*pOutFlags = SQLITE_OPEN_READWRITE;
return SQLITE_OK;
}
static int xClose(sqlite3_file* fileBase) {
auto fileData = static_cast<File*>(fileBase);
assert(!fileData->stream->fail());
// we have to manually call the destructors of the objects in the structure
// because we created them with placement-new
fileData->stream.~shared_ptr();
return SQLITE_OK;
}
static int xRead(sqlite3_file* fileBase, void* buffer, int quantity, sqlite3_int64 offset) {
auto fileData = static_cast<File*>(fileBase);
assert(fileData->stream);
assert(fileData->stream->good());
fileData->stream->sync();
// we try to seek to the offset we want to read
fileData->stream->seekg(offset, std::ios::beg);
// if this fails, we'll just tell SQLite that we couldn't read the quantity it wanted
if (fileData->stream->fail()) {
fileData->stream->clear();
memset(static_cast<char*>(buffer), 0, quantity);
return SQLITE_IOERR_SHORT_READ;
}
// reading data
fileData->stream->read(static_cast<char*>(buffer), quantity);
fileData->stream->clear();
// if we reached EOF, gcount will be < to the quantity we have to read
// if this happens, SQLite asks us to fill the rest of the buffer with 0s
const auto gcount = fileData->stream->gcount();
if (gcount < quantity) {
memset(static_cast<char*>(buffer) + gcount, 0, static_cast<size_t>(quantity - gcount));
return SQLITE_IOERR_SHORT_READ;
}
return SQLITE_OK;
}
static int xWrite(sqlite3_file* fileBase, const void* buffer, int quantity, sqlite3_int64 offset) {
auto fileData = static_cast<File*>(fileBase);
assert(!fileData->stream->fail());
fileData->stream->sync();
// contrary to reading operating, SQLite doesn't accept partial writes
// either we succeed or we fail
fileData->stream->seekp(offset, std::ios::beg);
if (fileData->stream->fail()) {
fileData->stream->clear();
return SQLITE_IOERR_WRITE;
}
fileData->stream->write(static_cast<const char*>(buffer), quantity);
if (fileData->stream->fail()) {
fileData->stream->clear();
return SQLITE_IOERR_WRITE;
}
return SQLITE_OK;
}
static int xTruncate(sqlite3_file* fileBase, sqlite3_int64 size) {
// it is not possible to truncate a stream
// it makes sense to truncate a file or a buffer, but not a generic stream
// however it is possible to implement the xTruncate function as a no-op
return SQLITE_OK;
}
static int xSync(sqlite3_file* fileBase, int) {
// the flag passed as parameter is supposed to make a difference between a "partial sync" and a "full sync"
// we don't care and just call sync
auto fileData = static_cast<File*>(fileBase);
return fileData->stream->sync();
}
static int xFileSize(sqlite3_file* fileBase, sqlite3_int64* outputSize) {
// this function outputs the size of the file, wherever the read pointer or write pointer is
auto fileData = static_cast<File*>(fileBase);
assert(!fileData->stream->fail());
// we don't care about restoring the previous read pointer location,
// since the next operation will move it anyway
*outputSize = fileData->stream->seekg(0, std::ios::end).tellg();
assert(*outputSize != -1);
if (fileData->stream->fail())
fileData->stream->clear();
return SQLITE_OK;
}
static int xLock(sqlite3_file* fileBase, int level) {
auto fileData = static_cast<File*>(fileBase);
assert(level < std::numeric_limits<decltype(fileData->lockLevel)>::max());
fileData->lockLevel = level;
return SQLITE_OK;
}
static int xUnlock(sqlite3_file* fileBase, int level) {
auto fileData = static_cast<File*>(fileBase);
assert(level >= 0);
fileData->lockLevel = level;
return SQLITE_OK;
}
static int xCheckReservedLock(sqlite3_file* fileBase, int* pResOut) {
// this function outputs "true" if the file is locked,
// ie. if its lock level is >= 1
auto fileData = static_cast<File*>(fileBase);
*pResOut = (fileData->lockLevel >= 1);
return SQLITE_OK;
}
static int xFileControl(sqlite3_file* fileBase, int op, void* pArg) {
// this function is bit weird because it's supposed to handle generic operations
// the 'op' parameter is the operation code, and 'pArg' points to the arguments of the operation
auto fileData = static_cast<File*>(fileBase);
switch(op) {
case SQLITE_FCNTL_LOCKSTATE:
// outputs the current lock level of the file in reinterpret_cast<int*>(pArg)
*reinterpret_cast<int*>(pArg) = fileData->lockLevel;
break;
case SQLITE_FCNTL_SIZE_HINT:
// gives a hint about the size of the final file in reinterpret_cast<int*>(pArg)
break;
case SQLITE_FCNTL_CHUNK_SIZE:
// gives a hint about the size of blocks of data that SQLite will write at once
break;
// some operations are not documented (and not used in practice),
// so I'll leave them alone
case SQLITE_GET_LOCKPROXYFILE: return SQLITE_ERROR;
case SQLITE_SET_LOCKPROXYFILE: return SQLITE_ERROR;
case SQLITE_LAST_ERRNO: return SQLITE_ERROR;
}
return SQLITE_OK;
}
static int xSectorSize(sqlite3_file*) {
// returns the size of a sector of the HDD,
// we just return a dummy value
return 512;
}
static int xDeviceCharacteristics(sqlite3_file*) {
// returns the capabilities of the HDD
// see http://www.sqlite.org/c3ref/c_iocap_atomic.html
return SQLITE_IOCAP_ATOMIC | SQLITE_IOCAP_SAFE_APPEND | SQLITE_IOCAP_SEQUENTIAL;
}
static int xDelete(sqlite3_vfs*, const char* zName, int syncDir) {
// deletes a file ; this is called on 'journal' or 'wal' files
// these files are treated temporary by 'xOpen' (see above) and are destroyed when 'xClose' is called anyway
return SQLITE_OK;
}
static int xAccess(sqlite3_vfs*, const char* zName, int flags, int* pResOut) {
// depending on the value of 'flags':
// * outputs true if the file exists
// * outputs true if the file can be read
// * outputs true if the file can be written
// we handle all cases at once by returning true only if the file is the name of our main database
*pResOut = (strlen(zName) == sizeof(void*) * 2);
return SQLITE_OK;
}
static int xFullPathname(sqlite3_vfs*, const char* zName, int nOut, char* zOut) {
// this function turns a relative path into an absolute path
// since our file names are just lexical_cast'ed pointers, we just strcpy
strcpy_s(zOut, nOut, zName);
return SQLITE_OK;
}
static int xRandomness(sqlite3_vfs*, int nByte, char* zOut) {
// this function generates a random serie of characters to write in 'zOut'
// we use C++0x's <random> features
static std::mt19937 randomGenerator;
static std::uniform_int<char> randomDistributor;
for (auto i = 0; i < nByte; ++i)
zOut[i] = randomDistributor(randomGenerator);
return SQLITE_OK;
}
static int xSleep(sqlite3_vfs*, int microseconds) {
std::this_thread::sleep(std::chrono::microseconds(microseconds));
return SQLITE_OK;
}
static int xCurrentTime(sqlite3_vfs*, double* output) {
// this function should return the number of days elapsed since
// "noon in Greenwich on November 24, 4714 B.C according to the proleptic Gregorian calendar"
// I picked this constant from sqlite3.c which will make our life easier
static const double unixEpoch = 2440587.5;
*output = unixEpoch + double(time(nullptr)) / (60.*60.*24.);
return SQLITE_OK;
}
static int xCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64* output) {
// this function should return the number of milliseconds elapsed since
// "noon in Greenwich on November 24, 4714 B.C according to the proleptic Gregorian calendar"
// I picked this constant from sqlite3.c which will make our life easier
// note: I wonder if it is not hundredth of seconds instead
static const sqlite3_int64 unixEpoch = 24405875 * sqlite3_int64(60*60*24*100);
*output = unixEpoch + time(nullptr) * 1000;
return SQLITE_OK;
}
};
// creating the VFS structure
// TODO: some functions are not implemented due to lack of documentation ; I'll have to read sqlite3.c to find out
static sqlite3_vfs readStructure;
memset(&readStructure, 0, sizeof(readStructure));
readStructure.iVersion = 2;
readStructure.szOsFile = sizeof(File);
readStructure.mxPathname = 256;
readStructure.zName = vfsName;
readStructure.pAppData = nullptr;
readStructure.xOpen = &Functions::xOpen;
readStructure.xDelete = &Functions::xDelete;
readStructure.xAccess = &Functions::xAccess;
readStructure.xFullPathname = &Functions::xFullPathname;
/*readStructure.xDlOpen = &Functions::xOpen;
readStructure.xDlError = &Functions::xOpen;
readStructure.xDlSym = &Functions::xOpen;
readStructure.xDlClose = &Functions::xOpen;*/
readStructure.xRandomness = &Functions::xRandomness;
readStructure.xSleep = &Functions::xSleep;
readStructure.xCurrentTime = &Functions::xCurrentTime;
//readStructure.xGetLastError = &Functions::xOpen;
readStructure.xCurrentTimeInt64 = &Functions::xCurrentTimeInt64;
// the second parameter of this function tells if
// it should be made the default file system
sqlite3_vfs_register(&readStructure, false);
return vfsName;
}
关于c++ - 将 SQLite 与 std::iostream 一起使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3839158/
我正在开发一个小型图书馆,我需要做的一件事是让访问者访问一些数据并返回结果。 在一些较旧的 C++ 代码中,访问者需要声明一个 typedef return_type .例如,boost::stati
我正在尝试使用std:map类型的键和值制作std::any Visual Studio 2017 std::map m("lastname", "Ivanov"); std::cout (m["la
我已经在 C++ 的 map 中声明了一个集合为 std::map> .如何循环访问或打印设定值? 最佳答案 如果你知道如何迭代 std::map或 std::set单独地,您应该可以毫无问题地组合迭
如何循环? 我已经试过了: //----- code std::vector >::iterator it; for ( it = users.begin(); it != users.end();
我有两个用例。 A.我想同步访问两个线程的队列。 B.我想同步两个线程对队列的访问并使用条件变量,因为其中一个线程将等待另一个线程将内容存储到队列中。 对于用例 A,我看到了使用 std::lock_
我正在查看这两种类型特征的文档,但不确定有什么区别。我不是语言律师,但据我所知,它们都适用于“memcpy-able”类型。 它们可以互换使用吗? 最佳答案 不,这些术语不能互换使用。这两个术语都表示
我有以下测试代码,其中有一个参数 fS,它是 ofstream 的容器: #include #include #include #include int
这是这个问题的延续 c++ function ptr in unorderer_map, compile time error 我试图使用 std::function 而不是函数指针,并且只有当函数是
std::unordered_map str_bool_map = { {"a", true}, {"b", false}, {"c", true} }; 我们可以在此映射上使
我有以下对象 std::vector> vectorList; 然后我添加到这个使用 std::vector vec_tmp; vec_tmp.push_back(strDRG); vec_tmp.p
为什么 std::initializer_list不支持std::get<> , std::tuple_size和 std::tuple_element ?在constexpr中用得很多现在的表达式,
我有一个像这样定义的变量 auto drum = std::make_tuple ( std::make_tuple ( 0.3f , Ex
假设我有一个私有(private)std::map在我的类(class)里std::map 。我怎样才能将其转换为std::map返回给用户?我想要下面的原型(prototype) const std
假设我有一个私有(private)std::map在我的类(class)里std::map 。我怎样才能将其转换为std::map返回给用户?我想要下面的原型(prototype) const std
问题 我正在尝试将 lambda 闭包传递给 std::thread,它使用任意封闭参数调用任意封闭函数。 template std::thread timed_thread(Function&& f
我想创建一个模板类,可以容纳容器和容器的任意组合。例如,std::vector或 std::map ,例如。 我尝试了很多组合,但我必须承认模板的复杂性让我不知所措。我编译的关闭是这样的: templ
我有一个 std::vector>我将其分配给相同类型的第二个 vector 。 我收到这个编译器错误: /opt/gcc-8.2.0/include/c++/8.2.0/bits/stl_algob
有时候,我们有一个工厂可以生成一个 std::unique_ptr vector ,后来我们想在类/线程/你命名的之间共享这些指针。因此,最好改用 std::shared_ptr 。当然有一种方法可以
这个问题在这里已经有了答案: Sorting a vector of custom objects (14 个答案) 关闭 6 年前。 我创建了一个 vector vector ,我想根据我定义的参
我有三个类(class)成员: public: std::vector > getObjects(); std::vector > getObjects() const; privat
我是一名优秀的程序员,十分优秀!