pqxxmigrate.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2004 Adam Pigg <adam@piggz.co.uk>
3 Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this program; see the file COPYING. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19*/
20
21#include "pqxxmigrate.h"
22#include "pg_type.h"
23
24#include <tqstring.h>
25#include <kdebug.h>
26#include <tqstringlist.h>
27
28//I maybe should not use stl?
29#include <string>
30#include <vector>
31
32#include <kexidb/cursor.h>
33#include <kexidb/utils.h>
34#include <kexidb/drivermanager.h>
35#include <kexiutils/identifier.h>
36#include <kexidb/drivers/pqxx/pqxxcursor.h> //for pgsqlCStrToVariant()
37
38using namespace KexiDB;
39using namespace KexiMigration;
40
41/*
42This is the implementation for the pqxx specific import routines
43Thi is currently pre alpha and in no way is it meant
44to compile, let alone work. This is meant as an example of
45what the system might be and is a work in progress
46*/
47
48KEXIMIGRATE_DRIVER_INFO( PqxxMigrate, pqxx )
49
50//==================================================================================
51//Constructor
52/*PqxxMigrate::PqxxMigrate()
53 : KexiMigrate(parent, name, args)
54{
55 m_res=0;
56 m_trans=0;
57 m_conn=0;
58}*/
59
60PqxxMigrate::PqxxMigrate(TQObject *parent, const char *name, const TQStringList &args)
61 : KexiMigrate(parent, name, args)
62{
63 m_res=0;
64 m_trans=0;
65 m_conn=0;
67 m_kexiDBDriver = manager.driver("pqxx");
68}
69//==================================================================================
70//Destructor
71PqxxMigrate::~PqxxMigrate()
72{
73 clearResultInfo();
74}
75
76//==================================================================================
77//This is probably going to be quite complex...need to get the types for all columns
78//any any other attributes required by kexi
79//helped by reading the 'tables' test program
80bool PqxxMigrate::drv_readTableSchema(
81 const TQString& originalName, KexiDB::TableSchema& tableSchema)
82{
83// m_table = new KexiDB::TableSchema(table);
84
85 //TODO IDEA: ask for user input for captions
86//moved m_table->setCaption(table + " table");
87
88 //Perform a query on the table to get some data
89 if (query("select * from \"" + originalName + "\" limit 1"))
90 {
91 //Loop round the fields
92 for (uint i = 0; i < (uint)m_res->columns(); i++)
93 {
94 TQString fldName(m_res->column_name(i));
95#if PQXX_VERSION_MAJOR < 7
96 KexiDB::Field::Type fldType = type(m_res->column_type(i), fldName);
97#else
98 KexiDB::Field::Type fldType = type(m_res->column_type(m_res->column_name(i)), fldName);
99#endif
100 TQString fldID( KexiUtils::string2Identifier(fldName) );
101 const pqxx::oid toid = tableOid(originalName);
102 if (toid==0)
103 return false;
104 KexiDB::Field *f = new KexiDB::Field(fldID, fldType);
105 f->setCaption(fldName);
106 f->setPrimaryKey(primaryKey(toid, i));
107 f->setUniqueKey(uniqueKey(toid, i));
108 f->setAutoIncrement(autoInc(toid, i));//This should be safe for all field types
109 tableSchema.addField(f);
110
111 // Do this for var/char types
112 //m_f->setLength(m_res->at(0)[i].size());
113
114 // Do this for numeric type
115 /*m_f->setScale(0);
116 m_f->setPrecision(0);*/
117
118 kdDebug() << "Added field [" << f->name() << "] type [" << f->typeName()
119 << "]" << endl;
120 }
121 return true;
122 }
123 else
124 {
125 return false;
126 }
127}
128
129//==================================================================================
130//get a list of tables and put into the supplied string list
131bool PqxxMigrate::drv_tableNames(TQStringList& tableNames)
132{
133 /*
134 //pg_ = standard postgresql tables, pga_ = tables added by pgaccess, sql_ = probably information schemas, kexi__ = existing kexi tables
135 if (query("SELECT relname FROM pg_class WHERE ((relkind = 'r') AND ((relname !~ '^pg_') AND (relname !~ '^pga_') AND (relname !~ '^sql_') AND (relname !~ '^kexi__')))"))
136 */
137 if (query("SELECT relname FROM pg_class WHERE ((relkind = 'r') AND ((relname !~ '^pg_') AND (relname !~ '^pga_') AND (relname !~ '^sql_')))"))
138 {
139 for (pqxx::result::const_iterator c = m_res->begin(); c != m_res->end(); ++c)
140 {
141 // Copy the result into the return list
142 tableNames << TQString::fromLatin1 (c[0].c_str());
143 }
144 return true;
145 }
146 else
147 {
148 return false;
149 }
150}
151
152//==================================================================================
153//Convert a postgresql type to a kexi type
154KexiDB::Field::Type PqxxMigrate::type(int t, const TQString& fname)
155{
156 switch(t)
157 {
158 case UNKNOWNOID:
160 case BOOLOID:
162 case INT2OID:
164 case INT4OID:
166 case INT8OID:
168 case FLOAT4OID:
170 case FLOAT8OID:
172 case NUMERICOID:
174 case DATEOID:
175 return KexiDB::Field::Date;
176 case TIMEOID:
177 return KexiDB::Field::Time;
178 case TIMESTAMPOID:
179 return KexiDB::Field::DateTime;
180 case BYTEAOID:
181 return KexiDB::Field::BLOB;
182 case BPCHAROID:
183 return KexiDB::Field::Text;
184 case VARCHAROID:
185 return KexiDB::Field::Text;
186 case TEXTOID:
188 }
189
190 //Ask the user what to do with this field
191 return userType(fname);
192}
193
194//==================================================================================
195//Connect to the db backend
196bool PqxxMigrate::drv_connect()
197{
198 kdDebug() << "drv_connect: " << m_migrateData->sourceName << endl;
199
200 TQString conninfo;
201 TQString socket;
202
203 //Setup local/remote connection
204 if (m_migrateData->source->hostName.isEmpty())
205 {
206 if (m_migrateData->source->fileName().isEmpty())
207 {
208 socket="/tmp/.s.PGSQL.5432";
209 }
210 else
211 {
212 socket=m_migrateData->source->fileName();
213 }
214 }
215 else
216 {
217 conninfo = "host='" + m_migrateData->source->hostName + "'";
218 }
219
220 //Build up the connection string
221 if (m_migrateData->source->port == 0)
222 m_migrateData->source->port = 5432;
223
224 conninfo += TQString::fromLatin1(" port='%1'").arg(m_migrateData->source->port);
225
226 conninfo += TQString::fromLatin1(" dbname='%1'").arg(m_migrateData->sourceName);
227
228 if (!m_migrateData->source->userName.isNull())
229 conninfo += TQString::fromLatin1(" user='%1'").arg(m_migrateData->source->userName);
230
231 if (!m_migrateData->source->password.isNull())
232 conninfo += TQString::fromLatin1(" password='%1'").arg(m_migrateData->source->password);
233
234 try
235 {
236 m_conn = new pqxx::connection( conninfo.latin1() );
237 return true;
238 }
239 catch(const std::exception &e)
240 {
241 kdDebug() << "PqxxMigrate::drv_connect:exception - " << e.what() << endl;
242 }
243 catch(...)
244 {
245 kdDebug() << "PqxxMigrate::drv_connect:exception(...)??" << endl;
246 }
247 return false;
248}
249
250//==================================================================================
251//Connect to the db backend
252bool PqxxMigrate::drv_disconnect()
253{
254 if (m_conn)
255 {
256#if PQXX_VERSION_MAJOR < 7
257 m_conn->disconnect();
258#endif
259 delete m_conn;
260 m_conn = 0;
261 }
262 return true;
263}
264//==================================================================================
265//Perform a query on the database and store result in m_res
266bool PqxxMigrate::query(const TQString& statement)
267{
268 kdDebug() << "query: " << statement.latin1() << endl;
269
270 Q_ASSERT (m_conn);
271
272 // Clear the last result information...
273 clearResultInfo ();
274
275 try
276 {
277 //Create a transaction
278 m_trans = new pqxx::nontransaction(*m_conn);
279 //Create a result opject through the transaction
280 m_res = new pqxx::result(m_trans->exec(statement.latin1()));
281 //Commit the transaction
282 m_trans->commit();
283 //If all went well then return true, errors picked up by the catch block
284 return true;
285 }
286 catch (const std::exception &e)
287 {
288 //If an error ocurred then put the error description into _dbError
289 kdDebug() << "pqxxImport::query:exception - " << e.what() << endl;
290 return false;
291 }
292 catch(...)
293 {
294 kdDebug() << "PqxxMigrate::query:exception(...)??" << endl;
295 }
296 return true;
297}
298
299//=========================================================================
300//Clears the current result
301void PqxxMigrate::clearResultInfo()
302{
303 delete m_res;
304 m_res = 0;
305
306 delete m_trans;
307 m_trans = 0;
308}
309
310//=========================================================================
311//Return the OID for a table
312pqxx::oid PqxxMigrate::tableOid(const TQString& table)
313{
314 TQString statement;
315 static TQString otable;
316 static pqxx::oid toid;
317
318 pqxx::nontransaction* tran = 0;
319 pqxx::result* tmpres = 0;
320
321 //Some simple result caching
322 if (table == otable)
323 {
324 kdDebug() << "Returning table OID from cache..." << endl;
325 return toid;
326 }
327 else
328 {
329 otable = table;
330 }
331
332 try
333 {
334 statement = "SELECT relfilenode FROM pg_class WHERE (relname = '";
335 statement += table;
336 statement += "')";
337
338 tran = new pqxx::nontransaction(*m_conn, "find_t_oid");
339 tmpres = new pqxx::result(tran->exec(statement.latin1()));
340
341 tran->commit();
342 if (tmpres->size() > 0)
343 {
344 //We have a key field for this table, lets check if its this column
345 tmpres->at(0).at(0).to(toid);
346 }
347 else
348 {
349 toid = 0;
350 }
351 }
352 catch(const std::exception &e)
353 {
354 kdDebug() << "pqxxSqlDB::tableOid:exception - " << e.what() << endl;
355 kdDebug() << "pqxxSqlDB::tableOid:failed statement - " << statement << endl;
356 toid = 0;
357 }
358 catch(...)
359 {
360 kdDebug() << "PqxxMigrate::tableOid:exception(...)??" << endl;
361 }
362 delete tmpres;
363 tmpres = 0;
364
365 delete tran;
366 tran = 0;
367
368 kdDebug() << "OID for table [" << table << "] is [" << toid << "]" << endl;
369 return toid;
370}
371
372//=========================================================================
373//Return whether or not the curent field is a primary key
374//TODO: Add result caching for speed
375bool PqxxMigrate::primaryKey(pqxx::oid table_uid, int col) const
376{
377 TQString statement;
378 bool pkey;
379 int keyf;
380
381 pqxx::nontransaction* tran = 0;
382 pqxx::result* tmpres = 0;
383
384 try
385 {
386 statement = TQString("SELECT indkey FROM pg_index WHERE ((indisprimary = true) AND (indrelid = %1))").arg(table_uid);
387
388 tran = new pqxx::nontransaction(*m_conn, "find_pkey");
389 tmpres = new pqxx::result(tran->exec(statement.latin1()));
390
391 tran->commit();
392 if (tmpres->size() > 0)
393 {
394 //We have a key field for this table, lets check if its this column
395 tmpres->at(0).at(0).to(keyf);
396 if (keyf-1 == col) //-1 because pg counts from 1 and we count from 0
397 {
398 pkey = true;
399 kdDebug() << "Field is pkey" << endl;
400 }
401 else
402 {
403 pkey = false;
404 kdDebug() << "Field is NOT pkey" << endl;
405 }
406 }
407 else
408 {
409 pkey = false;
410 kdDebug() << "Field is NOT pkey" << endl;
411 }
412 }
413 catch(const std::exception &e)
414 {
415 kdDebug() << "pqxxSqlDB::primaryKey:exception - " << e.what() << endl;
416 kdDebug() << "pqxxSqlDB::primaryKey:failed statement - " << statement << endl;
417 pkey = false;
418 }
419 delete tmpres;
420 tmpres = 0;
421
422 delete tran;
423 tran = 0;
424
425 return pkey;
426}
427
428//=========================================================================
433tristate PqxxMigrate::drv_queryStringListFromSQL(
434 const TQString& sqlStatement, uint columnNumber, TQStringList& stringList, int numRecords)
435{
436 std::string result;
437 int i = 0;
438 if (query(sqlStatement))
439 {
440 for (pqxx::result::const_iterator it = m_res->begin();
441 it != m_res->end() && (numRecords == -1 || i < numRecords); ++it, i++)
442 {
443 if (it.size() > 0 && it.size() > columnNumber) {
444 it.at(columnNumber).to(result);
445 stringList.append( TQString::fromUtf8(result.c_str()) );
446 }
447 else {
448 clearResultInfo();
449 return cancelled;
450 }
451 }
452 }
453 else
454 return false;
455 clearResultInfo();
456/* delete tmpres;
457 tmpres = 0;
458
459 delete tran;
460 tran = 0;*/
461
462 if (i < numRecords)
463 return cancelled;
464
465 return true;
466 /*
467 if (d->executeSQL(sqlStatement)) {
468 MYSQL_RES *res = mysql_use_result(d->mysql);
469 if (res != NULL) {
470 MYSQL_ROW row = mysql_fetch_row(res);
471 if (!row) {
472 tristate r = mysql_errno(d->mysql) ? false : cancelled;
473 mysql_free_result(res);
474 return r;
475 }
476 uint numFields = mysql_num_fields(res);
477 if (columnNumber > (numFields-1)) {
478 kdWarning() << "PqxxMigrate::drv_querySingleStringFromSQL("<<sqlStatement
479 << "): columnNumber too large ("
480 << columnNumber << "), expected 0.." << numFields << endl;
481 mysql_free_result(res);
482 return false;
483 }
484 unsigned long *lengths = mysql_fetch_lengths(res);
485 if (!lengths) {
486 mysql_free_result(res);
487 return false;
488 }
489 string = TQString::fromLatin1(row[columnNumber], lengths[columnNumber]);
490 mysql_free_result(res);
491 } else {
492 kdDebug() << "PqxxMigrate::drv_querySingleStringFromSQL(): null result" << endl;
493 }
494 return true;
495 } else {
496 return false;
497 }*/
498}
499
500tristate PqxxMigrate::drv_fetchRecordFromSQL(const TQString& sqlStatement,
501 KexiDB::RowData& data, bool &firstRecord)
502{
503 if (firstRecord || !m_res) {
504 if (m_res)
505 clearResultInfo();
506 if (!query(sqlStatement))
507 return false;
508 m_fetchRecordFromSQL_iter = m_res->begin();
509 firstRecord = false;
510 }
511 else
512 ++m_fetchRecordFromSQL_iter;
513
514 if (m_fetchRecordFromSQL_iter == m_res->end()) {
515 clearResultInfo();
516 return cancelled;
517 }
518
519 std::string result;
520 const int numFields = m_fetchRecordFromSQL_iter.size();
521 data.resize(numFields);
522 for (int i=0; i < numFields; i++)
523 data[i] = KexiDB::pgsqlCStrToVariant(m_fetchRecordFromSQL_iter.at(i));
524 return true;
525}
526
527//=========================================================================
529bool PqxxMigrate::drv_copyTable(const TQString& srcTable, KexiDB::Connection *destConn,
530 KexiDB::TableSchema* dstTable)
531{
532 if (query(TQString("SELECT * FROM ") + srcTable.latin1()))
533 {
534 pqxx::work T(*m_conn, "PqxxMigrate::drv_copyTable");
535
536 //Loop round each row, reading into a vector of strings
537 const KexiDB::QueryColumnInfo::Vector fieldsExpanded( dstTable->query()->fieldsExpanded() );
538 for (pqxx::result::const_iterator rowIt = m_res->begin(); rowIt != m_res->end(); ++rowIt)
539 {
540 TQValueList<TQVariant> vals;
541 int index = 0;
542 for (int i = 0; i < rowIt.size(); ++i, index++)
543 {
544 if (fieldsExpanded.at(index)->field->type()==KexiDB::Field::BLOB ||
545 fieldsExpanded.at(index)->field->type()==KexiDB::Field::LongText)
546 {
547 vals.append(KexiDB::pgsqlByteaToByteArray(rowIt.at(i).c_str(), rowIt.at(i).size()));
548 }
549 else if (fieldsExpanded.at(index)->field->type()==KexiDB::Field::Boolean)
550 {
551 vals.append(TQString(rowIt.at(i).c_str()).lower() == "t" ? TQVariant(true) : TQVariant(false));
552 }
553 else
554 {
555 vals.append(KexiDB::cstringToVariant(rowIt.at(i).c_str(), fieldsExpanded.at(index)->field, rowIt.at(i).size()));
556 }
557 }
558 if (!destConn->insertRecord(*dstTable, vals))
559 {
560 return false;
561 }
562 updateProgress();
563 }
564 m_res->clear();
565 }
566 return true;
567}
568
569//=========================================================================
570//Return whether or not the curent field is a primary key
571//TODO: Add result caching for speed
572bool PqxxMigrate::uniqueKey(pqxx::oid table_uid, int col) const
573{
574 TQString statement;
575 bool ukey;
576 int keyf;
577
578 pqxx::nontransaction* tran = 0;
579 pqxx::result* tmpres = 0;
580
581 try
582 {
583 statement = TQString("SELECT indkey FROM pg_index WHERE ((indisunique = true) AND (indrelid = %1))").arg(table_uid);
584
585 tran = new pqxx::nontransaction(*m_conn, "find_ukey");
586 tmpres = new pqxx::result(tran->exec(statement.latin1()));
587
588 tran->commit();
589 if (tmpres->size() > 0)
590 {
591 //We have a key field for this table, lets check if its this column
592 tmpres->at(0).at(0).to(keyf);
593 if (keyf-1 == col) //-1 because pg counts from 1 and we count from 0
594 {
595 ukey = true;
596 kdDebug() << "Field is unique" << endl;
597 }
598 else
599 {
600 ukey = false;
601 kdDebug() << "Field is NOT unique" << endl;
602 }
603 }
604 else
605 {
606 ukey = false;
607 kdDebug() << "Field is NOT unique" << endl;
608 }
609 }
610 catch(const std::exception &e)
611 {
612 kdDebug() << "uniqueKey:exception - " << e.what() << endl;
613 kdDebug() << "uniqueKey:failed statement - " << statement << endl;
614 ukey = false;
615 }
616
617 delete tmpres;
618 tmpres = 0;
619
620 delete tran;
621 tran = 0;
622
623 return ukey;
624}
625
626//==================================================================================
627//TODO::Implement
628bool PqxxMigrate::autoInc(pqxx::oid /*table_uid*/, int /*col*/) const
629{
630 return false;
631}
632
633//==================================================================================
634//TODO::Implement
635bool PqxxMigrate::notNull(pqxx::oid /*table_uid*/, int /*col*/) const
636{
637 return false;
638}
639
640//==================================================================================
641//TODO::Implement
642bool PqxxMigrate::notEmpty(pqxx::oid /*table_uid*/, int /*col*/) const
643{
644 return false;
645}
646
647//==================================================================================
648//Return a list of database names
649/*bool PqxxMigrate::drv_getDatabasesList( TQStringList &list )
650{
651 KexiDBDrvDbg << "pqxxSqlConnection::drv_getDatabaseList" << endl;
652
653 if (executeSQL("SELECT datname FROM pg_database WHERE datallowconn = TRUE"))
654 {
655 std::string N;
656 for (pqxx::result::const_iterator c = m_res->begin(); c != m_res->end(); ++c)
657 {
658 // Read value of column 0 into a string N
659 c[0].to(N);
660 // Copy the result into the return list
661 list << TQString::fromLatin1 (N.c_str());
662 KexiDBDrvDbg << N.c_str() << endl;
663 }
664 return true;
665 }
666
667 return false;
668}*/
669
670
671#include "pqxxmigrate.moc"
Provides database connection, allowing queries and data modification.
Definition connection.h:62
Database driver management, e.g. finding and loading drivers.
Driver * driver(const TQString &name)
FieldList & addField(Field *field)
Meta-data for a field.
Definition field.h:73
void setCaption(const TQString &caption)
Definition field.h:493
@ ShortInteger
Definition field.h:86
@ InvalidType
Definition field.h:84
void setPrimaryKey(bool p)
Definition field.cpp:524
void setUniqueKey(bool u)
Definition field.cpp:541
void setAutoIncrement(bool a)
Definition field.cpp:515
static TQString typeName(uint type)
Definition field.cpp:173
QueryColumnInfo::Vector fieldsExpanded(FieldsExpandedOptions options=Default)
QuerySchema * query()
Imports non-native databases into Kexi projects.
Definition keximigrate.h:79
3-state logical type with three values: true, false and cancelled and convenient operators.
Definition tristate.h:100
High-level database connectivity library with database backend drivers.
TQVariant cstringToVariant(const char *data, KexiDB::Field *f, int length=-1)
TQValueVector< TQVariant > RowData
structure for storing single record with type information
Definition connection.h:47
KEXI_DB_EXPORT TQByteArray pgsqlByteaToByteArray(const char *data, int length)
Framework for importing databases into native KexiDB databases.
KEXIUTILS_EXPORT TQString string2Identifier(const TQString &s)