What is the best way to read/write objects in a file with the following conditions - sql

I am willing to store objects in a database. The purpose is to be able to read / write these objects with the program. The requirements are the following:
Objects can be complex using Qt classes such as QList, QString ... or even can contain other objects that use QObjects
The database should not be readable or modified by human (no text file, and if I use sqlite database, it has to be encrypted in a way)
I should be able to remove, read an object by its name and count the number of objects in the database, without loading everything in the memory
I asked a question here, to do this with a QDataStream with a minimalist example. But it seems it is not the best way to proceed.
Would you have some suggestions regarding the solutions that exist for this purpose?
I have tried the following but it does not fulfill the requirements:
Storing text in sqlite with QtSQL: but the data is accessible by using sqlitemanager for example, can be modified or removed by humans. Moreover, I have no idea regarding the way to store QList for example or other objects that I created and contain QObject (for example, 2 QList)
Storing binary data using QDataStream: in this case, I cannot count the number of objects in my file, neither read or remove a specific object without loading the entire file in memory.
I would be grateful if you could give me some suggestions or provide example, even if the example is minimalist.

I finally found a solution, especially thanks to Igor Tandetnik and thanks to the topic here
I haven't quite finalized, there is a small imperfection because I have to define an object of my user class that I don't use in order to call the readFromDB function to generate my object from the db.
On the other hand, I get this error message "QSqlDatabasePrivate::addDatabase: duplicate connection name 'qt_sql_default_connection', old connection removed" each time I call my database.
Anyway, it's a bit late now, and I think it might help some people so I post this minimalist imperfect code below. I'll post an update in the next few days.
Thanks again.
#include "QString"
#include "QFile"
#include "QDataStream"
#include "qdebug.h"
#include "QtSql"
#include "QSqlDatabase"
#include "qmessagebox.h"
class User
{
protected:
QString name;
QList<QString> childrens;
public:
QString getName(){ return name;}
QList<QString> getChildrens(){ return childrens;}
void setName(QString x) {name = x;}
void setChildrens(QList<QString> x) {childrens = x;}
friend QDataStream &operator<<(QDataStream &out, const User &t)
{
out << t.name << t.childrens;
return out;
}
friend QDataStream &operator>>(QDataStream &in, User &t)
{
QString inname;
QList<QString> inchildrens;
in >> inname >> inchildrens;
t.name = inname;
t.childrens = inchildrens;
return in;
}
QByteArray object2blob( const User& user )
{
QByteArray result;
QDataStream bWrite( &result, QIODevice::WriteOnly );
bWrite << user;
return result;
}
User blob2object( const QByteArray& buffer )
{
User result;
QDataStream bRead( buffer );
bRead >> result;
return result;
}
int saveToDB( const User& user )
{
QSqlDatabase myDB = QSqlDatabase::addDatabase("QSQLITE");
myDB.setDatabaseName( "file.db");
if (!myDB.open())
{
qDebug()<<"Failed to open SQL database of registered users";
}
else
{
qDebug()<<"Successfully opening SQL database of registered users";
QSqlQuery query;
query.prepare( "CREATE TABLE IF NOT EXISTS users (name TEXT, childrens BLOB)" );
if( !query.exec() )
{
qDebug() << query.lastError();
}
else
{
qDebug() << "Table created!";
query.prepare( "INSERT INTO users (name,childrens) VALUES (:name,:childrens)" );
query.bindValue(":name", name);
query.bindValue( ":childrens", object2blob(user) );
query.exec();
}
query.clear();
myDB.close();
}
QSqlDatabase::removeDatabase("UserConnection");
return 0;
}
User readFromDB( QString name )
{
User result;
QSqlDatabase myDB = QSqlDatabase::addDatabase("QSQLITE");
myDB.setDatabaseName( "file.db");
if (!myDB.open())
{
qDebug()<<"Failed to open SQL database of registered users";
}
else
{
QSqlQuery query;
query.prepare( "SELECT * FROM users WHERE name ='"+ name +"'" );
//query.bindValue( 0, name );
if ( query.exec() && query.next() ) {
result = blob2object( query.value( 1 ).toByteArray() );
}
query.clear();
myDB.close();
}
QSqlDatabase::removeDatabase("UserConnection");
qDebug()<<result.getChildrens();
return result;
}
};
////////////////////////////////////////////////////////////////
int main()
{
User u;
u.setName("Georges");
u.setChildrens(QList<QString>()<<"Jeanne"<<"Jean");
u.saveToDB(u);
User v;
v.setName("Alex");
v.setChildrens(QList<QString>()<<"Matthew");
v.saveToDB(v);
User w;
w.setName("Mario");
w.saveToDB(w);
User to_read; //to improve here
User a = to_read.readFromDB("Georges");
qDebug()<<a.getChildrens();
return 0;
}

