From 816f29a9c7ffd6696a03aa3da78bdf732ec9936c Mon Sep 17 00:00:00 2001
From: znone <glyc@sina.com.cn>
Date: Mon, 25 Mar 2019 15:15:59 +0000
Subject: [PATCH] 为 SQLite 的 BLOB 字段实现一个符合标准的流。

---
 include/qtl_sqlite.hpp |  568 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 README.md              |   18 +
 2 files changed, 585 insertions(+), 1 deletions(-)

diff --git a/README.md b/README.md
index f800776..30c8f44 100644
--- a/README.md
+++ b/README.md
@@ -250,6 +250,24 @@
 - qtl::sqlite::query_result
 表示一个SQLite的查询结果集,用于以迭代器方式遍历查询结果。
 
+### SQLite的Blob字段
+
+通过QTL,可以通过标准流的方式访问SQLite的BLOB字段。
+下面的代码,先用数字0-9向BLOB字段填充,然后再次读取字段内容并显示到屏幕。
+
+```C++
+int64_t id=db->insert("INSERT INTO test_blob (Filename, Content, MD5) values(?, ?, ?)",
+	forward_as_tuple("sample", qtl::const_blob_data(nullptr, 1024), nullptr));
+
+qtl::sqlite::blobstream bs(*db, "test_blob", "Content", id);
+generate_n(ostreambuf_iterator<char>(bs), bs.blob_size()/sizeof(char), [i=0]() mutable { 
+	return char('0'+(i++)%10);
+});
+copy(istream_iterator<char>(bs), istream_iterator<char>(), ostream_iterator<char>(cout, nullptr));
+cout<<endl;
+
+```
+
 ## 有关ODBC的说明
 
 通过ODBC访问数据库时,包含头文件qtl_odbc.hpp。
diff --git a/include/qtl_sqlite.hpp b/include/qtl_sqlite.hpp
index 512d942..ded2e57 100644
--- a/include/qtl_sqlite.hpp
+++ b/include/qtl_sqlite.hpp
@@ -70,7 +70,12 @@
 		close();
 		verify_error(sqlite3_prepare_v2(db, query_text, (int)text_length, &m_stmt, &tail));
 		if(tail!=NULL)
-			m_tail_text.assign(tail, query_text+text_length);
+		{
+			if(text_length==-1)
+				m_tail_text.assign(tail);
+			else
+				m_tail_text.assign(tail, query_text+text_length);
+		}
 		else
 			m_tail_text.clear();
 	}
@@ -134,6 +139,10 @@
 	{
 		verify_error(sqlite3_bind_zeroblob(m_stmt, index+1, (int)n));
 	}
+	//void bind_zero_blob(int index, sqlite3_uint64 n)
+	//{
+	//	verify_error(sqlite3_bind_zeroblob64(m_stmt, index+1, (int)n));
+	//}
 	void bind_param(int index, qtl::null)
 	{
 		verify_error(sqlite3_bind_null(m_stmt, index+1));
@@ -484,6 +493,563 @@
 	sqlite3* m_db;
 };
 
