Wrapper für SQLite3: Design



  • Hallo,

    da ich keinen für mich brauchbaren C++-Wrapper für SQLite gefunden habe (keine Unterstützung für Version 3, kein Unicode, kommerziell, ...), dachte ich, ich probiere einfach selbst einen zu schreiben. Ich poste hier erstmal nur den Header ohne Implementierung, damit der Beitrag nicht so lang wird, wenn nötig, poste ich aber natürlich auch den Rest.

    Nun meine Frage: Was haltet ihr von dem (Klassen-)Design? Was kann man noch verbessern?

    Sven

    #pragma once
    #ifndef SQLITEWRAP_INCLUDED
    #define SQLITEWRAP_INCLUDED
    
    #include <string>
    #include <vector>
    #include <map>
    #include <cassert>
    #include <boost/any.hpp>
    #include <sqlite3.h>
    
    namespace sqlite
    {
    	class Exception {   };
    
    	class Query;
    
    	class Database
    	{
    	public:
    		class BadFile : public Exception {   };
    
    		Database(const std::wstring& file);
    		~Database();
    		void DoSimpleQuery(const std::wstring& sql);
    		Query* GenerateQuery(const std::wstring& sql);
    		void DeleteQuery(Query* query);
    		template<class functor> void RegisterCollation(const std::wstring& name, functor func);
    		bool IsCollationRegistered(const std::wstring& name);
    		void UnregisterCollation(const std::wstring& name);
    
    		static std::wstring GetSqliteVersion();
    		static std::wstring EscapeString(std::wstring str);
    
    	private:
    		struct CollationInfo
    		{
    			boost::any func;
    		};
    
    		Database(const Database& db); //Kopieren ist nicht sinnvoll
    		Database& operator=(const Database& db); //Zuweisen ist nicht sinnvoll
    
    		template<class functor> static int CollationCallback(void* userData,
    			int size1, const void* str1, int size2, const void* str2);
    
    		static std::wstring StringToWString(const std::string& str);
    
    		sqlite3* m_dbHandle;
    		std::vector<Query*> m_pendingQueries;
    		std::map<std::wstring, CollationInfo*> m_collations;
    	};
    
    	typedef std::vector<char> Blob;
    	typedef std::vector<boost::any> Row;
    	typedef std::pair<std::wstring, std::wstring> ColumnInfo;
    	typedef std::vector<ColumnInfo> Columns;
    
    	class Query
    	{
    		friend class Database;
    
    	public:
    		class Bad : public Exception {   };
    		class BadExecution : public Bad {   };
    		class RuntimeError : public BadExecution {   };
    		class LogicalError : public BadExecution {   };
    		class Busy : public BadExecution {   };
    
    		bool operator>>(Row& row);
    		template<class functor> void ForEachRow(functor& func);
    		Columns GetColumnInfos();
    
    	protected:
    		// nur die Klasse Database darf Query-Objekte erstellen und zerstören, da sie von ihr
    		// abhängig sind und unbedingt _vor_ der Zerstörung der DB zerstört werden müssen
    		Query(sqlite3* dbHandle, const std::wstring& sql);
    		~Query();
    
    	private:
    		Query(const Query& query); //Kopieren ist nicht sinnvoll
    		Query& operator=(const Query& query); //Zuweisen ist nicht sinnvoll
    
    		boost::any GetValue(int column);
    
    		sqlite3_stmt* m_queryHandle;
    	};
    }
    
    #endif
    


  • Ist eine ganz schöne Sache, die Du da vorhast. (hoffentlich dann auch frei verfügbar 😉 ) Aber warum möchtest Du Dich auf SQLite beschränken. Klar, für's erste sollte es reichen, aber generell würde ich das offen halten, d.h. Namespace evtl ändern, aus Klasse Database zunächst 2 machen, also DatabaseBase und davon ableitend DatabaseSQLite.
    Dann würde ich die Methodennamen teilweise anders benennen:
    GenerateQuery -> QueryCreate und
    DeleteQuery -> QueryDestroy, ....
    Aber das ist natürlich reine Geschmackssache. Ich würde das nur irgendwie konsistent halten. Also wenn DeleteQuery, dann auch NewQuery, wegen new und delete, wie man es von C++ kennt. Alternativ verwende ich halt auch gerne Create und Destroy. Aber wie schon gesagt - das ist halt nur mein schlechter Geschmack.
    Gut, und dann wäre natürlich noch ein "Table" nützlich oder es bei "Query" belassen, jedenfalls noch Schreiboperationen ermöglichen, aber das hattest Du sicherlich eh noch vor.

    Viel Erfolg bei Deinem Projekt!
    Gruß,
    Sven(27)



  • Hallo,

    erst einmal vielen Dank für deine Antwort!

    wischmop2 schrieb:

    Ist eine ganz schöne Sache, die Du da vorhast.

    Nunja, es soll ja keine komplette Datenbankengine, sondern nur nur ein einfacher C++-Wrapper für das SQLite-Interface werden.

    Aber warum möchtest Du Dich auf SQLite beschränken.

    Eine Erweiterung auf andere Datenbanken halte ich vorerst nicht für sinnvoll, da es ja doch große Unterschiede zwischen SQLite und anderen Datenbanken (s. z. B. Client-Server <-> "alles in einer lokalen Library" etc.) gibt. Ich denke, das würde das Interface unnötig verkomplizieren. Außerdem werde ich das in absehbarer Zukunft auch aus zeitlichen Gründen wahrscheinlich nicht schaffen. Aber das Ziel ist ja auch nur ein OO-Wrapper für SQLite, damit man (in kleineren und mittleren Programmen) die Vorteile von SQLite nutzen kann ohne das unpraktische C-Interface benutzen zu müssen.

    Dann würde ich die Methodennamen teilweise anders benennen

    Du hast Recht, das ist so nicht besonders schön. Ich werde sie in "CreateQuery" und "DestroyQuery" umbenennen.

    jedenfalls noch Schreiboperationen ermöglichen

    Wie genau meinst du das? Daten einfügen, löschen etc. kann man doch per SQL, am einfachsten mittels DoSimpleQuery().

    Was meinen die anderen zu dem Design?

    Gruß,
    Sven



  • Sven25 schrieb:

    jedenfalls noch Schreiboperationen ermöglichen

    Wie genau meinst du das? Daten einfügen, löschen etc. kann man doch per SQL, am einfachsten mittels DoSimpleQuery().

    Klar kann man das auch per DoSimpleQuery() machen, aber dann hast Du den Datensatz noch nicht in der momentanen Query drin, in der er eigentlich sein solle (zumindestens vermute ich das, kenne SQLite nicht). Dann ist es i.d.R. auch so, dass dies erhebliche Performanceeinbrüche mit sich bringen kann. Ob Du davon etwas merkst kommt natürlich drauf an, was Du vorhast damit zu machen. Aber wenn nun mal eine millionen Datensätze eingefügt werden sollen und dann jedes mal ein SQL-String a la "INSERT (...) INTO ..." abgesetzt werden muss, wird man es merken.

    Viele API's die ich kenne (z.B. VCL von Borland, oder in Visual Basic) haben solche Objekte wie hier Dein Objekt vom Typ Query noch Methoden wie
    - Query::edit(): macht den Datensatz auf dem Query momentan steht editierbar
    - Query::update(): übernimmt die Änderungen die am Datensatz vorgenommen wurden in die Datenbank (bzw. könnte -1 zurück geben oder eine Exception werfen, falls Integritätsverletzung vorliegt)
    - Query::new(): fügt einen neuen Datensatz an, der dann automatisch zum schreiben geöffent ist (muss also noch ein update() folgen)
    - Query::delete(): löscht aktuellen Datensatz aus der Datenbank
    - Query::next(): springt auf den nächsten Datensatz (entsprechend prev oder auch lookup denkbar)
    - Query::set(feldname, wert): klar!
    usw.



  • Hmmm, soweit ich weiß ist so etwas mit SQLite gar nicht möglich, die einzige Möglichkeit, Zugriff auf die DB zu bekommen, ist SQL. Es ist eben kein großer DB-Server, sondern eine Library, die es einem Programm ermöglicht, Daten in einer Datei zu speichern und mit SQL auf sie zuzugreifen (siehe auch http://www.sqlite.org). Trotzdem danke für den Vorschlag!

    Sven


Anmelden zum Antworten