Related

Unable to add list to the record

I'm trying to add a record to the database having 3 bins, one of which is a list using my c++ program(I'm using Aerospike's C client library) but after I execute my program, only 2 of the bins are added in the record. For some reasons, I'm not able to add the 3rd bin having the list value.
Given below is the program:
#include<aerospike/aerospike.h>
#include<aerospike/as_arraylist.h>
#include <aerospike/as_record.h>
#include<aerospike/as_record_iterator.h>
#include <aerospike/as_policy.h>
#include<aerospike/aerospike_key.h>
#include<aerospike/as_std.h>
#include<aerospike/as_integer.h>
#include<aerospike/as_string.h>
#include<iostream>
#include<aerospike/as_hashmap.h>
#include<string>
using namespace std;
int main()
{
as_error err;
as_config config;
as_config_init(&config);
config.policies.write.key = AS_POLICY_KEY_SEND;
as_config_add_host(&config, "182.18.182.192", 3000);
aerospike as;
aerospike_init(&as, &config);
if(aerospike_connect(&as, &err)!=AEROSPIKE_OK)
{
printf("error(%d) %s at [%s:%d]", err.code, err.message, err.file, err.line);
}
as_key key;
as_record rec;
as_record_init(&rec, 2);
int n;
cout<<endl<<"Enter the number of records to enter: ";
cin>>n;
for(int i=0;i<n;i++)
{
cout<<endl<<"Enter the name: ";
string pk;
getline(cin>>ws,pk);
as_key_init(&key, "test", "vishal_set", pk.c_str());
cout<<endl<<"Enter the age: ";
int age;
cin>>age;
as_record_set_int64(&rec, "age", age);
cout<<endl<<"Enter the contact no: ";
string contact_no;
getline(cin>>ws,contact_no);
as_record_set_str(&rec, "contact_no", contact_no.c_str());
as_arraylist list;
as_arraylist_inita(&list,2);
as_arraylist_append_str (&list, "playing football");
as_arraylist_append_str (&list, "playing cricket");
cout<<endl<<"Number of elements in the list: "<<as_arraylist_size(&list)<<endl;
if(!(as_record_set_list(&rec, "hobbies", (as_list *)&list)))
{
printf("\n[%s::%d]Error\n",__FILE__,__LINE__);
}
if (aerospike_key_put(&as, &err,NULL, &key, &rec) != AEROSPIKE_OK)
{
printf("error(%d) %s at [%s:%d]", err.code, err.message, err.file, err.line);
}
}
if(aerospike_close(&as,&err)!=AEROSPIKE_OK)
{
printf("error(%d) %s at [%s:%d]", err.code, err.message, err.file, err.line);
}
aerospike_destroy(&as);
return 0;
}
Fortunately, I've found the reason why my list was not getting added to the record myself. It turned out to be really silly mistake.
The reason was that I had done a mistake in initializing as_record. Since I was adding 3 bins to the record therefore, I should have initialised it as as_record_init(&rec, 3);.

Saving randomly generated passwords to a text file in order to display them later

