1 #ifndef DB_SQLITE_SQLITEWRAPP__HPP
2 #define DB_SQLITE_SQLITEWRAPP__HPP
3 /*  $Id: sqlitewrapp.hpp 576166 2018-12-11 15:51:54Z grichenk $
4  * ===========================================================================
5  *
6  *                            PUBLIC DOMAIN NOTICE
7  *               National Center for Biotechnology Information
8  *
9  *  This software/database is a "United States Government Work" under the
10  *  terms of the United States Copyright Act.  It was written as part of
11  *  the author's official duties as a United States Government employee and
12  *  thus cannot be copyrighted.  This software/database is freely available
13  *  to the public for use. The National Library of Medicine and the U.S.
14  *  Government have not placed any restriction on its use or reproduction.
15  *
16  *  Although all reasonable efforts have been taken to ensure the accuracy
17  *  and reliability of the software and data, the NLM and the U.S.
18  *  Government do not and cannot warrant the performance or results that
19  *  may be obtained by using this software or data. The NLM and the U.S.
20  *  Government disclaim all warranties, express or implied, including
21  *  warranties of performance, merchantability or fitness for any particular
22  *  purpose.
23  *
24  *  Please cite the author in any work or product based on this material.
25  *
26  * ===========================================================================
27  *
28  * Authors:  Pavel Ivanov
29  *
30  * File Description:
31  *   Convenient wrappers for SQLite-related objects and most commonly used
32  *   functions. Wrappers require SQLite 3.6.14 or higher with async vfs
33  *   extension. All wrappers are written in assumption that no one else calls
34  *   any sqlite3* functions except the wrapper itself.
35  */
36 
37 #include <corelib/ncbiexpt.hpp>
38 #include <corelib/ncbimisc.hpp>
39 #include <corelib/ncbistr.hpp>
40 #include <corelib/ncbimtx.hpp>
41 #include <corelib/ncbi_system.hpp>
42 #include <corelib/obj_pool.hpp>
43 
44 #include <sqlite3.h>
45 #ifdef HAVE_SQLITE3ASYNC_H
46 # include <sqlite3async.h>
47 #endif
48 
49 #include <deque>
50 
51 
52 BEGIN_NCBI_SCOPE
53 
54 
55 class CSQLITE_Exception;
56 class CSQLITE_Connection;
57 class CSQLITE_Statement;
58 class CSQLITE_Blob;
59 
60 
61 /// Utility class for some global-purpose functions tuning SQLite as a whole
62 /// as opposed to tuning connection-by-connection.
63 class CSQLITE_Global
64 {
65 public:
66     /// Install non-default cache for all SQLite databases. Can be useful
67     /// if default policy of working with cache is inappropriate for your
68     /// application. For more details on sqlite3_pcache_methods look into
69     /// comments before it in sqlite3.h.
70     /// Method can be called only before SQLite is initialized with
71     /// Initialize() method.
72     static void SetCustomPageCache(sqlite3_pcache_methods* methods);
73 
74     /// Initialization of SQLite and tuning some default parameters.
75     /// For these SQLite wrappers to work properly this method should be
76     /// called before any work with other classes.
77     static void Initialize(void);
78     /// Finish all SQLite operations. Method should be called at the end of
79     /// application execution. After it has been called any further attempts
80     /// to use wrapper classes will likely cause application crash.
81     static void Finalize(void);
82 
83     /// Enable use of the same cache by different connections to the same
84     /// database file. Setting can be changed at any time and will affect all
85     /// connections opened after changing.
86     static void EnableSharedCache(bool enable = true);
87     /// Get default virtual file system installed in SQLite.
88     /// Method can be called only after SQLite is initialized with
89     /// Initialize() method.
90     static sqlite3_vfs* GetDefaultVFS(void);
91     /// Register new virtual file system in SQLite.
92     /// If set_default is TRUE then all connections that will be opened after
93     /// call to this method will use this virtual file system. When
94     /// set_default is FALSE then connections will have to state explicitly
95     /// if they want to use this new vfs (although for now this functionality
96     /// is not yet implemented in CSQLITE_Connection).
97     /// Method can be called only after SQLite is initialized with
98     /// Initialize() method.
99     static void RegisterCustomVFS(sqlite3_vfs* vfs, bool set_default = true);
100 
101 #ifdef HAVE_SQLITE3ASYNC_H
102     /// Setup the option for asynchronous file system to do the actual locking
103     /// of database files on disk. If lock_files is TRUE then files will be
104     /// actually locked on hard disk, if it is FALSE files will not be locked
105     /// and so consistency of access to databases opened with asynchronous
106     /// writing flag set will be maintained only inside current process.
107     /// Method should be called only when there's no connections opened.
108     static void SetAsyncWritesFileLocking(bool lock_files);
109     /// Launch background thread doing all asynchronous writes to databases.
110     /// Method is no-op if background thread has already launched.
111     static void RunAsyncWritesThread(void);
112 #endif  // HAVE_SQLITE3ASYNC_H
113 
114 private:
115     CSQLITE_Global(void);
116     CSQLITE_Global(CSQLITE_Global&);
117     CSQLITE_Global& operator= (CSQLITE_Global&);
118 };
119 
120 
121 /// Object factory creating sqlite3* handles.
122 /// For internal use in CSQLITE_Connection's internal pool.
123 class CSQLITE_HandleFactory
124 {
125 public:
126     CSQLITE_HandleFactory(CSQLITE_Connection* conn);
127 
128     /// Create new database handle
129     sqlite3* CreateObject(void);
130 
131     /// Destroy database handle
132     void DeleteObject(sqlite3* handle);
133 
134 private:
135     /// Connection object this factory is bound to
136     CSQLITE_Connection* m_Conn;
137 };
138 
139 
140 /// Connection to SQLite database.
141 class CSQLITE_Connection
142 {
143 public:
144     /// Flags controlling specifics of database connection operation.
145     /// Only one flag can be used in each group of flags (separated by
146     /// comments).
147     enum EOperationFlags {
148         // Mode of operation in multi-threaded environment
149         fInternalMT = 0x00,  ///< Object and all statements and blobs created
150                              ///< on top of it should support internal
151                              ///< synchronization between different threads
152         fExternalMT = 0x01,  ///< Object and all statements and blobs created
153                              ///< on top of it will not be used from different
154                              ///< threads simultaneously, thus performance is
155                              ///< optimized, object creates only one low-level
156                              ///< SQLite connection
157         /// Default value for multi-threading group of flags
158         fDefaultMT  = fInternalMT,
159         eAllMT      = fInternalMT + fExternalMT,
160 
161         // Mode of vacuuming and shrinking file size
162         fVacuumOn      = 0x00,  ///< Vacuuming is on, database file has always
163                                 ///< the minimum size possible
164         fVacuumOff     = 0x02,  ///< Vacuuming is off, database file can only
165                                 ///< grow
166         fVacuumManual  = 0x04,  ///< Vacuuming is only by request
167         /// Default value for vacuuming group of flags
168         fDefaultVacuum = fVacuumOn,
169         eAllVacuum     = fVacuumOn + fVacuumOff + fVacuumManual,
170 
171         // Mode of journaling of all transactions
172         fJournalDelete   = 0x00,  ///< Journal on disk, journal file is
173                                   ///< deleted after each transaction
174         fJournalTruncate = 0x08,  ///< Journal on disk, size of journal file
175                                   ///< is nullified after each transaction
176         fJournalPersist  = 0x10,  ///< Journal on disk, journal file can only
177                                   ///< grow, never shrinks
178         fJournalMemory   = 0x20,  ///< Journaling is entirely in-memory
179         fJournalOff      = 0x40,  ///< Journaling is completely off (not
180                                   ///< recommended - transactions cannot be
181                                   ///< rollbacked unless they consist of just
182                                   ///< one simple operation)
183         /// Default value for journaling group of flags
184         fDefaultJournal  = fJournalDelete,
185         eAllJournal      = fJournalDelete + fJournalTruncate + fJournalPersist
186                            + fJournalMemory + fJournalOff,
187 
188         // Mode of reliable synchronization with disk database file
189         fSyncFull    = 0x000,  ///< Full synchronization, database cannot be
190                                ///< corrupted
191         fSyncOn      = 0x080,  ///< Synchronization is on, there is a slight
192                                ///< chance of database corruption when
193                                ///< OS crashes
194         fSyncOff     = 0x100,  ///< Synchronization is off, database can be
195                                ///< corrupted on OS crash or power outage
196         /// Default value for synchronization group of flags
197         fDefaultSync = fSyncFull,
198         eAllSync     = fSyncOff + fSyncOn + fSyncFull,
199 
200         /// Mode of storing temporary data
201         fTempToMemory = 0x000,  ///< Store all temporary tables in memory
202         fTempToFile   = 0x200,  ///< Use actual disk file for temporary
203                                 ///< storage
204         /// Default value for "temporary" group of flags
205         fDefaultTemp  = fTempToMemory,
206         eAllTemp      = fTempToMemory + fTempToFile,
207 
208         /// Mode of doing all writes to the database
209         fWritesSync    = 0x000,  ///< All writes are synchronous - when update
210                                  ///< has executed data is already in file
211                                  ///< (though @sa fSyncOff)
212 #ifdef HAVE_SQLITE3ASYNC_H
213         fWritesAsync   = 0x400,  ///< All writes are asynchronous - updates
214                                  ///< return as quick as they can, actual
215                                  ///< writes to database happen in background
216                                  ///< thread (@sa
217                                  ///< CSQLITE_Global::RunAsyncWritesThread)
218 #endif
219         /// Default value for writes mode group of flags
220         fDefaultWrites = fWritesSync,
221         eAllWrites     = fWritesSync
222 #ifdef HAVE_SQLITE3ASYNC_H
223                          + fWritesAsync
224 #endif
225                          ,
226 
227         fReadOnly      = 0x8000, ///< The DB is read-only. Also forces fVacuumOff flag.
228 
229         /// Default value of all flags
230         eDefaultFlags = fDefaultMT + fDefaultVacuum + fDefaultJournal
231                         + fDefaultSync + fDefaultTemp + fDefaultWrites
232     };
233     /// Bit mask of EOperationFlags
234     typedef int TOperationFlags;
235 
236     /// Connect to SQLite database.
237     /// NOTE: Vacuuming flags have any value here only when new database is
238     ///       created. When old database is opened with some data in it
239     ///       then vacuuming flags are no-op, flags used at creation are in
240     ///       effect.
241     /// NOTE: Connection should be destroyed after all statements or blobs
242     ///       created on top of it. destroying statements after connection
243     ///       was destroyed will result in segmentation fault.
244     ///
245     /// @param file_name
246     ///   File name of SQLite database. If it doesn't exist it will be
247     ///   created. If directory for file doesn't exist exception will be
248     ///   thrown. If file name is empty connection to temporary file will be
249     ///   open. This file will be deleted after connection is closed. If file
250     ///   name is ":memory:" then connection to in-memory database will be
251     ///   open. In both cases (temporary file and in-memory database) flag
252     ///   fExternalMT should be set (it is not checked but you will get
253     ///   malfunctioning otherwise).
254     /// @param flags
255     ///   Flags for object operations
256     CSQLITE_Connection(CTempString     file_name,
257                        TOperationFlags flags = eDefaultFlags);
258     ~CSQLITE_Connection(void);
259 
260     /// Get database file name for the connection
261     const string&   GetFileName(void) const;
262     /// Get flags controlling database connection operation
263     TOperationFlags GetFlags   (void) const;
264     /// Change flags controlling database connection operation.
265     /// NOTE: Vacuuming flags cannot be changed here. New flags will be
266     ///       applied only to newly created low-level SQLite connections.
267     ///       Thus in fInternalMT mode it is recommended to destroy all
268     ///       CSQLITE_Statement and CSQLITE_Blob objects before changing
269     ///       flags. In fExternalMT mode this cleaning of statements is
270     ///       mandatory - you will get memory and other resources leak
271     ///       otherwise.
272     void SetFlags(TOperationFlags flags);
273     /// Set page size for the database (in bytes). Setting has any value only
274     /// if database is empty (has no tables) and no statements are created
275     /// yet. Page size should be power of 2 and be less than or equal to
276     /// 32768. If page size is not set default value is used.
277     void SetPageSize(unsigned int size);
278     /// Get page size used in the database (in bytes)
279     unsigned int GetPageSize(void);
280     /// Set recommended size of the database cache (number of pages). If value
281     /// is not set or set to 0 then default SQLite value is used.
282     void SetCacheSize(unsigned int size);
283     /// Get recommended size of the database cache. If cache size was not set
284     /// by SetCacheSize() method then this method returns 0, though actually
285     /// SQLite uses some default value.
286     unsigned int GetCacheSize(void);
287 
288     /// Try to shrink database and free up to max_free_size bytes of disk
289     /// space. Method is no-op when vacuum mode is other than fVacuumManual.
290     ///
291     /// @sa EOperationFlags
292     void Vacuum(size_t max_free_size);
293     /// Create statement for executing manual vacuuming.
294     /// Caller is responsible for deleting returned statement object.
295     ///
296     /// @sa Vacuum
297     CSQLITE_Statement* CreateVacuumStmt(size_t max_free_size);
298 
299     /// Execute sql statement without returning any results.
300     void ExecuteSql(CTempString sql);
301     /// Delete database under this connection. Method makes sure that journal
302     /// file is deleted along with database file itself. All CSQLITE_Statement
303     /// and CSQLITE_Blob objects on this connection should be deleted before
304     /// call to this method. Any use of connection after call to this method
305     /// will cause the database file to be created again.
306     void DeleteDatabase(void);
307 
308     /// Create a read-only copy of the database in memory.
309     /// The in-memory database is never synchronized with the original file.
310     /// If 'shared' is true, a single copy of in-memory database is used.
311     /// Otherwise each call to the function creates a new database and
312     /// the returned connection is the only way to access it.
313     /// On error return NULL.
314     static CSQLITE_Connection* CreateInMemoryDatabase(CTempString file_name,
315                                                       bool        shared = false);
316 
317 public:
318     // For internal use only
319 
320     /// Lock and return low-level connection handle
321     sqlite3* LockHandle(void);
322     /// Unlock unused low-level connection handle. This handle will be used
323     /// later by some other statement. Essentially no-op in eExternalMT mode.
324     ///
325     /// @sa EMultiThreadMode
326     void UnlockHandle(sqlite3* handle);
327     /// Setup newly created low-level connection handle.
328     /// Executes all necessary pragmas according to operation flags set.
329     void SetupNewConnection(sqlite3* handle);
330 
331 private:
332     CSQLITE_Connection(const CSQLITE_Connection&);
333     CSQLITE_Connection& operator=(const CSQLITE_Connection&);
334 
335     /// Check that only one flag in specific group of flags is given
336     void x_CheckFlagsValidity(TOperationFlags flags, EOperationFlags mask);
337     /// Execute sql statement on given low-level connection handle
338     void x_ExecuteSql(sqlite3* handle, CTempString sql);
339 
340 
341     typedef CObjPool<sqlite3, CSQLITE_HandleFactory>  THandlePool;
342 
343     /// File name of SQLite database
344     string           m_FileName;
345     /// Flags for object operation
346     TOperationFlags  m_Flags;
347     /// Page size inside the database
348     unsigned int     m_PageSize;
349     /// Recommended size of cache for the database
350     unsigned int     m_CacheSize;
351     /// Pool of low-level database connections
352     THandlePool      m_HandlePool;
353     // In-memory database can not use handle pool. The only handle
354     // is locked for the whole database life.
355     sqlite3*         m_InMemory;
356 };
357 
358 
359 /// SQL statement executing on SQLite database
360 class CSQLITE_Statement
361 {
362 public:
363     /// Create and prepare statement for given database connection.
364     /// If sql is empty nothing is prepared in the constructor.
365     CSQLITE_Statement(CSQLITE_Connection* conn, CTempString sql = kEmptyStr);
366     /// Create and prepare statement for particular low-level connection.
367     /// If sql is empty nothing is prepared in the constructor.
368     CSQLITE_Statement(sqlite3* conn_handle, CTempString sql = kEmptyStr);
369     ~CSQLITE_Statement();
370 
371     /// Change sql text for the object and prepare new statement
372     void SetSql(CTempString sql);
373 
374     /// Bind integer value to parameter index.
375     /// Parameters' numbers start from 1.
376     void Bind          (int index, int           val);
377     /// Bind unsigned integer value to parameter index.
378     /// Parameters' numbers start from 1.
379     void Bind          (int index, unsigned int  val);
380     /// Bind integer value to parameter index.
381     /// Parameters' numbers start from 1.
382     void Bind          (int index, long          val);
383     /// Bind unsigned integer value to parameter index.
384     /// Parameters' numbers start from 1.
385     void Bind          (int index, unsigned long val);
386 #if !NCBI_INT8_IS_LONG
387     /// Bind 64-bit integer value to parameter index.
388     /// Parameters' numbers start from 1.
389     void Bind          (int index, Int8          val);
390     /// Bind unsigned 64-bit integer value to parameter index.
391     /// Parameters' numbers start from 1.
392     void Bind          (int index, Uint8         val);
393 #endif
394     /// Bind double value to parameter index.
395     /// Parameters' numbers start from 1.
396     void Bind          (int index, double        val);
397     /// Bind text value to parameter index.
398     /// Parameters' numbers start from 1. Value of parameter is copied inside
399     /// the method so it may disappear after call.
400     void Bind          (int index, CTempString   val);
401     /// Bind text value to parameter index.
402     /// Parameters' numbers start from 1. Value of parameter is not copied,
403     /// pointer is remembered instead. So given value should exist until
404     /// statement is executed.
405     void Bind          (int index, const char*   data, size_t size);
406     /// Bind blob value to parameter index.
407     /// Parameters' numbers start from 1. Value of parameter is not copied,
408     /// pointer is remembered instead. So given value should exist until
409     /// statement is executed.
410     void Bind          (int index, const void*   data, size_t size);
411     /// Bind blob value of given size containing only zeros to parameter
412     /// index. Parameters' numbers start from 1.
413     void BindZeroedBlob(int index, size_t        size);
414     /// Bind null value to parameter index.
415     void BindNull      (int index);
416 
417     /// Execute statement without returning any result
418     void Execute(void);
419     /// Step through results of the statement.
420     /// If statement wasn't executed yet it starts executing. If statement
421     /// already returned some rows then it moves to the next row. Be aware
422     /// that when statement begins executing until it returns all rows or
423     /// is reseted the database is locked so that nobody else can write to it.
424     /// So it's recommended to step through all results as quick as possible.
425     ///
426     /// @return
427     ///   TRUE if new row is available in the result.
428     ///   FALSE if no more rows are available and thus statement finished
429     ///   executing and released all locks held.
430     bool Step(void);
431     /// Reset the statement to release all locks and to be ready to execute
432     /// again.
433     void Reset(void);
434     /// Reset all bindings to the statement to their initial NULL values
435     void ClearBindings(void);
436 
437     /// Get number of columns in result set. Method should be executed only
438     /// after Step() returned TRUE, otherwise returned value is undefined.
439     int GetColumnsCount(void) const;
440     /// Get name of column at index col_ind in result set. The leftmost column
441     /// of result set has the index 0. Method should be executed only after
442     /// Step() returned TRUE, otherwise returned value is undefined.
443     CStringUTF8 GetColumnName(int col_ind) const;
444 
445     /// Get integer value from column col_ind in current row. The leftmost
446     /// column of result set has index 0. Method should be executed only
447     /// after Step() returned TRUE, otherwise returned value is undefined.
448     int    GetInt   (int col_ind) const;
449     /// Get 64-bit integer value from column col_ind in current row.
450     /// The leftmost column of result set has the index 0. Method should be
451     /// executed only after Step() returned TRUE, otherwise returned value is
452     /// undefined.
453     Int8   GetInt8  (int col_ind) const;
454     /// Get double value from column col_ind in current row. The leftmost
455     /// column of result set has the index 0. Method should be executed only
456     /// after Step() returned TRUE, otherwise returned value is undefined.
457     double GetDouble(int col_ind) const;
458     /// Get text value from column col_ind in current row. The leftmost
459     /// column of result set has the index 0. Method should be executed only
460     /// after Step() returned TRUE, otherwise returned value is undefined.
461     string GetString(int col_ind) const;
462     /// Get size of blob value from column col_ind in current row. The leftmost
463     /// column of result set has the index 0. Method should be executed only
464     /// after Step() returned TRUE, otherwise returned value is undefined.
465     size_t GetBlobSize(int col_ind) const;
466     /// Read blob value from column col_ind in current row. The leftmost
467     /// column of result set has the index 0. Method should be executed only
468     /// after Step() returned TRUE, otherwise returned value is undefined.
469     ///
470     /// @param col_ind
471     ///   Index of column to read (left-most is 0)
472     /// @param buffer
473     ///   Pointer to buffer where to write the data
474     /// @param size
475     ///   Size of buffer available for writing
476     /// @return
477     ///   Number of bytes read from result set and put into buffer
478     size_t GetBlob  (int col_ind, void* buffer, size_t size) const;
479 
480     /// Get rowid of the row inserted in last statement execution.
481     /// If connection is working in eExternalMT mode then method should be
482     /// called after statement execution and before execution of any other
483     /// statement in the same connection. If last executed statement was not
484     /// insert then 0 is returned.
485     Int8 GetLastInsertedRowid(void) const;
486     /// Get number of rows changed during last insert, delete or update
487     /// statement. Number does not include rows changed by triggers or by
488     /// any other indirect means. If called when no insert, update or delete
489     /// statement was executed result is undefined.
490     int  GetChangedRowsCount (void) const;
491 
492 private:
493     CSQLITE_Statement(const CSQLITE_Statement&);
494     CSQLITE_Statement& operator=(const CSQLITE_Statement&);
495 
496     /// Prepare new statement if it's not empty
497     void x_Prepare(CTempString sql);
498     /// Finalize current statement
499     void x_Finalize(void);
500 
501 
502     /// Connection this statement belongs to
503     CSQLITE_Connection* m_Conn;
504     /// Low-level connection handle provided for this statement
505     sqlite3*            m_ConnHandle;
506     /// Low-level statement handle
507     sqlite3_stmt*       m_StmtHandle;
508 };
509 
510 
511 /// "Scoped" statement activity object.
512 /// Object binds to some statement and ensures that by the end of the scope
513 /// statement will be reseted and all database locks will be released. Besides
514 /// that object acts as smart pointer to the statement.
515 class CSQLITE_StatementLock
516 {
517 public:
518     /// Bind activity control to the given statement
519     CSQLITE_StatementLock(CSQLITE_Statement* stmt);
520 
521     ~CSQLITE_StatementLock(void);
522 
523     /// Smart pointer's transformation
524     CSQLITE_Statement& operator*  (void);
525 
526     /// Smart pointer's transformation
527     CSQLITE_Statement* operator-> (void);
528 
529     /// Smart pointer's transformation
530     operator CSQLITE_Statement*   (void);
531 
532 private:
533     CSQLITE_StatementLock(const CSQLITE_StatementLock&);
534     CSQLITE_StatementLock& operator= (const CSQLITE_StatementLock&);
535 
536     /// Statement this object is bound to
537     CSQLITE_Statement* m_Stmt;
538 };
539 
540 
541 // Structures for internal use only
542 template <bool readwrite> struct SSQLITE_BlobOpen;
543 struct SSQLITE_BlobClose;
544 
545 /// Object reading and writing blob directly (mostly without executing select
546 /// or update statements), can read and write blob incrementally.
547 /// Class assumes that from the moment of its creation till deletion nobody
548 /// else writes to the blob and/or changes its size.
549 class CSQLITE_Blob
550 {
551 public:
552     /// Create object reading and writing blob
553     /// Identified row with blob should exist in database, exception is
554     /// thrown otherwise.
555     ///
556     /// @param conn
557     ///   Connection which object will work over
558     /// @param table
559     ///   Table name where blob is located
560     /// @param column
561     ///   Column name where blob is located
562     /// @param rowid
563     ///   Rowid of the row where blob is located
564     CSQLITE_Blob(CSQLITE_Connection* conn,
565                  CTempString         table,
566                  CTempString         column,
567                  Int8                rowid);
568     /// Create object reading and writing blob
569     /// Identified row with blob should exist in database, exception is
570     /// thrown otherwise.
571     ///
572     /// @param conn
573     ///   Connection which object will work over
574     /// @param db_name
575     ///   Database name where blob is located
576     /// @param table
577     ///   Table name where blob is located
578     /// @param column
579     ///   Column name where blob is located
580     /// @param rowid
581     ///   Rowid of the row where blob is located
582     CSQLITE_Blob(CSQLITE_Connection* conn,
583                  CTempString         db_name,
584                  CTempString         table,
585                  CTempString         column,
586                  Int8                rowid);
587     ~CSQLITE_Blob(void);
588 
589     /// Set current position inside the blob to desired value
590     void   ResetPosition(size_t position = 0);
591     /// Get current position inside the blob
592     size_t GetPosition  (void);
593     /// Get size of the blob
594     size_t GetSize(void);
595 
596     /// Read from blob at current position
597     ///
598     /// @param buffer
599     ///   Pointer to buffer where to write read data
600     /// @param size
601     ///   Size of buffer available for writing
602     /// @return
603     ///   Number of bytes read from blob. If current position is beyond end
604     ///   of the blob then 0 is returned and buffer is unchanged.
605     size_t Read   (void*       buffer, size_t size);
606     /// Write to blob at current position
607     ///
608     /// @param data
609     ///   Pointer to data to write
610     /// @param size
611     ///   Number of bytes to write
612     void   Write  (const void* data,   size_t size);
613     /// Set statement to use when appending to existing blob if necessary.
614     /// Statement should be of the form
615     /// "update ... set ... = ...||?2 where rowid=?1"
616     /// If not set object creates its own statement on the first append.
617     /// Created statement is destroyed together with this blob object.
618     /// Statement set by this method is not destroyed with blob object.
619     /// This method can be useful if one wants to cache statement between
620     /// different blob objects creation and deletion to not prepare it with
621     /// each blob object.
622     void SetAppendStatement(CSQLITE_Statement* stmt);
623 
624 private:
625     CSQLITE_Blob(const CSQLITE_Blob&);
626     CSQLITE_Blob& operator= (const CSQLITE_Blob&);
627 
628     /// Initialize the object
629     void x_Init     (void);
630     /// Open low-level blob object
631     ///
632     /// @param readwrite
633     ///   TRUE if there's need for write access, FALSE if read-only access is
634     ///   sufficient
635     void x_OpenBlob (bool readwrite = false);
636     /// Close low-level blob object
637     void x_CloseBlob(void);
638 
639     template <bool readwrite> friend struct SSQLITE_BlobOpen;
640     friend struct SSQLITE_BlobClose;
641 
642 
643     /// Connection this blob object belongs to
644     CSQLITE_Connection*        m_Conn;
645     /// Low-level connection handle provided for this blob object
646     sqlite3*                   m_ConnHandle;
647     /// Statement used for appending to existing blob value
648     AutoPtr<CSQLITE_Statement> m_AppendStmt;
649     /// Database name for blob
650     string                     m_Database;
651     /// Table name for blob
652     string                     m_Table;
653     /// Column name for blob
654     string                     m_Column;
655     /// Rowid for the row where blob is located
656     Int8                       m_Rowid;
657     /// Low-level handle of the blob
658     sqlite3_blob*              m_BlobHandle;
659     /// Size of the blob
660     size_t                     m_Size;
661     /// Current position inside the blob
662     size_t                     m_Position;
663 };
664 
665 
666 /// Exception thrown from all SQLite-related objects and functions
667 class CSQLITE_Exception : public CException
668 {
669 public:
670     enum EErrCode {
671         eUnknown,       ///< Unknown error
672         eWrongFlags,    ///< Incorrect set of flags in connection constructor
673         eDBOpen,        ///< Error during database opening
674         eStmtPrepare,   ///< Error preparing statement
675         eStmtFinalize,  ///< Error finalizing statement
676         eStmtBind,      ///< Error binding statement parameters
677         eStmtStep,      ///< Error stepping through the statement
678         eStmtReset,     ///< Error reseting statement
679         eBlobOpen,      ///< Error opening blob object
680         eBlobClose,     ///< Error closing blob object
681         eBlobRead,      ///< Error reading directly from blob
682         eBlobWrite,     ///< Error writing directly to blob
683         eConstraint,    ///< Constraint violation during statement execution
684         eDeadLock,      ///< SQLite detected possible deadlock between
685                         ///< different threads
686         eBadCall        ///< Method called when there's no enough capabilities
687                         ///< to finish it successfully
688     };
689     virtual const char* GetErrCodeString(void) const override;
690     NCBI_EXCEPTION_DEFAULT(CSQLITE_Exception, CException);
691 };
692 
693 
694 
695 //////////////////////////////////////////////////////////////////////////
696 // Inline functions
697 //////////////////////////////////////////////////////////////////////////
698 
699 inline
CSQLITE_Statement(CSQLITE_Connection * conn,CTempString sql)700 CSQLITE_Statement::CSQLITE_Statement(CSQLITE_Connection* conn,
701                                      CTempString         sql)
702     : m_Conn(conn),
703       m_ConnHandle(NULL),
704       m_StmtHandle(NULL)
705 {
706     x_Prepare(sql);
707 }
708 
709 inline
CSQLITE_Statement(sqlite3 * conn_handle,CTempString sql)710 CSQLITE_Statement::CSQLITE_Statement(sqlite3*    conn_handle,
711                                      CTempString sql)
712     : m_Conn(NULL),
713       m_ConnHandle(conn_handle),
714       m_StmtHandle(NULL)
715 {
716     x_Prepare(sql);
717 }
718 
719 inline void
SetSql(CTempString sql)720 CSQLITE_Statement::SetSql(CTempString sql)
721 {
722     x_Finalize();
723     x_Prepare(sql);
724 }
725 
726 inline void
Execute(void)727 CSQLITE_Statement::Execute(void)
728 {
729     while (Step())
730     {}
731 }
732 
733 inline void
Bind(int index,Uint8 val)734 CSQLITE_Statement::Bind(int index, Uint8 val)
735 {
736     // SQLite doesn't understand unsigned types anyway
737     Bind(index, static_cast<Int8>(val));
738 }
739 
740 #if !NCBI_INT8_IS_LONG
741 
742 inline void
Bind(int index,long val)743 CSQLITE_Statement::Bind(int index, long val)
744 {
745     Bind(index, static_cast<Int8>(val));
746 }
747 
748 inline void
Bind(int index,unsigned long val)749 CSQLITE_Statement::Bind(int index, unsigned long val)
750 {
751     Bind(index, static_cast<Uint8>(val));
752 }
753 
754 #endif
755 
756 inline void
Bind(int index,int val)757 CSQLITE_Statement::Bind(int index, int val)
758 {
759     Bind(index, static_cast<long>(val));
760 }
761 
762 inline void
Bind(int index,unsigned int val)763 CSQLITE_Statement::Bind(int index, unsigned int val)
764 {
765     Bind(index, static_cast<unsigned long>(val));
766 }
767 
768 inline int
GetColumnsCount(void) const769 CSQLITE_Statement::GetColumnsCount(void) const
770 {
771     _ASSERT(m_StmtHandle);
772     return sqlite3_column_count(m_StmtHandle);
773 }
774 
775 inline int
GetInt(int col_ind) const776 CSQLITE_Statement::GetInt(int col_ind) const
777 {
778     _ASSERT(m_StmtHandle);
779     return sqlite3_column_int(m_StmtHandle, col_ind);
780 }
781 
782 inline Int8
GetInt8(int col_ind) const783 CSQLITE_Statement::GetInt8(int col_ind) const
784 {
785     _ASSERT(m_StmtHandle);
786     return sqlite3_column_int64(m_StmtHandle, col_ind);
787 }
788 
789 inline double
GetDouble(int col_ind) const790 CSQLITE_Statement::GetDouble(int col_ind) const
791 {
792     _ASSERT(m_StmtHandle);
793     return sqlite3_column_double(m_StmtHandle, col_ind);
794 }
795 
796 inline Int8
GetLastInsertedRowid(void) const797 CSQLITE_Statement::GetLastInsertedRowid(void) const
798 {
799     _ASSERT(m_ConnHandle);
800     return sqlite3_last_insert_rowid(m_ConnHandle);
801 }
802 
803 inline int
GetChangedRowsCount(void) const804 CSQLITE_Statement::GetChangedRowsCount(void) const
805 {
806     _ASSERT(m_ConnHandle);
807     return sqlite3_changes(m_ConnHandle);
808 }
809 
810 
811 inline const string&
GetFileName(void) const812 CSQLITE_Connection::GetFileName(void) const
813 {
814     return m_FileName;
815 }
816 
817 inline CSQLITE_Connection::TOperationFlags
GetFlags(void) const818 CSQLITE_Connection::GetFlags(void) const
819 {
820     return m_Flags;
821 }
822 
823 inline unsigned int
GetPageSize(void)824 CSQLITE_Connection::GetPageSize(void)
825 {
826     return m_PageSize;
827 }
828 
829 inline void
SetPageSize(unsigned int size)830 CSQLITE_Connection::SetPageSize(unsigned int size)
831 {
832     m_PageSize = size;
833     m_HandlePool.Clear();
834 }
835 
836 inline unsigned int
GetCacheSize(void)837 CSQLITE_Connection::GetCacheSize(void)
838 {
839     return m_CacheSize == (unsigned int)(-1) ? 0 : m_CacheSize;
840 }
841 
842 inline void
SetCacheSize(unsigned int size)843 CSQLITE_Connection::SetCacheSize(unsigned int size)
844 {
845     m_CacheSize = size;
846     m_HandlePool.Clear();
847 }
848 
849 inline sqlite3*
LockHandle(void)850 CSQLITE_Connection::LockHandle(void)
851 {
852     if ( m_InMemory ) {
853         return m_InMemory;
854     }
855     sqlite3* result = m_HandlePool.Get();
856     if ((m_Flags & eAllMT) == fExternalMT) {
857         m_HandlePool.Return(result);
858     }
859     return result;
860 }
861 
862 inline void
UnlockHandle(sqlite3 * handle)863 CSQLITE_Connection::UnlockHandle(sqlite3* handle)
864 {
865     if (m_InMemory  &&  handle == m_InMemory) {
866         return;
867     }
868     if ((m_Flags & eAllMT) == fInternalMT) {
869         m_HandlePool.Return(handle);
870     }
871 }
872 
873 inline void
ExecuteSql(CTempString sql)874 CSQLITE_Connection::ExecuteSql(CTempString sql)
875 {
876     CSQLITE_Statement stmt(this, sql);
877     stmt.Execute();
878 }
879 
880 inline void
Vacuum(size_t max_free_size)881 CSQLITE_Connection::Vacuum(size_t max_free_size)
882 {
883     AutoPtr<CSQLITE_Statement> stmt(CreateVacuumStmt(max_free_size));
884     stmt->Execute();
885 }
886 
887 
888 inline
CSQLITE_StatementLock(CSQLITE_Statement * stmt)889 CSQLITE_StatementLock::CSQLITE_StatementLock(CSQLITE_Statement* stmt)
890     : m_Stmt(stmt)
891 {
892     _ASSERT(stmt);
893 }
894 
895 inline CSQLITE_Statement&
operator *(void)896 CSQLITE_StatementLock::operator* (void)
897 {
898     return *m_Stmt;
899 }
900 
901 inline CSQLITE_Statement*
operator ->(void)902 CSQLITE_StatementLock::operator-> (void)
903 {
904     return m_Stmt;
905 }
906 
907 inline
operator CSQLITE_Statement*(void)908 CSQLITE_StatementLock::operator CSQLITE_Statement* (void)
909 {
910     return m_Stmt;
911 }
912 
913 
914 inline
~CSQLITE_Blob(void)915 CSQLITE_Blob::~CSQLITE_Blob(void)
916 {
917     x_CloseBlob();
918     if (m_ConnHandle) {
919         m_Conn->UnlockHandle(m_ConnHandle);
920     }
921 }
922 
923 inline size_t
GetPosition(void)924 CSQLITE_Blob::GetPosition(void)
925 {
926     return m_Position;
927 }
928 
929 inline void
ResetPosition(size_t position)930 CSQLITE_Blob::ResetPosition(size_t position /* = 0 */)
931 {
932     m_Position = position;
933 }
934 
935 inline void
SetAppendStatement(CSQLITE_Statement * stmt)936 CSQLITE_Blob::SetAppendStatement(CSQLITE_Statement* stmt)
937 {
938     m_AppendStmt.reset(stmt, eNoOwnership);
939 }
940 
941 END_NCBI_SCOPE
942 
943 #endif /* DB_SQLITE_SQLITEWRAPP__HPP */
944