+// stream for blob field
+
+class blobbuf : public std::streambuf
+{
+public:
+	blobbuf() 
+	{
+		init();
+	}
+	blobbuf(const blobbuf&) = delete;
+	blobbuf(blobbuf&& src) : std::streambuf(std::move(src))
+	{
+		init();
+		swap(src);
+	}
+	virtual ~blobbuf()
+	{
+		if(m_blob)
+		{
+			close();
+		}
+	}
+
+	blobbuf& operator=(const blobbuf&) = delete;
+	blobbuf& operator=(blobbuf&& src)
+	{
+		if(this!=&src)
+		{
+			reset_back();
+			close();
+			swap(src);
+		}
+		return *this;
+	}
+
+	void swap( blobbuf& other )
+	{
+		std::swap(m_blob, other.m_blob);
+		std::swap(m_inpos, other.m_inpos);
+		std::swap(m_outpos, other.m_outpos);
+
+		std::streambuf::swap(other);
+		std::swap(m_back_char, other.m_back_char);
+		if(eback() == &other.m_back_char)
+			set_back();
+		else
+			reset_back();
+
+		if(other.eback()==&m_back_char)
+			other.set_back();
+		else
+			other.reset_back();
+	}
+
+	static void init_blob(database& db, const char* table, const char* column, int64_t row, int length)
+	{
+		statement stmt;
+		std::ostringstream oss;
+		oss<< "UPDATE " << table << " SET " << column << "=? WHERE rowid=?";
+		stmt.open(db.handle(), oss.str().data());
+		stmt.bind_zero_blob(0, length);
+		stmt.bind_param(1, row);
+		stmt.fetch();
+	}
+	static void init_blob(database& db, const std::string& table, const std::string& column, int64_t row, int length)
+	{
+		return init_blob(db, table.c_str(), column.c_str(), row, length);
+	}
+
+	bool is_open() const { return m_blob!=nullptr; }
+
+	blobbuf* open(database& db, const char* table, const char* column, sqlite3_int64 row, 
+		std::ios_base::openmode mode, const char* dbname="main")
+	{
+		int flags=0;
+		if(mode&std::ios_base::out) flags=1;
+		if(sqlite3_blob_open(db.handle(), dbname, table, column, row, flags, &m_blob)==SQLITE_OK)
+		{
+			m_size=sqlite3_blob_bytes(m_blob)/sizeof(char);
+			// prepare buffer
+			size_t bufsize=std::min<size_t>(default_buffer_size, m_size);
+			if(mode&std::ios_base::in)
+			{
+				m_inbuf.resize(bufsize);
+				m_inpos=0;
+				setg(m_inbuf.data(), m_inbuf.data(), m_inbuf.data());
+			}
+			if(mode&std::ios_base::out)
+			{
+				m_outbuf.resize(bufsize);
+				m_outpos=0;
+				setp(m_outbuf.data(), m_outbuf.data()+bufsize);
+			}
+		}
+		return this;
+	}
+	blobbuf* open(database& db, const std::string& table, const std::string& column, sqlite3_int64 row, 
+		std::ios_base::openmode mode, const char* dbname="main")
+	{
+		return open(db, table.c_str(), column.c_str(), row, mode, dbname);
+	}
+
+	blobbuf* close()
+	{
+		if(m_blob==nullptr) 
+			return nullptr;
+
+		overflow();
+		sqlite3_blob_close(m_blob);
+		init();
+		return this;
+	}
+
+	std::streamoff blob_size() const { return std::streamoff(m_size); }
+
+	void flush() 
+	{
+		if(m_blob) 
+			overflow();
+	}
+
+protected:
+	enum { default_buffer_size = 4096 };
+
+	virtual pos_type seekoff( off_type off, std::ios_base::seekdir dir,
+		std::ios_base::openmode which = std::ios_base::in | std::ios_base::out ) override
+	{
+		if(!is_open())
+			return pos_type(off_type(-1));
+
+		pos_type pos=0;
+		if(which&std::ios_base::out)
+		{
+			pos=seekoff(m_outpos, off, dir);
+		}
+		else if(which&std::ios_base::in)
+		{
+			pos=seekoff(m_inpos, off, dir);
+		}
+		return seekpos(pos, which);
+	}
+
+	virtual pos_type seekpos( pos_type pos,
+		std::ios_base::openmode which = std::ios_base::in | std::ios_base::out) override
+	{
+		if(!is_open())
+			return pos_type(off_type(-1));
+		if(pos>=m_size)
+			return pos_type(off_type(-1));
+
+		if(which&std::ios_base::out)
+		{
+			if(pos<m_outpos || pos>=m_outpos+off_type(egptr()-pbase()))
+			{
+				overflow();
+				m_outpos=pos;
+				setp(m_outbuf.data(), m_outbuf.data()+m_outbuf.size());
+			}
+			else
+			{
+				pbump(off_type(pos-pabs()));
+			}
+		}
+		else if(which&std::ios_base::in)
+		{
+			if(pos<m_inpos || pos>=m_inpos+off_type(epptr()-eback()))
+			{
+				m_inpos=pos;
+				setg(m_inbuf.data(), m_inbuf.data(), m_inbuf.data());
+			}
+			else
+			{
+				gbump(off_type(pos-gabs()));
+			}
+		}
+		return pos;
+	}
+
+	virtual std::streamsize showmanyc() override
+	{
+		return m_size-pabs();
+	}
+
+	//reads characters from the associated input sequence to the get area 
+	virtual int_type underflow() override
+	{
+		if(!is_open()) 
+			return traits_type::eof();
+
+		if(pptr()>pbase())
+			overflow();
+
+		off_type count=egptr()-eback();
+		pos_type next_pos;
+		if(count==0 && eback()==m_inbuf.data())
+		{
+			setg(m_inbuf.data(), m_inbuf.data(), m_inbuf.data()+m_inbuf.size());
+			count=m_inbuf.size();
+		}
+		else
+		{
+			next_pos=m_inpos+pos_type(count);
+		}
+		if(next_pos>=m_size)
+			return traits_type::eof();
+
+		count=std::min(count, m_size-next_pos);
+		if(sqlite3_blob_read(m_blob, eback(), count, next_pos)!=SQLITE_OK)
+			return traits_type::eof();
+		m_inpos=next_pos;
+		setg(eback(), eback(), eback()+count);
+		return traits_type::to_int_type(*gptr());
+	}
+
+	/*//reads characters from the associated input sequence to the get area and advances the next pointer 
+	virtual int_type uflow() override
+	{
+
+	}*/
+
+	//writes characters to the associated output sequence from the put area 
+	virtual int_type overflow( int_type ch = traits_type::eof() ) override
+	{
+		if(!is_open()) 
+			return traits_type::eof();
+
+		if(pptr()!=pbase())
+		{
+			size_t count = pptr()-pbase();
+			if(sqlite3_blob_write(m_blob, pbase(), count, m_outpos)!=SQLITE_OK)
+				return traits_type::eof();
+
+			auto intersection = interval_intersection(m_inpos, egptr()-eback(), m_outpos, epptr()-pbase());
+			if(intersection.first!=intersection.second)
+			{
+				commit(intersection.first, intersection.second);
+			}
+
+			m_outpos+=count;
+			setp(pbase(), epptr());
+		}
+		if(!traits_type::eq_int_type(ch, traits_type::eof()))
+		{
+			char_type c = traits_type::to_char_type(ch);
+			if(m_outpos>=m_size)
+				return traits_type::eof();
+			if(sqlite3_blob_write(m_blob, &c, 1, m_outpos)!=SQLITE_OK)
+				traits_type::eof();
+			auto intersection = interval_intersection(m_inpos, egptr()-eback(), m_outpos, 1);
+			if(intersection.first!=intersection.second)
+			{
+				eback()[intersection.first-m_inpos]=c;
+			}
+			m_outpos+=1;
+			
+		}
+		return ch;
+	}
+
+	virtual int_type pbackfail( int_type c = traits_type::eof() ) override
+	{
+		if (gptr() == 0
+			|| gptr() <= eback()
+			|| (!traits_type::eq_int_type(traits_type::eof(), c)
+			&& !traits_type::eq(traits_type::to_char_type(c), gptr()[-1])))
+		{
+			return (traits_type::eof());	// can't put back, fail
+		}
+		else
+		{	// back up one position and store put-back character
+			gbump(-1);
+			if (!traits_type::eq_int_type(traits_type::eof(), c))
+				*gptr() = traits_type::to_char_type(c);
+			return (traits_type::not_eof(c));
+		}	
+	}
+
+private:
+	sqlite3_blob* m_blob;
+	std::vector<char> m_inbuf;
+	std::vector<char> m_outbuf;
+	pos_type m_size;
+	pos_type m_inpos;	//position in the input sequence
+	pos_type m_outpos;	//position in the output sequence
+
+	void init()
+	{
+		m_blob=nullptr;
+		m_size=0;
+		m_inpos=m_outpos=0;
+		m_set_eback=m_set_egptr=nullptr;
+		m_back_char=0;
+		setg(nullptr, nullptr, nullptr);
+		setp(nullptr, nullptr);
+	}
+
+	off_type seekoff(off_type position, off_type off, std::ios_base::seekdir dir)
+	{
+		off_type result=0;
+		switch(dir)
+		{
+		case std::ios_base::beg:
+			result=off;
+		case std::ios_base::cur:
+			result=position+off;
+		case std::ios_base::end:
+			result=m_size-off;
+		}
+		if(result>m_size)
+			result=m_size;
+		return result;
+	}
+
+	void reset_back()
+	{	// restore buffer after putback
+		if (this->eback() == &m_back_char)
+			this->setg(m_set_eback, m_set_eback, m_set_egptr);
+	}
+
+	void set_back()
+	{	// set up putback area
+		if (this->eback() != &m_back_char)
+		{	// save current get buffer
+			m_set_eback = this->eback();
+			m_set_egptr = this->egptr();
+		}
+		this->setg(&m_back_char, &m_back_char, &m_back_char + 1);
+	}
+
+	char_type *m_set_eback { nullptr };	// saves eback() during one-element putback
+	char_type *m_set_egptr { nullptr };	// saves egptr()
+	char_type m_back_char { 0 };
+
+	void move_data(blobbuf& other)
+	{
+		m_blob=other.m_blob;
+		other.m_blob=nullptr;
+		m_size=other.m_size;
+		other.m_size=0;
+		m_inpos=other.m_inpos;
+		other.m_inpos=0;
+		m_outpos=other.m_outpos;
+		other.m_outpos=0;
+	}
+
+	static std::pair<pos_type, pos_type> interval_intersection(pos_type first1, pos_type last1, pos_type first2, pos_type last2)
+	{
+		if(first1>first2)
+		{
+			std::swap(first1, first2);
+			std::swap(last1, last2);
+		}
+
+		if(first2<last1)
+			return std::make_pair(first2, std::min(last1, last2));
+		else
+			return std::make_pair(0, 0);
+	}
+
+	static std::pair<pos_type, pos_type> interval_intersection(pos_type first1, off_type count1, pos_type first2, off_type count2)
+	{
+		return interval_intersection(first1, first1+count1, first2, first2+count2);
+	}
+
+	void commit(off_type first, off_type last)
+	{
+		char_type* src= pbase()+(first-m_outpos);
+		char_type* dest = eback()+(first-m_inpos);
+		memmove(dest, src, last-first);
+	}
+
+	pos_type gabs() const // absolute offset of input pointer in blob field
+	{
+		return m_inpos+off_type(gptr()-eback());
+	}
+
+	pos_type pabs() const // absolute offset of output pointer in blob field
+	{
+		return m_outpos+off_type(pptr()-pbase());
+	}
+};
+
+class iblobstream : public std::istream
+{
+public:
+	iblobstream() : std::istream(&m_buffer) { }
+	iblobstream(database& db, const char* table, const char* column, sqlite3_int64 row, 
+		std::ios_base::openmode mode=std::ios_base::in, const char* dbname="main")
+		: std::istream(&m_buffer)
+	{
+		open(db, table, column, row, mode, dbname);
+	}
+	iblobstream(database& db, const std::string& table, const std::string& column, sqlite3_int64 row, 
+		std::ios_base::openmode mode=std::ios_base::in, const char* dbname="main")
+		: std::istream(&m_buffer)
+	{
+		open(db, table, column, row, mode, dbname);
+	}
+	iblobstream(const iblobstream&) = delete;
+	iblobstream(iblobstream&& src) : std::istream(&m_buffer), m_buffer(std::move(src.m_buffer)) { }
+
+	iblobstream& operator=(const iblobstream&) = delete;
+	iblobstream& operator=(iblobstream&& src) 
+	{
+		m_buffer.operator =(std::move(src.m_buffer));
+	}
+
+	bool is_open() const { return m_buffer.is_open(); }
+
+	void open(database& db, const char* table, const char* column, sqlite3_int64 row, 
+		std::ios_base::openmode mode=std::ios_base::in, const char* dbname="main")
+	{
+		if(m_buffer.open(db, table, column, row, mode|std::ios_base::in, dbname)==nullptr)
+			this->setstate(std::ios_base::failbit);
+		else
+			this->clear();
+	}
+	void open(database& db, const std::string& table, const std::string& column, sqlite3_int64 row, 
+		std::ios_base::openmode mode=std::ios_base::in, const char* dbname="main")
+	{
+		open(db, table.c_str(), column.c_str(), row, mode, dbname);
+	}
+
+	void close()
+	{
+		if(m_buffer.close()==nullptr)
+			this->setstate(std::ios_base::failbit);
+	}
+
+	blobbuf* rdbuf() const
+	{
+		return const_cast<blobbuf*>(&m_buffer);
+	}
+
+	std::streamoff blob_size() const { return m_buffer.blob_size(); }
+
+private:
+	blobbuf m_buffer;
+};
+
+class oblobstream : public std::ostream
+{
+public:
+	oblobstream() : std::ostream(&m_buffer) { }
+	oblobstream(database& db, const char* table, const char* column, sqlite3_int64 row, 
+		std::ios_base::openmode mode=std::ios_base::in, const char* dbname="main")
+		: std::ostream(&m_buffer)
+	{
+		open(db, table, column, row, mode, dbname);
+	}
+	oblobstream(database& db, const std::string& table, const std::string& column, sqlite3_int64 row, 
+		std::ios_base::openmode mode=std::ios_base::in, const char* dbname="main")
+		: std::ostream(&m_buffer)
+	{
+		open(db, table, column, row, mode, dbname);
+	}
+	oblobstream(const oblobstream&) = delete;
+	oblobstream(oblobstream&& src) : std::ostream(&m_buffer), m_buffer(std::move(src.m_buffer)) { }
+
+	oblobstream& operator=(const oblobstream&) = delete;
+	oblobstream& operator=(oblobstream&& src) 
+	{
+		m_buffer.operator =(std::move(src.m_buffer));
+	}
+
+	bool is_open() const { return m_buffer.is_open(); }
+
+	void open(database& db, const char* table, const char* column, sqlite3_int64 row, 
+		std::ios_base::openmode mode=std::ios_base::out, const char* dbname="main")
+	{
+		if(m_buffer.open(db, table, column, row, mode|std::ios_base::out, dbname)==nullptr)
+			this->setstate(std::ios_base::failbit);
+		else
+			this->clear();
+	}
+	void open(database& db, const std::string& table, const std::string& column, sqlite3_int64 row, 
+		std::ios_base::openmode mode=std::ios_base::out, const char* dbname="main")
+	{
+		open(db, table.c_str(), column.c_str(), row, mode, dbname);
+	}
+
+	void close()
+	{
+		if(m_buffer.close()==nullptr)
+			this->setstate(std::ios_base::failbit);
+	}
+
+	blobbuf* rdbuf() const
+	{
+		return const_cast<blobbuf*>(&m_buffer);
+	}
+
+	std::streamoff blob_size() const { return m_buffer.blob_size(); }
+
+private:
+	blobbuf m_buffer;
+};
+
+class blobstream : public std::iostream
+{
+public:
+	blobstream() : std::iostream(&m_buffer) { }
+	blobstream(database& db, const char* table, const char* column, sqlite3_int64 row, 
+		std::ios_base::openmode mode=std::ios_base::in, const char* dbname="main")
+		: std::iostream(&m_buffer) 
+	{
+		open(db, table, column, row, mode, dbname);
+	}
+	blobstream(database& db, const std::string& table, const std::string& column, sqlite3_int64 row, 
+		std::ios_base::openmode mode=std::ios_base::in, const char* dbname="main")
+		: std::iostream(&m_buffer) 
+	{
+		open(db, table, column, row, mode, dbname);
+	}
+	blobstream(const blobstream&) = delete;
+	blobstream(blobstream&& src) : std::iostream(&m_buffer), m_buffer(std::move(src.m_buffer)) { }
+
+	blobstream& operator=(const blobstream&) = delete;
+	blobstream& operator=(blobstream&& src) 
+	{
+		m_buffer.operator =(std::move(src.m_buffer));
+	}
+
+	bool is_open() const { return m_buffer.is_open(); }
+
+	void open(database& db, const char* table, const char* column, sqlite3_int64 row, 
+		std::ios_base::openmode mode=std::ios_base::in|std::ios_base::out, const char* dbname="main")
+	{
+		if(m_buffer.open(db, table, column, row, mode|std::ios_base::in|std::ios_base::out, dbname)==nullptr)
+			this->setstate(std::ios_base::failbit);
+		else
+			this->clear();
+	}
+	void open(database& db, const std::string& table, const std::string& column, sqlite3_int64 row, 
+		std::ios_base::openmode mode=std::ios_base::in|std::ios_base::out, const char* dbname="main")
+	{
+		open(db, table.c_str(), column.c_str(), row, mode, dbname);
+	}
+
+	void close()
+	{
+		if(m_buffer.close()==nullptr)
+			this->setstate(std::ios_base::failbit);
+	}
+
+	blobbuf* rdbuf() const
+	{
+		return const_cast<blobbuf*>(&m_buffer);
+	}
+
+	std::streamoff blob_size() const { return m_buffer.blob_size(); }
+
+private:
+	blobbuf m_buffer;
+};
+
+
 typedef qtl::transaction<database> transaction;
 
 template<typename Record>

--
Gitblit v1.9.3