I'm currently in a traineeship and I currently have to softwares I'm working on. The most important was requested yesterday and I'm stucked on the failure of its main feature: saving passwords.
The application is developped in C++\CLR using Visual Studio 2013 (Couldn't install MFC libraries somehow, installation kept failing and crashing even after multiple reboots.) and aims to generate a password from a seed provided by the user. The generated password will be save onto a .txt file. If the seed has already been used then the previously generated password will show up.
Unfortunately I can't save the password and seed to the file, though I can write the seed if I don't get to the end of the document. I went for the "if line is empty then write this to the document" but it doesn't work and I can't find out why. However I can read the passwords without any problem.
Here's the interresting part of the source:
int seed;
char genRandom() {
static const char letters[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
int stringLength = sizeof(letters) - 1;
return letters[rand() % stringLength];
}
System::Void OK_Click(System::Object^ sender, System::EventArgs^ e) {
fstream passwords;
if (!(passwords.is_open())) {
passwords.open("passwords.txt", ios::in | ios::out);
}
string gen = msclr::interop::marshal_as<std::string>(GENERATOR->Text), line, genf = gen;
bool empty_line_found = false;
while (empty_line_found == false) {
getline(passwords, line);
if (gen == line) {
getline(passwords, line);
PASSWORD->Text = msclr::interop::marshal_as<System::String^>(line);
break;
}
if (line.empty()) {
for (unsigned int i = 0; i < gen.length(); i++) {
seed += gen[i];
}
srand(seed);
string pass;
for (int i = 0; i < 10; ++i) {
pass += genRandom();
}
passwords << pass << endl << gen << "";
PASSWORD->Text = msclr::interop::marshal_as<System::String^>(pass);
empty_line_found = true;
}
}
}
I've also tried replacing ios::in by ios::app and it doesn't work. And yes I have included fstream, iostream, etc.
Thanks in advance!
[EDIT]
Just solved this problem. Thanks Rook for putting me on the right way. It feels like a silly way to do it, but I've closed the file and re-openned it using ios::app to write at the end of it. I also solved a stupid mistake resulting in writing the password before the seed and not inserting a final line so the main loop can still work. Here's the code in case someone ends up with the same problem:
int seed;
char genRandom() {
static const char letters[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
int stringLength = sizeof(letters) - 1;
return letters[rand() % stringLength];
}
System::Void OK_Click(System::Object^ sender, System::EventArgs^ e) {
fstream passwords;
if (!(passwords.is_open())) {
passwords.open("passwords.txt", ios::in | ios::out);
}
string gen = msclr::interop::marshal_as<std::string>(GENERATOR->Text), line, genf = gen;
bool empty_line_found = false;
while (empty_line_found == false) {
getline(passwords, line);
if (gen == line) {
getline(passwords, line);
PASSWORD->Text = msclr::interop::marshal_as<System::String^>(line);
break;
}
if (line.empty()) {
passwords.close();
passwords.open("passwords.txt", ios::app);
for (unsigned int i = 0; i < gen.length(); i++) {
seed += gen[i];
}
srand(seed);
string pass;
for (int i = 0; i < 10; ++i) {
pass += genRandom();
}
passwords << gen << endl << pass << endl << "";
PASSWORD->Text = msclr::interop::marshal_as<System::String^>(pass);
empty_line_found = true;
}
}
passwords.close();
}
So, here's an interesting thing:
passwords << pass << endl << gen << "";
You're not ending that with a newline. This means the very end of your file could be missing a newline too. This has an interesting effect when you do this on the final line:
getline(passwords, line);
getline will read until it sees a line ending, or an EOF. If there's no newline, it'll hit that EOF and then set the EOF bit on the stream. That means the next time you try to do this:
passwords << pass << endl << gen << "";
the stream will refuse to write anything, because it is in an eof state. There are various things you can do here, but the simplest would be to do passwords.clear() to remove any error flags like eof. I'd be very cautious about accidentally clearing genuine error flags though; read the docs for fstream carefully.
I also reiterate my comment about C++/CLR being a glue language, and not a great language for general purpose development, which would be best done using C++ or a .net language, such as C#. If you're absolutely wedded to C++/CLR for some reason, you may as well make use of the extensive .net library so you don't have to pointlessly martial managed types back and forth. See System::IO::FileStream for example.

Null reference exception c++

I keep getting this error in the console:
Unhandled exception: System.NullReferenceException
Here's the code:
class Car {
public:
int X;
int Y;
};
class SpecificCar : public Car {
};
class Container {
public:
int AmountOfCars = 0;
Car **cars = nullptr;
void AddCar(Car *ptr);
};
void Container::AddCar(Car *ptr) {
if(AmountOfCars == 0) {
cars[0] = ptr; //Debbuger says that the problem in question is located here
AmountOfCars++;
}
int main() {
Container container;
Car *ptr = new SpecificCar;
ptr->X = 1;
ptr->Y = 5;
container.AddCar(ptr);
}
While your Container by design isn't storing Cars, it still has to store pointers to cars. You'll have to come up with a method. The Standard offers std::vector<Car> as well as std::vector<Car*> but you're free to come up with anything else. Still, if you don't want the Standard methods, it's really up to you what else you want to do.
Car **cars is not a dynamic container, it's a pointer to a memory region. What you did there is just utterly wrong. You still have to allocate an array of pointers to be able to fill data there, such as
cars = new Car*[5];
With that you can address with indices from 0 to 4 inside array cars[]. Yet again this is not dynamic, your best bet is an std::vector<Car*>, if you want to go your own ways then malloc()/realloc(), maybe linked listing if you really want to bother with it.
The problem is that, in class Container, you defined a member cars initialized to nullptr.
The best way to fix the issue is to use a std::vector<Car*> for cars. If you absolutely don't want to use a vector (why ?), in class Container, you may replace:
Car **cars = nullptr;
by something like:
static const int MAX_AMOUNT_OF_CARS = 100;
Car* cars[MAX_AMOUNT_OF_CARS];
which will define a proper array of Car*; then, you will be able to use cars[0], cars[i], ...
I figure you're trying to teach yourself about memory management. I've rewritten your class and AddCar() to be more what you want. Accessing or removing a car and deleting the container are left as an exercise for the student. (Look at this as pseudo-code. I haven't compiled or run it.)
class Container
{
Car ** cars_ = nullptr;
int capacity_ = 0; // how much room we have for car pointers
int AmountOfCars_ = 0; // how many car pointers we actually contain
public:
int AmountOfCars() const { return AmountOfCars_; }
void AddCar(Car *ptr);
};
void Container::AddCar(Car *ptr)
{
if ( AmountOfCars_ + 1 > capacity_ ) // ensure we have capacity for another Car *
{
if ( capacity_ == 0 ) // if we have none set to 2, so we'll initially allocate room for 4
capacity_ = 2;
int newcapacity = capacity_ * 2; // double the capacity
Cars ** newcars = new Car*[ newcapacity ]; // allocate a new pointer array
memcpy( newcars, cars_, capacity_ * sizeof(Car*) ); // we're just moving pointers
delete cars_; // get rid of the old pointer array
cars_ = newcars; // point to the new pointer array
capacity_ = newcapacity; // update the capacity
}
++AmountOfCars_; // increase the number of cars
cars[ AmountOfCars_ ] = ptr; // and copy the pointer into the slot
}

QSqlQuery prepared statements - proper usage

I'm trying to determine the proper way to use prepared statements with QSqlQuery. The docs are not very specific on this subject.
void select(const QSqlDatabase &database) {
QSqlQuery query(database);
query.prepare("SELECT * FROM theUniverse WHERE planet = :planet");
query.bindValue(":planet", "earth");
query.exec();
}
So will this snippet create a permanent prepared statement in the connection database? Will this prepared statement persist between calls to select(), i.e. will it be saved when the function returns and QSqlQuery query is disposed?
Or should I create QSqlQuery on the heap and use the same instance over and over again?
Ok, the idea is that you have to create QSqlQuery on heap, prepare the query and do the following with it:
QSqlQuery::bindValue(s)
QSqlQuery::exec
read data with QSqlQuery::[next|first|last|...]
QSqlQuery::finish
rinse and repeat
the following snipped is useful to create, prepare and retrieve queries on heap:
QSqlDatabase database;
QMap<QString, QSqlQuery *> queries; //dont forget to delete them later!
QSqlQuery *prepareQuery(const QString &query)
{
QSqlQuery *ret = 0;
if (!queries.contains(query)) {
QSqlQuery *q = new QSqlQuery(database);
q->prepare(query);
queries[query] = ret = q;
} else {
ret = queries[query];
}
}
return ret;
}

What's the simplest way to execute a query in Visual C++

I'm using Visual C++ 2005 and would like to know the simplest way to connect to a MS SQL Server and execute a query.
I'm looking for something as simple as ADO.NET's SqlCommand class with it's ExecuteNonQuery(), ExecuteScalar() and ExecuteReader().
Sigh offered an answer using CDatabase and ODBC.
Can anybody demonstrate how it would be done using ATL consumer templates for OleDb?
Also what about returning a scalar value from the query?
With MFC use CDatabase and ExecuteSQL if going via a ODBC connection.
CDatabase db(ODBCConnectionString);
db.Open();
db.ExecuteSQL(blah);
db.Close();
You should be able to use OTL for this. It's pretty much:
#define OTL_ODBC_MSSQL_2008 // Compile OTL 4/ODBC, MS SQL 2008
//#define OTL_ODBC // Compile OTL 4/ODBC. Uncomment this when used with MS SQL 7.0/ 2000
#include <otlv4.h> // include the OTL 4.0 header file
#include <stdio>
int main()
{
otl_connect db; // connect object
otl_connect::otl_initialize(); // initialize ODBC environment
try
{
int myint;
db.rlogon("scott/tiger#mssql2008"); // connect to the database
otl_stream select(10, "select someint from test_tab", db);
while (!select.eof())
{
select >> myint;
std::cout<<"myint = " << myint << std::endl;
}
}
catch(otl_exception& p)
{
std::cerr << p.code << std::endl; // print out error code
std::cerr << p.sqlstate << std::endl; // print out error SQLSTATE
std::cerr << p.msg << std::endl; // print out error message
std::cerr << p.stm_text << std::endl; // print out SQL that caused the error
std::cerr << p.var_info << std::endl; // print out the variable that caused the error
}
db.logoff(); // disconnect from the database
return 0;
}
The nice thing about OTL, IMO, is that it's very fast, portable (I've used it on numerous platforms), and connects to a great many different databases.
I used this recently:
#include <ole2.h>
#import "msado15.dll" no_namespace rename("EOF", "EndOfFile")
#include <oledb.h>
void CMyDlg::OnBnClickedButton1()
{
if ( FAILED(::CoInitialize(NULL)) )
return;
_RecordsetPtr pRs = NULL;
//use your connection string here
_bstr_t strCnn(_T("Provider=SQLNCLI;Server=.\\SQLExpress;AttachDBFilename=C:\\Program Files\\Microsoft SQL Server\\MSSQL.1\\MSSQL\\Data\\db\\db.mdf;Database=mydb;Trusted_Connection=Yes;MARS Connection=true"));
_bstr_t a_Select(_T("select * from Table"));
try {
pRs.CreateInstance(__uuidof(Recordset));
pRs->Open(a_Select.AllocSysString(), strCnn.AllocSysString(), adOpenStatic, adLockReadOnly, adCmdText);
//obtain entire restult as comma separated text:
CString text((LPCWSTR)pRs->GetString(adClipString, -1, _T(","), _T(""), _T("NULL")));
//iterate thru recordset:
long count = pRs->GetRecordCount();
COleVariant var;
CString strColumn1;
CString column1(_T("column1_name"));
for(int i = 1; i <= count; i++)
{
var = pRs->GetFields()->GetItem(column1.AllocSysString())->GetValue();
strColumn1 = (LPCTSTR)_bstr_t(var);
}
}
catch(_com_error& e) {
CString err((LPCTSTR)(e.Description()));
MessageBox(err, _T("error"), MB_OK);
_asm nop; //
}
// Clean up objects before exit.
if (pRs)
if (pRs->State == adStateOpen)
pRs->Close();
::CoUninitialize();
}
Try the Microsoft Enterprise Library. A version should be available here for C++. The SQlHelper class impliments the methods you are looking for from the old ADO days. If you can get your hands on version 2 you can even use the same syntax.